Merge "Don't strict GNSS measurement test" into pie-cts-dev am: 9277303cad am: 31fd37b405
am: 79f8eec5a4

Change-Id: I8ccdf3989135ee9b471d1e4dc879e7668feed337
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 4870b15..47f12b8 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -11,6 +11,7 @@
                       apps/CtsVerifierUSBCompanion/
                       libs/
                       tests/autofillservice/
+                      tests/contentcaptureservice/
                       tests/tests/animation/
                       tests/tests/graphics/
                       tests/tests/hardware/
@@ -20,3 +21,4 @@
                       tests/tests/uirendering/
                       tests/tests/view/
                       tests/tests/widget/
+                      common/device-side/util/
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5eb3af3..5f1e481 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 a21108e..ae12e10 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -63,6 +63,4 @@
         echo ">> Unit test for $M failed" >&2
 done
 
-alias gpylint='gpylint --disable=F0401 --disable=C6304 --rcfile=$CAMERA_ITS_TOP"/build/scripts/gpylint_rcfile"'
-# F0401 ignores import errors since gpylint does not have the python paths
-# C6304 ignore Copyright line errors.
+alias gpylint='gpylint --rcfile=$CAMERA_ITS_TOP"/build/scripts/gpylint_rcfile"'
diff --git a/apps/CameraITS/build/scripts/gpylint_rcfile b/apps/CameraITS/build/scripts/gpylint_rcfile
index 37f43f7..f92c613 100644
--- a/apps/CameraITS/build/scripts/gpylint_rcfile
+++ b/apps/CameraITS/build/scripts/gpylint_rcfile
@@ -13,7 +13,10 @@
 # --enable=similarities". If you want to run only the classes checker, but have
 # no Warning level messages displayed, use"--disable=all --enable=classes
 # --disable=W"
-disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression
+disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression, F0401, C6304, C0111
+# F0401 ignores import errors since gpylint does not have the python paths
+# C6304 ignore Copyright line errors.
+# C0111 ignore Docstring at top of file.
 
 # Enable the message, report, category or checker with the given id(s). You can
 # either give multiple identifier separated by comma (,) or put this option
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index 61ec7e1..584e423 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -191,6 +191,17 @@
     """
     return raw16(props) or raw10(props) or raw12(props)
 
+def y8(props):
+    """Returns whether a device supports Y8 output.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return len(its.objects.get_available_output_sizes("y8", props)) > 0
+
 def post_raw_sensitivity_boost(props):
     """Returns whether a device supports post RAW sensitivity boost..
 
@@ -513,6 +524,20 @@
     return False
 
 
+def sync_latency(props):
+    """Returns sync latency in number of frames.
+
+    If undefined, 8 frames.
+
+    Returns:
+        integer number of frames
+    """
+    sync_latency = props['android.sync.maxLatency']
+    if sync_latency < 0:
+        sync_latency = 8
+    return sync_latency
+
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index 2004846..b0a3d56 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -22,10 +22,41 @@
 import its.image
 import numpy
 
+CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
+                          'test_images', 'ISO12233.png')
+CHART_HEIGHT = 13.5  # cm
+CHART_DISTANCE_RFOV = 30.0  # cm
+CHART_DISTANCE_WFOV = 22.0  # cm
+CHART_SCALE_START = 0.65
+CHART_SCALE_STOP = 1.35
+CHART_SCALE_STEP = 0.025
+
+FOV_THRESH_TELE = 60
+FOV_THRESH_WFOV = 90
+
+SCALE_RFOV_IN_WFOV_BOX = 0.67
+SCALE_TELE_IN_RFOV_BOX = 0.67
+SCALE_TELE_IN_WFOV_BOX = 0.5
+
 VGA_HEIGHT = 480
 VGA_WIDTH = 640
 
 
+def calc_chart_scaling(chart_distance, camera_fov):
+    chart_scaling = 1.0
+    camera_fov = float(camera_fov)
+    if (FOV_THRESH_TELE < camera_fov < FOV_THRESH_WFOV and
+                numpy.isclose(chart_distance, CHART_DISTANCE_WFOV, rtol=0.1)):
+        chart_scaling = SCALE_RFOV_IN_WFOV_BOX
+    elif (camera_fov <= FOV_THRESH_TELE and
+          numpy.isclose(chart_distance, CHART_DISTANCE_WFOV, rtol=0.1)):
+        chart_scaling = SCALE_TELE_IN_WFOV_BOX
+    elif (camera_fov <= FOV_THRESH_TELE and
+          numpy.isclose(chart_distance, CHART_DISTANCE_RFOV, rtol=0.1)):
+        chart_scaling = SCALE_TELE_IN_RFOV_BOX
+    return chart_scaling
+
+
 def scale_img(img, scale=1.0):
     """Scale and image based on a real number scale factor."""
     dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
@@ -50,8 +81,9 @@
     Defines PNG reference file, chart size and distance, and scaling range.
     """
 
-    def __init__(self, chart_file, height, distance, scale_start, scale_stop,
-                 scale_step, camera_id=None):
+    def __init__(self, chart_file=None, height=None, distance=None,
+                 scale_start=None, scale_stop=None, scale_step=None,
+                 camera_id=None):
         """Initial constructor for class.
 
         Args:
@@ -63,12 +95,12 @@
             scale_step:     float; step value for scaling for chart search
             camera_id:      int; camera used for extractor
         """
-        self._file = chart_file
-        self._height = height
-        self._distance = distance
-        self._scale_start = scale_start
-        self._scale_stop = scale_stop
-        self._scale_step = scale_step
+        self._file = chart_file or CHART_FILE
+        self._height = height or CHART_HEIGHT
+        self._distance = distance or CHART_DISTANCE_RFOV
+        self._scale_start = scale_start or CHART_SCALE_START
+        self._scale_stop = scale_stop or CHART_SCALE_STOP
+        self._scale_step = scale_step or CHART_SCALE_STEP
         self.xnorm, self.ynorm, self.wnorm, self.hnorm, self.scale = its.image.chart_located_per_argv()
         if not self.xnorm:
             with its.device.ItsSession(camera_id) as cam:
@@ -160,7 +192,7 @@
         for scale in numpy.arange(scale_start, scale_stop, scale_step):
             scene_scaled = scale_img(scene_gray, scale)
             if (scene_scaled.shape[0] < chart.shape[0] or
-                scene_scaled.shape[1] < chart.shape[1]):
+                        scene_scaled.shape[1] < chart.shape[1]):
                 continue
             result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
             _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 8b02230..0f7aad5 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -12,20 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.error
-import os
-import os.path
-import sys
-import re
 import json
-import time
-import unittest
+import os
 import socket
-import subprocess
-import hashlib
-import numpy
 import string
+import subprocess
+import sys
+import time
 import unicodedata
+import unittest
+
+import its.error
+import numpy
 
 
 class ItsSession(object):
@@ -71,11 +69,15 @@
     INTENT_START = 'com.android.cts.verifier.camera.its.START'
     ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
     EXTRA_VERSION = 'camera.its.extra.VERSION'
-    CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
+    CURRENT_ITS_VERSION = '1.0'  # version number to sync with CtsVerifier
     EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
     EXTRA_RESULTS = 'camera.its.extra.RESULTS'
     ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
 
+    # This string must be in sync with ItsService. Updated when interface
+    # between script and ItsService is changed.
+    ITS_SERVICE_VERSION = "1.0"
+
     RESULT_PASS = 'PASS'
     RESULT_FAIL = 'FAIL'
     RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
@@ -143,7 +145,7 @@
                 if forward_info[0] == self.device_id and \
                    remote_p == ItsSession.REMOTE_PORT:
                     port = local_p
-                    break;
+                    break
                 else:
                     used_ports.append(local_p)
 
@@ -383,6 +385,23 @@
             raise its.error.Error('Invalid command response')
         return data['objValue']['cameraIdArray']
 
+    def check_its_version_compatible(self):
+        """Check the java side ItsService is compatible with current host script.
+           Raise ItsException if versions are incompatible
+
+        Returns: None
+        """
+        cmd = {}
+        cmd["cmdName"] = "getItsVersion"
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'ItsVersion':
+            raise its.error.Error('ItsService is incompatible with host python script')
+        server_version = data['strValue']
+        if self.ITS_SERVICE_VERSION != server_version:
+            raise its.error.Error('Version mismatch ItsService(%s) vs host script(%s)' % (
+                    server_version, ITS_SERVICE_VERSION))
+
     def get_camera_properties(self):
         """Get the camera properties object for the device.
 
@@ -529,7 +548,7 @@
 
         The out_surfaces field can specify the width(s), height(s), and
         format(s) of the captured image. The formats may be "yuv", "jpeg",
-        "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
+        "dng", "raw", "raw10", "raw12", "rawStats" or "y8". The default is a YUV420
         frame ("yuv") corresponding to a full sensor frame.
 
         Optionally the out_surfaces field can specify physical camera id(s) if the
@@ -787,7 +806,8 @@
         # out in any order for that capture.
         nbufs = 0
         bufs = {"raw":[], "raw10":[], "raw12":[],
-                "rawStats":[], "dng":[], "jpeg":[]}
+                "rawStats":[], "dng":[], "jpeg":[],
+                "y8":[]}
         yuv_bufs = {size:[] for size in yuv_sizes}
         mds = []
         physical_mds = []
@@ -796,7 +816,7 @@
         while nbufs < ncap*nsurf or len(mds) < ncap:
             jsonObj,buf = self.__read_response_from_socket()
             if jsonObj['tag'] in ['jpegImage', 'rawImage', \
-                    'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
+                    'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage', 'y8Image'] \
                     and buf is not None:
                 fmt = jsonObj['tag'][:-5]
                 bufs[fmt].append(buf)
@@ -837,18 +857,37 @@
 
                 if j in physical_cam_ids:
                     obj["data"] = physical_buffers[physical_cam_ids[j]][i]
-                elif fmt == 'yuv':
+                elif fmt == "yuv":
                     buf_size = widths[j] * heights[j] * 3 / 2
                     obj["data"] = yuv_bufs[buf_size][i]
                 else:
                     obj["data"] = bufs[fmt][i]
                 objs.append(obj)
-            rets.append(objs if ncap>1 else objs[0])
+            rets.append(objs if ncap > 1 else objs[0])
         self.sock.settimeout(self.SOCK_TIMEOUT)
-        return rets if len(rets)>1 else rets[0]
+        if len(rets) > 1 or (isinstance(rets[0], dict) and
+                             isinstance(cap_request, list)):
+            return rets
+        else:
+            return rets[0]
+
+def do_capture_with_latency(cam, req, sync_latency, fmt=None):
+    """Helper function to take enough frames with do_capture to allow sync latency.
+
+    Args:
+        cam:            camera object
+        req:            request for camera
+        sync_latency:   integer number of frames
+        fmt:            format for the capture
+    Returns:
+        single capture with the unsettled frames discarded
+    """
+    caps = cam.do_capture([req]*(sync_latency+1), fmt)
+    return caps[-1]
+
 
 def get_device_id():
-    """ Return the ID of the device that the test is running on.
+    """Return the ID of the device that the test is running on.
 
     Return the device ID provided in the command line if it's connected. If no
     device ID is provided in the command line and there is only one device
@@ -984,6 +1023,7 @@
 
     return device_bfp
 
+
 def _run(cmd):
     """Replacement for os.system, with hiding of stdout+stderr messages.
     """
@@ -991,6 +1031,7 @@
         subprocess.check_call(
                 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
 
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index 3ea6fa3..289bf08 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -81,6 +81,9 @@
         assert(props is not None)
         r,gr,gb,b = convert_capture_to_planes(cap, props)
         return convert_raw_to_rgb_image(r,gr,gb,b, props, cap["metadata"])
+    elif cap["format"] == "y8":
+        y = cap["data"][0:w*h]
+        return convert_y8_to_rgb_image(y, w, h)
     else:
         raise its.error.Error('Invalid format %s' % (cap["format"]))
 
@@ -469,6 +472,21 @@
     rgb.reshape(w*h*3)[:] = flt.reshape(w*h*3)[:]
     return rgb.astype(numpy.float32) / 255.0
 
+def convert_y8_to_rgb_image(y_plane, w, h):
+    """Convert a Y 8-bit image to an RGB image.
+
+    Args:
+        y_plane: The packed 8-bit Y plane.
+        w: The width of the image.
+        h: The height of the image.
+
+    Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0].
+    """
+    y3 = numpy.dstack([y_plane, y_plane, y_plane])
+    rgb = numpy.empty([h, w, 3], dtype=numpy.uint8)
+    rgb.reshape(w*h*3)[:] = y3.reshape(w*h*3)[:]
+    return rgb.astype(numpy.float32) / 255.0
 
 def load_rgb_image(fname):
     """Load a standard image file (JPG, PNG, etc.).
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 78d8b42..1f457f4 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -161,7 +161,7 @@
 
     Args:
         fmt: the output format, as a string in
-            ["jpg", "yuv", "raw", "raw10", "raw12"].
+            ["jpg", "yuv", "raw", "raw10", "raw12", "y8"].
         props: the object returned from its.device.get_camera_properties().
         max_size: (Optional) A (w,h) tuple.
             Sizes larger than max_size (either w or h)  will be discarded.
@@ -174,7 +174,7 @@
     """
     AR_TOLERANCE = 0.03
     fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26,"yuv":0x23,
-                 "jpg":0x100, "jpeg":0x100}
+                 "jpg":0x100, "jpeg":0x100, "y8":0x20203859}
     configs = props['android.scaler.streamConfigurationMap']\
                    ['availableStreamConfigurations']
     fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
diff --git a/apps/CameraITS/tests/scene0/test_camera_properties.py b/apps/CameraITS/tests/scene0/test_camera_properties.py
deleted file mode 100644
index eb638f0..0000000
--- a/apps/CameraITS/tests/scene0/test_camera_properties.py
+++ /dev/null
@@ -1,43 +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 its.caps
-import its.device
-import its.objects
-import pprint
-
-def main():
-    """Basic test to query and print out camera properties.
-    """
-
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-
-        pprint.pprint(props)
-
-        its.caps.skip_unless(its.caps.manual_sensor(props))
-
-        # Test that a handful of required keys are present.
-        assert(props.has_key('android.sensor.info.sensitivityRange'))
-        assert(props.has_key('android.sensor.orientation'))
-        assert(props.has_key('android.scaler.streamConfigurationMap'))
-        assert(props.has_key('android.lens.facing'))
-
-        print "JPG sizes:", its.objects.get_available_output_sizes("jpg", props)
-        print "RAW sizes:", its.objects.get_available_output_sizes("raw", props)
-        print "YUV sizes:", its.objects.get_available_output_sizes("yuv", props)
-
-if __name__ == '__main__':
-    main()
-
diff --git a/apps/CameraITS/tests/scene0/test_gyro_bias.py b/apps/CameraITS/tests/scene0/test_gyro_bias.py
index 44be95f..c860ac8 100644
--- a/apps/CameraITS/tests/scene0/test_gyro_bias.py
+++ b/apps/CameraITS/tests/scene0/test_gyro_bias.py
@@ -12,48 +12,45 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os
+import time
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import time
-from matplotlib import pylab
-import os.path
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
 import numpy
 
+NAME = os.path.basename(__file__).split('.')[0]
+N = 20  # Number of samples averaged together, in the plot.
+MEAN_THRESH = 0.01  # PASS/FAIL threshold for gyro mean drift
+VAR_THRESH = 0.001  # PASS/FAIL threshold for gyro variance drift
+
+
 def main():
     """Test if the gyro has stable output when device is stationary.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    # Number of samples averaged together, in the plot.
-    N = 20
-
-    # Pass/fail thresholds for gyro drift
-    MEAN_THRESH = 0.01
-    VAR_THRESH = 0.001
-
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         # Only run test if the appropriate caps are claimed.
         its.caps.skip_unless(its.caps.sensor_fusion(props) and
             cam.get_sensors().get("gyro"))
 
-        print "Collecting gyro events"
+        print 'Collecting gyro events'
         cam.start_sensor_events()
         time.sleep(5)
-        gyro_events = cam.get_sensor_events()["gyro"]
+        gyro_events = cam.get_sensor_events()['gyro']
 
     nevents = (len(gyro_events) / N) * N
     gyro_events = gyro_events[:nevents]
-    times = numpy.array([(e["time"] - gyro_events[0]["time"])/1000000000.0
+    times = numpy.array([(e['time'] - gyro_events[0]['time'])/1000000000.0
                          for e in gyro_events])
-    xs = numpy.array([e["x"] for e in gyro_events])
-    ys = numpy.array([e["y"] for e in gyro_events])
-    zs = numpy.array([e["z"] for e in gyro_events])
+    xs = numpy.array([e['x'] for e in gyro_events])
+    ys = numpy.array([e['y'] for e in gyro_events])
+    zs = numpy.array([e['z'] for e in gyro_events])
 
     # Group samples into size-N groups and average each together, to get rid
     # of individual random spikes in the data.
@@ -62,17 +59,19 @@
     ys = ys.reshape(nevents/N, N).mean(1)
     zs = zs.reshape(nevents/N, N).mean(1)
 
-    pylab.plot(times, xs, 'r', label="x")
-    pylab.plot(times, ys, 'g', label="y")
-    pylab.plot(times, zs, 'b', label="z")
-    pylab.xlabel("Time (seconds)")
-    pylab.ylabel("Gyro readings (mean of %d samples)"%(N))
+    pylab.plot(times, xs, 'r', label='x')
+    pylab.plot(times, ys, 'g', label='y')
+    pylab.plot(times, zs, 'b', label='z')
+    pylab.xlabel('Time (seconds)')
+    pylab.ylabel('Gyro readings (mean of %d samples)'%(N))
     pylab.legend()
-    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+    matplotlib.pyplot.savefig('%s_plot.png' % (NAME))
 
-    for samples in [xs,ys,zs]:
-        assert(samples.mean() < MEAN_THRESH)
-        assert(numpy.var(samples) < VAR_THRESH)
+    for samples in [xs, ys, zs]:
+        mean = samples.mean()
+        var = numpy.var(samples)
+        assert mean < MEAN_THRESH, 'mean: %.3f, TOL=%.2f' % (mean, MEAN_THRESH)
+        assert var < VAR_THRESH, 'var: %.4f, TOL=%.3f' % (var, VAR_THRESH)
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene0/test_jitter.py b/apps/CameraITS/tests/scene0/test_jitter.py
index 6a156dd..c75ee60 100644
--- a/apps/CameraITS/tests/scene0/test_jitter.py
+++ b/apps/CameraITS/tests/scene0/test_jitter.py
@@ -12,24 +12,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
-import os.path
-from matplotlib import pylab
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+# PASS/FAIL thresholds
+MIN_AVG_FRAME_DELTA = 30  # at least 30ms delta between frames
+MAX_VAR_FRAME_DELTA = 0.01  # variance of frame deltas
+MAX_FRAME_DELTA_JITTER = 0.3  # max ms gap from the average frame delta
+
+NAME = os.path.basename(__file__).split('.')[0]
+
 
 def main():
-    """Measure jitter in camera timestamps.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    # Pass/fail thresholds
-    MIN_AVG_FRAME_DELTA = 30 # at least 30ms delta between frames
-    MAX_VAR_FRAME_DELTA = 0.01 # variance of frame deltas
-    MAX_FRAME_DELTA_JITTER = 0.3 # max ms gap from the average frame delta
+    """Measure jitter in camera timestamps."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -41,25 +43,32 @@
 
         # Print out the millisecond delta between the start of each exposure
         tstamps = [c['metadata']['android.sensor.timestamp'] for c in caps]
-        deltas = [tstamps[i]-tstamps[i-1] for i in range(1,len(tstamps))]
+        deltas = [tstamps[i]-tstamps[i-1] for i in range(1, len(tstamps))]
         deltas_ms = [d/1000000.0 for d in deltas]
         avg = sum(deltas_ms) / len(deltas_ms)
         var = sum([d*d for d in deltas_ms]) / len(deltas_ms) - avg * avg
         range0 = min(deltas_ms) - avg
         range1 = max(deltas_ms) - avg
-        print "Average:", avg
-        print "Variance:", var
-        print "Jitter range:", range0, "to", range1
+        print 'Average:', avg
+        print 'Variance:', var
+        print 'Jitter range:', range0, 'to', range1
 
         # Draw a plot.
         pylab.plot(range(len(deltas_ms)), deltas_ms)
-        matplotlib.pyplot.savefig("%s_deltas.png" % (NAME))
+        pylab.title(NAME)
+        pylab.xlabel('frame number')
+        pylab.ylabel('jitter (ms)')
+        matplotlib.pyplot.savefig('%s_deltas.png' % (NAME))
 
         # Test for pass/fail.
-        assert(avg > MIN_AVG_FRAME_DELTA)
-        assert(var < MAX_VAR_FRAME_DELTA)
-        assert(abs(range0) < MAX_FRAME_DELTA_JITTER)
-        assert(abs(range1) < MAX_FRAME_DELTA_JITTER)
+        emsg = 'avg: %.4fms, TOL: %.fms' % (avg, MIN_AVG_FRAME_DELTA)
+        assert avg > MIN_AVG_FRAME_DELTA, emsg
+        emsg = 'var: %.4fms, TOL: %.2fms' % (var, MAX_VAR_FRAME_DELTA)
+        assert var < MAX_VAR_FRAME_DELTA, emsg
+        emsg = 'range0: %.4fms, range1: %.4fms, TOL: %.2fms' % (
+                range0, range1, MAX_FRAME_DELTA_JITTER)
+        assert abs(range0) < MAX_FRAME_DELTA_JITTER, emsg
+        assert abs(range1) < MAX_FRAME_DELTA_JITTER, emsg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index b716141..7923f95 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -37,6 +37,7 @@
         sens_step = (sens_range[1] - sens_range[0]) / NUM_STEPS
         sens_list = range(sens_range[0], sens_range[1], sens_step)
         e = min(props['android.sensor.info.exposureTimeRange'])
+        assert e != 0
         reqs = [its.objects.manual_capture_request(s, e) for s in sens_list]
         _, fmt = its.objects.get_fastest_manual_capture_settings(props)
 
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index 1b76806..357bd02 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -32,15 +32,9 @@
         its.caps.skip_unless(its.caps.manual_sensor(props) and
                              its.caps.per_frame_control(props))
 
-        # determine capture format
-        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)
-
+        valid_formats = ['yuv', 'jpg']
+        if its.caps.raw16(props):
+            valid_formats.insert(0, 'raw')
         # grab exp/gain ranges from camera
         sensor_exp_range = props['android.sensor.info.exposureTimeRange']
         sens_range = props['android.sensor.info.sensitivityRange']
@@ -48,6 +42,7 @@
         print 'sensor s range:', sens_range
 
         # determine if exposure test range is within sensor reported range
+        assert sensor_exp_range[0] != 0
         exp_range = []
         if sensor_exp_range[0] < TEST_EXP_RANGE[0]:
             exp_range.append(TEST_EXP_RANGE[0])
@@ -58,55 +53,69 @@
         else:
             exp_range.append(sensor_exp_range[1])
 
-        # build requests
-        reqs = []
-        index_list = []
-        for exp in exp_range:
-            for sens in sens_range:
-                reqs.append(its.objects.manual_capture_request(sens, exp))
-                index_list.append((exp, sens))
-
-        # take shots
-        caps = cam.do_capture(reqs, fmt)
-
-        # extract exp/sensitivity data
         data = {}
-        for i, cap in enumerate(caps):
-            e_read = cap['metadata']['android.sensor.exposureTime']
-            s_read = cap['metadata']['android.sensor.sensitivity']
-            data[index_list[i]] = (e_read, s_read)
+        # build requests
+        for fmt in valid_formats:
+            print 'format: %s' % fmt
+            size = its.objects.get_available_output_sizes(fmt, props)[-1]
+            out_surface = {'width': size[0], 'height': size[1], 'format': fmt}
+
+            reqs = []
+            index_list = []
+            for exp in exp_range:
+                for sens in sens_range:
+                    reqs.append(its.objects.manual_capture_request(sens, exp))
+                    index_list.append((fmt, exp, sens))
+                    print 'exp_write: %d, sens_write: %d' % (exp, sens)
+
+            # take shots
+            caps = cam.do_capture(reqs, out_surface)
+
+            # extract exp/sensitivity data
+            for i, cap in enumerate(caps):
+                e_read = cap['metadata']['android.sensor.exposureTime']
+                s_read = cap['metadata']['android.sensor.sensitivity']
+                data[index_list[i]] = (fmt, e_read, s_read)
 
         # check read/write match across all shots
         e_failed = []
         s_failed = []
-        for e_write in exp_range:
-            for s_write in sens_range:
-                (e_read, s_read) = data[(e_write, s_write)]
-                if e_write < e_read or e_read/float(e_write) <= RTOL_EXP_GAIN:
-                    e_failed.append({'e_write': e_write,
-                                     'e_read': e_read,
-                                     's_write': s_write,
-                                     's_read': s_read})
-                if s_write < s_read or s_read/float(s_write) <= RTOL_EXP_GAIN:
-                    s_failed.append({'e_write': e_write,
-                                     'e_read': e_read,
-                                     's_write': s_write,
-                                     's_read': s_read})
+        for fmt_write in valid_formats:
+            for e_write in exp_range:
+                for s_write in sens_range:
+                    fmt_read, e_read, s_read = data[(
+                            fmt_write, e_write, s_write)]
+                    if (e_write < e_read or
+                                e_read/float(e_write) <= RTOL_EXP_GAIN):
+                        e_failed.append({'format': fmt_read,
+                                         'e_write': e_write,
+                                         'e_read': e_read,
+                                         's_write': s_write,
+                                         's_read': s_read})
+                    if (s_write < s_read or
+                                s_read/float(s_write) <= RTOL_EXP_GAIN):
+                        s_failed.append({'format': fmt_read,
+                                         'e_write': e_write,
+                                         'e_read': e_read,
+                                         's_write': s_write,
+                                         's_read': s_read})
 
         # print results
         if e_failed:
             print '\nFAILs for exposure time'
             for fail in e_failed:
-                print ' e_write: %d, e_read: %d, RTOL: %.2f, ' % (
-                        fail['e_write'], fail['e_read'], RTOL_EXP_GAIN),
+                print ' format: %s, e_write: %d, e_read: %d, RTOL: %.2f, ' % (
+                        fail['format'], fail['e_write'], fail['e_read'],
+                        RTOL_EXP_GAIN),
                 print 's_write: %d, s_read: %d, RTOL: %.2f' % (
                         fail['s_write'], fail['s_read'], RTOL_EXP_GAIN)
         if s_failed:
             print 'FAILs for sensitivity(ISO)'
             for fail in s_failed:
-                print 's_write: %d, s_read: %d, RTOL: %.2f, ' % (
-                        fail['s_write'], fail['s_read'], RTOL_EXP_GAIN),
-                print ' e_write: %d, e_read: %d, RTOL: %.2f' % (
+                print ' format: %s, s_write: %d, s_read: %d, RTOL: %.2f, ' % (
+                        fail['format'], fail['s_write'], fail['s_read'],
+                        RTOL_EXP_GAIN),
+                print 'e_write: %d, e_read: %d, RTOL: %.2f' % (
                         fail['e_write'], fail['e_read'], RTOL_EXP_GAIN)
 
         # assert PASS/FAIL
diff --git a/apps/CameraITS/tests/scene0/test_tonemap_curve.py b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
new file mode 100644
index 0000000..a1db43a
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
@@ -0,0 +1,170 @@
+# 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.
+
+import os
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+NAME = os.path.basename(__file__).split('.')[0]
+PATTERN = 2  # Note scene0/test_test_patterns must PASS
+COLOR_BARS = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
+              'BLUE', 'BLACK']
+COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
+                 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1],
+                 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
+DELTA = 0.0005  # crop on edge of color bars
+RAW_TOL = 0.001  # 1 DN in [0:1] (1/(1023-64)
+RGB_VAR_TOL = 0.0039  # 1/255
+RGB_MEAN_TOL = 0.1
+TONEMAP_MAX = 0.5
+
+
+def check_raw_pattern(img_raw):
+    """Check for RAW capture matches color bar pattern.
+
+    Args:
+        img_raw: RAW image
+    """
+
+    print 'Checking RAW/PATTERN match'
+    n_bars = len(COLOR_BARS)
+    color_match = []
+    for i in range(n_bars):
+        print 'patch:', i,
+        raw_patch = its.image.get_image_patch(
+                img_raw, float(i)/n_bars+DELTA, 0.0, 1.0/n_bars-2*DELTA, 1.0)
+        raw_means = its.image.compute_image_means(raw_patch)
+        for color in COLOR_BARS:
+            if np.allclose(COLOR_CHECKER[color], raw_means, atol=RAW_TOL):
+                color_match.append(color)
+                print '%s' % color
+    assert set(color_match) == set(COLOR_BARS), 'RAW does not have all colors'
+
+
+def check_yuv_vs_raw(img_raw, img_yuv):
+    """Check for YUV vs RAW match in 8 patches.
+
+    Check for correct values and color consistency
+
+    Args:
+        img_raw: RAW image
+        img_yuv: YUV image
+    """
+
+    print 'Checking YUV/RAW match'
+    n_bars = len(COLOR_BARS)
+    color_match_errs = []
+    color_variance_errs = []
+    for i in range(n_bars):
+        raw_patch = its.image.get_image_patch(
+                img_raw, float(i)/n_bars+DELTA, 0.0, 1.0/n_bars-2*DELTA, 1.0)
+        yuv_patch = its.image.get_image_patch(
+                img_yuv, float(i)/n_bars+DELTA, 0.0, 1.0/n_bars-2*DELTA, 1.0)
+        raw_means = np.array(its.image.compute_image_means(raw_patch))
+        raw_vars = np.array(its.image.compute_image_variances(raw_patch))
+        yuv_means = np.array(its.image.compute_image_means(yuv_patch))
+        yuv_means /= TONEMAP_MAX  # Normalize to tonemap max
+        yuv_vars = np.array(its.image.compute_image_variances(yuv_patch))
+        if not np.allclose(raw_means, yuv_means, atol=RGB_MEAN_TOL):
+            color_match_errs.append('RAW: %s, RGB(norm): %s, ATOL: %.2f' % (
+                    str(raw_means), str(np.round(yuv_means, 3)), RGB_MEAN_TOL))
+        if not np.allclose(raw_vars, yuv_vars, atol=RGB_VAR_TOL):
+            color_variance_errs.append('RAW: %s, RGB: %s, ATOL: %.4f' % (
+                    str(raw_vars), str(yuv_vars), RGB_VAR_TOL))
+    if color_match_errs:
+        print '\nColor match errors'
+        for err in color_match_errs:
+            print err
+    if color_variance_errs:
+        print '\nColor variance errors'
+        for err in color_variance_errs:
+            print err
+    assert not color_match_errs
+    assert not color_variance_errs
+
+
+def test_tonemap_curve(cam, props):
+    """test tonemap curve with sensor test pattern.
+
+    Args:
+        cam: An open device session.
+        props: Properties of cam
+    """
+
+    avail_patterns = props['android.sensor.availableTestPatternModes']
+    print 'avail_patterns: ', avail_patterns
+    sens_min, _ = props['android.sensor.info.sensitivityRange']
+    exp = min(props['android.sensor.info.exposureTimeRange'])
+
+    # Linear tonemap with maximum of 0.5
+    tmap = sum([[i/63.0, i/126.0] for i in range(64)], [])
+
+    if PATTERN in avail_patterns:
+        # RAW image
+        req_raw = its.objects.manual_capture_request(int(sens_min), exp)
+        req_raw['android.sensor.testPatternMode'] = PATTERN
+        fmt_raw = {'format': 'raw'}
+        cap_raw = cam.do_capture(req_raw, fmt_raw)
+        img_raw = its.image.convert_capture_to_rgb_image(
+                cap_raw, props=props)
+
+        # Save RAW pattern
+        its.image.write_image(img_raw, '%s_raw_%d.jpg' % (
+                NAME, PATTERN), True)
+        check_raw_pattern(img_raw)
+
+        # YUV image
+        req_yuv = its.objects.manual_capture_request(int(sens_min), exp)
+        req_yuv['android.sensor.testPatternMode'] = PATTERN
+        req_yuv['android.tonemap.mode'] = 0
+        req_yuv['android.tonemap.curve'] = {
+                'red': tmap, 'green': tmap, 'blue': tmap}
+        fmt_yuv = {'format': 'yuv', 'width': 640, 'height': 480}
+        cap_yuv = cam.do_capture(req_yuv, fmt_yuv)
+        img_yuv = its.image.convert_capture_to_rgb_image(cap_yuv, True)
+
+        # Save YUV pattern
+        its.image.write_image(img_yuv, '%s_yuv_%d.jpg' % (
+                NAME, PATTERN), True)
+
+        # Check pattern for correctness
+        check_yuv_vs_raw(img_raw, img_yuv)
+    else:
+        print 'Pattern not in android.sensor.availableTestPatternModes.'
+        assert 0
+
+
+def main():
+    """Test conversion of test pattern from RAW to YUV.
+
+    android.sensor.testPatternMode
+    2: COLOR_BARS
+    """
+
+    print '\nStarting %s' % NAME
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw16(props) and
+                             its.caps.manual_sensor(props) and
+                             its.caps.per_frame_control(props) and
+                             its.caps.manual_post_proc(props))
+
+        test_tonemap_curve(cam, props)
+
+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 ae4583f..f72f229 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -12,10 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import time
+
+import its.caps
 import its.device
 import its.objects
-import its.caps
-import time
+
 
 def main():
     """Test if image and motion sensor events are in the same time domain.
@@ -36,7 +38,7 @@
         ts_image0 = cap['metadata']['android.sensor.timestamp']
 
         # Get the timestamps of motion events.
-        print "Reading sensor measurements"
+        print 'Reading sensor measurements'
         sensors = cam.get_sensors()
         cam.start_sensor_events()
         time.sleep(2.0)
@@ -45,20 +47,20 @@
         ts_sensor_last = {}
         for sensor, existing in sensors.iteritems():
             if existing:
-                assert(len(events[sensor]) > 0)
-                ts_sensor_first[sensor] = events[sensor][0]["time"]
-                ts_sensor_last[sensor] = events[sensor][-1]["time"]
+                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']
 
         # Get the timestamp of another image.
         cap = cam.do_capture(req, fmt)
         ts_image1 = cap['metadata']['android.sensor.timestamp']
 
-        print "Image timestamps:", ts_image0, ts_image1
+        print 'Image timestamps:', ts_image0, ts_image1
 
         # The motion timestamps must be between the two image timestamps.
         for sensor, existing in sensors.iteritems():
             if existing:
-                print "%s timestamps: %d %d" % (sensor, ts_sensor_first[sensor],
+                print '%s timestamps: %d %d' % (sensor, ts_sensor_first[sensor],
                                                 ts_sensor_last[sensor])
                 assert ts_image0 < ts_sensor_first[sensor] < ts_image1
                 assert ts_image0 < ts_sensor_last[sensor] < ts_image1
diff --git a/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf b/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
new file mode 100644
index 0000000..1c28a35
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
index a7b5add..e5d33f3 100644
--- a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
@@ -12,14 +12,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
-import os.path
-import math
 import numpy as np
 
+NAME = os.path.basename(__file__).split(".")[0]
+
+
 def main():
     """Capture auto and manual shots that should look the same.
 
@@ -29,12 +32,10 @@
     however there can be variations in brightness/contrast due to different
     "auto" ISP blocks that may be disabled in the manual flows.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.manual_sensor(props) and
-                             its.caps.manual_post_proc(props) and
+        its.caps.skip_unless(its.caps.read_3a(props) and
                              its.caps.per_frame_control(props))
         mono_camera = its.caps.mono_camera(props)
 
@@ -44,7 +45,7 @@
         if debug:
             fmt = largest_yuv
         else:
-            match_ar = (largest_yuv['width'], largest_yuv['height'])
+            match_ar = (largest_yuv["width"], largest_yuv["height"])
             fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
         sens, exp, gains, xform, focus = cam.do_3a(get_results=True,
                                                    mono_camera=mono_camera)
@@ -79,10 +80,10 @@
         print "Manual wb transform:", xform_m1
 
         # Manual capture 2: WB + tonemap
-        gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[])
+        gamma = sum([[i/63.0, math.pow(i/63.0, 1/2.2)] for i in xrange(64)], [])
         req["android.tonemap.mode"] = 0
         req["android.tonemap.curve"] = {
-            "red": gamma, "green": gamma, "blue": gamma}
+                "red": gamma, "green": gamma, "blue": gamma}
         cap_man2 = cam.do_capture(req, fmt)
         img_man2 = its.image.convert_capture_to_rgb_image(cap_man2)
         its.image.write_image(img_man2, "%s_manual_wb_tm.jpg" % (NAME))
@@ -94,14 +95,14 @@
 
         # Check that the WB gains and transform reported in each capture
         # result match with the original AWB estimate from do_3a.
-        for g,x in [(gains_m1,xform_m1),(gains_m2,xform_m2)]:
-            assert(all([abs(xform[i] - x[i]) < 0.05 for i in range(9)]))
-            assert(all([abs(gains[i] - g[i]) < 0.05 for i in range(4)]))
+        for g, x in [(gains_m1, xform_m1), (gains_m2, xform_m2)]:
+            assert all([abs(xform[i] - x[i]) < 0.05 for i in range(9)])
+            assert all([abs(gains[i] - g[i]) < 0.05 for i in range(4)])
 
         # Check that auto AWB settings are close
-        assert(all([np.isclose(xform_a[i], xform[i], rtol=0.25, atol=0.1) for i in range(9)]))
-        assert(all([np.isclose(gains_a[i], gains[i], rtol=0.25, atol=0.1) for i in range(4)]))
+        assert all([np.isclose(xform_a[i], xform[i], rtol=0.25, atol=0.1) for i in range(9)])
+        assert all([np.isclose(gains_a[i], gains[i], rtol=0.25, atol=0.1) for i in range(4)])
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_black_white.py b/apps/CameraITS/tests/scene1/test_black_white.py
index 18bc001..bd5f096 100644
--- a/apps/CameraITS/tests/scene1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1/test_black_white.py
@@ -12,19 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
-from matplotlib import pylab
-import os.path
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split(".")[0]
+
 
 def main():
-    """Test that the device will produce full black+white images.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
+    """Test that the device will produce full black+white images."""
 
     r_means = []
     g_means = []
@@ -32,62 +33,73 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.manual_sensor(props) and
-                             its.caps.per_frame_control(props))
+        its.caps.skip_unless(its.caps.manual_sensor(props))
+        sync_latency = its.caps.sync_latency(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'])
+            match_ar = (largest_yuv["width"], largest_yuv["height"])
             fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
 
-        expt_range = props['android.sensor.info.exposureTimeRange']
-        sens_range = props['android.sensor.info.sensitivityRange']
+        expt_range = props["android.sensor.info.exposureTimeRange"]
+        sens_range = props["android.sensor.info.sensitivityRange"]
 
         # Take a shot with very low ISO and exposure time. Expect it to
         # be black.
-        print "Black shot: sens = %d, exp time = %.4fms" % (
-                sens_range[0], expt_range[0]/1000000.0)
         req = its.objects.manual_capture_request(sens_range[0], expt_range[0])
-        cap = cam.do_capture(req, fmt)
+        cap = its.device.do_capture_with_latency(cam, req, sync_latency, fmt)
         img = its.image.convert_capture_to_rgb_image(cap)
-        its.image.write_image(img, "%s_black.jpg" % (NAME))
+        its.image.write_image(img, "%s_black.jpg" % NAME)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         black_means = its.image.compute_image_means(tile)
         r_means.append(black_means[0])
         g_means.append(black_means[1])
         b_means.append(black_means[2])
         print "Dark pixel means:", black_means
+        r_exp = cap["metadata"]["android.sensor.exposureTime"]
+        r_iso = cap["metadata"]["android.sensor.sensitivity"]
+        print "Black shot write values: sens = %d, exp time = %.4fms" % (
+                sens_range[0], expt_range[0]/1000000.0)
+        print "Black shot read values: sens = %d, exp time = %.4fms\n" % (
+                r_iso, r_exp/1000000.0)
 
         # Take a shot with very high ISO and exposure time. Expect it to
         # be white.
-        print "White shot: sens = %d, exp time = %.2fms" % (
-                sens_range[1], expt_range[1]/1000000.0)
         req = its.objects.manual_capture_request(sens_range[1], expt_range[1])
-        cap = cam.do_capture(req, fmt)
+        cap = its.device.do_capture_with_latency(cam, req, sync_latency, fmt)
         img = its.image.convert_capture_to_rgb_image(cap)
-        its.image.write_image(img, "%s_white.jpg" % (NAME))
+        its.image.write_image(img, "%s_white.jpg" % NAME)
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
         white_means = its.image.compute_image_means(tile)
         r_means.append(white_means[0])
         g_means.append(white_means[1])
         b_means.append(white_means[2])
         print "Bright pixel means:", white_means
+        r_exp = cap["metadata"]["android.sensor.exposureTime"]
+        r_iso = cap["metadata"]["android.sensor.sensitivity"]
+        print "White shot write values: sens = %d, exp time = %.2fms" % (
+                sens_range[1], expt_range[1]/1000000.0)
+        print "White shot read values: sens = %d, exp time = %.2fms\n" % (
+                r_iso, r_exp/1000000.0)
 
         # Draw a plot.
-        pylab.plot([0,1], r_means, 'r')
-        pylab.plot([0,1], g_means, 'g')
-        pylab.plot([0,1], b_means, 'b')
-        pylab.ylim([0,1])
+        pylab.title("test_black_white")
+        pylab.plot([0, 1], r_means, "-ro")
+        pylab.plot([0, 1], g_means, "-go")
+        pylab.plot([0, 1], b_means, "-bo")
+        pylab.xlabel("Capture Number")
+        pylab.ylabel("Output Values (Normalized)")
+        pylab.ylim([0, 1])
         matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
 
-        for val in black_means:
-            assert(val < 0.025)
-        for val in white_means:
-            assert(val > 0.975)
+        for black_mean in black_means:
+            assert black_mean < 0.025
+        for white_mean in white_means:
+            assert white_mean > 0.975
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
index edb8995..92239db 100644
--- a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
+++ b/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
@@ -12,14 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
+
+from matplotlib import pylab
+import matplotlib.pyplot
 import numpy
 
+BURST_LEN = 50
+BURSTS = 5
+COLORS = ["R", "G", "B"]
+FRAMES = BURST_LEN * BURSTS
+NAME = os.path.basename(__file__).split(".")[0]
+SPREAD_THRESH = 0.03
+
+
 def main():
     """Take long bursts of images and check that they're all identical.
 
@@ -27,25 +38,19 @@
     frames that are processed differently or have artifacts. Uses manual
     capture settings.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    BURST_LEN = 50
-    BURSTS = 5
-    FRAMES = BURST_LEN * BURSTS
-
-    SPREAD_THRESH = 0.03
 
     with its.device.ItsSession() as cam:
 
         # Capture at the smallest resolution.
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.manual_sensor(props) and
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
                              its.caps.per_frame_control(props))
+        debug = its.caps.debug_mode()
 
         _, fmt = its.objects.get_fastest_manual_capture_settings(props)
         e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
         req = its.objects.manual_capture_request(s, e)
-        w,h = fmt["width"], fmt["height"]
+        w, h = fmt["width"], fmt["height"]
 
         # Capture bursts of YUV shots.
         # Get the mean values of a center patch for each.
@@ -53,10 +58,10 @@
         r_means = []
         g_means = []
         b_means = []
-        imgs = numpy.empty([FRAMES,h,w,3])
+        imgs = numpy.empty([FRAMES, h, w, 3])
         for j in range(BURSTS):
             caps = cam.do_capture([req]*BURST_LEN, [fmt])
-            for i,cap in enumerate(caps):
+            for i, cap in enumerate(caps):
                 n = j*BURST_LEN + i
                 imgs[n] = its.image.convert_capture_to_rgb_image(cap)
                 tile = its.image.get_image_patch(imgs[n], 0.45, 0.45, 0.1, 0.1)
@@ -65,21 +70,35 @@
                 g_means.append(means[1])
                 b_means.append(means[2])
 
-        # Dump all images.
-        print "Dumping images"
-        for i in range(FRAMES):
-            its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME,i))
+        # Dump all images if debug
+        if debug:
+            print "Dumping images"
+            for i in range(FRAMES):
+                its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME, i))
 
         # The mean image.
         img_mean = imgs.mean(0)
         its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
 
-        # Pass/fail based on center patch similarity.
-        for means in [r_means, g_means, b_means]:
-            spread = max(means) - min(means)
-            print spread
-            assert(spread < SPREAD_THRESH)
+        # Plot means vs frames
+        frames = range(FRAMES)
+        pylab.title(NAME)
+        pylab.plot(frames, r_means, "-ro")
+        pylab.plot(frames, g_means, "-go")
+        pylab.plot(frames, b_means, "-bo")
+        pylab.ylim([0, 1])
+        pylab.xlabel("frame number")
+        pylab.ylabel("RGB avg [0, 1]")
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
 
-if __name__ == '__main__':
+        # PASS/FAIL based on center patch similarity.
+        for plane, means in enumerate([r_means, g_means, b_means]):
+            spread = max(means) - min(means)
+            msg = "%s spread: %.5f, SPREAD_THRESH: %.3f" % (
+                    COLORS[plane], spread, SPREAD_THRESH)
+            print msg
+            assert spread < SPREAD_THRESH, msg
+
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1/test_capture_result.py
index a3b81fa..19d0145 100644
--- a/apps/CameraITS/tests/scene1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1/test_capture_result.py
@@ -12,16 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
-import os.path
-import numpy
 import matplotlib.pyplot
+import mpl_toolkits.mplot3d  # Required for 3d plot to work
+import numpy
 
-# Required for 3d plot to work
-import mpl_toolkits.mplot3d
 
 def main():
     """Test that valid data comes back in CaptureResult objects.
@@ -38,13 +37,13 @@
                              its.caps.manual_post_proc(props) and
                              its.caps.per_frame_control(props))
 
-        manual_tonemap = [0,0, 1,1] # Linear
+        manual_tonemap = [0,0, 1,1]  # Linear
         manual_transform = its.objects.float_to_rational(
                 [-1.5,-1.0,-0.5, 0.0,0.5,1.0, 1.5,2.0,3.0])
         manual_gains = [1,1.5,2.0,3.0]
         manual_region = [{"x":8,"y":8,"width":128,"height":128,"weight":1}]
-        manual_exp_time = min(props['android.sensor.info.exposureTimeRange'])
-        manual_sensitivity = min(props['android.sensor.info.sensitivityRange'])
+        manual_exp_time = min(props["android.sensor.info.exposureTimeRange"])
+        manual_sensitivity = min(props["android.sensor.info.sensitivityRange"])
 
         # The camera HAL may not support different gains for two G channels.
         manual_gains_ok = [[1,1.5,2.0,3.0],[1,1.5,1.5,3.0],[1,2.0,2.0,3.0]]
@@ -53,67 +52,80 @@
         auto_req["android.statistics.lensShadingMapMode"] = 1
 
         manual_req = {
-            "android.control.mode": 0,
-            "android.control.aeMode": 0,
-            "android.control.awbMode": 0,
-            "android.control.afMode": 0,
-            "android.sensor.sensitivity": manual_sensitivity,
-            "android.sensor.exposureTime": manual_exp_time,
-            "android.colorCorrection.mode": 0,
-            "android.colorCorrection.transform": manual_transform,
-            "android.colorCorrection.gains": manual_gains,
-            "android.tonemap.mode": 0,
-            "android.tonemap.curve": {"red": manual_tonemap,
-                                      "green": manual_tonemap,
-                                      "blue": manual_tonemap},
-            "android.control.aeRegions": manual_region,
-            "android.control.afRegions": manual_region,
-            "android.control.awbRegions": manual_region,
-            "android.statistics.lensShadingMapMode":1
-            }
+                "android.control.mode": 0,
+                "android.control.aeMode": 0,
+                "android.control.awbMode": 0,
+                "android.control.afMode": 0,
+                "android.sensor.sensitivity": manual_sensitivity,
+                "android.sensor.exposureTime": manual_exp_time,
+                "android.colorCorrection.mode": 0,
+                "android.colorCorrection.transform": manual_transform,
+                "android.colorCorrection.gains": manual_gains,
+                "android.tonemap.mode": 0,
+                "android.tonemap.curve": {"red": manual_tonemap,
+                                          "green": manual_tonemap,
+                                          "blue": manual_tonemap},
+                "android.control.aeRegions": manual_region,
+                "android.control.afRegions": manual_region,
+                "android.control.awbRegions": manual_region,
+                "android.statistics.lensShadingMapMode": 1
+                }
 
+        sync_latency = its.caps.sync_latency(props)
         print "Testing auto capture results"
-        lsc_map_auto = test_auto(cam, props)
+        lsc_map_auto = test_auto(cam, props, sync_latency)
         print "Testing manual capture results"
-        test_manual(cam, lsc_map_auto, props)
+        test_manual(cam, lsc_map_auto, props, sync_latency)
         print "Testing auto capture results again"
-        test_auto(cam, props)
+        test_auto(cam, props, sync_latency)
 
-# A very loose definition for two floats being close to each other;
-# there may be different interpolation and rounding used to get the
-# two values, and all this test is looking at is whether there is
-# something obviously broken; it's not looking for a perfect match.
+
 def is_close_float(n1, n2):
+    """A very loose definition for two floats being close to each other.
+
+    there may be different interpolation and rounding used to get the
+    two values, and all this test is looking at is whether there is
+    something obviously broken; it's not looking for a perfect match.
+
+    Args:
+        n1:     float 1
+        n2:     float 2
+    Returns:
+        Boolean
+    """
     return abs(n1 - n2) < 0.05
 
+
 def is_close_rational(n1, n2):
     return is_close_float(its.objects.rational_to_float(n1),
                           its.objects.rational_to_float(n2))
 
+
 def draw_lsc_plot(w_map, h_map, lsc_map, name):
     for ch in range(4):
         fig = matplotlib.pyplot.figure()
-        ax = fig.gca(projection='3d')
+        ax = fig.gca(projection="3d")
         xs = numpy.array([range(w_map)] * h_map).reshape(h_map, w_map)
         ys = numpy.array([[i]*w_map for i in range(h_map)]).reshape(
                 h_map, w_map)
         zs = numpy.array(lsc_map[ch::4]).reshape(h_map, w_map)
         ax.plot_wireframe(xs, ys, zs)
-        matplotlib.pyplot.savefig("%s_plot_lsc_%s_ch%d.png"%(NAME,name,ch))
+        matplotlib.pyplot.savefig("%s_plot_lsc_%s_ch%d.png"%(NAME, name, ch))
 
-def test_auto(cam, props):
+
+def test_auto(cam, props, sync_latency):
     # Get 3A lock first, so the auto values in the capture result are
     # populated properly.
-    rect = [[0,0,1,1,1]]
+    rect = [[0, 0, 1, 1, 1]]
     mono_camera = its.caps.mono_camera(props)
     cam.do_3a(rect, rect, rect, do_af=False, mono_camera=mono_camera)
 
-    cap = cam.do_capture(auto_req)
+    cap = its.device.do_capture_with_latency(cam, auto_req, sync_latency)
     cap_res = cap["metadata"]
 
     gains = cap_res["android.colorCorrection.gains"]
     transform = cap_res["android.colorCorrection.transform"]
-    exp_time = cap_res['android.sensor.exposureTime']
+    exp_time = cap_res["android.sensor.exposureTime"]
     lsc_obj = cap_res["android.statistics.lensShadingCorrectionMap"]
     lsc_map = lsc_obj["map"]
     w_map = lsc_obj["width"]
@@ -125,11 +137,11 @@
     print "Transform:", [its.objects.rational_to_float(t)
                          for t in transform]
     if props["android.control.maxRegionsAe"] > 0:
-        print "AE region:", cap_res['android.control.aeRegions']
+        print "AE region:", cap_res["android.control.aeRegions"]
     if props["android.control.maxRegionsAf"] > 0:
-        print "AF region:", cap_res['android.control.afRegions']
+        print "AF region:", cap_res["android.control.afRegions"]
     if props["android.control.maxRegionsAwb"] > 0:
-        print "AWB region:", cap_res['android.control.awbRegions']
+        print "AWB region:", cap_res["android.control.awbRegions"]
     print "LSC map:", w_map, h_map, lsc_map[:8]
 
     assert(ctrl_mode == 1)
@@ -157,8 +169,9 @@
 
     return lsc_map
 
-def test_manual(cam, lsc_map_auto, props):
-    cap = cam.do_capture(manual_req)
+
+def test_manual(cam, lsc_map_auto, props, sync_latency):
+    cap = its.device.do_capture_with_latency(cam, manual_req, sync_latency)
     cap_res = cap["metadata"]
 
     gains = cap_res["android.colorCorrection.gains"]
@@ -166,7 +179,7 @@
     curves = [cap_res["android.tonemap.curve"]["red"],
               cap_res["android.tonemap.curve"]["green"],
               cap_res["android.tonemap.curve"]["blue"]]
-    exp_time = cap_res['android.sensor.exposureTime']
+    exp_time = cap_res["android.sensor.exposureTime"]
     lsc_obj = cap_res["android.statistics.lensShadingCorrectionMap"]
     lsc_map = lsc_obj["map"]
     w_map = lsc_obj["width"]
@@ -179,11 +192,11 @@
                          for t in transform]
     print "Tonemap:", curves[0][1::16]
     if props["android.control.maxRegionsAe"] > 0:
-        print "AE region:", cap_res['android.control.aeRegions']
+        print "AE region:", cap_res["android.control.aeRegions"]
     if props["android.control.maxRegionsAf"] > 0:
-        print "AF region:", cap_res['android.control.afRegions']
+        print "AF region:", cap_res["android.control.afRegions"]
     if props["android.control.maxRegionsAwb"] > 0:
-        print "AWB region:", cap_res['android.control.awbRegions']
+        print "AWB region:", cap_res["android.control.awbRegions"]
     print "LSC map:", w_map, h_map, lsc_map[:8]
 
     assert(ctrl_mode == 0)
@@ -218,6 +231,7 @@
 
     draw_lsc_plot(w_map, h_map, lsc_map, "manual")
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_crop_regions.py b/apps/CameraITS/tests/scene1/test_crop_regions.py
index 6d3dad1..59f884c 100644
--- a/apps/CameraITS/tests/scene1/test_crop_regions.py
+++ b/apps/CameraITS/tests/scene1/test_crop_regions.py
@@ -12,26 +12,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
 import numpy
 
-def main():
-    """Test that crop regions work.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
+NAME = os.path.basename(__file__).split(".")[0]
+# A list of 5 regions, specified in normalized (x,y,w,h) coords.
+# The regions correspond to: TL, TR, BL, BR, CENT
+REGIONS = [(0.0, 0.0, 0.5, 0.5),
+           (0.5, 0.0, 0.5, 0.5),
+           (0.0, 0.5, 0.5, 0.5),
+           (0.5, 0.5, 0.5, 0.5),
+           (0.25, 0.25, 0.5, 0.5)]
 
-    # A list of 5 regions, specified in normalized (x,y,w,h) coords.
-    # The regions correspond to: TL, TR, BL, BR, CENT
-    REGIONS = [(0.0, 0.0, 0.5, 0.5),
-               (0.5, 0.0, 0.5, 0.5),
-               (0.0, 0.5, 0.5, 0.5),
-               (0.5, 0.5, 0.5, 0.5),
-               (0.25, 0.25, 0.5, 0.5)]
+
+def main():
+    """Test that crop regions work."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -39,29 +39,30 @@
                              its.caps.freeform_crop(props) and
                              its.caps.per_frame_control(props))
 
-        a = props['android.sensor.info.activeArraySize']
+        a = props["android.sensor.info.activeArraySize"]
         ax, ay = a["left"], a["top"]
         aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
         e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
         print "Active sensor region (%d,%d %dx%d)" % (ax, ay, aw, ah)
 
         # Uses a 2x digital zoom.
-        assert(its.objects.get_max_digital_zoom(props) >= 2)
+        assert its.objects.get_max_digital_zoom(props) >= 2
 
         # Capture a full frame.
-        req = its.objects.manual_capture_request(s,e)
+        req = its.objects.manual_capture_request(s, e)
         cap_full = cam.do_capture(req)
         img_full = its.image.convert_capture_to_rgb_image(cap_full)
-        its.image.write_image(img_full, "%s_full.jpg" % (NAME))
         wfull, hfull = cap_full["width"], cap_full["height"]
+        its.image.write_image(
+                img_full, "%s_full_%dx%d.jpg" % (NAME, wfull, hfull))
 
         # Capture a burst of crop region frames.
         # Note that each region is 1/2x1/2 of the full frame, and is digitally
         # zoomed into the full size output image, so must be downscaled (below)
         # by 2x when compared to a tile of the full image.
         reqs = []
-        for x,y,w,h in REGIONS:
-            req = its.objects.manual_capture_request(s,e)
+        for x, y, w, h in REGIONS:
+            req = its.objects.manual_capture_request(s, e)
             req["android.scaler.cropRegion"] = {
                     "top": int(ah * y),
                     "left": int(aw * x),
@@ -70,7 +71,7 @@
             reqs.append(req)
         caps_regions = cam.do_capture(reqs)
         match_failed = False
-        for i,cap in enumerate(caps_regions):
+        for i, cap in enumerate(caps_regions):
             a = cap["metadata"]["android.scaler.cropRegion"]
             ax, ay = a["left"], a["top"]
             aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
@@ -83,13 +84,14 @@
             its.image.write_image(img_crop, "%s_crop%d.jpg" % (NAME, i))
             min_diff = None
             min_diff_region = None
-            for j,(x,y,w,h) in enumerate(REGIONS):
-                tile_full = its.image.get_image_patch(img_full, x,y,w,h)
+            for j, (x, y, w, h) in enumerate(REGIONS):
+                tile_full = its.image.get_image_patch(img_full, x, y, w, h)
                 wtest = min(tile_full.shape[1], aw)
                 htest = min(tile_full.shape[0], ah)
                 tile_full = tile_full[0:htest:, 0:wtest:, ::]
                 tile_crop = img_crop[0:htest:, 0:wtest:, ::]
-                its.image.write_image(tile_full, "%s_fullregion%d.jpg"%(NAME,j))
+                its.image.write_image(
+                        tile_full, "%s_fullregion%d.jpg" % (NAME, j))
                 diff = numpy.fabs(tile_full - tile_crop).mean()
                 if min_diff is None or diff < min_diff:
                     min_diff = diff
@@ -99,8 +101,8 @@
             print "Crop image %d (%d,%d %dx%d) best match with region %d"%(
                     i, ax, ay, aw, ah, min_diff_region)
 
-        assert(not match_failed)
+        assert not match_failed
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
index c447ae5..c60f71c 100644
--- a/apps/CameraITS/tests/scene1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -125,7 +125,7 @@
         print 'Diffs (%s):'%(ch), diffs
         for j, diff in enumerate(diffs):
             thresh = max(DIFF_THRESH, FRAC_THRESH*var_expected[i][j])
-            assert diff <= thresh
+            assert diff <= thresh, 'diff: %.5f, thresh: %.4f' % (diff, thresh)
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
index d087ab1..cc36990 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -12,27 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
-import its.device
-import its.caps
-import its.objects
 import os.path
-from matplotlib import pylab
+import its.caps
+import its.device
+import its.image
+import its.objects
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
 import numpy
 
-#AE must converge within this number of auto requests for EV
-THREASH_CONVERGE_FOR_EV = 8
+LOCKED = 3
+MAX_LUMA_DELTA_THRESH = 0.05
+NAME = os.path.basename(__file__).split('.')[0]
+THRESH_CONVERGE_FOR_EV = 8  # AE must converge in this num auto reqs for EV
+
 
 def main():
-    """Tests that EV compensation is applied.
-    """
-    LOCKED = 3
-
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    MAX_LUMA_DELTA_THRESH = 0.05
+    """Tests that EV compensation is applied."""
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -76,34 +72,40 @@
             # by tone curves.
             req['android.tonemap.mode'] = 0
             req['android.tonemap.curve'] = {
-                'red': [0.0,0.0, 1.0,1.0],
-                'green': [0.0,0.0, 1.0,1.0],
-                'blue': [0.0,0.0, 1.0,1.0]}
-            caps = cam.do_capture([req]*THREASH_CONVERGE_FOR_EV, fmt)
+                    'red': [0.0, 0.0, 1.0, 1.0],
+                    'green': [0.0, 0.0, 1.0, 1.0],
+                    'blue': [0.0, 0.0, 1.0, 1.0]}
+            caps = cam.do_capture([req]*THRESH_CONVERGE_FOR_EV, fmt)
 
             for cap in caps:
-                if (cap['metadata']['android.control.aeState'] == LOCKED):
+                if cap['metadata']['android.control.aeState'] == LOCKED:
                     y = its.image.convert_capture_to_planes(cap)[0]
-                    tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+                    tile = its.image.get_image_patch(y, 0.45, 0.45, 0.1, 0.1)
                     lumas.append(its.image.compute_image_means(tile)[0])
                     break
-            assert(cap['metadata']['android.control.aeState'] == LOCKED)
+            assert cap['metadata']['android.control.aeState'] == LOCKED
 
-        print "ev_step_size_in_stops", ev_per_step
+        print 'ev_step_size_in_stops', ev_per_step
         shift_mid = ev_shifts[imid]
         luma_normal = lumas[imid] / shift_mid
-        expected_lumas = [min(1.0, luma_normal * ev_shift) for ev_shift in ev_shifts]
+        expected_lumas = [min(1.0, luma_normal*ev_shift) for ev_shift in ev_shifts]
 
-        pylab.plot(ev_steps, lumas, 'r')
-        pylab.plot(ev_steps, expected_lumas, 'b')
-        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+        pylab.plot(ev_steps, lumas, '-ro')
+        pylab.plot(ev_steps, expected_lumas, '-bo')
+        pylab.title(NAME)
+        pylab.xlabel('EV Compensation')
+        pylab.ylabel('Mean Luma (Normalized)')
 
-        luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(ev_steps))]
+        matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
+
+        luma_diffs = [expected_lumas[i]-lumas[i] for i in range(len(ev_steps))]
         max_diff = max(abs(i) for i in luma_diffs)
         avg_diff = abs(numpy.array(luma_diffs)).mean()
-        print "Max delta between modeled and measured lumas:", max_diff
-        print "Avg delta between modeled and measured lumas:", avg_diff
-        assert(max_diff < MAX_LUMA_DELTA_THRESH)
+        print 'Max delta between modeled and measured lumas:', max_diff
+        print 'Avg delta between modeled and measured lumas:', avg_diff
+        assert max_diff < MAX_LUMA_DELTA_THRESH, 'diff: %.3f, THRESH: %.2f' % (
+                max_diff, MAX_LUMA_DELTA_THRESH)
+
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
index 32e5001..3d85517 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -86,11 +86,14 @@
                         greens.append(rgb_means[1])
                         blues.append(rgb_means[2])
                         print 'lumas in AE locked captures: ', luma_locked
+                        msg = 'AE locked lumas: %s, RTOL: %.2f' % (
+                                str(luma_locked), LUMA_LOCKED_TOL)
                         assert np.isclose(min(luma_locked), max(luma_locked),
-                                          rtol=LUMA_LOCKED_TOL)
+                                          rtol=LUMA_LOCKED_TOL), msg
             assert caps[THRESH_CONVERGE_FOR_EV-1]['metadata']['android.control.aeState'] == LOCKED
 
         pylab.plot(evs, lumas, '-ro')
+        pylab.title(NAME)
         pylab.xlabel('EV Compensation')
         pylab.ylabel('Mean Luma (Normalized)')
         matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
@@ -109,13 +112,13 @@
             else:
                 break
         # Only allow positive EVs to give saturated image
-        assert len(lumas) > 2
+        assert len(lumas) > 2, '3 or more unsaturated images needed'
         luma_diffs = np.diff(lumas)
         min_luma_diffs = min(luma_diffs)
         print 'Min of the luma value difference between adjacent ev comp: ',
         print min_luma_diffs
         # All luma brightness should be increasing with increasing ev comp.
-        assert min_luma_diffs > 0
+        assert min_luma_diffs > 0, 'Luma is not increasing!'
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index cac49d0..dabe7f0 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -64,12 +64,9 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
-
-        process_raw = (its.caps.raw16(props) and
-                       its.caps.manual_sensor(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:
@@ -91,7 +88,8 @@
             print 'Testing s:', s_test, 'e:', e_test
             req = its.objects.manual_capture_request(
                     s_test, e_test, 0.0, True, props)
-            cap = cam.do_capture(req, fmt)
+            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
@@ -104,7 +102,7 @@
                         (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: %.2fms, e_read: %.2fms, TOL=%.f%%' % (
+            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
@@ -122,12 +120,13 @@
             # do same in RAW space if possible
             if process_raw and debug:
                 aaw, aah = get_raw_active_array_size(props)
-                raw_cap = cam.do_capture(req,
-                                         {'format': 'rawStats',
-                                          'gridWidth': aaw/IMG_STATS_GRID,
-                                          'gridHeight': aah/IMG_STATS_GRID})
-                r, gr, gb, b = its.image.convert_capture_to_planes(raw_cap,
-                                                                   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]
diff --git a/apps/CameraITS/tests/scene1/test_format_combos.py b/apps/CameraITS/tests/scene1/test_format_combos.py
deleted file mode 100644
index ca65e4f..0000000
--- a/apps/CameraITS/tests/scene1/test_format_combos.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import its.image
-import its.caps
-import its.device
-import its.objects
-import its.error
-import its.target
-import sys
-import os
-
-NAME = os.path.basename(__file__).split(".")[0]
-STOP_AT_FIRST_FAILURE = False  # change to True to have test break @ 1st FAIL
-
-
-def main():
-    """Test different combinations of output formats."""
-
-    with its.device.ItsSession() as cam:
-
-        props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.raw16(props))
-
-        successes = []
-        failures = []
-        debug = its.caps.debug_mode()
-
-        # Two different requests: auto, and manual.
-        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req_aut = its.objects.auto_capture_request()
-        req_man = its.objects.manual_capture_request(s, e)
-        reqs = [req_aut,  # R0
-                req_man]  # R1
-
-        # 10 different combos of output formats; some are single surfaces, and
-        # some are multiple surfaces.
-        wyuv, hyuv = its.objects.get_available_output_sizes("yuv", props)[-1]
-        wjpg, hjpg = its.objects.get_available_output_sizes("jpg", props)[-1]
-        fmt_yuv_prev = {"format": "yuv", "width": wyuv, "height": hyuv}
-        fmt_yuv_full = {"format": "yuv"}
-        fmt_jpg_prev = {"format": "jpeg", "width": wjpg, "height": hjpg}
-        fmt_jpg_full = {"format": "jpeg"}
-        fmt_raw_full = {"format": "raw"}
-        fmt_combos = [
-            [fmt_yuv_prev],                              # F0
-            [fmt_yuv_full],                              # F1
-            [fmt_jpg_prev],                              # F2
-            [fmt_jpg_full],                              # F3
-            [fmt_raw_full],                              # F4
-            [fmt_yuv_prev, fmt_jpg_prev],                # F5
-            [fmt_yuv_prev, fmt_jpg_full],                # F6
-            [fmt_yuv_prev, fmt_raw_full],                # F7
-            [fmt_yuv_prev, fmt_jpg_prev, fmt_raw_full],  # F8
-            [fmt_yuv_prev, fmt_jpg_full, fmt_raw_full]]  # F9
-
-        # Two different burst lengths: single frame, and 3 frames.
-        burst_lens = [1,  # B0
-                      3]  # B1
-
-        # There are 2x10x2=40 different combinations. Run through them all.
-        n = 0
-        for r,req in enumerate(reqs):
-            for f,fmt_combo in enumerate(fmt_combos):
-                for b,burst_len in enumerate(burst_lens):
-                    try:
-                        caps = cam.do_capture([req]*burst_len, fmt_combo)
-                        successes.append((n,r,f,b))
-                        print "==> Success[%02d]: R%d F%d B%d" % (n,r,f,b)
-
-                        # Dump the captures out to jpegs in debug mode.
-                        if debug:
-                            if not isinstance(caps, list):
-                                caps = [caps]
-                            elif isinstance(caps[0], list):
-                                caps = sum(caps, [])
-                            for c, cap in enumerate(caps):
-                                img = its.image.convert_capture_to_rgb_image(cap, props=props)
-                                its.image.write_image(img,
-                                    "%s_n%02d_r%d_f%d_b%d_c%d.jpg"%(NAME,n,r,f,b,c))
-
-                    except Exception as e:
-                        print e
-                        print "==> Failure[%02d]: R%d F%d B%d" % (n,r,f,b)
-                        failures.append((n,r,f,b))
-                        if STOP_AT_FIRST_FAILURE:
-                            sys.exit(1)
-                    n += 1
-
-        num_fail = len(failures)
-        num_success = len(successes)
-        num_total = len(reqs)*len(fmt_combos)*len(burst_lens)
-        num_not_run = num_total - num_success - num_fail
-
-        print "\nFailures (%d / %d):" % (num_fail, num_total)
-        for (n,r,f,b) in failures:
-            print "  %02d: R%d F%d B%d" % (n,r,f,b)
-        print "\nSuccesses (%d / %d):" % (num_success, num_total)
-        for (n,r,f,b) in successes:
-            print "  %02d: R%d F%d B%d" % (n,r,f,b)
-        if num_not_run > 0:
-            print "\nNumber of tests not run: %d / %d" % (num_not_run, num_total)
-        print ""
-
-        # The test passes if all the combinations successfully capture.
-        assert num_fail == 0
-        assert num_success == num_total
-
-if __name__ == '__main__':
-    main()
-
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1/test_jpeg.py
index 6b14411..3abeef5 100644
--- a/apps/CameraITS/tests/scene1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_jpeg.py
@@ -12,33 +12,36 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
-import math
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.01
+
 
 def main():
     """Test that converted YUV images and device JPEG images look the same.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.01
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+        sync_latency = its.caps.sync_latency(props)
 
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         # YUV
         size = its.objects.get_available_output_sizes("yuv", props)[0]
-        out_surface = {"width":size[0], "height":size[1], "format":"yuv"}
-        cap = cam.do_capture(req, out_surface)
+        out_surface = {"width": size[0], "height": size[1], "format": "yuv"}
+        cap = its.device.do_capture_with_latency(
+                cam, req, sync_latency, out_surface)
         img = its.image.convert_capture_to_rgb_image(cap)
         its.image.write_image(img, "%s_fmt=yuv.jpg" % (NAME))
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
@@ -46,8 +49,9 @@
 
         # JPEG
         size = its.objects.get_available_output_sizes("jpg", props)[0]
-        out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
-        cap = cam.do_capture(req, out_surface)
+        out_surface = {"width": size[0], "height": size[1], "format": "jpg"}
+        cap = its.device.do_capture_with_latency(
+                cam, req, sync_latency, out_surface)
         img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
         its.image.write_image(img, "%s_fmt=jpg.jpg" % (NAME))
         tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
@@ -56,7 +60,9 @@
         rms_diff = math.sqrt(
                 sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
         print "RMS difference:", rms_diff
-        assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+        msg = "RMS difference: %.4f, spec: %.3f" % (rms_diff,
+                                                    THRESHOLD_MAX_RMS_DIFF)
+        assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1/test_latching.py
index 79f0f1a..362b7b8 100644
--- a/apps/CameraITS/tests/scene1/test_latching.py
+++ b/apps/CameraITS/tests/scene1/test_latching.py
@@ -12,15 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-from matplotlib import pylab
-import os.path
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split('.')[0]
+
 
 def main():
     """Test that settings latch on the right frame.
@@ -29,14 +33,13 @@
     request parameters between shots. Checks that the images that come back
     have the expected properties.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.full_or_better(props))
 
-        _,fmt = its.objects.get_fastest_manual_capture_settings(props)
-        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        _, fmt = its.objects.get_fastest_manual_capture_settings(props)
+        e, s = its.target.get_target_exposure_combos(cam)['midExposureTime']
         e /= 2.0
 
         r_means = []
@@ -44,26 +47,26 @@
         b_means = []
 
         reqs = [
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
-            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
-            its.objects.manual_capture_request(s,  e,   0.0, True, props),
-            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
-            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
-            ]
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s*2, e, 0.0, True, props),
+                its.objects.manual_capture_request(s*2, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s*2, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+                its.objects.manual_capture_request(s, e, 0.0, True, props),
+                its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+                its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+                ]
 
         caps = cam.do_capture(reqs, fmt)
-        for i,cap in enumerate(caps):
+        for i, cap in enumerate(caps):
             img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, "%s_i=%02d.jpg" % (NAME, i))
+            its.image.write_image(img, '%s_i=%02d.jpg' % (NAME, i))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
             r_means.append(rgb_means[0])
@@ -72,15 +75,18 @@
 
         # Draw a plot.
         idxs = range(len(r_means))
-        pylab.plot(idxs, r_means, 'r')
-        pylab.plot(idxs, g_means, 'g')
-        pylab.plot(idxs, b_means, 'b')
-        pylab.ylim([0,1])
-        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+        pylab.plot(idxs, r_means, '-ro')
+        pylab.plot(idxs, g_means, '-go')
+        pylab.plot(idxs, b_means, '-bo')
+        pylab.ylim([0, 1])
+        pylab.title(NAME)
+        pylab.xlabel('capture')
+        pylab.ylabel('RGB means')
+        matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
 
         g_avg = sum(g_means) / len(g_means)
         g_ratios = [g / g_avg for g in g_means]
-        g_hilo = [g>1.0 for g in g_ratios]
+        g_hilo = [g > 1.0 for g in g_ratios]
         assert(g_hilo == [False, False, True, True, False, False, True,
                           False, True, False, True, False, True, True])
 
diff --git a/apps/CameraITS/tests/scene1/test_linearity.py b/apps/CameraITS/tests/scene1/test_linearity.py
index 1f4aa14..244a2e6 100644
--- a/apps/CameraITS/tests/scene1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1/test_linearity.py
@@ -12,17 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import numpy
-import math
-from matplotlib import pylab
-import os.path
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+import numpy
 
 NAME = os.path.basename(__file__).split('.')[0]
 RESIDUAL_THRESHOLD = 0.0003  # approximately each sample is off by 2/255
@@ -40,14 +39,14 @@
     linear R,G,B pixel data.
     """
     gamma_lut = numpy.array(
-        sum([[i/LM1, math.pow(i/LM1, 1/2.2)] for i in xrange(L)], []))
+            sum([[i/LM1, math.pow(i/LM1, 1/2.2)] for i in xrange(L)], []))
     inv_gamma_lut = numpy.array(
-        sum([[i/LM1, math.pow(i/LM1, 2.2)] for i in xrange(L)], []))
+            sum([[i/LM1, math.pow(i/LM1, 2.2)] for i in xrange(L)], []))
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+        sync_latency = its.caps.sync_latency(props)
 
         debug = its.caps.debug_mode()
         largest_yuv = its.objects.get_largest_yuv_format(props)
@@ -57,7 +56,7 @@
             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)["midSensitivity"]
+        e, s = its.target.get_target_exposure_combos(cam)['midSensitivity']
         s /= 2
         sens_range = props['android.sensor.info.sensitivityRange']
         sensitivities = [s*1.0/3.0, s*2.0/3.0, s, s*4.0/3.0, s*5.0/3.0]
@@ -68,20 +67,21 @@
         req['android.blackLevel.lock'] = True
         req['android.tonemap.mode'] = 0
         req['android.tonemap.curve'] = {
-            'red': gamma_lut.tolist(),
-            'green': gamma_lut.tolist(),
-            'blue': gamma_lut.tolist()}
+                'red': gamma_lut.tolist(),
+                'green': gamma_lut.tolist(),
+                'blue': gamma_lut.tolist()}
 
         r_means = []
         g_means = []
         b_means = []
 
         for sens in sensitivities:
-            req["android.sensor.sensitivity"] = sens
-            cap = cam.do_capture(req, fmt)
+            req['android.sensor.sensitivity'] = sens
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(
-                img, '%s_sens=%04d.jpg' % (NAME, sens))
+                    img, '%s_sens=%04d.jpg' % (NAME, sens))
             img = its.image.apply_lut_to_image(img, inv_gamma_lut[1::2] * LM1)
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
@@ -104,7 +104,9 @@
             line, residuals, _, _, _ = numpy.polyfit(range(len(sensitivities)),
                                                      means, 1, full=True)
             print 'Line: m=%f, b=%f, resid=%f'%(line[0], line[1], residuals[0])
-            assert residuals[0] < RESIDUAL_THRESHOLD
+            msg = 'residual: %.5f, THRESH: %.4f' % (
+                    residuals[0], RESIDUAL_THRESHOLD)
+            assert residuals[0] < RESIDUAL_THRESHOLD, msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1/test_locked_burst.py
index befbbed..76a8203 100644
--- a/apps/CameraITS/tests/scene1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1/test_locked_burst.py
@@ -12,15 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
-import its.device
-import its.objects
-import its.caps
 import os.path
-import numpy
-from matplotlib import pylab
-import matplotlib
-import matplotlib.pyplot
+import its.caps
+import its.device
+import its.image
+import its.objects
+
+BURST_LEN = 8
+COLORS = ['R', 'G', 'B']
+FPS_MAX_DIFF = 2.0
+NAME = os.path.basename(__file__).split('.')[0]
+SPREAD_THRESH_MANUAL_SENSOR = 0.01
+SPREAD_THRESH = 0.03
+VALUE_THRESH = 0.1
+
 
 def main():
     """Test 3A lock + YUV burst (using auto settings).
@@ -29,12 +34,6 @@
     don't have MANUAL_SENSOR or PER_FRAME_CONTROLS. The test checks
     YUV image consistency while the frame rate check is in CTS.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    BURST_LEN = 8
-    SPREAD_THRESH_MANUAL_SENSOR = 0.01
-    SPREAD_THRESH = 0.03
-    FPS_MAX_DIFF = 2.0
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -49,9 +48,10 @@
         fmt = its.objects.get_largest_yuv_format(props)
 
         # After 3A has converged, lock AE+AWB for the duration of the test.
+        print 'Locking AE & AWB'
         req = its.objects.fastest_auto_capture_request(props)
-        req["android.control.awbLock"] = True
-        req["android.control.aeLock"] = True
+        req['android.control.awbLock'] = True
+        req['android.control.aeLock'] = True
 
         # Capture bursts of YUV shots.
         # Get the mean values of a center patch for each.
@@ -59,23 +59,32 @@
         g_means = []
         b_means = []
         caps = cam.do_capture([req]*BURST_LEN, fmt)
-        for i,cap in enumerate(caps):
+        for i, cap in enumerate(caps):
             img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, "%s_frame%d.jpg"%(NAME,i))
+            its.image.write_image(img, '%s_frame%d.jpg'%(NAME, i))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             means = its.image.compute_image_means(tile)
             r_means.append(means[0])
             g_means.append(means[1])
             b_means.append(means[2])
 
-        # Pass/fail based on center patch similarity.
-        for means in [r_means, g_means, b_means]:
-            spread = max(means) - min(means)
-            print "Patch mean spread", spread, \
-                    " (min/max: ",  min(means), "/", max(means), ")"
+        # Assert center patch brightness & similarity
+        for i, means in enumerate([r_means, g_means, b_means]):
+            plane = COLORS[i]
+            min_means = min(means)
+            spread = max(means) - min_means
+            print '%s patch mean spread %.5f. means = [' % (plane, spread),
+            for j in range(BURST_LEN):
+                print '%.5f' % means[j],
+            print ']'
+            e_msg = 'Image too dark!  %s: %.5f, THRESH: %.2f' % (
+                    plane, min_means, VALUE_THRESH)
+            assert min_means > VALUE_THRESH, e_msg
             threshold = SPREAD_THRESH_MANUAL_SENSOR \
                     if its.caps.manual_sensor(props) else SPREAD_THRESH
-            assert(spread < threshold)
+            e_msg = '%s center patch spread: %.5f, THRESH: %.2f' % (
+                    plane, spread, threshold)
+            assert spread < threshold, e_msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1/test_multi_camera_match.py
index 0eee74a..76c8146 100644
--- a/apps/CameraITS/tests/scene1/test_multi_camera_match.py
+++ b/apps/CameraITS/tests/scene1/test_multi_camera_match.py
@@ -35,11 +35,9 @@
     yuv_sizes = {}
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props) and
-                             its.caps.logical_multi_camera(props) and
+        its.caps.skip_unless(its.caps.per_frame_control(props) and
                              its.caps.raw16(props) and
-                             its.caps.manual_sensor(props))
+                             its.caps.logical_multi_camera(props))
         ids = its.caps.logical_multi_camera_physical_ids(props)
         max_raw_size = its.objects.get_available_output_sizes('raw', props)[0]
         for i in ids:
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1/test_param_color_correction.py
index 83f4f7f..f49eba5 100644
--- a/apps/CameraITS/tests/scene1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1/test_param_color_correction.py
@@ -12,15 +12,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-from matplotlib import pylab
-import os.path
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split('.')[0]
+THRESHOLD_MAX_DIFF = 0.1
+
 
 def main():
     """Test that the android.colorCorrection.* params are applied when set.
@@ -31,15 +36,12 @@
 
     Uses a linear tonemap.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_DIFF = 0.1
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props) and
                              not its.caps.mono_camera(props))
+        sync_latency = its.caps.sync_latency(props)
 
         # Baseline request
         debug = its.caps.debug_mode()
@@ -50,23 +52,23 @@
             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)["midSensitivity"]
+        e, s = its.target.get_target_exposure_combos(cam)['midSensitivity']
         req = its.objects.manual_capture_request(s, e, 0.0, True, props)
-        req["android.colorCorrection.mode"] = 0
+        req['android.colorCorrection.mode'] = 0
 
         # Transforms:
         # 1. Identity
         # 2. Identity
         # 3. Boost blue
-        transforms = [its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,1]),
-                      its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,1]),
-                      its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,2])]
+        transforms = [its.objects.int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 1]),
+                      its.objects.int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 1]),
+                      its.objects.int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 2])]
 
         # Gains:
         # 1. Unit
         # 2. Boost red
         # 3. Unit
-        gains = [[1,1,1,1], [2,1,1,1], [1,1,1,1]]
+        gains = [[1, 1, 1, 1], [2, 1, 1, 1], [1, 1, 1, 1]]
 
         r_means = []
         g_means = []
@@ -77,36 +79,40 @@
         # 2. With a higher red gain, and identity transform.
         # 3. With unit gains, and a transform that boosts blue.
         for i in range(len(transforms)):
-            req["android.colorCorrection.transform"] = transforms[i]
-            req["android.colorCorrection.gains"] = gains[i]
-            cap = cam.do_capture(req, fmt)
+            req['android.colorCorrection.transform'] = transforms[i]
+            req['android.colorCorrection.gains'] = gains[i]
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, "%s_req=%d.jpg" % (NAME, i))
+            its.image.write_image(img, '%s_req=%d.jpg' % (NAME, i))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
             r_means.append(rgb_means[0])
             g_means.append(rgb_means[1])
             b_means.append(rgb_means[2])
             ratios = [rgb_means[0] / rgb_means[1], rgb_means[2] / rgb_means[1]]
-            print "Means = ", rgb_means, "   Ratios =", ratios
+            print 'Means = ', rgb_means, '   Ratios =', ratios
 
         # Draw a plot.
         domain = range(len(transforms))
-        pylab.plot(domain, r_means, 'r')
-        pylab.plot(domain, g_means, 'g')
-        pylab.plot(domain, b_means, 'b')
-        pylab.ylim([0,1])
-        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+        pylab.plot(domain, r_means, '-ro')
+        pylab.plot(domain, g_means, '-go')
+        pylab.plot(domain, b_means, '-bo')
+        pylab.ylim([0, 1])
+        pylab.title(NAME)
+        pylab.xlabel('Unity, R boost, B boost')
+        pylab.ylabel('RGB means')
+        matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
 
         # Expect G0 == G1 == G2, R0 == 0.5*R1 == R2, B0 == B1 == 0.5*B2
         # Also need to ensure that the image is not clamped to white/black.
-        assert(all(g_means[i] > 0.2 and g_means[i] < 0.8 for i in xrange(3)))
-        assert(abs(g_means[1] - g_means[0]) < THRESHOLD_MAX_DIFF)
-        assert(abs(g_means[2] - g_means[1]) < THRESHOLD_MAX_DIFF)
-        assert(abs(r_means[2] - r_means[0]) < THRESHOLD_MAX_DIFF)
-        assert(abs(r_means[1] - 2.0 * r_means[0]) < THRESHOLD_MAX_DIFF)
-        assert(abs(b_means[1] - b_means[0]) < THRESHOLD_MAX_DIFF)
-        assert(abs(b_means[2] - 2.0 * b_means[0]) < THRESHOLD_MAX_DIFF)
+        assert all(g_means[i] > 0.2 and g_means[i] < 0.8 for i in xrange(3))
+        assert abs(g_means[1] - g_means[0]) < THRESHOLD_MAX_DIFF
+        assert abs(g_means[2] - g_means[1]) < THRESHOLD_MAX_DIFF
+        assert abs(r_means[2] - r_means[0]) < THRESHOLD_MAX_DIFF
+        assert abs(r_means[1] - 2.0 * r_means[0]) < THRESHOLD_MAX_DIFF
+        assert abs(b_means[1] - b_means[0]) < THRESHOLD_MAX_DIFF
+        assert abs(b_means[2] - 2.0 * b_means[0]) < THRESHOLD_MAX_DIFF
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
index 90ad0b6..3995c6e 100644
--- a/apps/CameraITS/tests/scene1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
@@ -12,20 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-from matplotlib import pylab
-import os.path
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split('.')[0]
+
 
 def main():
-    """Test that the android.sensor.exposureTime parameter is applied.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
+    """Test that the android.sensor.exposureTime parameter is applied."""
 
     exp_times = []
     r_means = []
@@ -34,8 +36,8 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+        sync_latency = its.caps.sync_latency(props)
 
         debug = its.caps.debug_mode()
         largest_yuv = its.objects.get_largest_yuv_format(props)
@@ -45,13 +47,15 @@
             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)["midExposureTime"]
-        for i,e_mult in enumerate([0.8, 0.9, 1.0, 1.1, 1.2]):
-            req = its.objects.manual_capture_request(s, e * e_mult, 0.0, True, props)
-            cap = cam.do_capture(req, fmt)
+        e, s = its.target.get_target_exposure_combos(cam)['midExposureTime']
+        for i, e_mult in enumerate([0.8, 0.9, 1.0, 1.1, 1.2]):
+            req = its.objects.manual_capture_request(
+                    s, e * e_mult, 0.0, True, props)
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(
-                    img, "%s_frame%d.jpg" % (NAME, i))
+                    img, '%s_frame%d.jpg' % (NAME, i))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
             exp_times.append(e * e_mult)
@@ -60,16 +64,20 @@
             b_means.append(rgb_means[2])
 
     # Draw a plot.
-    pylab.plot(exp_times, r_means, 'r')
-    pylab.plot(exp_times, g_means, 'g')
-    pylab.plot(exp_times, b_means, 'b')
-    pylab.ylim([0,1])
-    matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+    pylab.plot(exp_times, r_means, '-ro')
+    pylab.plot(exp_times, g_means, '-go')
+    pylab.plot(exp_times, b_means, '-bo')
+    pylab.ylim([0, 1])
+    pylab.title(NAME)
+    pylab.xlabel('Exposure times (ns)')
+    pylab.ylabel('RGB means')
+    plot_name = '%s_plot_means.png' % NAME
+    matplotlib.pyplot.savefig(plot_name)
 
     # Test for pass/fail: check that each shot is brighter than the previous.
     for means in [r_means, g_means, b_means]:
         for i in range(len(means)-1):
-            assert(means[i+1] > means[i])
+            assert means[i+1] > means[i], 'See %s' % plot_name
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index cfd88d4..eb9628f 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -22,7 +22,7 @@
 NAME = os.path.basename(__file__).split('.')[0]
 GRADIENT_DELTA = 0.1
 Y_RELATIVE_DELTA_FLASH = 0.1  # 10%
-Y_RELATIVE_DELTA_TORCH = 0.05 # 5%
+Y_RELATIVE_DELTA_TORCH = 0.05  # 5%
 
 
 def main():
@@ -31,8 +31,8 @@
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.flash(props) and
-                             its.caps.per_frame_control(props))
+                             its.caps.flash(props))
+        sync_latency = its.caps.sync_latency(props)
 
         flash_modes_reported = []
         flash_states_reported = []
@@ -56,7 +56,8 @@
 
         for f in [0, 1, 2]:
             req['android.flash.mode'] = f
-            cap = cam.do_capture(req, fmt)
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
             flash_modes_reported.append(cap['metadata']['android.flash.mode'])
             flash_states_reported.append(cap['metadata']['android.flash.state'])
             y, _, _ = its.image.convert_capture_to_planes(cap, props)
diff --git a/apps/CameraITS/tests/scene1/test_param_sensitivity.py b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
index 0d40042..ea4b3ec 100644
--- a/apps/CameraITS/tests/scene1/test_param_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
@@ -12,22 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-from matplotlib import pylab
-import os.path
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_STEPS = 5
+
 
 def main():
-    """Test that the android.sensor.sensitivity parameter is applied.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    NUM_STEPS = 5
+    """Test that the android.sensor.sensitivity parameter is applied."""
 
     sensitivities = None
     r_means = []
@@ -36,8 +37,8 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+        sync_latency = its.caps.sync_latency(props)
 
         debug = its.caps.debug_mode()
         largest_yuv = its.objects.get_largest_yuv_format(props)
@@ -47,17 +48,18 @@
             match_ar = (largest_yuv['width'], largest_yuv['height'])
             fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
 
-        expt,_ = its.target.get_target_exposure_combos(cam)["midSensitivity"]
+        expt, _ = its.target.get_target_exposure_combos(cam)['midSensitivity']
         sens_range = props['android.sensor.info.sensitivityRange']
         sens_step = (sens_range[1] - sens_range[0]) / float(NUM_STEPS-1)
-        sensitivities = [sens_range[0] + i * sens_step for i in range(NUM_STEPS)]
+        sensitivities = [
+                sens_range[0] + i * sens_step for i in range(NUM_STEPS)]
 
         for s in sensitivities:
             req = its.objects.manual_capture_request(s, expt)
-            cap = cam.do_capture(req, fmt)
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(
-                    img, "%s_iso=%04d.jpg" % (NAME, s))
+            its.image.write_image(img, '%s_iso=%04d.jpg' % (NAME, s))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
             r_means.append(rgb_means[0])
@@ -65,16 +67,19 @@
             b_means.append(rgb_means[2])
 
     # Draw a plot.
-    pylab.plot(sensitivities, r_means, 'r')
-    pylab.plot(sensitivities, g_means, 'g')
-    pylab.plot(sensitivities, b_means, 'b')
-    pylab.ylim([0,1])
-    matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+    pylab.plot(sensitivities, r_means, '-ro')
+    pylab.plot(sensitivities, g_means, '-go')
+    pylab.plot(sensitivities, b_means, '-bo')
+    pylab.ylim([0, 1])
+    pylab.title(NAME)
+    pylab.xlabel('Gain (ISO)')
+    pylab.ylabel('RGB means')
+    matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
 
     # Test for pass/fail: check that each shot is brighter than the previous.
     for means in [r_means, g_means, b_means]:
         for i in range(len(means)-1):
-            assert(means[i+1] > means[i])
+            assert means[i+1] > means[i]
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_param_shading_mode.py b/apps/CameraITS/tests/scene1/test_param_shading_mode.py
index 45c9a12..85bacf0 100644
--- a/apps/CameraITS/tests/scene1/test_param_shading_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_shading_mode.py
@@ -12,15 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
+
 import its.caps
 import its.device
 import its.image
 import its.objects
+
 import matplotlib
-import numpy
-import os
-import os.path
 from matplotlib import pylab
+import numpy
+
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_SHADING_MODE_SWITCH_LOOPS = 3
+THRESHOLD_DIFF_RATIO = 0.15
+
 
 def main():
     """Test that the android.shading.mode param is applied.
@@ -28,10 +34,6 @@
     Switching shading modes and checks that the lens shading maps are
     modified as expected.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    NUM_SHADING_MODE_SWITCH_LOOPS = 3
-    THRESHOLD_DIFF_RATIO = 0.15
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -43,8 +45,8 @@
         mono_camera = its.caps.mono_camera(props)
 
         # lsc_off devices should always support OFF(0), FAST(1), and HQ(2)
-        assert(props.has_key("android.shading.availableModes") and
-               set(props["android.shading.availableModes"]) == set([0, 1, 2]))
+        assert(props.has_key('android.shading.availableModes') and
+               set(props['android.shading.availableModes']) == set([0, 1, 2]))
 
         # Test 1: Switching shading modes several times and verify:
         #   1. Lens shading maps with mode OFF are all 1.0
@@ -52,7 +54,7 @@
         #      shading modes.
         #   3. Lens shading maps with mode HIGH_QUALITY are similar after
         #      switching shading modes.
-        cam.do_3a(mono_camera=mono_camera);
+        cam.do_3a(mono_camera=mono_camera)
 
         # Get the reference lens shading maps for OFF, FAST, and HIGH_QUALITY
         # in different sessions.
@@ -60,57 +62,60 @@
         reference_maps = [[] for mode in range(3)]
         num_map_gains = 0
         for mode in range(1, 3):
-            req = its.objects.auto_capture_request();
-            req["android.statistics.lensShadingMapMode"] = 1
-            req["android.shading.mode"] = mode
-            cap_res = cam.do_capture(req)["metadata"]
-            lsc_map = cap_res["android.statistics.lensShadingCorrectionMap"]
-            assert(lsc_map.has_key("width") and
-                   lsc_map.has_key("height") and
-                   lsc_map["width"] != None and lsc_map["height"] != None)
+            req = its.objects.auto_capture_request()
+            req['android.statistics.lensShadingMapMode'] = 1
+            req['android.shading.mode'] = mode
+            cap_res = cam.do_capture(req)['metadata']
+            lsc_map = cap_res['android.statistics.lensShadingCorrectionMap']
+            assert(lsc_map.has_key('width') and
+                   lsc_map.has_key('height') and
+                   lsc_map['width'] is not None and
+                   lsc_map['height'] is not None)
             if mode == 1:
-                num_map_gains = lsc_map["width"] * lsc_map["height"] * 4
+                num_map_gains = lsc_map['width'] * lsc_map['height'] * 4
                 reference_maps[0] = [1.0] * num_map_gains
-            reference_maps[mode] = lsc_map["map"]
+            reference_maps[mode] = lsc_map['map']
 
         # Get the lens shading maps while switching modes in one session.
         reqs = []
         for i in range(NUM_SHADING_MODE_SWITCH_LOOPS):
             for mode in range(3):
-                req = its.objects.auto_capture_request();
-                req["android.statistics.lensShadingMapMode"] = 1
-                req["android.shading.mode"] = mode
-                reqs.append(req);
+                req = its.objects.auto_capture_request()
+                req['android.statistics.lensShadingMapMode'] = 1
+                req['android.shading.mode'] = mode
+                reqs.append(req)
 
         caps = cam.do_capture(reqs)
 
         # shading_maps[mode][loop]
         shading_maps = [[[] for loop in range(NUM_SHADING_MODE_SWITCH_LOOPS)]
-                for mode in range(3)]
+                        for mode in range(3)]
 
         # Get the shading maps out of capture results
         for i in range(len(caps)):
             shading_maps[i % 3][i / 3] = \
-                    caps[i]["metadata"] \
-                    ["android.statistics.lensShadingCorrectionMap"]["map"]
+                    caps[i]['metadata']['android.statistics.lensShadingCorrectionMap']['map']
 
         # Draw the maps
         for mode in range(3):
             for i in range(NUM_SHADING_MODE_SWITCH_LOOPS):
                 pylab.clf()
-                pylab.plot(range(num_map_gains), shading_maps[mode][i], 'r')
-                pylab.plot(range(num_map_gains), reference_maps[mode], 'g')
+                pylab.plot(range(num_map_gains), shading_maps[mode][i], '-ro')
+                pylab.plot(range(num_map_gains), reference_maps[mode], '-go')
                 pylab.xlim([0, num_map_gains])
                 pylab.ylim([0.9, 4.0])
-                matplotlib.pyplot.savefig("%s_ls_maps_mode_%d_loop_%d.png" %
-                                          (NAME, mode, i))
+                name = '%s_ls_maps_mode_%d_loop_%d' % (NAME, mode, i)
+                pylab.title(name)
+                pylab.xlabel('Map gains')
+                pylab.ylabel('Lens shading maps')
+                matplotlib.pyplot.savefig('%s.png' % name)
 
-        print "Verifying lens shading maps with mode OFF are all 1.0"
+        print 'Verifying lens shading maps with mode OFF are all 1.0'
         for i in range(NUM_SHADING_MODE_SWITCH_LOOPS):
-            assert(numpy.allclose(shading_maps[0][i], reference_maps[0]))
+            assert numpy.allclose(shading_maps[0][i], reference_maps[0])
 
         for mode in range(1, 3):
-            print "Verifying lens shading maps with mode", mode, "are similar"
+            print 'Verifying lens shading maps with mode', mode, 'are similar'
             for i in range(NUM_SHADING_MODE_SWITCH_LOOPS):
                 assert(numpy.allclose(shading_maps[mode][i],
                                       reference_maps[mode],
diff --git a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
index 73c001d..8b3ef86 100644
--- a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
+++ b/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
@@ -12,28 +12,31 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.device
+import os.path
+
 import its.caps
+import its.device
 import its.image
 import its.objects
 import its.target
-import os.path
-from matplotlib import pylab
+
 import matplotlib
-import matplotlib.pyplot
+from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split('.')[0]
+RATIO_THRESHOLD = 0.1  # Each raw image
+# Waive the check if raw pixel value is below this level (signal too small
+# that small black level error converts to huge error in percentage)
+RAW_PIXEL_VAL_THRESHOLD = 0.03
+
 
 def main():
-    """Capture a set of raw/yuv images with different
-        sensitivity/post Raw sensitivity boost combination
+    """Check post RAW sensitivity boost.
+
+        Capture a set of raw/yuv images with different
+        sensitivity/post RAW sensitivity boost combination
         and check if the output pixel mean matches request settings
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    # Each raw image
-    RATIO_THRESHOLD = 0.1
-    # Waive the check if raw pixel value is below this level (signal too small
-    # that small black level error converts to huge error in percentage)
-    RAW_PIXEL_VAL_THRESHOLD = 0.03
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -43,8 +46,8 @@
                              its.caps.per_frame_control(props) and
                              not its.caps.mono_camera(props))
 
-        w,h = its.objects.get_available_output_sizes(
-                "yuv", props, (1920, 1080))[0]
+        w, h = its.objects.get_available_output_sizes(
+                'yuv', props, (1920, 1080))[0]
 
         if its.caps.raw16(props):
             raw_format = 'raw'
@@ -52,19 +55,18 @@
             raw_format = 'raw10'
         elif its.caps.raw12(props):
             raw_format = 'raw12'
-        else: # should not reach here
+        else:  # should not reach here
             raise its.error.Error('Cannot find available RAW output format')
 
-        out_surfaces = [{"format": raw_format},
-                        {"format": "yuv", "width": w, "height": h}]
+        out_surfaces = [{'format': raw_format},
+                        {'format': 'yuv', 'width': w, 'height': h}]
 
         sens_min, sens_max = props['android.sensor.info.sensitivityRange']
         sens_boost_min, sens_boost_max = \
                 props['android.control.postRawSensitivityBoostRange']
 
-
         e_target, s_target = \
-                its.target.get_target_exposure_combos(cam)["midSensitivity"]
+                its.target.get_target_exposure_combos(cam)['midSensitivity']
 
         reqs = []
         settings = []
@@ -97,51 +99,60 @@
             (s, s_boost) = settings[i]
             raw_cap = raw_caps[i]
             yuv_cap = yuv_caps[i]
-            raw_rgb = its.image.convert_capture_to_rgb_image(raw_cap, props=props)
+            raw_rgb = its.image.convert_capture_to_rgb_image(
+                    raw_cap, props=props)
             yuv_rgb = its.image.convert_capture_to_rgb_image(yuv_cap)
-            raw_tile = its.image.get_image_patch(raw_rgb, 0.45,0.45,0.1,0.1)
-            yuv_tile = its.image.get_image_patch(yuv_rgb, 0.45,0.45,0.1,0.1)
+            raw_tile = its.image.get_image_patch(raw_rgb, 0.45, 0.45, 0.1, 0.1)
+            yuv_tile = its.image.get_image_patch(yuv_rgb, 0.45, 0.45, 0.1, 0.1)
             raw_rgb_means.append(its.image.compute_image_means(raw_tile))
             yuv_rgb_means.append(its.image.compute_image_means(yuv_tile))
-            its.image.write_image(raw_tile,
-                    "%s_raw_s=%04d_boost=%04d.jpg" % (NAME,s,s_boost))
-            its.image.write_image(yuv_tile,
-                    "%s_yuv_s=%04d_boost=%04d.jpg" % (NAME,s,s_boost))
-            print "s=%d, s_boost=%d: raw_means %s, yuv_means %s"%(
-                    s,s_boost,raw_rgb_means[-1], yuv_rgb_means[-1])
+            its.image.write_image(raw_tile, '%s_raw_s=%04d_boost=%04d.jpg' % (
+                    NAME, s, s_boost))
+            its.image.write_image(yuv_tile, '%s_yuv_s=%04d_boost=%04d.jpg' % (
+                    NAME, s, s_boost))
+            print 's=%d, s_boost=%d: raw_means %s, yuv_means %s'%(
+                    s, s_boost, raw_rgb_means[-1], yuv_rgb_means[-1])
 
         xs = range(len(reqs))
-        pylab.plot(xs, [rgb[0] for rgb in raw_rgb_means], 'r')
-        pylab.plot(xs, [rgb[1] for rgb in raw_rgb_means], 'g')
-        pylab.plot(xs, [rgb[2] for rgb in raw_rgb_means], 'b')
-        pylab.ylim([0,1])
-        matplotlib.pyplot.savefig("%s_raw_plot_means.png" % (NAME))
+        pylab.plot(xs, [rgb[0] for rgb in raw_rgb_means], '-ro')
+        pylab.plot(xs, [rgb[1] for rgb in raw_rgb_means], '-go')
+        pylab.plot(xs, [rgb[2] for rgb in raw_rgb_means], '-bo')
+        pylab.ylim([0, 1])
+        name = '%s_raw_plot_means' % NAME
+        pylab.title(name)
+        pylab.xlabel('requests')
+        pylab.ylabel('RGB means')
+        matplotlib.pyplot.savefig('%s.png' % name)
         pylab.clf()
-        pylab.plot(xs, [rgb[0] for rgb in yuv_rgb_means], 'r')
-        pylab.plot(xs, [rgb[1] for rgb in yuv_rgb_means], 'g')
-        pylab.plot(xs, [rgb[2] for rgb in yuv_rgb_means], 'b')
-        pylab.ylim([0,1])
-        matplotlib.pyplot.savefig("%s_yuv_plot_means.png" % (NAME))
+        pylab.plot(xs, [rgb[0] for rgb in yuv_rgb_means], '-ro')
+        pylab.plot(xs, [rgb[1] for rgb in yuv_rgb_means], '-go')
+        pylab.plot(xs, [rgb[2] for rgb in yuv_rgb_means], '-bo')
+        pylab.ylim([0, 1])
+        name = '%s_yuv_plot_means' % NAME
+        pylab.title(name)
+        pylab.xlabel('requests')
+        pylab.ylabel('RGB means')
+        matplotlib.pyplot.savefig('%s.png' % name)
 
-        rgb_str = ["R", "G", "B"]
+        rgb_str = ['R', 'G', 'B']
         # Test that raw means is about 2x brighter than next step
         for step in range(1, len(reqs)):
-            (s_prev, s_boost_prev) = settings[step - 1]
+            (s_prev, _) = settings[step - 1]
             (s, s_boost) = settings[step]
             expect_raw_ratio = s_prev / float(s)
             raw_thres_min = expect_raw_ratio * (1 - RATIO_THRESHOLD)
             raw_thres_max = expect_raw_ratio * (1 + RATIO_THRESHOLD)
             for rgb in range(3):
                 ratio = raw_rgb_means[step - 1][rgb] / raw_rgb_means[step][rgb]
-                print ("Step (%d,%d) %s channel: %f, %f, ratio %f," +
-                       " threshold_min %f, threshold_max %f") % (
+                print 'Step (%d,%d) %s channel: %f, %f, ratio %f,' % (
                         step-1, step, rgb_str[rgb],
                         raw_rgb_means[step - 1][rgb],
-                        raw_rgb_means[step][rgb],
-                        ratio, raw_thres_min, raw_thres_max)
-                if (raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD):
+                        raw_rgb_means[step][rgb], ratio),
+                print 'threshold_min %f, threshold_max %f' % (
+                        raw_thres_min, raw_thres_max)
+                if raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD:
                     continue
-                assert(raw_thres_min < ratio < raw_thres_max)
+                assert raw_thres_min < ratio < raw_thres_max
 
         # Test that each yuv step is about the same bright as their mean
         yuv_thres_min = 1 - RATIO_THRESHOLD
@@ -149,13 +160,13 @@
         for rgb in range(3):
             vals = [val[rgb] for val in yuv_rgb_means]
             for step in range(len(reqs)):
-                if (raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD):
+                if raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD:
                     vals = vals[:step]
             mean = sum(vals) / len(vals)
-            print "%s channel vals %s mean %f"%(rgb_str[rgb], vals, mean)
+            print '%s channel vals %s mean %f'%(rgb_str[rgb], vals, mean)
             for step in range(len(vals)):
                 ratio = vals[step] / mean
-                assert(yuv_thres_min < ratio < yuv_thres_max)
+                assert yuv_thres_min < ratio < yuv_thres_max
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
index b6b0514..debf22c 100644
--- a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
@@ -12,13 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.device
-import its.caps
-import its.objects
-import its.image
 import os.path
-from matplotlib import pylab
+import its.caps
+import its.device
+import its.image
+import its.objects
 import matplotlib
+from matplotlib import pylab
 
 GR_PLANE = 1  # GR plane index in RGGB data
 IMG_STATS_GRID = 9  # find used to find the center 11.11%
@@ -106,7 +106,9 @@
         # Test that each shot is noisier than the previous one.
         x.pop()  # remove last element in x index
         for i in x:
-            assert variances[i] < variances[i+1] / VAR_THRESH
+            msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
+                    variances[i], variances[i+1], VAR_THRESH)
+            assert variances[i] < variances[i+1] / VAR_THRESH, msg
 
 if __name__ == "__main__":
     main()
diff --git a/apps/CameraITS/tests/scene1/test_raw_exposure.py b/apps/CameraITS/tests/scene1/test_raw_exposure.py
index ca59aa8..fa3c9ff 100644
--- a/apps/CameraITS/tests/scene1/test_raw_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_raw_exposure.py
@@ -42,7 +42,6 @@
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.raw16(props) and
                              its.caps.manual_sensor(props) and
-                             its.caps.read_3a(props) and
                              its.caps.per_frame_control(props) and
                              not its.caps.mono_camera(props))
         debug = its.caps.debug_mode()
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
index db69e36..d8e2e4c 100644
--- a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
@@ -12,13 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.device
-import its.caps
-import its.objects
-import its.image
 import os.path
-from matplotlib import pylab
+import its.caps
+import its.device
+import its.image
+import its.objects
 import matplotlib.pyplot
+from matplotlib import pylab
 
 GR_PLANE = 1  # GR plane index in RGGB data
 IMG_STATS_GRID = 9  # find used to find the center 11.11%
@@ -94,7 +94,9 @@
 
         # Test that each shot is noisier than the previous one.
         for i in range(len(variances) - 1):
-            assert variances[i] < variances[i+1] / VAR_THRESH
+            msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
+                    variances[i], variances[i+1], VAR_THRESH)
+            assert variances[i] < variances[i+1] / VAR_THRESH, msg
 
 if __name__ == "__main__":
     main()
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
index f6eecae..0f84244 100644
--- a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -12,23 +12,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import matplotlib
-import matplotlib.pyplot
-import numpy
-import os.path
-from matplotlib import pylab
 
+import matplotlib
+from matplotlib import pylab
+import numpy
+
+NAME = os.path.basename(__file__).split(".")[0]
 NR_MODES = [0, 1, 2, 3, 4]
+NUM_FRAMES = 4
+SNR_TOLERANCE = 3  # unit in dB
 
 
 def main():
-    """Test that the android.noiseReduction.mode param is applied when set for
-       reprocessing requests.
+    """Test android.noiseReduction.mode is applied for reprocessing requests.
 
     Capture reprocessed images with the camera dimly lit. Uses a high analog
     gain to ensure the captured image is noisy.
@@ -38,11 +41,6 @@
     variance of this as the baseline.
     """
 
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    NUM_SAMPLES_PER_MODE = 4
-    SNR_TOLERANCE = 3 # unit in db
-
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
 
@@ -53,15 +51,16 @@
                               its.caps.private_reprocess(props)))
 
         # If reprocessing is supported, ZSL NR mode must be avaiable.
-        assert(its.caps.noise_reduction_mode(props, 4))
+        assert its.caps.noise_reduction_mode(props, 4)
 
         reprocess_formats = []
-        if (its.caps.yuv_reprocess(props)):
+        if its.caps.yuv_reprocess(props):
             reprocess_formats.append("yuv")
-        if (its.caps.private_reprocess(props)):
+        if its.caps.private_reprocess(props):
             reprocess_formats.append("private")
 
         for reprocess_format in reprocess_formats:
+            print "\nreprocess format:", reprocess_format
             # List of variances for R, G, B.
             snrs = [[], [], []]
             nr_modes_reported = []
@@ -75,10 +74,10 @@
             # TODO: Switch to reprocess_format->YUV when YUV reprocessing is
             #       supported.
             size = its.objects.get_available_output_sizes("jpg", props)[0]
-            out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+            out_surface = {"width": size[0], "height": size[1], "format": "jpg"}
             cap = cam.do_capture(req, out_surface, reprocess_format)
             img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
-            its.image.write_image(img, "%s_low_gain_fmt=jpg.jpg" % (NAME))
+            its.image.write_image(img, "%s_low_gain_fmt=jpg.jpg" % NAME)
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             ref_snr = its.image.compute_image_snrs(tile)
             print "Ref SNRs:", ref_snr
@@ -95,19 +94,19 @@
                 rgb_snr_list = []
                 # Capture several images to account for per frame noise
                 # variations
-                for n in range(NUM_SAMPLES_PER_MODE):
-                    req = its.objects.manual_capture_request(s, e)
-                    req["android.noiseReduction.mode"] = nr_mode
-                    cap = cam.do_capture(req, out_surface, reprocess_format)
-
-                    img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+                req = its.objects.manual_capture_request(s, e)
+                req["android.noiseReduction.mode"] = nr_mode
+                caps = cam.do_capture(
+                        [req]*NUM_FRAMES, out_surface, reprocess_format)
+                for n in range(NUM_FRAMES):
+                    img = its.image.decompress_jpeg_to_rgb_image(
+                            caps[n]["data"])
                     if n == 0:
                         its.image.write_image(
-                                img,
-                                "%s_high_gain_nr=%d_fmt=jpg.jpg"
-                                        %(NAME, nr_mode))
+                                img, "%s_high_gain_nr=%d_fmt=jpg.jpg" % (
+                                        NAME, nr_mode))
                         nr_modes_reported.append(
-                                cap["metadata"]["android.noiseReduction.mode"])
+                                caps[n]["metadata"]["android.noiseReduction.mode"])
 
                     tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
                     # Get the variances for R, G, and B channels
@@ -121,12 +120,12 @@
                             numpy.mean(g_snrs),
                             numpy.mean(b_snrs)]
                 print "NR mode", nr_mode, "SNRs:"
-                print "    R SNR:", rgb_snrs[0],\
-                        "Min:", min(r_snrs), "Max:", max(r_snrs)
-                print "    G SNR:", rgb_snrs[1],\
-                        "Min:", min(g_snrs), "Max:", max(g_snrs)
-                print "    B SNR:", rgb_snrs[2],\
-                        "Min:", min(b_snrs), "Max:", max(b_snrs)
+                print "    R SNR:", rgb_snrs[0],
+                print "Min:", min(r_snrs), "Max:", max(r_snrs)
+                print "    G SNR:", rgb_snrs[1],
+                print "Min:", min(g_snrs), "Max:", max(g_snrs)
+                print "    B SNR:", rgb_snrs[2],
+                print "Min:", min(b_snrs), "Max:", max(b_snrs)
 
                 for chan in range(3):
                     snrs[chan].append(rgb_snrs[chan])
@@ -136,6 +135,7 @@
             for channel in range(3):
                 pylab.plot(NR_MODES, snrs[channel], "-"+"rgb"[channel]+"o")
 
+            pylab.title(NAME + ", reprocess_fmt=" + reprocess_format)
             pylab.xlabel("Noise Reduction Mode")
             pylab.ylabel("SNR (dB)")
             pylab.xticks(NR_MODES)
@@ -145,30 +145,38 @@
             assert nr_modes_reported == NR_MODES
 
             for j in range(3):
-                # Larger is better
                 # Verify OFF(0) is not better than FAST(1)
-                assert(snrs[j][0] <
-                       snrs[j][1] + SNR_TOLERANCE)
+                msg = "FAST(1): %.2f, OFF(0): %.2f, TOL: %f" % (
+                        snrs[j][1], snrs[j][0], SNR_TOLERANCE)
+                assert snrs[j][0] < snrs[j][1] + SNR_TOLERANCE, msg
                 # Verify FAST(1) is not better than HQ(2)
-                assert(snrs[j][1] <
-                       snrs[j][2] + SNR_TOLERANCE)
+                msg = "HQ(2): %.2f, FAST(1): %.2f, TOL: %f" % (
+                        snrs[j][2], snrs[j][1], SNR_TOLERANCE)
+                assert snrs[j][1] < snrs[j][2] + SNR_TOLERANCE, msg
                 # Verify HQ(2) is better than OFF(0)
-                assert(snrs[j][0] < snrs[j][2])
+                msg = "HQ(2): %.2f, OFF(0): %.2f" % (snrs[j][2], snrs[j][0])
+                assert snrs[j][0] < snrs[j][2], msg
                 if its.caps.noise_reduction_mode(props, 3):
                     # Verify OFF(0) is not better than MINIMAL(3)
-                    assert(snrs[j][0] <
-                           snrs[j][3] + SNR_TOLERANCE)
+                    msg = "MINIMAL(3): %.2f, OFF(0): %.2f, TOL: %f" % (
+                            snrs[j][3], snrs[j][0], SNR_TOLERANCE)
+                    assert snrs[j][0] < snrs[j][3] + SNR_TOLERANCE, msg
                     # Verify MINIMAL(3) is not better than HQ(2)
-                    assert(snrs[j][3] <
-                           snrs[j][2] + SNR_TOLERANCE)
+                    msg = "MINIMAL(3): %.2f, HQ(2): %.2f, TOL: %f" % (
+                            snrs[j][3], snrs[j][2], SNR_TOLERANCE)
+                    assert snrs[j][3] < snrs[j][2] + SNR_TOLERANCE, msg
                     # Verify ZSL(4) is close to MINIMAL(3)
-                    assert(numpy.isclose(snrs[j][4], snrs[j][3],
-                                         atol=SNR_TOLERANCE))
+                    msg = "ZSL(4): %.2f, MINIMAL(3): %.2f, TOL: %f" % (
+                            snrs[j][4], snrs[j][3], SNR_TOLERANCE)
+                    assert numpy.isclose(snrs[j][4], snrs[j][3],
+                                         atol=SNR_TOLERANCE), msg
                 else:
                     # Verify ZSL(4) is close to OFF(0)
-                    assert(numpy.isclose(snrs[j][4], snrs[j][0],
-                                         atol=SNR_TOLERANCE))
+                    msg = "ZSL(4): %.2f, OFF(0): %.2f, TOL: %f" % (
+                            snrs[j][4], snrs[j][0], SNR_TOLERANCE)
+                    assert numpy.isclose(snrs[j][4], snrs[j][0],
+                                         atol=SNR_TOLERANCE), msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
index a7b9f6d..685f6eb 100644
--- a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
@@ -12,22 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
 import its.caps
 import its.device
+import its.image
 import its.objects
-import os.path
 import numpy
 
+MAX_SAME_DELTA = 0.03  # match number in test_burst_sameness_manual
+MIN_DIFF_DELTA = 0.10
+NAME = os.path.basename(__file__).split(".")[0]
+
+
 def main():
     """Test a sequence of shots with different tonemap curves.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
 
-    # There should be 3 identical frames followed by a different set of
-    # 3 identical frames.
-    MAX_SAME_DELTA = 0.03  # match number in test_burst_sameness_manual
-    MIN_DIFF_DELTA = 0.10
+    There should be 3 identical frames followed by a different set of
+    3 identical frames.
+    """
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -41,16 +43,17 @@
         if debug:
             fmt = largest_yuv
         else:
-            match_ar = (largest_yuv['width'], largest_yuv['height'])
+            match_ar = (largest_yuv["width"], largest_yuv["height"])
             fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
 
-        sens, exp_time, _,_,f_dist = cam.do_3a(do_af=True,get_results=True)
+        sens, exp_time, _, _, f_dist = cam.do_3a(do_af=True, get_results=True)
 
         means = []
 
         # Capture 3 manual shots with a linear tonemap.
-        req = its.objects.manual_capture_request(sens, exp_time, f_dist, True, props)
-        for i in [0,1,2]:
+        req = its.objects.manual_capture_request(
+                sens, exp_time, f_dist, True, props)
+        for i in [0, 1, 2]:
             cap = cam.do_capture(req, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
@@ -59,7 +62,7 @@
 
         # Capture 3 manual shots with the default tonemap.
         req = its.objects.manual_capture_request(sens, exp_time, f_dist, False)
-        for i in [3,4,5]:
+        for i in [3, 4, 5]:
             cap = cam.do_capture(req, fmt)
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
@@ -71,9 +74,12 @@
                   for i in range(len(means)-1)]
         print "Deltas between consecutive frames:", deltas
 
-        assert(all([abs(deltas[i]) < MAX_SAME_DELTA for i in [0,1,3,4]]))
-        assert(abs(deltas[2]) > MIN_DIFF_DELTA)
+        msg = "deltas: %s, MAX_SAME_DELTA: %.2f" % (
+                str(deltas), MAX_SAME_DELTA)
+        assert all([abs(deltas[i]) < MAX_SAME_DELTA for i in [0, 1, 3, 4]]), msg
+        assert abs(deltas[2]) > MIN_DIFF_DELTA, "delta: %.5f, THRESH: %.2f" % (
+                abs(deltas[2]), MIN_DIFF_DELTA)
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
index e7604c1..163437a 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
@@ -12,20 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
-import math
+
+import matplotlib.pylab
+import matplotlib.pyplot
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.03
+
 
 def main():
     """Test that the reported sizes and formats for image capture work.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.03
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -40,36 +45,48 @@
         rgbs = []
 
         for size in its.objects.get_available_output_sizes("yuv", props):
-            out_surface = {"width":size[0], "height":size[1], "format":"yuv"}
+            out_surface = {"width": size[0], "height": size[1], "format": "yuv"}
             cap = cam.do_capture(req, out_surface)
-            assert(cap["format"] == "yuv")
-            assert(cap["width"] == size[0])
-            assert(cap["height"] == size[1])
-            print "Captured YUV %dx%d" % (cap["width"], cap["height"])
+            assert cap["format"] == "yuv"
+            assert cap["width"] == size[0]
+            assert cap["height"] == size[1]
+            print "Captured YUV %dx%d" % (cap["width"], cap["height"]),
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(img, "%s_yuv_w%d_h%d.jpg"%(
-                    NAME,size[0],size[1]))
+                    NAME, size[0], size[1]))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb = its.image.compute_image_means(tile)
+            print "rgb =", rgb
             rgbs.append(rgb)
 
         for size in its.objects.get_available_output_sizes("jpg", props):
-            out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+            out_surface = {"width": size[0], "height": size[1], "format": "jpg"}
             cap = cam.do_capture(req, out_surface)
-            assert(cap["format"] == "jpeg")
-            assert(cap["width"] == size[0])
-            assert(cap["height"] == size[1])
+            assert cap["format"] == "jpeg"
+            assert cap["width"] == size[0]
+            assert cap["height"] == size[1]
             img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
             its.image.write_image(img, "%s_jpg_w%d_h%d.jpg"%(
-                    NAME,size[0], size[1]))
-            assert(img.shape[0] == size[1])
-            assert(img.shape[1] == size[0])
-            assert(img.shape[2] == 3)
-            print "Captured JPEG %dx%d" % (cap["width"], cap["height"])
+                    NAME, size[0], size[1]))
+            assert img.shape[0] == size[1]
+            assert img.shape[1] == size[0]
+            assert img.shape[2] == 3
+            print "Captured JPEG %dx%d" % (cap["width"], cap["height"]),
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb = its.image.compute_image_means(tile)
+            print "rgb =", rgb
             rgbs.append(rgb)
 
+        # Plot means vs format
+        matplotlib.pylab.title(NAME)
+        matplotlib.pylab.plot(range(len(rgbs)), [r[0] for r in rgbs], "-ro")
+        matplotlib.pylab.plot(range(len(rgbs)), [g[1] for g in rgbs], "-go")
+        matplotlib.pylab.plot(range(len(rgbs)), [b[2] for b in rgbs], "-bo")
+        matplotlib.pylab.ylim([0, 1])
+        matplotlib.pylab.xlabel("format number")
+        matplotlib.pylab.ylabel("RGB avg [0, 1]")
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
         max_diff = 0
         rgb0 = rgbs[0]
         for rgb1 in rgbs[1:]:
@@ -77,8 +94,10 @@
                     sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
             max_diff = max(max_diff, rms_diff)
         print "Max RMS difference:", max_diff
-        assert(max_diff < THRESHOLD_MAX_RMS_DIFF)
+        msg = "Max RMS difference: %.4f, spec: %.3f" % (max_diff,
+                                                        THRESHOLD_MAX_RMS_DIFF)
+        assert max_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
index 4a62120..1d4113f 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
@@ -12,16 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
-import os.path
+
+NAME = os.path.basename(__file__).split(".")[0]
+
 
 def main():
     """Test capturing a single frame as both DNG and YUV outputs.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -32,13 +35,12 @@
         cam.do_3a(mono_camera=mono_camera)
 
         req = its.objects.auto_capture_request()
-        max_dng_size = \
-                its.objects.get_available_output_sizes("raw", props)[0]
-        w,h = its.objects.get_available_output_sizes(
+        max_dng_size = its.objects.get_available_output_sizes("raw", props)[0]
+        w, h = its.objects.get_available_output_sizes(
                 "yuv", props, (1920, 1080), max_dng_size)[0]
-        out_surfaces = [{"format":"dng"},
-                        {"format":"yuv", "width":w, "height":h}]
-        cap_dng, cap_yuv = cam.do_capture(req, cam.CAP_DNG_YUV)
+        out_surfaces = [{"format": "dng"},
+                        {"format": "yuv", "width": w, "height": h}]
+        cap_dng, cap_yuv = cam.do_capture(req, out_surfaces)
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
         its.image.write_image(img, "%s.jpg" % (NAME))
@@ -49,6 +51,6 @@
         # No specific pass/fail check; test is assumed to have succeeded if
         # it completes.
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
index f559c2b..821eb35 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
@@ -12,20 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
-import math
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.01
+
 
 def main():
     """Test capturing a single frame as both YUV and JPEG outputs.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.01
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -33,10 +35,10 @@
 
         max_jpeg_size = \
                 its.objects.get_available_output_sizes("jpeg", props)[0]
-        w,h = its.objects.get_available_output_sizes(
+        w, h = its.objects.get_available_output_sizes(
                 "yuv", props, (1920, 1080), max_jpeg_size)[0]
-        fmt_yuv =  {"format":"yuv", "width":w, "height":h}
-        fmt_jpeg = {"format":"jpeg"}
+        fmt_yuv = {"format": "yuv", "width": w, "height": h}
+        fmt_jpeg = {"format": "jpeg"}
 
         # Use a manual request with a linear tonemap so that the YUV and JPEG
         # should look the same (once converted by the its.image module).
@@ -58,7 +60,9 @@
         rms_diff = math.sqrt(
                 sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
         print "RMS difference:", rms_diff
-        assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+        msg = "RMS difference: %.4f, spec: %.3f" % (rms_diff,
+                                                    THRESHOLD_MAX_RMS_DIFF)
+        assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
index cd284b7..75e70ae 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
@@ -12,20 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+import os.path
+
 import its.caps
 import its.device
+import its.image
 import its.objects
 import its.target
-import os.path
-import math
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
 
 def main():
     """Test capturing a single frame as both RAW12 and YUV outputs.
     """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    THRESHOLD_MAX_RMS_DIFF = 0.035
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -41,11 +43,11 @@
 
         max_raw12_size = \
                 its.objects.get_available_output_sizes("raw12", props)[0]
-        w,h = its.objects.get_available_output_sizes(
+        w, h = its.objects.get_available_output_sizes(
                 "yuv", props, (1920, 1080), max_raw12_size)[0]
-        cap_raw, cap_yuv = cam.do_capture(req,
-                [{"format":"raw12"},
-                 {"format":"yuv", "width":w, "height":h}])
+        cap_raw, cap_yuv = cam.do_capture(
+                req, [{"format": "raw12"},
+                      {"format": "yuv", "width": w, "height": h}])
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
         its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
@@ -62,7 +64,9 @@
         rms_diff = math.sqrt(
                 sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
         print "RMS difference:", rms_diff
-        assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+        msg = "RMS difference: %.4f, spec: %.3f" % (rms_diff,
+                                                    THRESHOLD_MAX_RMS_DIFF)
+        assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf b/apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf
new file mode 100644
index 0000000..de036b6
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_auto_per_frame_control.py b/apps/CameraITS/tests/scene2/test_auto_per_frame_control.py
new file mode 100644
index 0000000..4b8185a
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/test_auto_per_frame_control.py
@@ -0,0 +1,150 @@
+# 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.
+
+import os.path
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+
+import matplotlib
+from matplotlib import pylab
+import numpy as np
+
+AE_STATE_CONVERGED = 2
+CONTROL_AE_STATE_FLASH_REQUIRED = 4
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_CAPTURE = 30
+VALID_STABLE_LUMA_MIN = 0.1
+VALID_STABLE_LUMA_MAX = 0.9
+
+
+def is_awb_af_stable(prev_cap, cap):
+    awb_gains_0 = prev_cap['metadata']['android.colorCorrection.gains']
+    awb_gains_1 = cap['metadata']['android.colorCorrection.gains']
+    ccm_0 = prev_cap['metadata']['android.colorCorrection.transform']
+    ccm_1 = cap['metadata']['android.colorCorrection.transform']
+    focus_distance_0 = prev_cap['metadata']['android.lens.focusDistance']
+    focus_distance_1 = cap['metadata']['android.lens.focusDistance']
+
+    return (np.allclose(awb_gains_0, awb_gains_1, rtol=0.01) and
+            ccm_0 == ccm_1 and
+            np.isclose(focus_distance_0, focus_distance_1, rtol=0.01))
+
+
+def main():
+    """Tests PER_FRAME_CONTROL properties for auto capture requests.
+
+    If debug is required, MANUAL_POSTPROCESSING capability is implied
+    since its.caps.read_3a is valid for test. Debug can performed with
+    a defined tonemap curve:
+    req['android.tonemap.mode'] = 0
+    gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[])
+    req['android.tonemap.curve'] = {
+            'red': gamma, 'green': gamma, 'blue': gamma}
+    """
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.per_frame_control(props) and
+                             its.caps.read_3a(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)
+
+        req = its.objects.auto_capture_request()
+        caps = cam.do_capture([req]*NUM_CAPTURE, fmt)
+
+        total_gains = []
+        lumas = []
+        ae_states = []
+        for i, cap in enumerate(caps):
+            print '=========== frame %d ==========' % i
+            y = its.image.convert_capture_to_planes(cap)[0]
+            tile = its.image.get_image_patch(y, 0.45, 0.45, 0.1, 0.1)
+            luma = its.image.compute_image_means(tile)[0]
+
+            ae_state = cap['metadata']['android.control.aeState']
+            iso = cap['metadata']['android.sensor.sensitivity']
+            isp_gain = cap['metadata']['android.control.postRawSensitivityBoost']
+            exp_time = cap['metadata']['android.sensor.exposureTime']
+            total_gain = iso*isp_gain/100.0*exp_time/1000000.0
+            awb_state = cap['metadata']['android.control.awbState']
+            awb_gains = cap['metadata']['android.colorCorrection.gains']
+            ccm = cap['metadata']['android.colorCorrection.transform']
+            focus_distance = cap['metadata']['android.lens.focusDistance']
+
+            # Convert CCM from rational to float, as numpy arrays.
+            awb_ccm = np.array(its.objects.rational_to_float(ccm)).reshape(3, 3)
+
+            print 'AE: %d ISO: %d ISP_sen: %d exp(ms): %d tot_gain: %f' % (
+                    ae_state, iso, isp_gain, exp_time, total_gain),
+            print 'luma: %f' % luma
+            print 'fd: %f' % focus_distance
+            print 'AWB: %d, AWB gains: %s\n AWB matrix: %s' % (
+                    awb_state, str(awb_gains), str(awb_ccm))
+            print 'Tonemap curve:', cap['metadata']['android.tonemap.curve']
+
+            lumas.append(luma)
+            total_gains.append(total_gain)
+            ae_states.append(ae_state)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, '%s_frame_%d.jpg'% (NAME, i))
+
+        norm_gains = [x / max(total_gains) * max(lumas) for x in total_gains]
+        pylab.plot(range(len(lumas)), lumas, '-g.',
+                   label='Center patch brightness')
+        pylab.plot(range(len(norm_gains)), norm_gains, '-r.',
+                   label='Metadata AE setting product')
+        pylab.title(NAME)
+        pylab.xlabel('frame index')
+        pylab.legend()
+        matplotlib.pyplot.savefig('%s_plot.png' % (NAME))
+
+        for i in range(1, len(caps)):
+            if is_awb_af_stable(caps[i-1], caps[i]):
+                prev_total_gain = total_gains[i-1]
+                total_gain = total_gains[i]
+                delta_gain = total_gain - prev_total_gain
+                prev_luma = lumas[i-1]
+                luma = lumas[i]
+                delta_luma = luma - prev_luma
+                # luma and total_gain should change in same direction
+                msg = 'Frame %d to frame %d:' % (i-1, i)
+                msg += ' metadata gain %f->%f (%s), luma %f->%f (%s)' % (
+                        prev_total_gain, total_gain,
+                        'increasing' if delta_gain > 0.0 else 'decreasing',
+                        prev_luma, luma,
+                        'increasing' if delta_luma > 0.0 else 'decreasing')
+                assert delta_gain * delta_luma >= 0.0, msg
+            else:
+                print 'Frame %d->%d AWB/AF changed' % (i-1, i)
+
+        for i in range(len(lumas)):
+            luma = lumas[i]
+            ae_state = ae_states[i]
+            if (ae_state == AE_STATE_CONVERGED or
+                        ae_state == CONTROL_AE_STATE_FLASH_REQUIRED):
+                msg = 'Frame %d AE converged luma %f. valid range: (%f, %f)' % (
+                        i, luma, VALID_STABLE_LUMA_MIN, VALID_STABLE_LUMA_MAX)
+                assert VALID_STABLE_LUMA_MIN < luma < VALID_STABLE_LUMA_MAX, msg
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene2/test_effects.py b/apps/CameraITS/tests/scene2/test_effects.py
new file mode 100644
index 0000000..20cc7f3
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/test_effects.py
@@ -0,0 +1,105 @@
+# 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.
+
+import os.path
+import its.caps
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+# android.control.availableEffects
+EFFECTS = {0: 'OFF',
+           1: 'MONO',
+           2: 'NEGATIVE',
+           3: 'SOLARIZE',
+           4: 'SEPIA',
+           5: 'POSTERIZE',
+           6: 'WHITEBOARD',
+           7: 'BLACKBOARD',
+           8: 'AQUA'}
+MONO_UV_SPREAD_MAX = 2  # max spread for U & V channels [0:255] for mono image
+NAME = os.path.basename(__file__).split('.')[0]
+W, H = 640, 480
+YUV_MAX = 255.0  # normalization number for YUV images [0:1] --> [0:255]
+YUV_UV_SPREAD_MIN = 10  # min spread for U & V channels [0:255] for color image
+YUV_Y_SPREAD_MIN = 50  # min spread for Y channel [0:255] for color image
+
+
+def main():
+    """Test effects.
+
+    Test: capture frame for supported camera effects and check if generated
+    correctly. Note we only check effects OFF and MONO currently, but save
+    images for all supported effects.
+    """
+
+    print '\nStarting %s' % NAME
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        mono_camera = its.caps.mono_camera(props)
+        effects = props['android.control.availableEffects']
+        its.caps.skip_unless(effects)
+        cam.do_3a(mono_camera=mono_camera)
+        print 'Supported effects:', effects
+        failed = []
+        for effect in effects:
+            req = its.objects.auto_capture_request()
+            req['android.control.effectMode'] = effect
+            fmt = {'format': 'yuv', 'width': W, 'height': H}
+            cap = cam.do_capture(req, fmt)
+
+            # Save image
+            img = its.image.convert_capture_to_rgb_image(cap, props=props)
+            its.image.write_image(img, '%s_%s.jpg' % (NAME, EFFECTS[effect]))
+
+            # Simple checks
+            if effect is 0:
+                print 'Checking effects OFF...'
+                y, u, v = its.image.convert_capture_to_planes(cap, props)
+                y_min, y_max = np.amin(y)*YUV_MAX, np.amax(y)*YUV_MAX
+                msg = 'Y_range:%.f,%.f THRESH:%d, ' % (
+                        y_min, y_max, YUV_Y_SPREAD_MIN)
+                if (y_max-y_min) < YUV_Y_SPREAD_MIN:
+                    failed.append({'effect': EFFECTS[effect], 'error': msg})
+                if not mono_camera:
+                    u_min, u_max = np.amin(u)*YUV_MAX, np.amax(u)*YUV_MAX
+                    v_min, v_max = np.amin(v)*YUV_MAX, np.amax(v)*YUV_MAX
+                    msg += 'U_range:%.f,%.f THRESH:%d, ' % (
+                            u_min, u_max, YUV_UV_SPREAD_MIN)
+                    msg += 'V_range:%.f,%.f THRESH:%d' % (
+                            v_min, v_max, YUV_UV_SPREAD_MIN)
+                    if ((u_max-u_min) < YUV_UV_SPREAD_MIN or
+                                (v_max-v_min) < YUV_UV_SPREAD_MIN):
+                        failed.append({'effect': EFFECTS[effect], 'error': msg})
+            if effect is 1:
+                print 'Checking MONO effect...'
+                _, u, v = its.image.convert_capture_to_planes(cap, props)
+                u_min, u_max = np.amin(u)*YUV_MAX, np.amax(u)*YUV_MAX
+                v_min, v_max = np.amin(v)*YUV_MAX, np.amax(v)*YUV_MAX
+                msg = 'U_range:%.f,%.f, ' % (u_min, u_max)
+                msg += 'V_range:%.f,%.f, TOL:%d' % (
+                        v_min, v_max, MONO_UV_SPREAD_MAX)
+                if ((u_max-u_min) > MONO_UV_SPREAD_MAX or
+                            (v_max-v_min) > MONO_UV_SPREAD_MAX):
+                    failed.append({'effect': EFFECTS[effect], 'error': msg})
+        if failed:
+            print 'Failed effects:'
+            for fail in failed:
+                print ' %s: %s' % (fail['effect'], fail['error'])
+        assert not failed
+
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene2/test_format_combos.py b/apps/CameraITS/tests/scene2/test_format_combos.py
new file mode 100644
index 0000000..ff08d55
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/test_format_combos.py
@@ -0,0 +1,135 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.error
+import its.target
+import sys
+import os
+
+NAME = os.path.basename(__file__).split(".")[0]
+STOP_AT_FIRST_FAILURE = False  # change to True to have test break @ 1st FAIL
+
+
+def main():
+    """Test different combinations of output formats.
+    
+    Note the test does not require a specific target but does perform
+    both automatic and manual captures so it requires a fixed scene
+    where 3A can converge.
+    """
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.raw16(props))
+
+        successes = []
+        failures = []
+        debug = its.caps.debug_mode()
+
+        # Two different requests: auto, and manual.
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req_aut = its.objects.auto_capture_request()
+        req_man = its.objects.manual_capture_request(s, e)
+        reqs = [req_aut,  # R0
+                req_man]  # R1
+
+        # 10 different combos of output formats; some are single surfaces, and
+        # some are multiple surfaces.
+        wyuv, hyuv = its.objects.get_available_output_sizes("yuv", props)[-1]
+        wjpg, hjpg = its.objects.get_available_output_sizes("jpg", props)[-1]
+        fmt_yuv_prev = {"format": "yuv", "width": wyuv, "height": hyuv}
+        fmt_yuv_full = {"format": "yuv"}
+        fmt_jpg_prev = {"format": "jpeg", "width": wjpg, "height": hjpg}
+        fmt_jpg_full = {"format": "jpeg"}
+        fmt_raw_full = {"format": "raw"}
+        fmt_combos = [
+            [fmt_yuv_prev],                              # F0
+            [fmt_yuv_full],                              # F1
+            [fmt_jpg_prev],                              # F2
+            [fmt_jpg_full],                              # F3
+            [fmt_raw_full],                              # F4
+            [fmt_yuv_prev, fmt_jpg_prev],                # F5
+            [fmt_yuv_prev, fmt_jpg_full],                # F6
+            [fmt_yuv_prev, fmt_raw_full],                # F7
+            [fmt_yuv_prev, fmt_jpg_prev, fmt_raw_full],  # F8
+            [fmt_yuv_prev, fmt_jpg_full, fmt_raw_full]]  # F9
+
+        if its.caps.y8(props):
+            wy8, hy8 = its.objects.get_available_output_sizes("y8", props)[-1]
+            fmt_y8_prev = {"format": "y8", "width": wy8, "height": hy8}
+            fmt_y8_full = {"format": "y8"}
+            fmt_combos.append([fmt_y8_prev])
+            fmt_combos.append([fmt_y8_full])
+
+        # Two different burst lengths: single frame, and 3 frames.
+        burst_lens = [1,  # B0
+                      3]  # B1
+
+        # There are 2xlen(fmt_combos)x2 different combinations. Run through them all.
+        n = 0
+        for r,req in enumerate(reqs):
+            for f,fmt_combo in enumerate(fmt_combos):
+                for b,burst_len in enumerate(burst_lens):
+                    try:
+                        caps = cam.do_capture([req]*burst_len, fmt_combo)
+                        successes.append((n,r,f,b))
+                        print "==> Success[%02d]: R%d F%d B%d" % (n,r,f,b)
+
+                        # Dump the captures out to jpegs in debug mode.
+                        if debug:
+                            if not isinstance(caps, list):
+                                caps = [caps]
+                            elif isinstance(caps[0], list):
+                                caps = sum(caps, [])
+                            for c, cap in enumerate(caps):
+                                img = its.image.convert_capture_to_rgb_image(cap, props=props)
+                                its.image.write_image(img,
+                                    "%s_n%02d_r%d_f%d_b%d_c%d.jpg"%(NAME,n,r,f,b,c))
+
+                    except Exception as e:
+                        print e
+                        print "==> Failure[%02d]: R%d F%d B%d" % (n,r,f,b)
+                        failures.append((n,r,f,b))
+                        if STOP_AT_FIRST_FAILURE:
+                            sys.exit(1)
+                    n += 1
+
+        num_fail = len(failures)
+        num_success = len(successes)
+        num_total = len(reqs)*len(fmt_combos)*len(burst_lens)
+        num_not_run = num_total - num_success - num_fail
+
+        print "\nFailures (%d / %d):" % (num_fail, num_total)
+        for (n,r,f,b) in failures:
+            print "  %02d: R%d F%d B%d" % (n,r,f,b)
+        print "\nSuccesses (%d / %d):" % (num_success, num_total)
+        for (n,r,f,b) in successes:
+            print "  %02d: R%d F%d B%d" % (n,r,f,b)
+        if num_not_run > 0:
+            print "\nNumber of tests not run: %d / %d" % (num_not_run, num_total)
+        print ""
+
+        # The test passes if all the combinations successfully capture.
+        assert num_fail == 0
+        assert num_success == num_total
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf
new file mode 100644
index 0000000..805611d
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene3/test_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
index 76093ef..5f6efc8 100644
--- a/apps/CameraITS/tests/scene3/test_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
@@ -12,22 +12,26 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
+import its.cv2image
 import its.device
+import its.image
 import its.objects
 import its.target
-import math
-import matplotlib
-import matplotlib.pyplot
+
 import numpy
-import os.path
-from matplotlib import pylab
+
+NAME = os.path.basename(__file__).split(".")[0]
+NUM_SAMPLES = 4
+THRESH_REL_SHARPNESS_DIFF = 0.1
 
 
-def test_edge_mode(cam, edge_mode, sensitivity, exp, fd, out_surface):
-    """Return sharpness of the output image and the capture result metadata
-       for a capture request with the given edge mode, sensitivity, exposure
+def test_edge_mode(cam, edge_mode, sensitivity, exp, fd, out_surface, chart):
+    """Return sharpness of the output image and the capture result metadata.
+
+       Processes a capture request with a given edge mode, sensitivity, exposure
        time, focus distance, output surface parameter.
 
     Args:
@@ -39,7 +43,8 @@
             android.sensor.exposureTime.
         fd: Focus distance for the request as defined in
             android.lens.focusDistance
-        output_surface: Specifications of the output image format and size.
+        out_surface: Specifications of the output image format and size.
+        chart: object that contains chart information
 
     Returns:
         Object containing reported edge mode and the sharpness of the output
@@ -48,23 +53,21 @@
             "sharpness"
     """
 
-    NAME = os.path.basename(__file__).split(".")[0]
-    NUM_SAMPLES = 4
-
     req = its.objects.manual_capture_request(sensitivity, exp)
     req["android.lens.focusDistance"] = fd
     req["android.edge.mode"] = edge_mode
 
     sharpness_list = []
-    test_fmt = out_surface["format"]
     for n in range(NUM_SAMPLES):
         cap = cam.do_capture(req, out_surface, repeat_request=req)
-        img = its.image.convert_capture_to_rgb_image(cap)
+        y, _, _ = its.image.convert_capture_to_planes(cap)
+        chart.img = its.image.normalize_img(its.image.get_image_patch(
+                y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
         if n == 0:
-            its.image.write_image(img, "%s_edge=%d.jpg" % (NAME, edge_mode))
+            its.image.write_image(
+                    chart.img, "%s_edge=%d.jpg" % (NAME, edge_mode))
             res_edge_mode = cap["metadata"]["android.edge.mode"]
-        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-        sharpness_list.append(its.image.compute_image_sharpness(tile))
+        sharpness_list.append(its.image.compute_image_sharpness(chart.img))
 
     ret = {}
     ret["edge_mode"] = res_edge_mode
@@ -72,6 +75,7 @@
 
     return ret
 
+
 def main():
     """Test that the android.edge.mode param is applied correctly.
 
@@ -79,8 +83,6 @@
     sharpness as a baseline.
     """
 
-    THRESHOLD_RELATIVE_SHARPNESS_DIFF = 0.1
-
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
 
@@ -88,13 +90,17 @@
                              its.caps.per_frame_control(props) and
                              its.caps.edge_mode(props, 0))
 
+    # initialize chart class and locate chart in scene
+    chart = its.cv2image.Chart()
+
+    with its.device.ItsSession() as cam:
         mono_camera = its.caps.mono_camera(props)
         test_fmt = "yuv"
         size = its.objects.get_available_output_sizes(test_fmt, props)[0]
-        out_surface = {"width":size[0], "height":size[1], "format":test_fmt}
+        out_surface = {"width": size[0], "height": size[1], "format": test_fmt}
 
         # Get proper sensitivity, exposure time, and focus distance.
-        s,e,_,_,fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+        s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
 
         # Get the sharpness for each edge mode for regular requests
         sharpness_regular = []
@@ -105,24 +111,29 @@
                 edge_mode_reported_regular.append(edge_mode)
                 sharpness_regular.append(0)
                 continue
-            ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface)
+            ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface, chart)
             edge_mode_reported_regular.append(ret["edge_mode"])
             sharpness_regular.append(ret["sharpness"])
 
         print "Reported edge modes:", edge_mode_reported_regular
         print "Sharpness with EE mode [0,1,2,3]:", sharpness_regular
 
-        # Verify HQ(2) is sharper than OFF(0)
-        assert(sharpness_regular[2] > sharpness_regular[0])
+        print "Verify HQ(2) is sharper than OFF(0)"
+        assert sharpness_regular[2] > sharpness_regular[0]
 
-        # Verify OFF(0) is not sharper than FAST(1)
-        assert(sharpness_regular[1] >
-               sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+        print "Verify OFF(0) is not sharper than FAST(1)"
+        msg = "FAST: %.3f, OFF: %.3f, TOL: %.2f" % (
+                sharpness_regular[1], sharpness_regular[0],
+                THRESH_REL_SHARPNESS_DIFF)
+        assert (sharpness_regular[1] >
+                sharpness_regular[0] * (1.0 - THRESH_REL_SHARPNESS_DIFF)), msg
 
         # Verify FAST(1) is not sharper than HQ(2)
-        assert(sharpness_regular[2] >
-               sharpness_regular[1] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+        msg = "HQ: %.3f, FAST: %.3f, TOL: %.2f" % (
+                sharpness_regular[2], sharpness_regular[1],
+                THRESH_REL_SHARPNESS_DIFF)
+        assert (sharpness_regular[2] >
+                sharpness_regular[1] * (1.0 - THRESH_REL_SHARPNESS_DIFF)), msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
-
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index 9742e0b..b4677c7 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -25,11 +25,6 @@
 NAME = os.path.basename(__file__).split('.')[0]
 CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
                           'test_images', 'ISO12233.png')
-CHART_HEIGHT = 13.5  # cm
-CHART_DISTANCE = 30.0  # cm
-CHART_SCALE_START = 0.65
-CHART_SCALE_STOP = 1.35
-CHART_SCALE_STEP = 0.025
 CHART_ORIENTATIONS = ['nominal', 'flip', 'mirror', 'rotate']
 VGA_WIDTH = 640
 VGA_HEIGHT = 480
@@ -125,9 +120,7 @@
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.read_3a(props))
     # initialize chart class and locate chart in scene
-    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                               CHART_SCALE_START, CHART_SCALE_STOP,
-                               CHART_SCALE_STEP)
+    chart = its.cv2image.Chart()
 
     with its.device.ItsSession() as cam:
         fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index 6fea633..0862b3b 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -28,13 +28,6 @@
 VGA_WIDTH = 640
 VGA_HEIGHT = 480
 NAME = os.path.basename(__file__).split('.')[0]
-CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
-                          'test_images', 'ISO12233.png')
-CHART_HEIGHT = 13.5  # cm
-CHART_DISTANCE = 30.0  # cm
-CHART_SCALE_START = 0.65
-CHART_SCALE_STOP = 1.35
-CHART_SCALE_STEP = 0.025
 
 
 def test_lens_movement_reporting(cam, props, fmt, gain, exp, af_fd, chart):
@@ -109,9 +102,7 @@
         its.caps.skip_unless(its.caps.read_3a(props) and
                              its.caps.lens_approx_calibrated(props))
     # initialize chart class
-    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                               CHART_SCALE_START, CHART_SCALE_STOP,
-                               CHART_SCALE_STEP)
+    chart = its.cv2image.Chart()
 
     with its.device.ItsSession() as cam:
         mono_camera = its.caps.mono_camera(props)
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index 3978081..101b44c 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -29,13 +29,6 @@
 VGA_WIDTH = 640
 VGA_HEIGHT = 480
 NAME = os.path.basename(__file__).split('.')[0]
-CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
-                          'test_images', 'ISO12233.png')
-CHART_HEIGHT = 13.5  # cm
-CHART_DISTANCE = 30.0  # cm
-CHART_SCALE_START = 0.65
-CHART_SCALE_STOP = 1.35
-CHART_SCALE_STEP = 0.025
 
 
 def test_lens_position(cam, props, fmt, sensitivity, exp, chart):
@@ -126,9 +119,7 @@
         its.caps.skip_unless(its.caps.read_3a(props) and
                              its.caps.lens_calibrated(props))
     # initialize chart class
-    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                               CHART_SCALE_START, CHART_SCALE_STOP,
-                               CHART_SCALE_STEP)
+    chart = its.cv2image.Chart()
 
     with its.device.ItsSession() as cam:
         mono_camera = its.caps.mono_camera(props)
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index 049426a..722af18 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -12,23 +12,47 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import os.path
+
 import its.caps
+import its.cv2image
 import its.device
+import its.image
 import its.objects
 import its.target
-import math
+
 import matplotlib
-import matplotlib.pyplot
-import numpy
-import os.path
 from matplotlib import pylab
+import numpy
+
+NAME = os.path.basename(__file__).split(".")[0]
+NUM_SAMPLES = 4
+THRESH_REL_SHARPNESS_DIFF = 0.15
 
 
-def test_edge_mode(cam, edge_mode, sensitivity, exp, fd, out_surface,
+def check_edge_modes(sharpness):
+    """Check that the sharpness for the different edge modes is correct."""
+    print " Verify HQ(2) is sharper than OFF(0)"
+    assert sharpness[2] > sharpness[0]
+
+    print " Verify ZSL(3) is similar to OFF(0)"
+    e_msg = "ZSL: %.5f, OFF: %.5f, RTOL: %.2f" % (
+            sharpness[3], sharpness[0], THRESH_REL_SHARPNESS_DIFF)
+    assert numpy.isclose(sharpness[3], sharpness[0],
+                         THRESH_REL_SHARPNESS_DIFF), e_msg
+
+    print " Verify OFF(0) is not sharper than FAST(1)"
+    assert sharpness[1] > sharpness[0] * (1.0 - THRESH_REL_SHARPNESS_DIFF)
+
+    print " Verify FAST(1) is not sharper than HQ(2)"
+    assert sharpness[2] > sharpness[1] * (1.0 - THRESH_REL_SHARPNESS_DIFF)
+
+
+def test_edge_mode(cam, edge_mode, sensitivity, exp, fd, out_surface, chart,
                    reprocess_format=None):
-    """Return sharpness of the output image and the capture result metadata
-       for a capture request with the given edge mode, sensitivity, exposure
+    """Return sharpness of the output images and the capture result metadata.
+
+       Processes a capture request with a given edge mode, sensitivity, exposure
        time, focus distance, output surface parameter, and reprocess format
        (None for a regular request.)
 
@@ -41,7 +65,8 @@
             android.sensor.exposureTime.
         fd: Focus distance for the request as defined in
             android.lens.focusDistance
-        output_surface: Specifications of the output image format and size.
+        out_surface: Specifications of the output image format and size.
+        chart: object containing chart information
         reprocess_format: (Optional) The reprocessing format. If not None,
                 reprocessing will be enabled.
 
@@ -52,25 +77,23 @@
             "sharpness"
     """
 
-    NAME = os.path.basename(__file__).split(".")[0]
-    NUM_SAMPLES = 4
-
     req = its.objects.manual_capture_request(sensitivity, exp)
     req["android.lens.focusDistance"] = fd
     req["android.edge.mode"] = edge_mode
-    if (reprocess_format != None):
+    if reprocess_format:
         req["android.reprocess.effectiveExposureFactor"] = 1.0
 
     sharpness_list = []
+    caps = cam.do_capture([req]*NUM_SAMPLES, [out_surface], reprocess_format)
     for n in range(NUM_SAMPLES):
-        cap = cam.do_capture(req, out_surface, reprocess_format)
-        img = its.image.convert_capture_to_rgb_image(cap)
+        y, _, _ = its.image.convert_capture_to_planes(caps[n])
+        chart.img = its.image.normalize_img(its.image.get_image_patch(
+                y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
         if n == 0:
-            its.image.write_image(img, "%s_reprocess_fmt_%s_edge=%d.jpg" %
-                (NAME, reprocess_format, edge_mode))
-            res_edge_mode = cap["metadata"]["android.edge.mode"]
-        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-        sharpness_list.append(its.image.compute_image_sharpness(tile))
+            its.image.write_image(chart.img, "%s_reprocess_fmt_%s_edge=%d.jpg" %
+                                  (NAME, reprocess_format, edge_mode))
+            res_edge_mode = caps[n]["metadata"]["android.edge.mode"]
+        sharpness_list.append(its.image.compute_image_sharpness(chart.img))
 
     ret = {}
     ret["edge_mode"] = res_edge_mode
@@ -78,9 +101,9 @@
 
     return ret
 
+
 def main():
-    """Test that the android.edge.mode param is applied when set for
-       reprocessing requests.
+    """Test android.edge.mode param applied when set for reprocessing requests.
 
     Capture non-reprocess images for each edge mode and calculate their
     sharpness as a baseline.
@@ -90,8 +113,6 @@
     the sharpess of non-reprocess images.
     """
 
-    THRESHOLD_RELATIVE_SHARPNESS_DIFF = 0.15
-
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
 
@@ -101,21 +122,29 @@
                              (its.caps.yuv_reprocess(props) or
                               its.caps.private_reprocess(props)))
 
+    # initialize chart class and locate chart in scene
+    chart = its.cv2image.Chart()
+
+    with its.device.ItsSession() as cam:
         mono_camera = its.caps.mono_camera(props)
         # If reprocessing is supported, ZSL EE mode must be avaiable.
-        assert(its.caps.edge_mode(props, 3))
+        assert its.caps.edge_mode(props, 3), "EE mode not available!"
 
         reprocess_formats = []
-        if (its.caps.yuv_reprocess(props)):
+        if its.caps.yuv_reprocess(props):
             reprocess_formats.append("yuv")
-        if (its.caps.private_reprocess(props)):
+        if its.caps.private_reprocess(props):
             reprocess_formats.append("private")
 
         size = its.objects.get_available_output_sizes("jpg", props)[0]
-        out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+        out_surface = {"width": size[0], "height": size[1], "format": "jpg"}
 
         # Get proper sensitivity, exposure time, and focus distance.
-        s,e,_,_,fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+        s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+        # Intialize plot
+        pylab.figure("reprocess_result")
+        gr_color = {"yuv": "r", "private": "g", "none": "b"}
 
         # Get the sharpness for each edge mode for regular requests
         sharpness_regular = []
@@ -126,12 +155,15 @@
                 edge_mode_reported_regular.append(edge_mode)
                 sharpness_regular.append(0)
                 continue
-            ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface)
+            ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface, chart)
             edge_mode_reported_regular.append(ret["edge_mode"])
             sharpness_regular.append(ret["sharpness"])
 
-        print "Reported edge modes:", edge_mode_reported_regular
+        pylab.plot(range(4), sharpness_regular, "-"+gr_color["none"]+"o")
+        print "Reported edge modes",
+        print "regular requests:", edge_mode_reported_regular
         print "Sharpness with EE mode [0,1,2,3]:", sharpness_regular
+        print ""
 
         # Get the sharpness for each reprocess format and edge mode for
         # reprocess requests.
@@ -150,60 +182,44 @@
                     continue
 
                 ret = test_edge_mode(cam, edge_mode, s, e, fd, out_surface,
-                    reprocess_format)
+                                     chart, reprocess_format)
                 edge_mode_reported.append(ret["edge_mode"])
                 sharpnesses.append(ret["sharpness"])
 
             sharpnesses_reprocess.append(sharpnesses)
             edge_mode_reported_reprocess.append(edge_mode_reported)
 
-            print "Reported edge modes:", edge_mode_reported
-            print "Sharpness with EE mode [0,1,2,3] for %s reprocess:" % \
-                (reprocess_format) , sharpnesses
+            pylab.plot(range(4), sharpnesses,
+                       "-"+gr_color[reprocess_format]+"o")
+            print "Reported edge modes w/ request fmt %s:" % reprocess_format
+            print "Sharpness with EE mode [0,1,2,3] for %s reprocess:" % (
+                    reprocess_format), sharpnesses
+            print ""
 
-
-        # Verify HQ(2) is sharper than OFF(0)
-        assert(sharpness_regular[2] > sharpness_regular[0])
-
-        # Verify ZSL(3) is similar to OFF(0)
-        assert(numpy.isclose(sharpness_regular[3], sharpness_regular[0],
-                             THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-
-        # Verify OFF(0) is not sharper than FAST(1)
-        assert(sharpness_regular[1] >
-               sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-
-        # Verify FAST(1) is not sharper than HQ(2)
-        assert(sharpness_regular[2] >
-               sharpness_regular[1] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+        # Finalize plot
+        pylab.title("Red-YUV Reprocess  Green-Private Reprocess  Blue-None")
+        pylab.xlabel("Edge Enhance Mode")
+        pylab.ylabel("Sharpness")
+        pylab.xticks(range(4))
+        matplotlib.pyplot.savefig("%s_plot_EE.png" %
+                                  ("test_reprocess_edge_enhancement"))
+        print "regular requests:"
+        check_edge_modes(sharpness_regular)
 
         for reprocess_format in range(len(reprocess_formats)):
-            # Verify HQ(2) is sharper than OFF(0)
-            assert(sharpnesses_reprocess[reprocess_format][2] >
-                   sharpnesses_reprocess[reprocess_format][0])
+            print "\nreprocess format:", reprocess_format
+            check_edge_modes(sharpnesses_reprocess[reprocess_format])
 
-            # Verify ZSL(3) is similar to OFF(0)
-            assert(numpy.isclose(sharpnesses_reprocess[reprocess_format][3],
-                                 sharpnesses_reprocess[reprocess_format][0],
-                                 THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+            hq_div_off_reprocess = (sharpnesses_reprocess[reprocess_format][2] /
+                                    sharpnesses_reprocess[reprocess_format][0])
+            hq_div_off_regular = sharpness_regular[2] / sharpness_regular[0]
+            e_msg = "HQ/OFF_reprocess: %.4f, HQ/OFF_reg: %.4f, RTOL: %.2f" % (
+                    hq_div_off_reprocess, hq_div_off_regular,
+                    THRESH_REL_SHARPNESS_DIFF)
+            print " Verify reprocess HQ(2) ~= reg HQ(2) relative to OFF(0)"
+            assert numpy.isclose(hq_div_off_reprocess, hq_div_off_regular,
+                                 THRESH_REL_SHARPNESS_DIFF), e_msg
 
-            # Verify OFF(0) is not sharper than FAST(1)
-            assert(sharpnesses_reprocess[reprocess_format][1] >
-                   sharpnesses_reprocess[reprocess_format][0] *
-                   (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-
-            # Verify FAST(1) is not sharper than HQ(2)
-            assert(sharpnesses_reprocess[reprocess_format][2] >
-                   sharpnesses_reprocess[reprocess_format][1] *
-                   (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-
-            # Verify reprocessing HQ(2) is similar to regular HQ(2) relative to
-            # OFF(0)
-            assert(numpy.isclose(sharpnesses_reprocess[reprocess_format][2] /
-                                    sharpnesses_reprocess[reprocess_format][0],
-                                 sharpness_regular[2] / sharpness_regular[0],
-                                 THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf
new file mode 100644
index 0000000..589a3b4
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index 92dfd0d..658dfa0 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -190,6 +190,7 @@
     aspect_ratio_gt = 1  # ground truth
     failed_ar = []  # streams failed the aspect ration test
     failed_crop = []  # streams failed the crop test
+    failed_fov = []  # streams that fail FoV test
     format_list = []  # format list for multiple capture objects.
     # Do multi-capture of "iter" and "cmpr". Iterate through all the
     # available sizes of "iter", and only use the size specified for "cmpr"
@@ -246,7 +247,7 @@
             if its.caps.distortion_correction(props):
                 # The intrinsics and distortion coefficients are meant for full
                 # size RAW. Resize back to full size here.
-                img_raw = cv2.resize(img_raw, (0,0), fx=2.0, fy=2.0)
+                img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0)
                 # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
                 # [f_x, f_y] is the horizontal and vertical focal lengths,
                 # [c_x, c_y] is the position of the optical axis,
@@ -259,7 +260,7 @@
                 sensor_w = props["android.sensor.info.physicalSize"]["width"]
                 pixel_h = props["android.sensor.info.pixelArraySize"]["height"]
                 pixel_w = props["android.sensor.info.pixelArraySize"]["width"]
-                fd = float(props["android.lens.info.availableFocalLengths"][0])
+                fd = float(cap_raw["metadata"]["android.lens.focalLength"])
                 fd_w_pix = pixel_w * fd / sensor_w
                 fd_h_pix = pixel_h * fd / sensor_h
                 # transformation matrix
@@ -381,15 +382,15 @@
                                   atol=FMT_ATOL):
                         # scale check value based on aspect ratio
                         chk_percent = ref_fov["percent"] * ar_scaling[ar_check]
-
-                        msg = "FoV %%: %.2f, Ref FoV %%: %.2f, TOL=%.f%%, " % (
-                                fov_percent, chk_percent,
-                                FOV_PERCENT_RTOL*100)
-                        msg += "img: %dx%d, ref: %dx%d" % (w_iter, h_iter,
-                                                           ref_fov["w"],
-                                                           ref_fov["h"])
-                        assert np.isclose(fov_percent, chk_percent,
-                                          rtol=FOV_PERCENT_RTOL), msg
+                        if not np.isclose(fov_percent, chk_percent,
+                                          rtol=FOV_PERCENT_RTOL):
+                            msg = "FoV %%: %.2f, Ref FoV %%: %.2f, " % (
+                                    fov_percent, chk_percent)
+                            msg += "TOL=%.f%%, img: %dx%d, ref: %dx%d" % (
+                                    FOV_PERCENT_RTOL*100, w_iter, h_iter,
+                                    ref_fov["w"], ref_fov["h"])
+                            failed_fov.append(msg)
+                            its.image.write_image(img/255, img_name, True)
                 # check pass/fail for aspect ratio
                 # image size >= LARGE_SIZE: use THRESH_L_AR
                 # image size == 0 (extreme case): THRESH_XS_AR
@@ -453,11 +454,20 @@
             print "\nAspect ratio test summary"
             print "Images failed in the aspect ratio test:"
             print "Aspect ratio value: width / height"
-        for fa in failed_ar:
-            print "%s with %s %dx%d: %.3f;" % (fa["fmt_iter"], fa["fmt_cmpr"],
-                                               fa["w"], fa["h"], fa["ar"]),
-            print "valid range: %.3f ~ %.3f" % (fa["valid_range"][0],
-                                                fa["valid_range"][1])
+            for fa in failed_ar:
+                print "%s with %s %dx%d: %.3f;" % (
+                        fa["fmt_iter"], fa["fmt_cmpr"],
+                        fa["w"], fa["h"], fa["ar"]),
+                print "valid range: %.3f ~ %.3f" % (
+                        fa["valid_range"][0], fa["valid_range"][1])
+
+        # Print FoV test results
+        failed_image_number_for_fov_test = len(failed_fov)
+        if failed_image_number_for_fov_test > 0:
+            print "\nFoV test summary"
+            print "Images failed in the FoV test:"
+            for fov in failed_fov:
+                print fov
 
         # Print crop test results
         failed_image_number_for_crop_test = len(failed_crop)
@@ -466,16 +476,17 @@
             print "Images failed in the crop test:"
             print "Circle center position, (horizontal x vertical), listed",
             print "below is relative to the image center."
-        for fc in failed_crop:
-            print "%s with %s %dx%d: %.3f x %.3f;" % (
-                    fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
-                    fc["ct_hori"], fc["ct_vert"]),
-            print "valid horizontal range: %.3f ~ %.3f;" % (
-                    fc["valid_range_h"][0], fc["valid_range_h"][1]),
-            print "valid vertical range: %.3f ~ %.3f" % (
-                    fc["valid_range_v"][0], fc["valid_range_v"][1])
+            for fc in failed_crop:
+                print "%s with %s %dx%d: %.3f x %.3f;" % (
+                        fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
+                        fc["ct_hori"], fc["ct_vert"]),
+                print "valid horizontal range: %.3f ~ %.3f;" % (
+                        fc["valid_range_h"][0], fc["valid_range_h"][1]),
+                print "valid vertical range: %.3f ~ %.3f" % (
+                        fc["valid_range_v"][0], fc["valid_range_v"][1])
 
         assert failed_image_number_for_aspect_ratio_test == 0
+        assert failed_image_number_for_fov_test == 0
         if level3_device:
             assert failed_image_number_for_crop_test == 0
 
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 4c3c4d9..021d59b 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -263,11 +263,10 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+        its.caps.skip_unless(its.caps.read_3a(props) and
                              its.caps.per_frame_control(props) and
                              its.caps.logical_multi_camera(props) and
-                             its.caps.raw16(props) and
-                             its.caps.manual_sensor(props))
+                             its.caps.raw16(props))
         debug = its.caps.debug_mode()
         avail_fls = props['android.lens.info.availableFocalLengths']
         pose_reference = props['android.lens.poseReference']
diff --git a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
index 63ddbdd..2174126 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
@@ -39,11 +39,10 @@
 def _check_available_capabilities(props):
     """Returns True if all required test capabilities are present."""
     return all([
-            its.caps.compute_target_exposure(props),
+            its.caps.read_3a(props),
             its.caps.per_frame_control(props),
             its.caps.logical_multi_camera(props),
             its.caps.raw16(props),
-            its.caps.manual_sensor(props),
             its.caps.sensor_fusion(props)])
 
 
@@ -100,9 +99,9 @@
     pylab.ylabel("Rotation angle difference (degrees)")
     matplotlib.pyplot.savefig("%s_angle_diffs_plot.png" % (NAME))
 
+
 def _collect_data():
     """Returns list of pair of gray frames and camera ids used for captures."""
-    yuv_sizes = {}
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
 
@@ -145,6 +144,7 @@
 
         return frame_pairs_gray, ids
 
+
 def main():
     """Test frame timestamps captured by logical camera are within 10ms."""
     frame_pairs_gray, ids = _collect_data()
@@ -156,12 +156,12 @@
     # Remove frames where not enough squares were detected.
     filtered_pairs_angles = []
     for angle_1, angle_2 in frame_pairs_angles:
-        if angle_1 == None or angle_2 == None:
+        if angle_1 is None or angle_2 is None:
             continue
         filtered_pairs_angles.append([angle_1, angle_2])
 
-    print 'Using {} image pairs to compute angular difference.'.format(
-        len(filtered_pairs_angles))
+    print "Using {} image pairs to compute angular difference.".format(
+            len(filtered_pairs_angles))
 
     assert len(filtered_pairs_angles) > 20, (
         "Unable to identify enough frames with detected squares.")
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 265fc33..c5e2d1b 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -226,7 +226,7 @@
     if abs(best_shift - exact_best_shift) > 2.0 or a <= 0 or c <= 0:
         print "Test failed; bad fit to time-shift curve"
         print "best_shift %f, exact_best_shift %f, a %f, c %f" % (
-            best_shift, exact_best_shift, a, c)
+                best_shift, exact_best_shift, a, c)
         assert 0
 
     xfit = numpy.arange(candidates[0], candidates[-1], 0.05).tolist()
@@ -343,12 +343,13 @@
         p0_filtered = p0[mask]
         num_features = len(p0_filtered)
         if num_features < MIN_FEATURE_PTS:
-            print "Not enough feature points in frame", i
+            print "Not enough feature points in frame %s" % str(i-1).zfill(3)
             print "Need at least %d features, got %d" % (
-                MIN_FEATURE_PTS, num_features)
+                    MIN_FEATURE_PTS, num_features)
             assert 0
         else:
-            print "Number of features in frame %d is %d" % (i, num_features)
+            print "Number of features in frame %s is %d" % (
+                    str(i-1).zfill(3), num_features)
         p1, st, _ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0_filtered,
                                              None, **LK_PARAMS)
         tform = procrustes_rotation(p0_filtered[st == 1], p1[st == 1])
@@ -428,8 +429,8 @@
     """
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.sensor_fusion(props) and
-                             its.caps.manual_sensor(props) and
+        its.caps.skip_unless(its.caps.read_3a and
+                             its.caps.sensor_fusion(props) and
                              props["android.lens.facing"] != FACING_EXTERNAL and
                              cam.get_sensors().get("gyro"))
 
@@ -452,7 +453,7 @@
         req["android.control.aeTargetFpsRange"] = [fps, fps]
         req["android.sensor.frameDuration"] = int(1000.0/fps * MSEC_TO_NSEC)
         print "Capturing %dx%d with sens. %d, exp. time %.1fms at %dfps" % (
-            w, h, s, e*NSEC_TO_MSEC, fps)
+                w, h, s, e*NSEC_TO_MSEC, fps)
         caps = cam.do_capture([req]*int(fps*test_length), fmt)
 
         # Capture a bit more gyro samples for use in
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
index 330b32f..e25a3f5 100644
--- a/apps/CameraITS/tools/load_scene.py
+++ b/apps/CameraITS/tools/load_scene.py
@@ -18,6 +18,7 @@
 import sys
 import time
 
+import its.cv2image
 import numpy as np
 
 
@@ -46,25 +47,28 @@
         print 'Error: need to specify screen serial'
         assert False
 
-    remote_scene_file = '/sdcard/Download/%s.pdf' % scene
-    local_scene_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
-                                    scene)
-    if np.isclose(chart_distance, 20, rtol=0.1) and camera_fov < 90:
-        local_scene_file = os.path.join(local_scene_file,
-                                        scene+'_0.67_scaled.pdf')
+    src_scene_path = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests', scene)
+    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' % (
+                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' % (
+                scene, str(its.cv2image.SCALE_RFOV_IN_WFOV_BOX))
     else:
-        local_scene_file = os.path.join(local_scene_file, scene+'.pdf')
-    print 'Loading %s on %s' % (local_scene_file, screen_id)
-    cmd = 'adb -s %s push %s /mnt%s' % (screen_id, local_scene_file,
-                                        remote_scene_file)
+        file_name = '%s.pdf' % scene
+    src_scene_file = os.path.join(src_scene_path, file_name)
+    print 'Loading %s on %s' % (src_scene_file, screen_id)
+    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...
     # 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
     cmd = ("adb -s %s wait-for-device shell am start -d 'file://%s'"
-           " -a android.intent.action.VIEW" % (screen_id,
-                                               remote_scene_file))
+           " -a android.intent.action.VIEW" % (screen_id, dst_scene_file))
     subprocess.Popen(cmd.split())
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 4cc0151..b3e51d5 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -20,6 +20,7 @@
 import subprocess
 import sys
 import tempfile
+import threading
 import time
 
 import its.caps
@@ -30,49 +31,75 @@
 
 import numpy as np
 
+# For sanity checking the installed APK's target SDK version
+MIN_SUPPORTED_SDK_VERSION = 28  # P
+
 CHART_DELAY = 1  # seconds
 CHART_DISTANCE = 30.0  # cm
 CHART_HEIGHT = 13.5  # cm
+CHART_LEVEL = 96
 CHART_SCALE_START = 0.65
 CHART_SCALE_STOP = 1.35
 CHART_SCALE_STEP = 0.025
 FACING_EXTERNAL = 2
 NUM_TRYS = 2
-SCENE3_FILE = os.path.join(os.environ["CAMERA_ITS_TOP"], "pymodules", "its",
-                           "test_images", "ISO12233.png")
+PROC_TIMEOUT_CODE = -101  # terminated process return -process_id
+PROC_TIMEOUT_TIME = 300  # timeout in seconds for a process (5 minutes)
+SCENE3_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
+                           'test_images', 'ISO12233.png')
 SKIP_RET_CODE = 101  # note this must be same as tests/scene*/test_*
 VGA_HEIGHT = 480
 VGA_WIDTH = 640
 
 # Not yet mandated tests
 NOT_YET_MANDATED = {
-        "scene0": [
-                "test_jitter",
-                "test_burst_capture",
-                "test_test_patterns"
+        'scene0': [
+                'test_test_patterns',
+                'test_tonemap_curve'
         ],
-        "scene1": [
-                "test_ae_af",
-                "test_ae_precapture_trigger",
-                "test_crop_region_raw",
-                "test_ev_compensation_advanced",
-                "test_ev_compensation_basic",
-                "test_yuv_plus_jpeg"
+        'scene1': [
+                'test_ae_precapture_trigger'
         ],
-        "scene2": [
-                "test_num_faces"
+        'scene2': [
+                'test_auto_per_frame_control'
         ],
-        "scene3": [
-                "test_flip_mirror",
-                "test_lens_movement_reporting",
-                "test_lens_position"
-        ],
-        "scene4": [],
-        "scene5": [],
-        "sensor_fusion": []
+        'scene3': [],
+        'scene4': [],
+        'scene5': [],
+        'sensor_fusion': []
 }
 
 
+def run_subprocess_with_timeout(cmd, fout, ferr, outdir):
+    """Run subprocess with a timeout.
+
+    Args:
+        cmd:    list containing python command
+        fout:   stdout file for the test
+        ferr:   stderr file for the test
+        outdir: dir location for fout/ferr
+
+    Returns:
+        process status or PROC_TIMEOUT_CODE if timer maxes
+    """
+
+    proc = subprocess.Popen(
+            cmd, stdout=fout, stderr=ferr, cwd=outdir)
+    timer = threading.Timer(PROC_TIMEOUT_TIME, proc.kill)
+
+    try:
+        timer.start()
+        proc.communicate()
+        test_code = proc.returncode
+    finally:
+        timer.cancel()
+
+    if test_code < 0:
+        return PROC_TIMEOUT_CODE
+    else:
+        return test_code
+
+
 def calc_camera_fov(camera_id):
     """Determine the camera field of view from internal params."""
     with ItsSession(camera_id) as cam:
@@ -174,6 +201,7 @@
     tmp_dir = None
     skip_scene_validation = False
     chart_distance = CHART_DISTANCE
+    chart_level = CHART_LEVEL
 
     for s in sys.argv[1:]:
         if s[:7] == "camera=" and len(s) > 7:
@@ -193,8 +221,11 @@
             skip_scene_validation = True
         elif s[:5] == 'dist=' and len(s) > 5:
             chart_distance = float(re.sub('cm', '', s[5:]))
+        elif s[:11] == 'brightness=' and len(s) > 11:
+            chart_level = s[11:]
 
     chart_dist_arg = 'dist= ' + str(chart_distance)
+    chart_level_arg = 'brightness=' + str(chart_level)
     auto_scene_switch = chart_host_id is not None
     merge_result_switch = result_device_id is not None
 
@@ -241,6 +272,50 @@
     device_id_arg = "device=" + device_id
     print "Testing device " + device_id
 
+    # Sanity check CtsVerifier SDK level
+    # Here we only do warning as there is no guarantee on pm dump output formt not changed
+    # Also sometimes it's intentional to run mismatched versions
+    cmd = "adb -s %s shell pm dump com.android.cts.verifier" % (device_id)
+    dump_path = os.path.join(topdir, 'CtsVerifier.txt')
+    with open(dump_path, 'w') as fout:
+        fout.write('ITS minimum supported SDK version is %d\n--\n' % (MIN_SUPPORTED_SDK_VERSION))
+        fout.flush()
+        ret_code = subprocess.call(cmd.split(), stdout=fout)
+
+    if ret_code != 0:
+        print "Warning: cannot get CtsVerifier SDK version. Is CtsVerifier installed?"
+
+    ctsv_version = None
+    ctsv_version_name = None
+    with open(dump_path, 'r') as f:
+        target_sdk_found = False
+        version_name_found = False
+        for line in f:
+            match = re.search('targetSdk=([0-9]+)', line)
+            if match:
+                ctsv_version = int(match.group(1))
+                target_sdk_found = True
+            match = re.search('versionName=([\S]+)$', line)
+            if match:
+                ctsv_version_name = match.group(1)
+                version_name_found = True
+            if target_sdk_found and version_name_found:
+                break
+
+    if ctsv_version is None:
+        print "Warning: cannot get CtsVerifier SDK version. Is CtsVerifier installed?"
+    elif ctsv_version < MIN_SUPPORTED_SDK_VERSION:
+        print "Warning: CtsVerifier version (%d) < ITS version (%d), is this intentional?" % (
+                ctsv_version, MIN_SUPPORTED_SDK_VERSION)
+    else:
+        print "CtsVerifier targetSdk is", ctsv_version
+        if ctsv_version_name:
+            print "CtsVerifier version name is", ctsv_version_name
+
+    # Hard check on ItsService/host script version that should catch incompatible APK/script
+    with ItsSession() as cam:
+        cam.check_its_version_compatible()
+
     # Sanity Check for devices
     device_bfp = its.device.get_device_fingerprint(device_id)
     assert device_bfp is not None
@@ -264,14 +339,15 @@
 
     if auto_scene_switch:
         # merge_result only supports run_parallel_tests
-        if merge_result_switch and camera_ids[0] == '1':
-            print 'Skip chart screen'
+        if merge_result_switch and camera_ids[0] == "1":
+            print "Skip chart screen"
             time.sleep(1)
         else:
-            print 'Waking up chart screen: ', chart_host_id
-            screen_id_arg = ('screen=%s' % chart_host_id)
-            cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
-                                          'wake_up_screen.py'), screen_id_arg]
+            print "Waking up chart screen: ", chart_host_id
+            screen_id_arg = ("screen=%s" % chart_host_id)
+            cmd = ["python", os.path.join(os.environ["CAMERA_ITS_TOP"], "tools",
+                                          "wake_up_screen.py"), screen_id_arg,
+                   chart_level_arg]
             wake_code = subprocess.call(cmd)
             assert wake_code == 0
 
@@ -331,14 +407,13 @@
                 if cmd is not None:
                     valid_scene_code = subprocess.call(cmd, cwd=topdir)
                     assert valid_scene_code == 0
-            print "Start running ITS on camera %s, %s" % (camera_id, scene)
+            print 'Start running ITS on camera %s, %s' % (camera_id, scene)
             # Extract chart from scene for scene3 once up front
             chart_loc_arg = ''
             chart_height = CHART_HEIGHT
             if scene == 'scene3':
-                if float(camera_fov) < 90 and np.isclose(chart_distance, 22,
-                                                         rtol=0.1):
-                    chart_height *= 0.67
+                chart_height *= its.cv2image.calc_chart_scaling(
+                        chart_distance, camera_fov)
                 chart = its.cv2image.Chart(SCENE3_FILE, chart_height,
                                            chart_distance, CHART_SCALE_START,
                                            CHART_SCALE_STOP, CHART_SCALE_STEP,
@@ -380,13 +455,13 @@
                         cmd += sys.argv[1:] + [camera_id_arg] + [chart_loc_arg]
                         cmd += [chart_dist_arg]
                         with open(outpath, 'w') as fout, open(errpath, 'w') as ferr:
-                            test_code = subprocess.call(
-                                cmd, stderr=ferr, stdout=fout, cwd=outdir)
+                            test_code = run_subprocess_with_timeout(
+                                cmd, fout, ferr, outdir)
                     if test_code == 0 or test_code == SKIP_RET_CODE:
                         break
                     else:
                         socket_fail = evaluate_socket_failure(errpath)
-                        if socket_fail:
+                        if socket_fail or test_code == PROC_TIMEOUT_CODE:
                             if num_try != NUM_TRYS-1:
                                 print ' Retry %s/%s' % (scene, testname)
                             else:
diff --git a/apps/CameraITS/tools/set_charging_limits.py b/apps/CameraITS/tools/set_charging_limits.py
new file mode 100644
index 0000000..280e226
--- /dev/null
+++ b/apps/CameraITS/tools/set_charging_limits.py
@@ -0,0 +1,78 @@
+# 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.
+
+import os
+import subprocess
+import sys
+
+CHARGE_PERCENT_START = 40
+CHARGE_PERCENT_STOP = 60
+
+
+def set_device_charging_limits(device_id):
+    """Set the start/stop percentages for charging.
+
+    This can keep battery from overcharging.
+    Args:
+        device_id:  str; device ID to set limits
+    """
+    print 'Rooting device %s' % device_id
+    cmd = ('adb -s %s root' % device_id)
+    process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+    pout, perr = process.communicate()
+    if 'cannot' in pout.lower() or perr:  # 'cannot root' returns no error
+        print ' Warning: unable to root %s and set charging limits.' % device_id
+    else:
+        print ' Setting charging limits on %s' % device_id
+        cmd = ('adb -s %s shell setprop persist.vendor.charge.start.level %d' % (
+                device_id, CHARGE_PERCENT_START))
+        process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+        _, perr = process.communicate()
+        if not perr:
+            print ' Min: %d%%' % CHARGE_PERCENT_START
+        else:
+            print ' Warning: unable to set charging start limit.'
+
+        cmd = ('adb -s %s shell setprop persist.vendor.charge.stop.level %d' % (
+                device_id, CHARGE_PERCENT_STOP))
+        process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+        _, perr = process.communicate()
+        if not perr:
+            print ' Max: %d%%' % CHARGE_PERCENT_STOP
+        else:
+            print ' Warning: unable to set charging stop limit.'
+
+        print 'Unrooting device %s' % device_id
+        cmd = ('adb -s %s unroot' % device_id)
+        subprocess.call(cmd.split(), stdout=subprocess.PIPE)
+
+
+def main():
+    """Set charging limits for battery."""
+
+    device_id = None
+    for s in sys.argv[1:]:
+        if s[:7] == 'device=' and len(s) > 7:
+            device_id = s[7:]
+
+    if device_id:
+        set_device_charging_limits(device_id)
+    else:
+        print 'Usage: python %s device=$DEVICE_ID' % os.path.basename(__file__)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tools/wake_up_screen.py b/apps/CameraITS/tools/wake_up_screen.py
index b317b05..b9305cb 100644
--- a/apps/CameraITS/tools/wake_up_screen.py
+++ b/apps/CameraITS/tools/wake_up_screen.py
@@ -25,9 +25,15 @@
 def main():
     """Power up and unlock screen as needed."""
     screen_id = None
+    display_level = DISPLAY_LEVEL
     for s in sys.argv[1:]:
         if s[:7] == 'screen=' and len(s) > 7:
             screen_id = s[7:]
+        if s[:11] == 'brightness=' and len(s) > 11:
+            display_level = int(s[11:])
+            if display_level < 0 or display_level > 255:
+                print 'Invalid brightness value. Range is [0-255]'
+                display_level = DISPLAY_LEVEL
 
     if not screen_id:
         print 'Error: need to specify screen serial'
@@ -53,10 +59,14 @@
     subprocess.Popen(unlock.split())
     time.sleep(DISPLAY_CMD_WAIT)
 
-    # set brightness
-    print 'Tablet display brightness set to %d' % DISPLAY_LEVEL
+    # set to manual mode and set brightness
+    manual = ('adb -s %s shell settings put system screen_brightness_mode 0'
+              % screen_id)
+    subprocess.Popen(manual.split())
+    time.sleep(DISPLAY_CMD_WAIT)
+    print 'Tablet display brightness set to %d' % display_level
     bright = ('adb -s %s shell settings put system screen_brightness %d'
-              % (screen_id, DISPLAY_LEVEL))
+              % (screen_id, display_level))
     subprocess.Popen(bright.split())
     time.sleep(DISPLAY_CMD_WAIT)
 
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 98b95f4..2763fab 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -34,6 +34,7 @@
                                compatibility-common-util-devicesidelib \
                                cts-sensors-tests \
                                cts-location-tests \
+                               cts-camera-performance-tests \
                                ctstestrunner \
                                apache-commons-math \
                                androidplot \
@@ -58,8 +59,10 @@
 LOCAL_PACKAGE_NAME := CtsVerifier
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
-		libaudioloopback_jni \
+LOCAL_JNI_SHARED_LIBRARIES := \
+	libctsverifier_jni \
+	libctsnativemidi_jni \
+	libaudioloopback_jni \
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
@@ -81,6 +84,7 @@
 LOCAL_MODULE := cts-verifier-framework
 LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages android.support.v4
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 19
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SRC_FILES := \
     $(call java-files-in, src/com/android/cts/verifier) \
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index cea0e7c..7730ed7 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -70,9 +70,6 @@
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
     <uses-permission android:name="android.permission.SEND_SMS" />
 
-    <!-- Needed by UsbTest tapjacking -->
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
     <!-- Needed for Telecom self-managed ConnectionService tests. -->
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
 
@@ -102,14 +99,6 @@
         <provider android:name=".TestResultsProvider"
                 android:authorities="com.android.cts.verifier.testresultsprovider" />
 
-        <activity android:name=".admin.tapjacking.UsbTest" android:label="@string/usb_tapjacking_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_device_admin" />
-        </activity>
-
         <activity android:name=".admin.PolicySerializationTestActivity"
                 android:label="@string/da_policy_serialization_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -182,34 +171,6 @@
         <activity android:name=".IntentDrivenTestActivity"
                 android:stateNotNeeded="true"/>
 
-        <activity android:name=".admin.DeviceAdminKeyguardDisabledFeaturesActivity"
-                android:label="@string/da_kg_disabled_features_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_device_admin" />
-            <meta-data android:name="test_excluded_features"
-                       android:value="android.software.lockscreen_disabled" />
-            <meta-data android:name="test_required_features"
-                    android:value="android.software.device_admin" />
-        </activity>
-
-        <activity android:name=".admin.RedactedNotificationKeyguardDisabledFeaturesActivity"
-                android:label="@string/rn_kg_disabled_features_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_device_admin" />
-            <meta-data android:name="test_required_features"
-                    android:value="android.software.device_admin" />
-            <meta-data android:name="test_excluded_features"
-                    android:value="android.hardware.type.watch" />
-        </activity>
-
         <activity android:name=".admin.ScreenLockTestActivity"
                 android:label="@string/da_screen_lock_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -1289,6 +1250,18 @@
                 android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
         </activity>
 
+        <activity android:name=".biometrics.BiometricTest"
+            android:label="@string/biometric_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_security" />
+            <meta-data android:name="test_excluded_features"
+                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+        </activity>
+
         <activity android:name=".security.FingerprintBoundKeysTest"
                 android:label="@string/sec_fingerprint_bound_key_test"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
@@ -1303,7 +1276,7 @@
         </activity>
 
         <activity android:name=".security.BiometricPromptBoundKeysTest"
-            android:label="@string/sec_fingerprint_dialog_bound_key_test"
+            android:label="@string/sec_biometric_prompt_bound_key_test"
             android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1312,7 +1285,6 @@
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
             <meta-data android:name="test_excluded_features"
                 android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.fingerprint" />
         </activity>
 
         <activity android:name=".security.ScreenLockBoundKeysTest"
@@ -2072,6 +2044,16 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.flash" />
         </activity>
 
+        <activity android:name=".camera.performance.CameraPerformanceActivity"
+                  android:label="@string/camera_performance_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_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">
@@ -2154,6 +2136,16 @@
                 <category android:name="android.intent.category.DEFAULT"></category>
             </intent-filter>
         </activity>
+        <activity android:name=".wifi.TestListActivity"
+                  android:label="@string/wifi_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_networking" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+        </activity>
         <activity android:name=".wifiaware.TestListActivity"
                   android:label="@string/aware_test"
                   android:configChanges="keyboardHidden|orientation|screenSize">
@@ -2322,6 +2314,18 @@
                     android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
         </activity>
 
+        <activity android:name=".wifi.NetworkRequestSpecificNetworkSpecifierTestActivity"
+                  android:label="@string/wifi_test_network_request_specific"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".wifi.NetworkRequestPatternNetworkSpecifierTestActivity"
+                  android:label="@string/wifi_test_network_request_pattern"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".wifi.NetworkRequestUnavailableNetworkSpecifierTestActivity"
+                  android:label="@string/wifi_test_network_request_unavailable"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".p2p.GoNegRequesterTestListActivity"
                 android:label="@string/p2p_go_neg_requester"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2346,6 +2350,42 @@
                 android:label="@string/p2p_accept_client"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".p2p.P2pClientWithConfigTestListActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.P2pClientWithConfig2gBandTestListActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.P2pClientWithConfigFixedFrequencyTestListActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.P2pClientWithConfigTestActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.P2pClientWithConfig2gBandTestActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.P2pClientWithConfigFixedFrequencyTestActivity"
+                android:label="@string/p2p_join_go"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.GoWithConfigTestActivity"
+                android:label="@string/p2p_accept_client"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.GoWithConfig2gBandTestActivity"
+                android:label="@string/p2p_accept_client"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".p2p.GoWithConfigFixedFrequencyTestActivity"
+                android:label="@string/p2p_accept_client"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".p2p.ServiceRequesterTestListActivity"
                 android:label="@string/p2p_service_discovery_requester"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2674,6 +2714,10 @@
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
         </activity>
 
+        <activity android:name=".managedprovisioning.NonMarketAppsActivity"
+                  android:label="@string/provisioning_byod_non_market_apps">
+        </activity>
+
         <activity android:name=".managedprovisioning.KeyguardDisabledFeaturesActivity"
                 android:label="@string/provisioning_byod_keyguard_disabled_features">
         </activity>
@@ -2897,6 +2941,8 @@
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.BYOD_CHECK_DISK_ENCRYPTION" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_INTENT_FILTERS" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_CAPTURE_AND_CHECK_IMAGE" />
@@ -2915,6 +2961,7 @@
                 <action android:name="com.android.cts.verifier.managedprovisioning.LOCKSCREEN_NOTIFICATION" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.CLEAR_NOTIFICATION" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.TEST_SELECT_WORK_CHALLENGE" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.TEST_PATTERN_WORK_CHALLENGE" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.LAUNCH_CONFIRM_WORK_CREDENTIALS" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.TEST_ORGANIZATION_INFO" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.TEST_PARENT_PROFILE_PASSWORD" />
@@ -2922,6 +2969,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".managedprovisioning.ByodPrimaryHelperActivity">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_IN_PRIMARY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".managedprovisioning.NfcTestActivity">
             <meta-data android:name="test_required_features" android:value="android.hardware.nfc" />
         </activity>
@@ -3317,6 +3371,19 @@
                 android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
+        <activity android:name=".audio.ProAudioActivity"
+                  android:label="@string/pro_audio_latency_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_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+        </activity>
+
+        <!-- ProAudio test invokes the "Loopback" App -->
+        <activity android:name="org.drrickorang.loopback"/>
+
         <activity android:name=".audio.AudioLoopbackActivity"
                   android:label="@string/audio_loopback_test">
             <intent-filter>
@@ -3329,6 +3396,34 @@
                        android:value="android.hardware.type.watch:android.hardware.type.television" />
         </activity>
 
+        <activity android:name=".audio.MidiActivity"
+                  android:label="@string/midi_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_audio" />
+            <meta-data android:name="test_required_features"
+                android:value="android.hardware.usb.host:android.software.midi" />
+        </activity>
+
+        <activity android:name=".audio.NDKMidiActivity"
+                  android:label="@string/ndk_midi_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_audio" />
+            <meta-data android:name="test_required_features"
+                android:value="android.hardware.usb.host:android.software.midi" />
+        </activity>
+
+        <service android:name="com.android.cts.verifier.audio.midilib.MidiEchoTestService"
+            android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+            <meta-data android:name="android.media.midi.MidiDeviceService"
+                android:resource="@xml/echo_device_info" />
+        </service>
+
         <activity android:name=".audio.AudioFrequencyLineActivity"
                   android:label="@string/audio_frequency_line_test">
             <intent-filter>
@@ -3703,6 +3798,9 @@
                 android:value="config_voice_capable"/>
         </activity>
 
+        <activity android:name=".managedprovisioning.LockscreenMessageTestActivity"
+            android:label="@string/device_owner_customize_lockscreen_message" />
+
         <service android:name="com.android.cts.verifier.telecom.CtsConnectionService"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
diff --git a/apps/CtsVerifier/jni/midi/Android.mk b/apps/CtsVerifier/jni/midi/Android.mk
new file mode 100644
index 0000000..c76f0c7
--- /dev/null
+++ b/apps/CtsVerifier/jni/midi/Android.mk
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctsnativemidi_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+	com_android_cts_verifier_audio_midilib_NativeMidiManager.cpp \
+	MidiTestManager.cpp
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_C_INCLUDES += \
+    frameworks/base/media/native/midi/include \
+    frameworks/av/media/ndk/include \
+    system/core/include/cutils
+
+#LOCAL_CXX_STL := libc++_static
+#LOCAL_NDK_STL_VARIANT := libc++_static
+
+#APP_STL := stlport_static
+#APP_STL := gnustl_static
+
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := system
+
+LOCAL_SHARED_LIBRARIES := liblog libamidi \
+
+LOCAL_CFLAGS := \
+        -Wall -Werror \
+        -Wno-unused-parameter \
+        -Wno-unused-variable \
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.cpp b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
new file mode 100644
index 0000000..f491835
--- /dev/null
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
@@ -0,0 +1,352 @@
+/*
+ * 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.
+ */
+#include <cstring>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <midi.h>
+
+#define TAG "MidiTestManager"
+#include <android/log.h>
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+#include "MidiTestManager.h"
+
+static pthread_t readThread;
+
+static const bool DEBUG = false;
+static const bool DEBUG_MIDIDATA = false;
+
+//
+// MIDI Messages
+//
+// Channel Commands
+static const uint8_t kMIDIChanCmd_KeyDown = 9;
+static const uint8_t kMIDIChanCmd_KeyUp = 8;
+static const uint8_t kMIDIChanCmd_PolyPress = 10;
+static const uint8_t kMIDIChanCmd_Control = 11;
+static const uint8_t kMIDIChanCmd_ProgramChange = 12;
+static const uint8_t kMIDIChanCmd_ChannelPress = 13;
+static const uint8_t kMIDIChanCmd_PitchWheel = 14;
+// System Commands
+static const uint8_t kMIDISysCmd_SysEx = 0xF0;
+static const uint8_t kMIDISysCmd_EndOfSysEx =  0xF7;
+static const uint8_t kMIDISysCmd_ActiveSensing = 0xFE;
+static const uint8_t kMIDISysCmd_Reset = 0xFF;
+
+static void* readThreadRoutine(void * context) {
+    MidiTestManager* testManager = (MidiTestManager*)context;
+    return reinterpret_cast<void*>(static_cast<intptr_t>(testManager->ProcessInput()));
+}
+
+/*
+ * TestMessage
+ */
+#define makeMIDICmd(cmd, channel)  (uint8_t)((cmd << 4) | (channel & 0x0F))
+
+class TestMessage {
+public:
+    uint8_t*   mMsgBytes;
+    int     mNumMsgBytes;
+
+    TestMessage()
+        : mMsgBytes(NULL), mNumMsgBytes(0)
+    {}
+
+    ~TestMessage() {
+        delete[] mMsgBytes;
+    }
+
+    bool set(uint8_t* msgBytes, int numMsgBytes) {
+        if (msgBytes == NULL || numMsgBytes <= 0) {
+            return false;
+        }
+        mNumMsgBytes = numMsgBytes;
+        mMsgBytes = new uint8_t[numMsgBytes];
+        memcpy(mMsgBytes, msgBytes, mNumMsgBytes * sizeof(uint8_t));
+        return true;
+    }
+}; /* class TestMessage */
+
+/*
+ * MidiTestManager
+ */
+MidiTestManager::MidiTestManager()
+    : mTestModuleObj(NULL),
+      mTestStream(NULL), mNumTestStreamBytes(0),
+      mReceiveStreamPos(0),
+      mMidiSendPort(NULL), mMidiReceivePort(NULL),
+      mTestMsgs(NULL), mNumTestMsgs(0)
+{}
+
+MidiTestManager::~MidiTestManager(){
+    delete[] mTestStream;
+}
+
+void MidiTestManager::jniSetup(JNIEnv* env) {
+    env->GetJavaVM(&mJvm);
+
+    jclass clsMidiTestModule =
+        env->FindClass("com/android/cts/verifier/audio/NDKMidiActivity$NDKMidiTestModule");
+    if (DEBUG) {
+        ALOGI("gClsMidiTestModule:%p", clsMidiTestModule);
+    }
+
+    // public void endTest(int endCode)
+    mMidEndTest = env->GetMethodID(clsMidiTestModule, "endTest", "(I)V");
+    if (DEBUG) {
+        ALOGI("mMidEndTestgMidEndTest:%p", mMidEndTest);
+    }
+}
+
+void MidiTestManager::buildTestStream() {
+    // add up the total space
+    mNumTestStreamBytes = 0;
+    for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
+        mNumTestStreamBytes += mTestMsgs[msgIndex].mNumMsgBytes;
+    }
+
+    delete[] mTestStream;
+    mTestStream = new uint8_t[mNumTestStreamBytes];
+    int streamIndex = 0;
+    for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
+        for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
+            mTestStream[streamIndex++] = mTestMsgs[msgIndex].mMsgBytes[byteIndex];
+        }
+    }
+
+    // Reset stream position
+    mReceiveStreamPos = 0;
+}
+
+/**
+ * Compares the supplied bytes against the sent message stream at the current postion
+ * and advances the stream position.
+ */
+bool MidiTestManager::matchStream(uint8_t* bytes, int count) {
+    if (DEBUG) {
+        ALOGI("---- matchStream() count:%d", count);
+    }
+    bool matches = true;
+
+    for (int index = 0; index < count; index++) {
+        if (bytes[index] != mTestStream[mReceiveStreamPos]) {
+            matches = false;
+            if (DEBUG) {
+                ALOGI("---- mismatch @%d [%d : %d]",
+                        index, bytes[index], mTestStream[mReceiveStreamPos]);
+            }
+        }
+        mReceiveStreamPos++;
+    }
+
+    if (DEBUG) {
+        ALOGI("  returns:%d", matches);
+    }
+    return matches;
+}
+
+/**
+ * Writes out the list of MIDI messages to the output port.
+ * Returns total number of bytes sent.
+ */
+int MidiTestManager::sendMessages() {
+    if (DEBUG) {
+        ALOGI("---- sendMessages()...");
+        for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
+            if (DEBUG_MIDIDATA) {
+            ALOGI("--------");
+                for(int byteIndex = 0; byteIndex < mTestMsgs[msgIndex].mNumMsgBytes; byteIndex++) {
+                    ALOGI("  0x%X", mTestMsgs[msgIndex].mMsgBytes[byteIndex]);
+                }
+            }
+        }
+    }
+
+    int totalSent = 0;
+    for(int msgIndex = 0; msgIndex < mNumTestMsgs; msgIndex++) {
+        ssize_t numSent =
+            AMidiInputPort_send(mMidiSendPort,
+                    mTestMsgs[msgIndex].mMsgBytes, mTestMsgs[msgIndex].mNumMsgBytes);
+        totalSent += numSent;
+    }
+
+    if (DEBUG) {
+        ALOGI("---- totalSent:%d", totalSent);
+    }
+
+    return totalSent;
+}
+
+int MidiTestManager::ProcessInput() {
+    uint8_t readBuffer[128];
+    size_t totalNumReceived = 0;
+
+    bool testRunning = true;
+    int testResult = TESTSTATUS_NOTRUN;
+
+    int32_t opCode;
+    size_t numBytesReceived;
+    int64_t timeStamp;
+    while (testRunning) {
+        // AMidiOutputPort_receive is non-blocking, so let's not burn up the CPU unnecessarily
+        usleep(2000);
+
+        numBytesReceived = 0;
+        ssize_t numMessagesReceived =
+            AMidiOutputPort_receive(mMidiReceivePort, &opCode, readBuffer, 128,
+                        &numBytesReceived, &timeStamp);
+
+        if (testRunning &&
+            numBytesReceived > 0 &&
+            opCode == AMIDI_OPCODE_DATA &&
+            readBuffer[0] != kMIDISysCmd_ActiveSensing &&
+            readBuffer[0] != kMIDISysCmd_Reset) {
+            if (DEBUG) {
+                ALOGI("---- msgs:%zd, bytes:%zu", numMessagesReceived, numBytesReceived);
+            }
+            // Process Here
+            if (!matchStream(readBuffer, numBytesReceived)) {
+                testResult = TESTSTATUS_FAILED_MISMATCH;
+                testRunning = false;   // bail
+            }
+            totalNumReceived += numBytesReceived;
+            if (totalNumReceived > mNumTestStreamBytes) {
+                testResult = TESTSTATUS_FAILED_OVERRUN;
+                testRunning = false;   // bail
+            }
+            if (totalNumReceived == mNumTestStreamBytes) {
+                testResult = TESTSTATUS_PASSED;
+                testRunning = false;   // done
+            }
+        }
+    }
+
+    return testResult;
+}
+
+bool MidiTestManager::StartReading(AMidiDevice* nativeReadDevice) {
+    ALOGI("StartReading()...");
+
+    media_status_t m_status =
+        AMidiOutputPort_open(nativeReadDevice, 0, &mMidiReceivePort);
+    if (m_status != 0) {
+        ALOGE("Can't open MIDI device for reading err:%d", m_status);
+        return false;
+    }
+
+    // Start read thread
+    int status = pthread_create(&readThread, NULL, readThreadRoutine, this);
+    if (status != 0) {
+        ALOGE("Can't start readThread: %s (%d)", strerror(status), status);
+    }
+    return status == 0;
+}
+
+bool MidiTestManager::StartWriting(AMidiDevice* nativeWriteDevice) {
+    ALOGI("StartWriting()...");
+
+    media_status_t status =
+        AMidiInputPort_open(nativeWriteDevice, 0, &mMidiSendPort);
+    if (status != 0) {
+        ALOGE("Can't open MIDI device for writing err:%d", status);
+        return false;
+    }
+    return true;
+}
+
+uint8_t msg0[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 64, 120};
+//uint8_t msg0Alt[] = {makeMIDICmd(kMIDIChanCmd_KeyDown, 0), 65, 120};
+uint8_t msg1[] = {makeMIDICmd(kMIDIChanCmd_KeyUp, 0), 64, 35};
+
+bool MidiTestManager::RunTest(jobject testModuleObj, AMidiDevice* sendDevice,
+        AMidiDevice* receiveDevice) {
+    if (DEBUG) {
+        ALOGI("RunTest(%p, %p, %p)", testModuleObj, sendDevice, receiveDevice);
+    }
+
+    JNIEnv* env;
+    mJvm->AttachCurrentThread(&env, NULL);
+    if (env == NULL) {
+        EndTest(TESTSTATUS_FAILED_JNI);
+    }
+
+    mTestModuleObj = env->NewGlobalRef(testModuleObj);
+
+    // Call StartWriting first because StartReading starts a thread.
+    if (!StartWriting(sendDevice) || !StartReading(receiveDevice)) {
+        // Test call to EndTest will close any open devices.
+        EndTest(TESTSTATUS_FAILED_DEVICE);
+        return false; // bail
+    }
+
+    // setup messages
+    delete[] mTestMsgs;
+    mNumTestMsgs = 3;
+    mTestMsgs = new TestMessage[mNumTestMsgs];
+
+    int sysExSize = 8;
+    uint8_t* sysExMsg = new uint8_t[sysExSize];
+    sysExMsg[0] = kMIDISysCmd_SysEx;
+    for(int index = 1; index < sysExSize-1; index++) {
+        sysExMsg[index] = (uint8_t)index;
+    }
+    sysExMsg[sysExSize-1] = kMIDISysCmd_EndOfSysEx;
+
+    if (!mTestMsgs[0].set(msg0, sizeof(msg0)) ||
+        !mTestMsgs[1].set(msg1, sizeof(msg1)) ||
+        !mTestMsgs[2].set(sysExMsg, sysExSize)) {
+        return false;
+    }
+    delete[] sysExMsg;
+
+    buildTestStream();
+
+    // Inject an error
+    // mTestMsgs[0].set(msg0Alt, 3);
+
+    sendMessages();
+    void* threadRetval = (void*)TESTSTATUS_NOTRUN;
+    int status = pthread_join(readThread, &threadRetval);
+    if (status != 0) {
+        ALOGE("Failed to join readThread: %s (%d)", strerror(status), status);
+    }
+    EndTest(static_cast<int>(reinterpret_cast<intptr_t>(threadRetval)));
+    return true;
+}
+
+void MidiTestManager::EndTest(int endCode) {
+
+    JNIEnv* env;
+    mJvm->AttachCurrentThread(&env, NULL);
+    if (env == NULL) {
+        ALOGE("Error retrieving JNI Env");
+    }
+
+    env->CallVoidMethod(mTestModuleObj, mMidEndTest, endCode);
+    env->DeleteGlobalRef(mTestModuleObj);
+
+    // EndTest() will ALWAYS be called, so we can close the ports here.
+    if (mMidiSendPort != NULL) {
+        AMidiInputPort_close(mMidiSendPort);
+        mMidiSendPort = NULL;
+    }
+    if (mMidiReceivePort != NULL) {
+        AMidiOutputPort_close(mMidiReceivePort);
+        mMidiReceivePort = NULL;
+    }
+}
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.h b/apps/CtsVerifier/jni/midi/MidiTestManager.h
new file mode 100644
index 0000000..fe292b5
--- /dev/null
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#include <midi.h>
+
+#include <jni.h>
+
+class TestMessage;
+
+class MidiTestManager {
+public:
+    MidiTestManager();
+    ~MidiTestManager();
+
+    void jniSetup(JNIEnv* env);
+
+    bool RunTest(jobject testModuleObj, AMidiDevice* sendDevice, AMidiDevice* receiveDevice);
+    void EndTest(int testCode);
+
+    // Called by the thread routine.
+    int ProcessInput();
+
+private:
+   void buildTestStream();
+    bool matchStream(uint8_t* bytes, int count);
+
+    int sendMessages();
+
+    jobject mTestModuleObj;
+
+    // The send messages in a linear stream for matching.
+    uint8_t*   mTestStream;
+    int     mNumTestStreamBytes;
+    int     mReceiveStreamPos;
+
+    AMidiInputPort* mMidiSendPort;
+    AMidiOutputPort* mMidiReceivePort;
+
+    // The array of messages to send/receive
+    TestMessage*    mTestMsgs;
+    int             mNumTestMsgs;
+
+    // JNI
+    JavaVM* mJvm;
+    jmethodID mMidEndTest;
+
+    // Test result codes
+    static const int TESTSTATUS_NOTRUN = 0;
+    static const int TESTSTATUS_PASSED = 1;
+    static const int TESTSTATUS_FAILED_MISMATCH = 2;
+    static const int TESTSTATUS_FAILED_TIMEOUT = 3;
+    static const int TESTSTATUS_FAILED_OVERRUN = 4;
+    static const int TESTSTATUS_FAILED_DEVICE = 5;
+    static const int TESTSTATUS_FAILED_JNI = 6;
+
+    bool StartReading(AMidiDevice* nativeReadDevice);
+    bool StartWriting(AMidiDevice* nativeWriteDevice);
+};
diff --git a/apps/CtsVerifier/jni/midi/com_android_cts_verifier_audio_midilib_NativeMidiManager.cpp b/apps/CtsVerifier/jni/midi/com_android_cts_verifier_audio_midilib_NativeMidiManager.cpp
new file mode 100644
index 0000000..80c17a4
--- /dev/null
+++ b/apps/CtsVerifier/jni/midi/com_android_cts_verifier_audio_midilib_NativeMidiManager.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+#define TAG "NativeMidiManager-JNI"
+
+#include <android/log.h>
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
+
+#include <midi.h>
+
+#include "MidiTestManager.h"
+
+static MidiTestManager sTestManager;
+
+static bool DEBUG = false;
+
+extern "C" {
+
+void Java_com_android_cts_verifier_audio_midilib_NativeMidiManager_initN(
+        JNIEnv* env, jobject midiTestModule) {
+
+    sTestManager.jniSetup(env);
+}
+
+void Java_com_android_cts_verifier_audio_midilib_NativeMidiManager_startTest(
+        JNIEnv* env, jobject thiz, jobject testModuleObj, jobject midiObj) {
+
+    (void)thiz;
+
+    if (DEBUG) {
+        ALOGI("NativeMidiManager_startTest(%p, %p)", testModuleObj, midiObj);
+    }
+
+    media_status_t status;
+
+    AMidiDevice* nativeMidiDevice = NULL;
+    status = AMidiDevice_fromJava(env, midiObj, &nativeMidiDevice);
+    if (DEBUG) {
+        ALOGI("nativeSendDevice:%p, status:%d", nativeMidiDevice, status);
+    }
+
+    sTestManager.RunTest(testModuleObj, nativeMidiDevice, nativeMidiDevice);
+
+    status = AMidiDevice_release(nativeMidiDevice);
+    if (DEBUG) {
+        ALOGI("device release status:%d", status);
+    }
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index e4249c4..2e59465 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -18,6 +18,15 @@
     public <methods>;
 }
 
+# ensure we keep public camera test methods, these are needed at runtime
+-keepclassmembers class * extends android.hardware.camera2.cts.testcases.Camera2AndroidTestCase {
+    public <methods>;
+}
+
+-keepclassmembers class * extends android.hardware.cts.CameraTestCase {
+    public <methods>;
+}
+
 -keepclasseswithmembers class * extends com.android.cts.verifier.location.LocationModeTestActivity
 
 # keep mockito methods
@@ -35,7 +44,6 @@
 
 # Jack seems less rigorous than proguard when it comes to warning about
 # transitive dependencies.
--dontwarn com.android.org.bouncycastle.**
 -dontwarn com.android.okhttp.**
 -dontwarn org.opencv.**
 -dontwarn android.support.test.internal.runner.hidden.ExposedInstrumentationApi
diff --git a/apps/CtsVerifier/res/layout/biometric_test_main.xml b/apps/CtsVerifier/res/layout/biometric_test_main.xml
new file mode 100644
index 0000000..75a42f9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/biometric_test_main.xml
@@ -0,0 +1,52 @@
+<?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
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="10dip"
+    >
+
+    <Button android:id="@+id/biometric_start_test1_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:text="@string/biometric_start_test1"
+        />
+
+    <Button android:id="@+id/biometric_enroll_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/biometric_start_test2_button"
+        android:layout_centerHorizontal="true"
+        android:text="@string/biometric_enroll"
+        />
+
+    <Button android:id="@+id/biometric_start_test2_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:text="@string/biometric_start_test2"
+        />
+
+    <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/camera_performance.xml b/apps/CtsVerifier/res/layout/camera_performance.xml
new file mode 100644
index 0000000..bdac03f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/camera_performance.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="bottom"
+    android:orientation="vertical">
+
+    <include layout="@layout/pass_fail_buttons"/>
+
+        <ListView
+            android:id="@+id/android:list"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="2"
+            android:gravity="top"
+            android:scrollbars="vertical"/>
+
+        <TextView
+            android:id="@+id/test_instructions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            android:textSize="16dip"/>
+
+        <Button
+            android:id="@+id/prepare_test_button"
+            android:layout_width="match_parent"
+            android:visibility="gone"
+            android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/lockscreen_message.xml b/apps/CtsVerifier/res/layout/lockscreen_message.xml
new file mode 100644
index 0000000..dcf0315
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/lockscreen_message.xml
@@ -0,0 +1,55 @@
+<?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:id="@+id/root_view"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:text="@string/device_owner_customize_lockscreen_message_info"/>
+
+        <EditText
+                android:id="@+id/lockscreen_message_edit_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/device_owner_set_lockscreen_message_hint"
+                android:gravity="top|start"
+                android:windowSoftInputMode="adjustPan"
+                android:padding="16dp" />
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+            <Button android:id="@+id/lockscreen_message_set_button"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/device_owner_set_lockscreen_message_button"
+                    android:layout_weight="1"/>
+            <Button android:id="@+id/go_button"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:text="@string/go_button_text"
+                    android:layout_weight="1"/>
+        </LinearLayout>
+
+        <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/midi_activity.xml b/apps/CtsVerifier/res/layout/midi_activity.xml
new file mode 100644
index 0000000..d8daee1
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/midi_activity.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:text="@string/midiHasMIDILbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/midiHasMIDILbl"
+        android:textSize="18sp"/>
+
+    <!--  USB Test -->
+    <TextView
+        android:text="@string/midiUSBTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/usbMidiInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/usbMidiOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestUSBInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestUSBInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <!--  Virtual Test -->
+    <TextView
+        android:text="@string/midiVirtTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiVirtInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/midiVirtOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestVirtInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestVirtInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <!--  Bluetooth Test -->
+    <TextView
+        android:text="@string/midiBTTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiBTInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/midiBTOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestBTInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestBTInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ndk_midi_activity.xml b/apps/CtsVerifier/res/layout/ndk_midi_activity.xml
new file mode 100644
index 0000000..d8daee1
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ndk_midi_activity.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:text="@string/midiHasMIDILbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/midiHasMIDILbl"
+        android:textSize="18sp"/>
+
+    <!--  USB Test -->
+    <TextView
+        android:text="@string/midiUSBTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/usbMidiInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/usbMidiOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestUSBInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestUSBInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiUSBTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <!--  Virtual Test -->
+    <TextView
+        android:text="@string/midiVirtTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiVirtInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/midiVirtOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestVirtInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestVirtInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiVirtTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <!--  Bluetooth Test -->
+    <TextView
+        android:text="@string/midiBTTestLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiBTInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+                android:text="@string/midiBTOutputLbl"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTOutputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <Button
+        android:text="@string/midiTestBTInterfaceBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/midiTestBTInterfaceBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/midiStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/midiBTTestStatusLbl"
+            android:text="@string/midiNotRunLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/pro_audio.xml b/apps/CtsVerifier/res/layout/pro_audio.xml
new file mode 100644
index 0000000..71edb71
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pro_audio.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scrollView"
+    style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioHasLLAlbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioHasLLALbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioMidiHasMIDILbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioHasMIDILbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioMidiHasUSBHostLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioMidiHasUSBHostLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioMidiHasUSBPeripheralLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioMidiHasUSBPeripheralLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <CheckBox android:id="@+id/proAudioHasHDMICheckBox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/proAudioHasHDMICheckBox"
+        android:onClick="onCheckboxClicked"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioHDMISupportLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioHDMISupportLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <TextView
+        android:text="@string/proAudioInputLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/proAudioInputLbl"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:text="@string/proAudioOutputLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/proAudioOutputLbl"
+        android:textSize="18sp"/>
+
+    <Button
+        android:text="@string/audio_proaudio_roundtrip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/proAudio_runRoundtripBtn"/>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioRoundTripLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioRoundTripLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/proAudioConfidenceLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/proAudioConfidenceLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/tapjacking.xml b/apps/CtsVerifier/res/layout/tapjacking.xml
deleted file mode 100644
index 998e624..0000000
--- a/apps/CtsVerifier/res/layout/tapjacking.xml
+++ /dev/null
@@ -1,49 +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.
--->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-            style="@style/RootLayoutPadding"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-    <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical">
-
-        <RelativeLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" >
-            <TextView
-                    android:id="@+id/usb_tapjacking_instructions"
-                    style="@style/InstructionsSmallFont"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentRight="true"
-                    android:layout_alignParentTop="true" />
-
-            <Button android:id="@+id/tapjacking_btn"
-                    android:text="@string/usb_tapjacking_button_text"
-                    android:layout_below="@id/usb_tapjacking_instructions"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentRight="true"
-                    android:layout_marginLeft="20dip"
-                    android:layout_marginRight="20dip"/>
-        </RelativeLayout>
-
-        <include layout="@layout/pass_fail_buttons" />
-    </LinearLayout>
-</ScrollView>
-
diff --git a/apps/CtsVerifier/res/layout/usb_tapjacking_overlay.xml b/apps/CtsVerifier/res/layout/usb_tapjacking_overlay.xml
deleted file mode 100644
index 6a5ba58..0000000
--- a/apps/CtsVerifier/res/layout/usb_tapjacking_overlay.xml
+++ /dev/null
@@ -1,36 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/textView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:maxWidth="350dp"
-        android:paddingLeft="20dp"
-        android:paddingRight="20dp"
-        android:paddingBottom="6dp"
-        android:paddingTop="8dp"
-        android:background="#ffffff"
-        android:text="@string/usb_tapjacking_overlay_message"
-        android:textColor="#000000"
-        android:textSize="22sp" />
-</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/wifi_main.xml b/apps/CtsVerifier/res/layout/wifi_main.xml
new file mode 100644
index 0000000..fee710d
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/wifi_main.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+>
+
+    <FrameLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1.0"
+    >
+
+        <ScrollView android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+        >
+            <TextView android:id="@+id/wifi_info"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      style="@style/InstructionsFont"
+            />
+        </ScrollView>
+
+        <ProgressBar
+            android:id="@+id/wifi_progress"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            android:layout_gravity="center"
+            android:visibility="gone"
+        />
+    </FrameLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 207be80..80801f6 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -118,8 +118,6 @@
         settings that may specify a timeout.\n\nClick the \"Force Lock\" button to lock the screen.
         Your screen should be locked and require the password to be entered.
     </string>
-    <string name="da_kg_disabled_features_test">Keyguard Disabled Features Test</string>
-    <string name="rn_kg_disabled_features_test">Redacted Notifications Keyguard Disabled Features Test</string>
     <string name="da_force_lock">Force Lock</string>
     <string name="da_lock_success">It appears the screen was locked successfully!</string>
     <string name="da_lock_error">It does not look like the screen was locked...</string>
@@ -151,6 +149,15 @@
     </string>
     <string name="da_tapjacking_button_text">Enable device admin</string>
 
+    <!-- Strings for BiometricTest -->
+    <string name="biometric_test">Biometric Test</string>
+    <string name="biometric_test_info">
+        This test ensures that biometrics can be authenticated when templates are enrolled.
+    </string>
+    <string name="biometric_enroll">Enroll</string>
+    <string name="biometric_start_test1">Start Test 1</string>
+    <string name="biometric_start_test2">Start Test 2</string>
+
     <!-- Strings for lock bound keys test -->
     <string name="sec_lock_bound_key_test">Lock Bound Keys Test</string>
     <string name="sec_lock_bound_key_test_info">
@@ -159,6 +166,7 @@
         complete this test. If available, this test should be run by using fingerprint authentication
         as well as PIN/pattern/password authentication.
     </string>
+
     <string name="sec_fingerprint_bound_key_test">Fingerprint Bound Keys Test</string>
     <string name="sec_fingerprint_bound_key_test_info">
         This test ensures that Keystore cryptographic keys that are bound to fingerprint authentication
@@ -168,7 +176,12 @@
     <string name="sec_fp_dialog_message">Authenticate now with fingerprint</string>
     <string name="sec_fp_auth_failed">Authentication failed</string>
     <string name="sec_start_test">Start Test</string>
-    <string name="sec_fingerprint_dialog_bound_key_test">Fingerprint Bound Keys Test (System Dialog)</string>
+
+    <string name="sec_biometric_prompt_bound_key_test">Biometric Prompt Bound Keys Test</string>
+    <string name="sec_biometric_prompt_bound_key_test_info">
+        This test ensures that Keystore cryptographic keys that are bound to biometric authentication
+        are unusable without an authentication.
+    </string>
 
     <!-- Strings for BluetoothActivity -->
     <string name="bluetooth_test">Bluetooth Test</string>
@@ -1043,6 +1056,7 @@
     <!-- Magnetic Field -->
     <string name="snsr_mag_m_test">Magnetic Field Measurement Tests</string>
     <string name="snsr_mag_verify_norm">Verifying the Norm...</string>
+    <string name="snsr_mag_verify_offset">Verifying the Offset...</string>
     <string name="snsr_mag_verify_std_dev">Verifying the Standard Deviation...</string>
     <string name="snsr_mag_verify_calibrated_uncalibrated">Verifying the relationship between
         calibrated and uncalibrated measurements...</string>
@@ -1316,9 +1330,9 @@
         1. Install the Cts Verifier USB Companion app on a separate helper device.
         \n\n2. Start the device test companion in the Cts Verifier USB Companion.
         \n\n3. Connect the two devices. If using a OTG adapter make sure the adapter is directly connected to this device. If using an Type-C cable make sure that this device is set as "supply power to the attached device".
-        \n\n4. Confirm access to the USB device on this device.
+        \n\n4. Confirm access to the USB device on this device. Do <u>not</u> make this app the default app for the device.
         \n\n5. Confirm access to the USB accessory on the helper device.
-        \n\n6. Confirm access to the USB device on this device again.
+        \n\n6. Confirm access to the USB device on this device again. Do <u>not</u> make this app the default app for the device.
         \n\n7. Test will run and complete automatically in less than 30 seconds.
         \n\n8. Cancel all further dialogs on the helper device.
     </string>
@@ -1330,12 +1344,12 @@
         \n\nResult: A dialog should show up on this device asking for access to a USB device.
     </string>
     <string name="usb_device_test_step2">
-        Confirm access to the USB device on this device.
+        Confirm access to the USB device on this device. Do <u>not</u> make this app the default app for the device.
         \n\nResult: Dialogs should show up on this device and on the helper device asking for access to a USB device/accessory.
     </string>
     <string name="usb_device_test_step3">
         1. Confirm access to the USB accessory on the helper device.
-        \n2. Confirm access to the USB device on this device again.
+        \n2. Confirm access to the USB device on this device again. Do <u>not</u> make this app the default app for the device.
         \n\nResult: A progress indicator should appear or test will finish.
     </string>
     <string name="usb_device_test_step4">
@@ -1400,6 +1414,16 @@
     <string name="camera_flashlight_passed_text">All tests passed. Press Done or Pass button.
     </string>
 
+    <!-- Strings for the Camera Performance test activity -->
+    <string name="camera_performance_test">Camera Performance</string>
+    <string name="camera_performance_test_info">
+        This activity will run performance test cases. For optimal and consistent results please
+        make sure that all camera sensors are pointing in a direction with sufficiently bright
+        light source.
+    </string>
+    <string name="camera_performance_spinner_text">Running CTS performance test case...</string>
+    <string name="camera_performance_result_title">Test Result</string>
+
     <!-- Strings for StreamingVideoActivity -->
     <string name="streaming_video">Streaming Video Quality Verifier</string>
     <string name="streaming_video_info">This is a test for assessing the quality of streaming videos.  Play each stream and verify that the video is smooth and in sync with the audio, and that there are no quality problems.</string>
@@ -1407,6 +1431,44 @@
     <string name="sv_failed_title">Test Failed</string>
     <string name="sv_failed_message">Unable to play stream.  See log for details.</string>
 
+    <!-- Strings for TestListActivity -->
+    <string name="wifi_test">Wi-Fi Test</string>
+    <string name="wifi_test_info">
+        The Wi-Fi tests requires an open (no security) access point along with the DUT.
+    </string>
+    <string name="wifi_location_not_enabled">Wi-Fi / Location Mode is not enabled</string>
+    <string name="wifi_location_not_enabled_message">These tests require Wi-Fi and Location Mode to be enabled.
+        Click the button below to go to system settings and enable Wi-Fi and Location Mode.</string>
+    <string name="wifi_settings">Wi-Fi Settings</string>
+    <string name="location_settings">Location Settings</string>
+    <string name="wifi_setup_error">
+        Test failed.\n\nSet up error. Check whether Wi-Fi is enabled.</string>
+    <string name="wifi_unexpected_error">
+        Test failed.\n\nUnexpected error. Check logcat.</string>
+
+    <string name="wifi_status_initiating_scan">Initiating scan.</string>
+    <string name="wifi_status_scan_failure">Unable to initiate scan.</string>
+    <string name="wifi_status_open_network_not_found">Unable to find any open network in scan results.</string>
+    <string name="wifi_status_initiating_network_request">Initiating network request.</string>
+    <string name="wifi_status_network_wait_for_available">Waiting for network connection. Please click the network in the dialog that pops up for approving the request.</string>
+    <string name="wifi_status_network_available">"Connected to network."</string>
+    <string name="wifi_status_network_wait_for_unavailable">"Ensuring device does not connect to any network. You should see an empty dialog that pops up for approving the request."</string>
+    <string name="wifi_status_network_unavailable">"Did not connect to any network."</string>
+    <string name="wifi_status_network_wait_for_lost">Ensuring device does not disconnect from the network until the request is released.</string>
+    <string name="wifi_status_network_lost">Disconnected from the network.</string>
+    <string name="wifi_status_network_cb_timeout">Network callback timed out.</string>
+
+    <string name="wifi_status_test_success">Test completed successfully!</string>
+    <string name="wifi_status_test_failed">Test failed!</string>
+
+    <string name="wifi_test_network_request">Network Request tests</string>
+    <string name="wifi_test_network_request_specific">Network Request with a specific SSID and BSSID.</string>
+    <string name="wifi_test_network_request_specific_info">Tests whether the API can be used to a connect to network with a specific SSID and BSSID specified in the request.</string>
+    <string name="wifi_test_network_request_pattern">Network Request with a SSID and BSSID pattern.</string>
+    <string name="wifi_test_network_request_pattern_info">Tests whether the API can be used to a connect to network with a SSID and BSSID pattern specified in the request.</string>
+    <string name="wifi_test_network_request_unavailable">Network Request with a specific network that is unavailable.</string>
+    <string name="wifi_test_network_request_unavailable_info">Tests that the API fails to connect when an unavailable network is specified in the request.</string>
+
     <!-- Strings for P2pTestActivity -->
     <string name="p2p_test">Wi-Fi Direct Test</string>
     <string name="p2p_test_info">
@@ -1424,7 +1486,21 @@
     <string name="p2p_go_neg_responder_test">GO Negotiation Responder Test</string>
     <string name="p2p_go_neg_requester_test">GO Negotiation Requester Test</string>
     <string name="p2p_group_owner_test">Group Owner Test</string>
+    <string name="p2p_join_with_config">Group Join with Config</string>
+    <string name="p2p_join_with_config_2g_band">Group Join with Config 2G Band</string>
+    <string name="p2p_join_with_config_fixed_frequency">
+        Group Join with Config Fixed Frequency</string>
+    <string name="p2p_group_owner_with_config_test">Group Owner With Config Test</string>
+    <string name="p2p_group_owner_with_config_2g_band_test">
+        Group Owner With Config 2G Band Test</string>
+    <string name="p2p_group_owner_with_config_fixed_frequency_test">
+        Group Owner With Config Fixed Frequency Test</string>
     <string name="p2p_group_client_test">Group Client Test</string>
+    <string name="p2p_group_client_with_config_test">Group Client With Config Test</string>
+    <string name="p2p_group_client_with_config_2g_band_test">
+        Group Client With Config 2G Band Test</string>
+    <string name="p2p_group_client_with_config_fixed_frequency_test">
+        Group Client With Config Fixed Frequency Test</string>
     <string name="p2p_service_discovery_responder_test">
         Service Discovery Responder Test</string>
     <string name="p2p_service_discovery_requester_test">
@@ -1616,6 +1692,7 @@
     <string name="aware_status_network_requested">Network requested ...</string>
     <string name="aware_status_network_success">Network formed ...</string>
     <string name="aware_status_network_failed">Network request failure - timed out!</string>
+    <string name="aware_status_network_failed_leak">Failure: Network request success - but leaked information!</string>
     <string name="aware_status_sleeping_wait_for_responder">Pausing to let Responder time to set up ...</string>
     <string name="aware_status_ranging_peer_failure">Ranging to PeerHandle failure: %1$d failures of %2$d attempts!</string>
     <string name="aware_status_ranging_mac_failure">Ranging to MAC address failure: %1$d failures of %2$d attempts!</string>
@@ -1799,6 +1876,7 @@
     <string name="nas_note_enqueued_received">Check that notification was enqueued.</string>
     <string name="nls_note_received">Check that notification was received.</string>
     <string name="nls_payload_intact">Check that notification payload was intact.</string>
+    <string name="nls_audibly_alerted">Check that notification audibly alerting was reported correctly.</string>
     <string name="nas_adjustment_payload_intact">Check that the Assistant can adjust notifications.</string>
     <string name="nas_adjustment_enqueue_payload_intact">Check that the Assistant can adjust notifications on enqueue.</string>
     <string name="nas_create_channel">Check that the Assistant can create a Notification Channel for another app.</string>
@@ -1831,7 +1909,9 @@
     <string name="cp_disable_service">Please disable \"CTS Verifier\" under Do Not Disturb access and return here.</string>
     <string name="cp_start_settings">Launch Settings</string>
     <string name="cp_create_rule">Creating Automatic Zen Rule</string>
+    <string name="cp_create_rule_with_zen_policy">Creating Automatic Zen Rule with Zen Policy</string>
     <string name="cp_update_rule">Updating Automatic Zen Rule</string>
+    <string name="cp_update_rule_use_zen_policy">Updating Automatic Rule to Use Zen Policy</string>
     <string name="cp_subscribe_rule">Subscribing to Automatic Zen Rule</string>
     <string name="cp_service_started">Service should start once enabled.</string>
     <string name="cp_service_stopped">Service should stop once disabled.</string>
@@ -1889,6 +1969,12 @@
     <string name="widget_pass">Pass</string>
     <string name="widget_fail">Fail</string>
 
+    <string name="provisioning_byod_non_market_apps">Non-market app installation restrictions</string>
+    <string name="provisioning_byod_non_market_apps_info">
+        This test exercises user restrictions on installation of non-market apps. Follow
+        instructions in each test.
+    </string>
+
     <string name="provisioning_byod_nonmarket_allow">Enable non-market apps</string>
     <string name="provisioning_byod_nonmarket_allow_info">
         This test verifies that non-market apps can be installed if permitted.\n
@@ -1906,6 +1992,56 @@
         2. Verify that the installation of the package is refused.
     </string>
 
+    <string name="provisioning_byod_nonmarket_allow_global">Enable non-market apps (global restriction)</string>
+    <string name="provisioning_byod_nonmarket_allow_global_info">
+        This test verifies that non-market apps can be installed if permitted by device-wide block.\n
+        1. A package installation UI should appear.\n
+        2. If \'Cts Verifier\' is not allowed to install apps, a warning dialog will appear
+        blocking the install. In this case go to step 3, else skip to step 4.\n
+        3. Allow \'Cts Verifier\' to install apps. Return to package installer.\n
+        4. Accept the installation and verify that it succeeds (no error message is displayed).
+    </string>
+
+    <string name="provisioning_byod_nonmarket_deny_global">Disable non-market apps (global restriction)</string>
+    <string name="provisioning_byod_nonmarket_deny_global_info">
+        This test verifies that non-market apps cannot be installed unless permitted by device-wide block.\n
+        1. A package installation UI should appear.\n
+        2. Verify that the installation of the package is refused.
+    </string>
+
+    <string name="provisioning_byod_nonmarket_allow_global_primary">Enable primary user non-market apps (global restriction)</string>
+    <string name="provisioning_byod_nonmarket_allow_global_primary_info">
+        This test verifies that non-market apps from the primary user can be installed if permitted.\n
+        1. You should have received NotificationBot.apk together with the CTS verifier. If you built
+        the CTS verifier yourself, build the NotificationBot.apk by issuing the following command on
+        the host:\n
+        make NotificationBot\n
+        2. Upload the NotificationBot.apk to your device by issuing the following command on the
+        host:\n
+        adb push /path/to/NotificationBot.apk /data/local/tmp/\n
+        3. Press \"Go\" to install NotificationBot.apk in your personal profile. A package
+        installation UI should appear.\n
+        4. If \'Cts Verifier\' is not allowed to install apps, a warning dialog will appear
+        blocking the install. In this case go to step 5, else skip to step 6.\n
+        5. Allow \'Cts Verifier\' to install apps. Return to package installer.\n
+        6. Accept the installation and verify that it succeeds (no error message is displayed).
+    </string>
+
+    <string name="provisioning_byod_nonmarket_deny_global_primary">Disable primary user non-market apps (global restriction)</string>
+    <string name="provisioning_byod_nonmarket_deny_global_primary_info">
+        This test verifies that non-market apps from the primary user cannot be installed unless permitted.\n
+        1. You should have received NotificationBot.apk together with the CTS verifier. If you built
+        the CTS verifier yourself, build the NotificationBot.apk by issuing the following command on
+        the host:\n
+        make NotificationBot\n
+        2. Upload the NotificationBot.apk to your device by issuing the following command on the
+        host:\n
+        adb push /path/to/NotificationBot.apk /data/local/tmp/\n
+        3. Press \"Go\" to install NotificationBot.apk in your personal profile. A package
+        installation UI should appear.\n
+        4. Verify that the installation of the package is refused.
+    </string>
+
     <string name="provisioning_byod_capture_image_support">Camera support cross profile image capture</string>
     <string name="provisioning_byod_capture_image_support_info">
         This test verifies that images can be captured from the managed profile using the primary profile camera.\n
@@ -2048,7 +2184,17 @@
     <string name="provisioning_byod_confirm_work_credentials_header">
         CtsVerifier
     </string>
+    <string name="provisioning_byod_pattern_work_challenge">Confirm pattern lock test</string>
+    <string name="provisioning_byod_pattern_work_challenge_description">
+        This test verifies that when a work pattern lock is set, a work app can open correctly.
 
+        1. Verify that you get sent to the page for Choosing a new work lock.\n
+        2. Set a pattern lock.\n
+        3. Open a work app.\n
+        4. Verify that a screen asking you for your work credentials is shown.\n
+        5. Confirm your credentials and verify that the credentials you entered previously work.\n
+        6. The work app should be launched.
+    </string>
     <string name="provisioning_byod_recents">Recents redaction test</string>
     <string name="provisioning_byod_recents_info">
         This test verifies that if a work profile is locked with a separate password, Recents views
@@ -2078,14 +2224,16 @@
         Verify recents are not redacted when unlocked.
     </string>
     <string name="provisioning_byod_recents_verify_not_redacted_instruction">
-        1) Follow the instructions on-screen to remove the work password.\n
-        2) Open Recents.\n
-        3) Confirm that this "CTS Verifier" activity is shown in Recents.\n
-        4) Confirm that the contents of the activity <b>are not</b> hidden.\n
-        5) Return to this page and pass the test.
+        1) Press the Go button to go to security settings.\n
+        2) Make work profile use one lock with personal profile.\n
+        3) Open Recents.\n
+        4) Confirm that this "CTS Verifier" activity is shown in Recents.\n
+        5) Confirm that the contents of the activity <b>are not</b> hidden.\n
+        6) Return to this page and pass the test.
     </string>
     <string name="provisioning_byod_recents_remove_password">
-        The work profile still has a separate password. Please remove this before continuing.
+        The work profile still has a separate password. Please make it use one lock with the
+        personal profile.
     </string>
 
     <string name="provisioning_byod_keychain">KeyChain test</string>
@@ -2110,10 +2258,8 @@
         Press \'Go\'.\n
     </string>
     <string name="provisioning_byod_keychain_info_second_test">
-        Once you press \'Run 2nd test\', the same prompt should appear again.\n
-        This time, verify that the title is \"No certificates found\" and the list is empty,
-        then press \'Cancel\'.\n
-        \n
+        Once you press \'Run 2nd test\', the prompt should NOT appear.\n
+        Verify that the prompt does not appear at all.\n
         Mark the test as passed if the text at the bottom shows \"PASSED (2/2)\"\n
     </string>
 
@@ -2529,6 +2675,7 @@
         \n
         - A new set of work apps including CTS Verifier appear in the list.\n
         - Work badge overlay appears on work app\'s icon (see example icon below, color and style may vary).\n
+        - The work badge overlay has the same size and position on each work app\'s icon.
         \n
         Then navigate back to this screen using Recents button.
     </string>
@@ -2673,6 +2820,30 @@
         3. Go back to the cts-verifier tests using the back button, then mark the test accordingly.\n
     </string>
 
+    <string name="provisioning_byod_personal_ringtones">Personal ringtones</string>
+    <string name="provisioning_byod_personal_ringtones_instruction">
+        This test verifies that personal ringtones can be changed independently of work profile ringtones.\n
+        1. Press the go button to go to the sound settings page. Under \"Work profile sounds\" turn off the \"use personal profile sounds\" switch.\n
+        2. Change the personal \"Phone ringtone\", \"Default notification sound\", and \"Default alarm sound\" settings to different values.\n
+        3. Read the values displayed under \"Work phone ringtone\", \"Default work notification sound\", and \"Default work alarm sound\".\n
+        4. Verify that the work sounds are different to the personal values just set.\n
+        5. Go back to the cts-verifier tests using the back button, then mark the test accordingly.\n
+    </string>
+
+    <string name="provisioning_byod_uninstall_work_app">Uninstall work app from launcher</string>
+    <string name="provisioning_byod_uninstall_work_app_instruction">
+        This test verifies that work profile apps can be uninstalled.\n
+        1. You should have received NotificationBot.apk together with the CTS verifier. If you built the CTS verifier yourself, build the NotificationBot.apk by issuing the following command on the host:\n
+            make NotificationBot\n
+        2. Upload the NotificationBot.apk to your device by issuing the following command on the host:\n
+            adb push /path/to/NotificationBot.apk /data/local/tmp/\n
+        3. Pres \"Go\" to install NotificationBot.apk on your work profile.\n
+        4. If you are presented with a dialog to allow installing of unknown apps, enable that option.\n
+        5. Go to home screen.\n
+        6. Verify that the newly installed app, \"CTS Robot\", can successfully be uninstalled from the launcher.\n
+    </string>
+    <string name="provisioning_byod_uninstall_work_app_install_work_app">Install work app</string>
+
     <string name="provisioning_byod_turn_off_work">Turn off work profile</string>
     <string name="provisioning_byod_turn_off_work_info">This test verifies device behaviors when turning off work profile.</string>
     <string name="provisioning_byod_turn_off_work_instructions">
@@ -2813,8 +2984,9 @@
     <string name="set_device_owner_button_label">Set up device owner</string>
     <string name="set_device_owner_dialog_title">Set up device owner</string>
     <string name="set_device_owner_dialog_text">
-            This test requires CtsEmptyDeviceOwner.apk to be installed on the device.
-            Please set the device owner by enabling USB debugging on the device and issuing the following command on the host:\n
+            For this test you need to install CtsEmptyDeviceOwner.apk by running\n
+            adb install -r -t /path/to/CtsEmptyDeviceOwner.apk\n
+            Then you need to set this app as the device owner by running\n
             adb shell dpm set-device-owner com.android.cts.emptydeviceowner/.EmptyDeviceAdmin
     </string>
     <string name="device_owner_remove_device_owner_test">Remove device owner</string>
@@ -2871,9 +3043,9 @@
     <string name="device_owner_disable_statusbar_test">Disable status bar</string>
     <string name="device_owner_disable_statusbar_test_info">
             Please press the below button to disable the status bar and verify that quick settings, notifications
-            and the assist gesture are no longer available.\n
+            and the assistant gesture are no longer available.\n
             Next, press the button to reenable the status bar and verify that quick settings, notification
-            and the assist gesture are available again.\n
+            and the assistant gesture are available again.\n
             Please mark the test accordingly.
     </string>
     <string name="device_owner_disable_statusbar_button">Disable status bar</string>
@@ -2885,7 +3057,7 @@
             switch off the screen. Then press the power button to switch the screen back on and verify that
             no keyguard was shown.\n
             Next, press the button to reenable the keyguard and repeat the above steps, this time verifying that
-            a keyguard was shown again.\n
+            a keyguard was shown.\n
             Please mark the test accordingly.
     </string>
     <string name="device_owner_disable_keyguard_button">Disable keyguard</string>
@@ -2915,7 +3087,7 @@
             button, etc., isn\'t shown.\n
             6) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            7) The assist gesture isn\'t available.
+            7) The assistant gesture isn\'t available.
     </string>
     <string name="device_owner_lock_task_ui_system_info_test">Enable system info</string>
     <string name="device_owner_lock_task_ui_system_info_test_info">
@@ -2931,7 +3103,7 @@
             button, etc., isn\'t shown.\n
             6) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            7) The assist gesture isn\'t available.\n\n
+            7) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_notifications_test">Enable notifications</string>
@@ -2948,7 +3120,7 @@
             button, etc., isn\'t shown.\n
             5) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            6) The assist gesture isn\'t available.\n\n
+            6) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_home_test">Enable Home button</string>
@@ -2966,7 +3138,7 @@
             button, etc., isn\'t shown.\n
             6) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            7) The assist gesture isn\'t available.\n\n
+            7) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_recents_test">Enable Overview button</string>
@@ -2983,7 +3155,7 @@
             button, etc., isn\'t shown.\n
             4) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            5) The assist gesture isn\'t available.\n\n
+            5) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_global_actions_test">Enable global actions</string>
@@ -3001,7 +3173,7 @@
             4) The Overview button is hidden and the Overview gesture (swipe-up) does not work.\n
             5) Press the power button to turn off the screen, and press it again to turn the screen
             back on. Lock screen shouldn\'t be shown.\n
-            6) The assist gesture isn\'t available.\n\n
+            6) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_keyguard_test">Enable keyguard</string>
@@ -3018,7 +3190,7 @@
             4) The Overview button is hidden and the Overview gesture (swipe-up) does not work.\n
             5) Long-press the power button. The power button menu, which usually shows the power-off
             button, etc., isn\'t shown.\n
-            6) The assist gesture isn\'t available.\n\n
+            6) The assistant gesture isn\'t available.\n\n
             Mark the test as \'pass\' only if ALL of the above requirements are met.
     </string>
     <string name="device_owner_lock_task_ui_stop_lock_task_test">Stop LockTask mode</string>
@@ -3311,16 +3483,16 @@
     <string name="disallow_add_user_action">Adding a new user</string>
     <string name="disallow_adjust_volume">Disallow adjust volume</string>
     <string name="disallow_adjust_volume_action">Adjusting the volume</string>
-    <string name="disallow_config_date_time">Disallow config date and time settings</string>
+    <string name="disallow_config_date_time">Disallow config date time</string>
     <string name="disallow_config_date_time_action">Configuring auto time, time, auto date or date</string>
     <string name="disallow_config_location">Disallow config location</string>
     <string name="disallow_config_location_action">Enabling or disabling location in settings or quick settings</string>
     <string name="disallow_airplane_mode">Disallow airplane mode</string>
     <string name="disallow_airplane_mode_action">Toggling airplane mode switch bar or changing airplane mode state in quick settings</string>
-    <string name="disallow_config_screen_timeout">Disallow config sleep options settings</string>
-    <string name="disallow_config_screen_timeout_action">Configuring sleep options in Display or Battery page.</string>
-    <string name="disallow_config_brightness">Disallow config brightness settings</string>
-    <string name="disallow_config_brightness_action">Configuring brightness level or adaptive brightness in Display or Battery page, or toggling brightness slider in quick settings</string>
+    <string name="disallow_config_screen_timeout">Disallow config screen timeout</string>
+    <string name="disallow_config_screen_timeout_action">Configuring sleep options in Display settings</string>
+    <string name="disallow_config_brightness">Disallow config brightness</string>
+    <string name="disallow_config_brightness_action">Configuring brightness level or adaptive brightness in Display settings, or toggling brightness slider in quick settings</string>
     <string name="disallow_apps_control">Disallow controlling apps</string>
     <string name="disallow_apps_control_action">DISABLE/UNINSTALL/FORCE STOP-ing any app in the managed device/profile other than CtsVerifier</string>
     <string name="disallow_config_cell_broadcasts">Disallow config cell broadcasts</string>
@@ -3344,8 +3516,10 @@
     <string name="disallow_fun_action">Opening android easter egg game by tapping repeatedly on the \'Android version\' option</string>
     <string name="disallow_install_unknown_sources">Disallow install unknown sources</string>
     <string name="disallow_install_unknown_sources_action">Enabling \'Cts Verifier\' to install apps</string>
+    <string name="disallow_install_unknown_sources_globally">Disallow install unknown sources globally</string>
+    <string name="disallow_install_unknown_sources_globally_action">Enabling \'Cts Verifier\' to install apps device-wide</string>
     <string name="disallow_modify_accounts">Disallow modify accounts</string>
-    <string name="disallow_modify_accounts_action">Adding an account or removing an account (if you have already added one)</string>
+    <string name="disallow_modify_accounts_action">Adding an account, and also removing an account (after you have previously added one)</string>
     <string name="disallow_network_reset">Disallow network reset</string>
     <string name="disallow_network_reset_action">Resetting network settings</string>
     <string name="disallow_outgoing_beam">Disallow outgoing beam</string>
@@ -3353,23 +3527,21 @@
     <string name="disallow_remove_user">Disallow remove user</string>
     <string name="device_owner_disallow_remove_user_info">
         Please press \'Create uninitialized user\' to create a user that is not set up. Then press the
-        \'Set restriction\' button to set the user restriction. Then press \'Go\' to open \'Settings\',
-        and manually find and open \'Multiple users\' setting. \n\n
+        \'Set restriction\' button to set the user restriction.
+        Then press \'Go\' to open \'Multiple users\' setting. \n\n
 
         Mark this test as passed if:\n\n
-        - The uninitialized user cannot be removed.\n
-        - \'Remove user\' option is disabled with an info icon on it. Clicking on it triggers a support dialog.\n\n
-
+        - Main switch is disabled and in off position\n
+        \n
         Use the Back button to return to this page.
     </string>
     <string name="managed_user_disallow_remove_user_info">
         Please press the \'Set restriction\' button to set the user restriction.
-        Then press \'Go\' to open \'Settings\', and manually find and open \'Multiple users\' setting. \n\n
+        Then press \'Go\' to open \'Multiple users\' setting. \n\n
 
         Mark this test as passed if one of the following conditions is met:\n\n
-        - \'Remove user\' option is disabled with an info icon on it. Clicking on it triggers a support dialog.\n
-        - \'Remove user\' option cannot be found.\n \n
-
+        - Main switch is disabled and in off position\n
+        \n
         Use the Back button to return to this page.
     </string>
     <string name="device_owner_disallow_remove_user_create_user">Create uninitialized user</string>
@@ -3739,6 +3911,30 @@
     <string name="device_owner_enable_network_logging_button">Enable Network Logging</string>
     <string name="device_owner_disable_network_logging_button">Disable Network Logging</string>
 
+    <!-- Strings for Customize Lock Screen Message -->
+    <string name="device_owner_customize_lockscreen_message">Customize Lock Screen Message</string>
+    <string name="device_owner_customize_lockscreen_message_info">Please do the following:\n
+        1) Press the \"Go\" button to go to Settings\n
+        2) Find the \"Screen lock\" option and set it to \"Swipe\"\n
+        3) Use the Back button to return to this page\n
+        4) In the field below, write a lock screen message.\n
+        5) Press the \"Set lock screen message\" button\n
+        6) Press Power button once to lock the screen.\n
+        7) Press Power button again to reveal the lock screen.\n
+        8) Verify that the message from step 1) appears on the lock screen.\n
+        9) Unlock the screen and return to this page.\n
+        10) Press the \"Go\" button to go to Settings\n
+        11) Find the \"Lock screen message\" option\n
+        12) Verify that you cannot modify the setting.\n
+        13) Use the Back button to return to this page.\n
+        14) Press the \"Go\" button to go to Settings\n
+        15) Find the \"Screen lock\" option and set it to \"None\"\n
+        16) Use the Back button to return to this page
+    </string>
+    <string name="device_owner_set_lockscreen_message_button">Set lock screen message</string>
+    <string name="device_owner_set_lockscreen_message_hint">My lock screen message</string>
+    <string name="device_owner_lockscreen_message_cannot_be_empty">Lock screen message cannot be empty.</string>
+
     <string name="comp_test">Corporate Owned Managed Profile</string>
     <string name="comp_provision_profile_dialog_title">Provision work profile</string>
     <string name="comp_provision_profile_dialog_text">Press the OK button to start the managed provisioning flow, and complete the flow to create a work profile</string>
@@ -3763,11 +3959,11 @@
     <string name="device_owner_disallow_user_switch">Disallow user switch</string>
     <string name="device_owner_disallow_user_switch_info">
         Press \'Create uninitialized user\' to create a user that is not setup.
-        Then press Set restriction button to set the user restriction.
-        Then press Go to open the Settings, and manually find and open user settings section.
+        Then press \'Set restriction\' button to set the user restriction.
+        Then press \'Go\' to open multiple users settings.
         Confirm that:\n
         \n
-        - Selecting uninitialized user should not trigger user switch.\n
+        - Main switch is disabled and in off position\n
         \n
         In additional, if quick settings is available, confirm that user switcher is hidden or
         disabled.
@@ -4089,7 +4285,6 @@
     <string name="connectedPeripheral">Connected Peripheral</string>
     <string name="audio_uap_attribs_test">USB Audio Peripheral Attributes Test</string>
     <string name="uapPeripheralProfileStatus">Peripheral Profile Status</string>
-
     <string name="audio_uap_play_test">USB Audio Peripheral Play Test</string>
     <string name="uapPlayTestInstructions">Connect the USB Audio Interface Peripheral and press the
         PLAY button below. Verify that a tone is correctly played.</string>
@@ -4113,6 +4308,73 @@
     <string name="uapButtonsBtnDLbl">Button D - voice assist</string>
     <string name="uapButtonsRecognized">Recognized</string>
     <string name="uapButtonsNotRecognized">Not Recognized</string>
+    <string name="uapButtonsDisableAssistantTitle">Disable Google Assistant</string>
+    <string name="uapButtonsDisableAssistant">For this test to succeed it may be necessary
+        to disable the Google Assistant (Settings / Google / Search / Google Assistant Settings /
+        Devices / &lt;device name&gt; / Google Assistant)</string>
+
+    <!--  Pro Audio Tests -->
+    <string name="pro_audio_latency_test">Pro Audio Test</string>
+
+    <string name="proAudioHasLLAlbl">Has Low-Latency Audio</string>
+    <string name="proAudioInputLbl">Audio Input:</string>
+    <string name="proAudioOutputLbl">Audio Output:</string>
+    <string name="proAudioRoundTripLbl">Round Trip Latency:</string>
+    <string name="proAudioConfidenceLbl">Confidence:</string>
+
+    <string name="proAudioMidiHasMIDILbl">Has MIDI Support</string>
+    <string name="proAudioMIDIInputLbl">MIDI Input:</string>
+    <string name="proAudioMIDIOutputLbl">MIDI Output:</string>
+    <string name="proAudioMidiHasUSBHostLbl">USB Host Mode:</string>
+    <string name="proAudioMidiHasUSBPeripheralLbl">USB Peripheral Mode:</string>
+    <string name="proAudioHDMISupportLbl">HDMI Support:</string>
+    <string name="proAudioHasHDMICheckBox">Has HDMI Support</string>
+
+    <string name="audio_proaudio_loopbackbtn">Start Loopback</string>
+    <string name="audio_proaudio_loopbackInfoBtn">Loopback Instructions</string>
+    <string name="audio_proaudio_roundtrip">Round-Trip Test</string>
+    <string name="audio_proaudio_NA">N/A</string>
+    <string name="audio_proaudio_pending">pending...</string>
+
+    <!--  MIDI Test -->
+    <string name="midi_test">MIDI Test</string>
+    <string name="ndk_midi_test">Native MIDI API Test</string>
+    <string name="midi_info">
+       For the USB MIDI interface test it is required that you have connected a supported USB
+       Audio Peripheral device with standard MIDI 5-pin, DIN (round) connectors and a standard
+       MIDI cable. The cable must be connected to the MIDI input and output plugs on the
+       peripheral.
+       \nFor the USB Bluetooth test it is required that you connect a Yamaha MT-BT301 to the
+       correct MIDI plugs on the USB peripheral, the BT301 output jack to the USB interface
+       input jack and BT301 input plug to the USB interface output jack.
+       \nThe Virtual MIDI test does not require any MIDI interface hardware.
+    </string>
+
+    <string name="midiHasMIDILbl">Has MIDI Support</string>
+
+    <string name="midiUSBTestLbl">USB MIDI Loopback Test:</string>
+    <string name="usbMidiInputLbl">USB Input:</string>
+    <string name="usbMidiOutputLbl">USB Output:</string>
+    <string name="midiTestUSBInterfaceBtn">Test USB MIDI Interface</string>
+
+    <string name="midiVirtTestLbl">Virtual MIDI Loopback Test:</string>
+    <string name="midiVirtInputLbl">Virtual Input:</string>
+    <string name="midiVirtOutputLbl">Virtual Output:</string>
+    <string name="midiTestVirtInterfaceBtn">Test Virtual MIDI Interface</string>
+
+    <string name="midiBTTestLbl">Bluetooth MIDI Loopback Test:</string>
+    <string name="midiBTInputLbl">Bluetooth Input:</string>
+    <string name="midiBTOutputLbl">Bluetooth Output:</string>
+    <string name="midiTestBTInterfaceBtn">Test Bluetooth MIDI Interface</string>
+
+    <string name="midiStatusLbl">Status</string>
+    <string name="midiNotRunLbl">Not Run.</string>
+    <string name="midiPassedLbl">Passed.</string>
+    <string name="midiFailedMismatchLbl">Failed - Data Mismatch.</string>
+    <string name="midiFailedTimeoutLbl">Failed - Timeout.</string>
+    <string name="midiFailedOverrunLbl">Failed - Data Overrun.</string>
+    <string name="midiFailedDeviceLbl">Failed - Device Error.</string>
+    <string name="midiFailedJNILbl">Failed - JNI Error.</string>
 
     <!-- Audio general text -->
     <string name="audio_general_headset_port_exists">Does this device have a headset port?</string>
@@ -4508,6 +4770,23 @@
         buttons has been recognized the \"pass\" button will be enabled.
     </string>
 
+    <!-- Pro Audio Test -->
+    <string name="proaudio_test">Pro Audio Test</string>
+    <string name="proaudio_info">
+       This test requires that you have connected a supported USB Audio Peripheral device
+       (not a headset) and that peripheral\'s audio outputs are connected to the peripherals\'s
+       audio inputs. Alternatively, for devices with an analog audio jack or USB-c Digital
+       to Analog dongle, a Loopback Plug can be used. Also, any if there is an input level
+       control on the peripheral, it must be set to a non-zero value. When the test has
+       verified support for a valid audio peripheral, press the \"Round-Trip Test\" button
+       to complete the test. Note that it may be necessary to run the latency test more than
+       once to get a sufficient confidence value.
+    </string>
+    <string name="proaudio_hdmi_infotitle">HDMI Support</string>
+    <string name="proaudio_hdmi_message">Please connect an HDMI peripheral to validate
+        HDMI output attributes.
+    </string>
+
     <!-- Telecom tests -->
     <string name="telecom_enable_phone_account_test"> Telecom Enable Phone Account Test</string>
     <string name="telecom_enable_phone_account_info">
@@ -4591,4 +4870,5 @@
         Click the button below to confirm that the incoming call was answered.
     </string>
     <string name="telecom_incoming_self_mgd_confirm_answer_button">Confirm Answer</string>
+
 </resources>
diff --git a/apps/CtsVerifier/res/xml/echo_device_info.xml b/apps/CtsVerifier/res/xml/echo_device_info.xml
new file mode 100644
index 0000000..936216a
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/echo_device_info.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<devices>
+    <device manufacturer="AndroidCTS" product="MidiEcho" tags="echo,test">
+        <input-port name="input" />
+        <output-port name="output" />
+    </device>
+</devices>
diff --git a/apps/CtsVerifier/res/xml/filepaths.xml b/apps/CtsVerifier/res/xml/filepaths.xml
index 2d555a2..c33bd0e 100644
--- a/apps/CtsVerifier/res/xml/filepaths.xml
+++ b/apps/CtsVerifier/res/xml/filepaths.xml
@@ -1,3 +1,5 @@
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
     <files-path path="images/" name="images" />
+    <external-path name="external_files" path="."/>
+    <root-path name="external_files" path="/data/local/tmp/" />
 </paths>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java
deleted file mode 100644
index c8bffdf..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java
+++ /dev/null
@@ -1,83 +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.cts.verifier.admin;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.hardware.fingerprint.FingerprintManager;
-import android.provider.Settings;
-
-import com.android.cts.verifier.ArrayTestListAdapter;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.DialogTestListActivity;
-import com.android.cts.verifier.managedprovisioning.ByodHelperActivity;
-import com.android.cts.verifier.managedprovisioning.DeviceAdminTestReceiver;
-import com.android.cts.verifier.managedprovisioning.KeyguardDisabledFeaturesActivity;
-
-import java.util.List;
-
-/**
- * Tests for Device Admin keyguard disabled features.
- */
-public class DeviceAdminKeyguardDisabledFeaturesActivity extends KeyguardDisabledFeaturesActivity {
-    @Override
-    protected int getKeyguardDisabledFeatures() {
-        return DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL;
-    }
-
-    @Override
-    protected void setKeyguardDisabledFeatures() {
-        int flags = getKeyguardDisabledFeatures();
-        mDpm.setKeyguardDisabledFeatures(getAdminComponent(), flags);
-    }
-
-    @Override
-    protected String getTestIdPrefix() {
-        return "DeviceAdmin_";
-    }
-
-    @Override
-    protected void setupTests(ArrayTestListAdapter adapter) {
-        setupFingerprintTests(adapter);
-        if (hasTrustAgents()) {
-            setupDisableTrustAgentsTest(adapter);
-        }
-
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
-            adapter.add(new DialogTestListItem(this, R.string.device_admin_keyguard_disable_camera,
-                    getTestIdPrefix()+"KeyguardDisableCamera",
-                    R.string.device_admin_keyguard_disable_camera_instruction,
-                    new Intent(ByodHelperActivity.ACTION_LOCKNOW)));
-        }
-
-        adapter.add(new DialogTestListItem(this, R.string.device_admin_disable_notifications,
-                "DeviceAdmin_DisableNotifications",
-                R.string.device_admin_disable_notifications_instruction,
-                new Intent(ByodHelperActivity.ACTION_NOTIFICATION_ON_LOCKSCREEN)));
-    }
-
-    private boolean hasTrustAgents() {
-        PackageManager packageManager = getPackageManager();
-        Intent intent = new Intent("android.service.trust.TrustAgentService");
-        List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, 0);
-       return resolveInfos.size() > 0;
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/admin/RedactedNotificationKeyguardDisabledFeaturesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/admin/RedactedNotificationKeyguardDisabledFeaturesActivity.java
deleted file mode 100644
index 711fd8c..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/admin/RedactedNotificationKeyguardDisabledFeaturesActivity.java
+++ /dev/null
@@ -1,48 +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.cts.verifier.admin;
-
-import android.app.admin.DevicePolicyManager;
-
-import android.content.Intent;
-
-
-import com.android.cts.verifier.ArrayTestListAdapter;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.DialogTestListActivity;
-import com.android.cts.verifier.managedprovisioning.ByodHelperActivity;
-
-/**
- * Tests for Device Admin keyguard redacted notification feature. This test is taken out from
- * DeviceAdminKeyguardDisabledFeaturesActivity class, because KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
- * would mask KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS.
- *  */
-
-public class RedactedNotificationKeyguardDisabledFeaturesActivity
-    extends DeviceAdminKeyguardDisabledFeaturesActivity {
-  @Override
-  protected int getKeyguardDisabledFeatures() {
-    return DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-  }
-
-  @Override
-  protected void setupTests(ArrayTestListAdapter adapter) {
-    adapter.add(new DialogTestListItem(this, R.string.device_admin_disable_unredacted_notifications,
-        "DeviceAdmin_DisableUnredactedNotifications",
-        R.string.device_admin_disable_unredacted_notifications_instruction,
-        new Intent(ByodHelperActivity.ACTION_NOTIFICATION_ON_LOCKSCREEN)));
-  }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/admin/tapjacking/UsbTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/admin/tapjacking/UsbTest.java
deleted file mode 100644
index 6371857..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/admin/tapjacking/UsbTest.java
+++ /dev/null
@@ -1,122 +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.verifier.admin.tapjacking;
-
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-public class UsbTest extends PassFailButtons.Activity {
-    private View mOverlay;
-    private TextView mUsbTapjackingInstructions;
-    private Button mTriggerOverlayButton;
-
-    public static final String LOG_TAG = "UsbTest";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.tapjacking);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.usb_tapjacking_test,
-                R.string.usb_tapjacking_test_info, -1);
-
-        String usbDebuggingComponent = getString(R.string.usb_tapjacking_usb_debugging_component);
-        mUsbTapjackingInstructions = findViewById(R.id.usb_tapjacking_instructions);
-        mUsbTapjackingInstructions.setText(
-                getString(R.string.usb_tapjacking_test_instructions, usbDebuggingComponent));
-        //initialise the escalate button and set a listener
-        mTriggerOverlayButton = findViewById(R.id.tapjacking_btn);
-        mTriggerOverlayButton.setEnabled(true);
-        mTriggerOverlayButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (!Settings.canDrawOverlays(v.getContext())) {
-                    // show settings permission
-                    startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
-                }
-
-                if (!Settings.canDrawOverlays(v.getContext())) {
-                    Toast.makeText(v.getContext(), R.string.usb_tapjacking_error_toast2,
-                            Toast.LENGTH_LONG).show();
-                    return;
-                }
-                showOverlay();
-            }
-        });
-    }
-
-    private void showOverlay() {
-        if (mOverlay != null)
-            return;
-
-        WindowManager windowManager = (WindowManager) getApplicationContext().
-                getSystemService(WINDOW_SERVICE);
-        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_FULLSCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
-        );
-        layoutParams.format = PixelFormat.TRANSLUCENT;
-        layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
-        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-        layoutParams.x = 0;
-        layoutParams.y = dipToPx(-46);
-        layoutParams.gravity = Gravity.CENTER;
-        layoutParams.windowAnimations = 0;
-
-        mOverlay = View.inflate(getApplicationContext(), R.layout.usb_tapjacking_overlay,
-                null);
-        windowManager.addView(mOverlay, layoutParams);
-    }
-
-    private void hideOverlay() {
-        if (mOverlay != null) {
-            WindowManager windowManager = (WindowManager) getApplicationContext().getSystemService(
-                    WINDOW_SERVICE);
-            windowManager.removeViewImmediate(mOverlay);
-            mOverlay = null;
-        }
-    }
-
-    private int dipToPx(int dip) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
-                getResources().getDisplayMetrics()));
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        hideOverlay();
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
new file mode 100644
index 0000000..64012c0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
@@ -0,0 +1,900 @@
+/*
+ * 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.verifier.audio;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.midilib.MidiEchoTestService;
+import com.android.cts.verifier.PassFailButtons;
+
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+/*
+ * A note about the USB MIDI device.
+ * Any USB MIDI peripheral with standard female DIN jacks can be used. A standard MIDI cable
+ * plugged into both input and output is required for the USB Loopback Test. A Bluetooth MIDI
+ * device like the Yamaha MD-BT01 plugged into both input and output is required for the
+ * Bluetooth Loopback test.
+ */
+
+/*
+ *  A note about the "virtual MIDI" device...
+ * See file MidiEchoTestService for implementation of the echo server itself.
+ * This service is started by the main manifest file (AndroidManifest.xml).
+ */
+
+/*
+ * A note about Bluetooth MIDI devices...
+ * Any Bluetooth MIDI device needs to be paired with the DUT with the "MIDI+BTLE" application
+ * available in the Play Store:
+ * (https://play.google.com/store/apps/details?id=com.mobileer.example.midibtlepairing).
+ */
+
+/**
+ * CTS Verifier Activity for MIDI test
+ */
+public class MidiActivity extends PassFailButtons.Activity implements View.OnClickListener {
+
+    private static final String TAG = "MidiActivity";
+    private static final boolean DEBUG = false;
+
+    private MidiManager mMidiManager;
+
+    // Flags
+    private boolean mHasMIDI;
+
+    // Test "Modules"
+    private MidiTestModule      mUSBTestModule;
+    private MidiTestModule      mVirtTestModule;
+    private BTMidiTestModule    mBTTestModule;
+
+    // Test State
+    private final Object    mTestLock = new Object();
+    private boolean     mTestRunning;
+
+    // Timeout handling
+    private static final int TEST_TIMEOUT_MS = 1000;
+    private final Timer mTimeoutTimer = new Timer();
+
+    // Widgets
+    private Button      mUSBTestBtn;
+    private Button      mVirtTestBtn;
+    private Button      mBTTestBtn;
+
+    private TextView    mUSBIInputDeviceLbl;
+    private TextView    mUSBOutputDeviceLbl;
+    private TextView    mUSBTestStatusTxt;
+
+    private TextView    mVirtInputDeviceLbl;
+    private TextView    mVirtOutputDeviceLbl;
+    private TextView    mVirtTestStatusTxt;
+
+    private TextView    mBTInputDeviceLbl;
+    private TextView    mBTOutputDeviceLbl;
+    private TextView    mBTTestStatusTxt;
+
+    private Intent mMidiServiceIntent;
+    private MidiServiceConnection mMidiServiceConnection;
+
+    public MidiActivity() {
+        super();
+    }
+
+    private boolean hasMIDI() {
+        // CDD Section C-1-4: android.software.midi
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
+    }
+
+    private void showConnectedMIDIPeripheral() {
+        // USB
+        mUSBIInputDeviceLbl.setText(mUSBTestModule.getInputName());
+        mUSBOutputDeviceLbl.setText(mUSBTestModule.getOutputName());
+        mUSBTestBtn.setEnabled(mUSBTestModule.isTestReady());
+
+        // Virtual MIDI
+        mVirtInputDeviceLbl.setText(mVirtTestModule.getInputName());
+        mVirtOutputDeviceLbl.setText(mVirtTestModule.getOutputName());
+        mVirtTestBtn.setEnabled(mVirtTestModule.isTestReady());
+
+        // Bluetooth
+        mBTInputDeviceLbl.setText(mBTTestModule.getInputName());
+        mBTOutputDeviceLbl.setText(mBTTestModule.getOutputName());
+        mBTTestBtn.setEnabled(mBTTestModule.isTestReady());
+    }
+
+    private boolean calcTestPassed() {
+        boolean hasPassed = false;
+        if (!mHasMIDI) {
+            // if it doesn't report MIDI support, then it doesn't have to pass the other tests.
+            hasPassed = true;
+        } else {
+            hasPassed = mUSBTestModule.hasTestPassed() &&
+                    mVirtTestModule.hasTestPassed() &&
+                    mBTTestModule.hasTestPassed();
+        }
+
+        getPassButton().setEnabled(hasPassed);
+        return hasPassed;
+    }
+
+    private void scanMidiDevices() {
+        if (DEBUG) {
+            Log.i(TAG, "scanMidiDevices()....");
+        }
+
+        MidiDeviceInfo[] devInfos = mMidiManager.getDevices();
+        mUSBTestModule.scanDevices(devInfos);
+        mVirtTestModule.scanDevices(devInfos);
+        mBTTestModule.scanDevices(devInfos);
+
+        showConnectedMIDIPeripheral();
+    }
+
+    //
+    // UI Updaters
+    //
+    private void enableTestButtons(boolean enable) {
+        if (DEBUG) {
+            Log.i(TAG, "enableTestButtons" + enable + ")");
+        }
+
+        runOnUiThread(new Runnable() {
+            public void run() {
+                if (enable) {
+                    // remember, a given test might not be enabled, so we can't just enable
+                    // all of the buttons
+                    showConnectedMIDIPeripheral();
+                } else {
+                    mUSBTestBtn.setEnabled(enable);
+                    mVirtTestBtn.setEnabled(enable);
+                    mBTTestBtn.setEnabled(enable);
+                }
+            }
+        });
+    }
+
+    private void showUSBTestStatus() {
+        mUSBTestStatusTxt.setText(mUSBTestModule.getTestStatusString());
+    }
+
+    private void showVirtTestStatus() {
+        mVirtTestStatusTxt.setText(mVirtTestModule.getTestStatusString());
+    }
+
+    private void showBTTestStatus() {
+        mBTTestStatusTxt.setText(mBTTestModule.getTestStatusString());
+    }
+
+    // Need this to update UI from MIDI read thread
+    public void updateTestStateUI() {
+        runOnUiThread(new Runnable() {
+            public void run() {
+                calcTestPassed();
+                showUSBTestStatus();
+                showVirtTestStatus();
+                showBTTestStatus();
+            }
+        });
+    }
+
+    class MidiServiceConnection implements ServiceConnection {
+        private static final String TAG = "MidiServiceConnection";
+        @Override
+        public void  onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) {
+                Log.i(TAG, "MidiServiceConnection.onServiceConnected()");
+            }
+            scanMidiDevices();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG) {
+                Log.i(TAG, "MidiServiceConnection.onServiceDisconnected()");
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (DEBUG) {
+            Log.i(TAG, "---- onCreate()");
+        }
+
+        setContentView(R.layout.midi_activity);
+
+        // Standard PassFailButtons.Activity initialization
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.midi_test, R.string.midi_info, -1);
+
+        // May as well calculate this right off the bat.
+        mHasMIDI = hasMIDI();
+        ((TextView)findViewById(R.id.midiHasMIDILbl)).setText("" + mHasMIDI);
+
+        // USB Test Widgets
+        mUSBIInputDeviceLbl = (TextView)findViewById(R.id.midiUSBInputLbl);
+        mUSBOutputDeviceLbl = (TextView)findViewById(R.id.midiUSBOutputLbl);
+        mUSBTestBtn = (Button)findViewById(R.id.midiTestUSBInterfaceBtn);
+        mUSBTestBtn.setOnClickListener(this);
+        mUSBTestStatusTxt = (TextView)findViewById(R.id.midiUSBTestStatusLbl);
+
+        // Virtual MIDI Test Widgets
+        mVirtInputDeviceLbl = (TextView)findViewById(R.id.midiVirtInputLbl);
+        mVirtOutputDeviceLbl = (TextView)findViewById(R.id.midiVirtOutputLbl);
+        mVirtTestBtn = (Button)findViewById(R.id.midiTestVirtInterfaceBtn);
+        mVirtTestBtn.setOnClickListener(this);
+        mVirtTestStatusTxt = (TextView)findViewById(R.id.midiVirtTestStatusLbl);
+
+        // Bluetooth MIDI Test Widgets
+        mBTInputDeviceLbl = (TextView)findViewById(R.id.midiBTInputLbl);
+        mBTOutputDeviceLbl = (TextView)findViewById(R.id.midiBTOutputLbl);
+        mBTTestBtn = (Button)findViewById(R.id.midiTestBTInterfaceBtn);
+        mBTTestBtn.setOnClickListener(this);
+        mBTTestStatusTxt = (TextView)findViewById(R.id.midiBTTestStatusLbl);
+
+        // Setup Test Modules
+        mUSBTestModule = new MidiTestModule(MidiDeviceInfo.TYPE_USB);
+        mVirtTestModule = new MidiTestModule(MidiDeviceInfo.TYPE_VIRTUAL);
+        mBTTestModule = new BTMidiTestModule();
+
+        // Init MIDI Stuff
+        mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+
+        mMidiServiceIntent = new Intent(this, MidiEchoTestService.class);
+
+        // Initial MIDI Device Scan
+        scanMidiDevices();
+
+        // Plug in device connect/disconnect callback
+        mMidiManager.registerDeviceCallback(new MidiDeviceCallback(), new Handler(getMainLooper()));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (DEBUG) {
+            Log.i(TAG, "---- Loading Virtual MIDI Service ...");
+        }
+        mMidiServiceConnection = new MidiServiceConnection();
+        boolean isBound =
+            bindService(mMidiServiceIntent,  mMidiServiceConnection,  Context.BIND_AUTO_CREATE);
+        if (DEBUG) {
+            Log.i(TAG, "---- Virtual MIDI Service loaded: " + isBound);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (DEBUG) {
+            Log.i(TAG, "---- onPause()");
+        }
+
+        unbindService(mMidiServiceConnection);
+        mMidiServiceConnection = null;
+    }
+
+    /**
+     * Callback class for MIDI device connect/disconnect.
+     */
+    private class MidiDeviceCallback extends MidiManager.DeviceCallback {
+        private static final String TAG = "MidiDeviceCallback";
+
+        @Override
+        public void onDeviceAdded(MidiDeviceInfo device) {
+            scanMidiDevices();
+        }
+
+        @Override
+        public void onDeviceRemoved(MidiDeviceInfo device) {
+            scanMidiDevices();
+        }
+    } /* class MidiDeviceCallback */
+
+    //
+    // View.OnClickListener Override - Handles button clicks
+    //
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+        case R.id.midiTestUSBInterfaceBtn:
+            mUSBTestModule.startLoopbackTest();
+            break;
+
+        case R.id.midiTestVirtInterfaceBtn:
+            mVirtTestModule.startLoopbackTest();
+            break;
+
+        case R.id.midiTestBTInterfaceBtn:
+            mBTTestModule.startLoopbackTest();
+            break;
+
+        default:
+            assert false : "Unhandled button click";
+        }
+    }
+
+    /**
+     * A class to hold the MidiDeviceInfo and ports objects associated with a MIDI I/O peripheral.
+     */
+    private class MidiIODevice {
+        private static final String TAG = "MidiIODevice";
+
+        private final int mDeviceType;
+
+        public MidiDeviceInfo mSendDevInfo;
+        public MidiDeviceInfo mReceiveDevInfo;
+
+        public MidiInputPort   mSendPort;
+        public MidiOutputPort  mReceivePort;
+
+        public MidiIODevice(int deviceType) {
+            mDeviceType = deviceType;
+        }
+
+        public void scanDevices(MidiDeviceInfo[] devInfos) {
+            if (DEBUG) {
+                Log.i(TAG, "---- scanDevices() typeID: " + mDeviceType);
+            }
+            mSendDevInfo = null;
+            mReceiveDevInfo = null;
+            mSendPort = null;
+            mReceivePort = null;
+
+            for(MidiDeviceInfo devInfo : devInfos) {
+                // Inputs?
+                int numInPorts = devInfo.getInputPortCount();
+                if (numInPorts <= 0) {
+                    continue; // none?
+                }
+                if (devInfo.getType() == mDeviceType && mSendDevInfo == null) {
+                    mSendDevInfo = devInfo;
+                }
+
+                // Outputs?
+                int numOutPorts = devInfo.getOutputPortCount();
+                if (numOutPorts <= 0) {
+                    continue; // none?
+                }
+                if (devInfo.getType() == mDeviceType && mReceiveDevInfo == null) {
+                    mReceiveDevInfo = devInfo;
+                }
+
+                if (mSendDevInfo != null && mReceiveDevInfo != null) {
+                    break;  // we have an in and out device, so we can stop scanning
+                }
+            }
+
+            if (DEBUG) {
+                if (mSendDevInfo != null) {
+                    Log.i(TAG, "---- mSendDevInfo: " + mSendDevInfo);
+                }
+                if (mReceiveDevInfo != null) {
+                    Log.i(TAG, "---- mReceiveDevInfo: " + mReceiveDevInfo);
+                }
+            }
+        }
+
+        protected void openPorts(MidiDevice device, MidiReceiver receiver) {
+            if (DEBUG) {
+                Log.i(TAG, "---- openPorts()");
+            }
+            MidiDeviceInfo deviceInfo = device.getInfo();
+            int numOutputs = deviceInfo.getOutputPortCount();
+            if (numOutputs > 0) {
+                mReceivePort = device.openOutputPort(0);
+                mReceivePort.connect(receiver);
+            }
+
+            int numInputs = deviceInfo.getInputPortCount();
+            if (numInputs != 0) {
+                mSendPort = device.openInputPort(0);
+            }
+        }
+
+        public void closePorts() {
+            if (DEBUG) {
+                Log.i(TAG, "---- closePorts()");
+            }
+            try {
+                if (mSendPort != null) {
+                    mSendPort.close();
+                    mSendPort = null;
+                }
+                if (mReceivePort != null) {
+                    mReceivePort.close();
+                    mReceivePort = null;
+                }
+            } catch (IOException ex) {
+                Log.e(TAG, "IOException Closing MIDI ports: " + ex);
+            }
+        }
+
+        public String getInputName() {
+            if (mReceiveDevInfo != null) {
+                return mDeviceType == MidiDeviceInfo.TYPE_VIRTUAL
+                        ? "Virtual MIDI Device"
+                        : mReceiveDevInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+            } else {
+                return "";
+            }
+        }
+
+        public String getOutputName() {
+            if (mSendDevInfo != null) {
+                return mDeviceType == MidiDeviceInfo.TYPE_VIRTUAL
+                        ? "Virtual MIDI Device"
+                        : mSendDevInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+            } else {
+                return "";
+            }
+        }
+    }   /* class MidiIODevice */
+
+    //
+    // MIDI Messages
+    //
+    public static final int kMIDICmd_KeyDown = 9;
+    public static final int kMIDICmd_KeyUp = 8;
+    public static final int kMIDICmd_PolyPress = 10;
+    public static final int kMIDICmd_Control = 11;
+    public static final int kMIDICmd_ProgramChange = 12;
+    public static final int kMIDICmd_ChannelPress = 13;
+    public static final int kMIDICmd_PitchWheel = 14;
+    public static final int kMIDICmd_SysEx = 15;
+    public static final int kMIDICmd_EndOfSysEx = 0b11110111;
+
+    private class TestMessage {
+        public byte[]   mMsgBytes;
+
+        public boolean matches(byte[] msg, int offset, int count) {
+            // Length
+            if (DEBUG) {
+                Log.i(TAG, "  count [" + count + " : " + mMsgBytes.length + "]");
+            }
+            if (count != mMsgBytes.length) {
+                return false;
+            }
+
+            // Data
+            for(int index = 0; index < count; index++) {
+                if (DEBUG) {
+                    Log.i(TAG, "  [" + msg[offset + index] + " : " + mMsgBytes[index] + "]");
+                }
+                if (msg[offset + index] != mMsgBytes[index]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    } /* class TestMessage */
+
+    private static byte makeMIDICmd(int cmd, int channel) {
+        return (byte)((cmd << 4) | (channel & 0x0F));
+    }
+
+    /**
+     * A class to control and represent the state of a given test.
+     * It hold the data needed for IO, and the logic for sending, receiving and matching
+     * the MIDI data stream.
+     */
+    private class MidiTestModule {
+        private static final String TAG = "MidiTestModule";
+
+        // Test Peripheral
+        MidiIODevice mIODevice;
+
+        // Test Status
+        protected static final int TESTSTATUS_NOTRUN = 0;
+        protected static final int TESTSTATUS_PASSED = 1;
+        protected static final int TESTSTATUS_FAILED_MISMATCH = 2;
+        protected static final int TESTSTATUS_FAILED_TIMEOUT = 3;
+
+        protected int mTestStatus = TESTSTATUS_NOTRUN;
+        protected boolean mTestMismatched;
+
+        // Test Data
+        // - The set of messages to send
+        private TestMessage[] mTestMessages;
+
+        // - The stream of message data to walk through when MIDI data is received.
+        private byte[] mMIDIDataStream;
+        private int mReceiveStreamPos;
+
+        public MidiTestModule(int deviceType) {
+            mIODevice = new MidiIODevice(deviceType);
+            setupTestMessages();
+        }
+
+        // UI Helper
+        public String getTestStatusString() {
+            Resources appResources = getApplicationContext().getResources();
+            switch (mTestStatus) {
+            case TESTSTATUS_NOTRUN:
+                return appResources.getString(R.string.midiNotRunLbl);
+
+            case TESTSTATUS_PASSED:
+                return appResources.getString(R.string.midiPassedLbl);
+
+            case TESTSTATUS_FAILED_MISMATCH:
+                return appResources.getString(R.string.midiFailedMismatchLbl);
+
+            case TESTSTATUS_FAILED_TIMEOUT:
+            {
+                String messageStr = appResources.getString(R.string.midiFailedTimeoutLbl);
+                return messageStr + " @" + mReceiveStreamPos;
+            }
+
+            default:
+                return "Unknown Test Status.";
+            }
+        }
+
+        public void scanDevices(MidiDeviceInfo[] devInfos) {
+            mIODevice.scanDevices(devInfos);
+        }
+
+        public void showTimeoutMessage() {
+            runOnUiThread(new Runnable() {
+                public void run() {
+                    synchronized (mTestLock) {
+                        if (mTestRunning) {
+                            if (DEBUG) {
+                                Log.i(TAG, "---- Test Failed - TIMEOUT");
+                            }
+                            mTestStatus = TESTSTATUS_FAILED_TIMEOUT;
+                            updateTestStateUI();
+                        }
+                    }
+                }
+            });
+        }
+
+        public void startLoopbackTest() {
+            synchronized (mTestLock) {
+                mTestRunning = true;
+                enableTestButtons(false);
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "---- startLoopbackTest()");
+            }
+
+            mTestStatus = TESTSTATUS_NOTRUN;
+            mTestMismatched = false;
+            mReceiveStreamPos = 0;
+
+            // These might be left open due to a failing, previous test
+            // so just to be sure...
+            closePorts();
+
+            if (mIODevice.mSendDevInfo != null) {
+                mMidiManager.openDevice(mIODevice.mSendDevInfo, new TestModuleOpenListener(), null);
+            }
+
+            // Start the timeout timer
+            TimerTask task = new TimerTask() {
+                @Override
+                public void run() {
+                    synchronized (mTestLock) {
+                        if (mTestRunning) {
+                            // Timeout
+                            showTimeoutMessage();
+                            enableTestButtons(true);
+                        }
+                    }
+                }
+            };
+            mTimeoutTimer.schedule(task, TEST_TIMEOUT_MS);
+        }
+
+        protected void openPorts(MidiDevice device) {
+            mIODevice.openPorts(device, new MidiMatchingReceiver());
+        }
+
+        protected void closePorts() {
+            mIODevice.closePorts();
+        }
+
+        public String getInputName() {
+            return mIODevice.getInputName();
+        }
+
+        public String getOutputName() {
+            return mIODevice.getOutputName();
+        }
+
+        public boolean isTestReady() {
+            return mIODevice.mReceiveDevInfo != null && mIODevice.mSendDevInfo != null;
+        }
+
+        public boolean hasTestPassed() {
+            return mTestStatus == TESTSTATUS_PASSED;
+        }
+
+        // A little explanation here... It seems reasonable to send complete MIDI messages, i.e.
+        // as a set of discrete pakages.
+        // However the looped-back data may not be delivered in message-size packets, so it makes more
+        // sense to look at that as a stream of bytes.
+        // So we build a set of messages to send, and then create the equivalent stream of bytes
+        // from that to match against when received back in from the looped-back device.
+        private void setupTestMessages() {
+            int NUM_TEST_MESSAGES = 3;
+            mTestMessages = new TestMessage[NUM_TEST_MESSAGES];
+
+            //
+            // Set up any set of messages you want
+            // Except for the command IDs, the data values are purely arbitrary and meaningless
+            // outside of being matched.
+            // KeyDown
+            mTestMessages[0] = new TestMessage();
+            mTestMessages[0].mMsgBytes = new byte[]{makeMIDICmd(kMIDICmd_KeyDown, 0), 64, 12};
+
+            // KeyUp
+            mTestMessages[1] = new TestMessage();
+            mTestMessages[1].mMsgBytes = new byte[]{makeMIDICmd(kMIDICmd_KeyDown, 0), 64, 45};
+
+            // SysEx
+            // NOTE: A sysex on the MT-BT01 seems to top out at sometimes as low as 40 bytes.
+            // It is not clear, but needs more research. For now choose a conservative size.
+            int sysExSize = 32;
+            byte[] sysExMsg = new byte[sysExSize];
+            sysExMsg[0] = makeMIDICmd(kMIDICmd_SysEx, 0);
+            for(int index = 1; index < sysExSize-1; index++) {
+                sysExMsg[index] = (byte)index;
+            }
+            sysExMsg[sysExSize-1] = (byte)kMIDICmd_EndOfSysEx;
+            mTestMessages[2] = new TestMessage();
+            mTestMessages[2].mMsgBytes = sysExMsg;
+
+            //
+            // Now build the stream to match against
+            //
+            int streamSize = 0;
+            for (int msgIndex = 0; msgIndex < mTestMessages.length; msgIndex++) {
+                streamSize += mTestMessages[msgIndex].mMsgBytes.length;
+            }
+
+            mMIDIDataStream = new byte[streamSize];
+
+            int offset = 0;
+            for (int msgIndex = 0; msgIndex < mTestMessages.length; msgIndex++) {
+                int numBytes = mTestMessages[msgIndex].mMsgBytes.length;
+                System.arraycopy(mTestMessages[msgIndex].mMsgBytes, 0,
+                        mMIDIDataStream, offset, numBytes);
+                offset += numBytes;
+            }
+            mReceiveStreamPos = 0;
+        }
+
+        /**
+         * Compares the supplied bytes against the sent message stream at the current postion
+         * and advances the stream position.
+         */
+        private boolean matchStream(byte[] bytes, int offset, int count) {
+            if (DEBUG) {
+                Log.i(TAG, "---- matchStream() offset:" + offset + " count:" + count);
+            }
+            boolean matches = true;
+
+            for (int index = 0; index < count; index++) {
+                if (bytes[offset + index] != mMIDIDataStream[mReceiveStreamPos]) {
+                    matches = false;
+                    if (DEBUG) {
+                        Log.i(TAG, "---- mismatch @" + index + " [" + bytes[offset + index] +
+                                " : " + mMIDIDataStream[mReceiveStreamPos] + "]");
+                    }
+                }
+                mReceiveStreamPos++;
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "  returns:" + matches);
+            }
+            return matches;
+        }
+
+        /**
+         * Writes out the list of MIDI messages to the output port.
+         */
+        private void sendMessages() {
+            if (DEBUG) {
+                Log.i(TAG, "---- sendMessages()...");
+            }
+            int totalSent = 0;
+            if (mIODevice.mSendPort != null) {
+                try {
+                    for(TestMessage msg : mTestMessages) {
+                        mIODevice.mSendPort.send(msg.mMsgBytes, 0, msg.mMsgBytes.length);
+                        totalSent += msg.mMsgBytes.length;
+                    }
+                } catch (IOException ex) {
+                    Log.i(TAG, "---- IOException " + ex);
+                }
+            }
+            if (DEBUG) {
+                Log.i(TAG, "---- totalSent:" + totalSent);
+            }
+        }
+
+        /**
+         * Listens for MIDI device opens. Opens I/O ports and sends out the apriori
+         * setup messages.
+         */
+        class TestModuleOpenListener implements MidiManager.OnDeviceOpenedListener {
+            @Override
+            public void onDeviceOpened(MidiDevice device) {
+                if (DEBUG) {
+                    Log.i(TAG, "---- onDeviceOpened()");
+                }
+                openPorts(device);
+                sendMessages();
+            }
+        }
+
+        /**
+         * A MidiReceiver subclass whose job it is to monitor incomming messages
+         * and match them against the stream sent by the test.
+         */
+        private class MidiMatchingReceiver extends MidiReceiver {
+            private static final String TAG = "MidiMatchingReceiver";
+
+            @Override
+            public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+                if (!matchStream(msg, offset, count)) {
+                    mTestMismatched = true;
+                }
+                if (DEBUG) {
+                    Log.i(TAG, "onSend() @" + mReceiveStreamPos);
+                }
+                if (mReceiveStreamPos == mMIDIDataStream.length) {
+                    synchronized (mTestLock) {
+                        mTestRunning = false;
+                    }
+
+                    if (DEBUG) {
+                        Log.i(TAG, "---- Test Complete");
+                    }
+                    // defer closing the ports to outside of this callback.
+                    new Thread(new Runnable() {
+                        public void run() {
+                            mTestStatus = mTestMismatched
+                                    ? TESTSTATUS_FAILED_MISMATCH : TESTSTATUS_PASSED;
+                            Log.i(TAG, "---- mTestStatus:" + mTestStatus);
+                            closePorts();
+                        }
+                    }).start();
+
+                    enableTestButtons(true);
+                    updateTestStateUI();
+                }
+            }
+        } /* class MidiMatchingReceiver */
+    } /* class MidiTestModule */
+
+    /**
+     * Test Module for Bluetooth Loopback.
+     * This is a specialization of MidiTestModule (which has the connections for the BL device
+     * itself) with and added MidiIODevice object for the USB audio device which does the
+     * "looping back".
+     */
+    private class BTMidiTestModule extends MidiTestModule {
+        private static final String TAG = "BTMidiTestModule";
+        private MidiIODevice mUSBLoopbackDevice = new MidiIODevice(MidiDeviceInfo.TYPE_USB);
+
+        public BTMidiTestModule() {
+            super(MidiDeviceInfo.TYPE_BLUETOOTH );
+        }
+
+        @Override
+        public void scanDevices(MidiDeviceInfo[] devInfos) {
+            // (normal) Scan for BT MIDI device
+            super.scanDevices(devInfos);
+            // Find a USB Loopback Device
+            mUSBLoopbackDevice.scanDevices(devInfos);
+        }
+
+        private void openUSBEchoDevice(MidiDevice device) {
+            MidiDeviceInfo deviceInfo = device.getInfo();
+            int numOutputs = deviceInfo.getOutputPortCount();
+            if (numOutputs > 0) {
+                mUSBLoopbackDevice.mReceivePort = device.openOutputPort(0);
+                mUSBLoopbackDevice.mReceivePort.connect(new USBMidiEchoReceiver());
+            }
+
+            int numInputs = deviceInfo.getInputPortCount();
+            if (numInputs != 0) {
+                mUSBLoopbackDevice.mSendPort = device.openInputPort(0);
+            }
+        }
+
+        public void startLoopbackTest() {
+            if (DEBUG) {
+                Log.i(TAG, "---- startLoopbackTest()");
+            }
+            // Setup the USB Loopback Device
+            mUSBLoopbackDevice.closePorts();
+
+            if (mIODevice.mSendDevInfo != null) {
+                mMidiManager.openDevice(
+                        mUSBLoopbackDevice.mSendDevInfo, new USBLoopbackOpenListener(), null);
+            }
+
+            // Now start the test as usual
+            super.startLoopbackTest();
+        }
+
+        /**
+         * We need this OnDeviceOpenedListener to open the USB-Loopback device
+         */
+        private class USBLoopbackOpenListener implements MidiManager.OnDeviceOpenedListener {
+            @Override
+            public void onDeviceOpened(MidiDevice device) {
+                if (DEBUG) {
+                    Log.i("USBLoopbackOpenListener", "---- onDeviceOpened()");
+                }
+                mUSBLoopbackDevice.openPorts(device, new USBMidiEchoReceiver());
+            }
+        } /* class USBLoopbackOpenListener */
+
+        /**
+         * MidiReceiver subclass for BlueTooth Loopback Test
+         *
+         * This class receives bytes from the USB Interface (presumably coming from the
+         * Bluetooth MIDI peripheral) and echoes them back out (presumably to the Bluetooth
+         * MIDI peripheral).
+         */
+        private class USBMidiEchoReceiver extends MidiReceiver {
+            private int mTotalBytesEchoed;
+
+            @Override
+            public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+                mTotalBytesEchoed += count;
+                if (DEBUG) {
+                    Log.i(TAG, "---- USBMidiEchoReceiver.onSend() count:" + count +
+                            " total:" + mTotalBytesEchoed);
+                }
+                mUSBLoopbackDevice.mSendPort.onSend(msg, offset, count, timestamp);
+            }
+        } /* class USBMidiEchoReceiver */
+    } /* class BTMidiTestModule */
+} /* class MidiActivity */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NDKMidiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NDKMidiActivity.java
new file mode 100644
index 0000000..f6746f5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NDKMidiActivity.java
@@ -0,0 +1,692 @@
+/*
+ * 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.verifier.audio;
+
+import java.io.IOException;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.lang.InterruptedException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import com.android.cts.verifier.audio.midilib.NativeMidiManager;
+
+import com.android.cts.verifier.audio.midilib.MidiEchoTestService;
+import com.android.cts.verifier.PassFailButtons;
+
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+/*
+ * A note about the USB MIDI device.
+ * Any USB MIDI peripheral with standard female DIN jacks can be used. A standard MIDI cable
+ * plugged into both input and output is required for the USB Loopback Test. A Bluetooth MIDI
+ * device like the Yamaha MD-BT01 plugged into both input and output is required for the
+ * Bluetooth Loopback test.
+ */
+
+/*
+ * A note about the "virtual MIDI" device...
+ * See file MidiEchoTestService for implementation of the echo server itself.
+ * This service is started by the main manifest file (AndroidManifest.xml).
+ */
+
+/*
+ * A note about Bluetooth MIDI devices...
+ * Any Bluetooth MIDI device needs to be paired with the DUT with the "MIDI+BTLE" application
+ * available in the Play Store:
+ * (https://play.google.com/store/apps/details?id=com.mobileer.example.midibtlepairing).
+ */
+
+/**
+ * CTS Verifier Activity for MIDI test
+ */
+public class NDKMidiActivity extends PassFailButtons.Activity implements View.OnClickListener {
+
+    private static final String TAG = "NDKMidiActivity";
+    private static final boolean DEBUG = false;
+
+    private MidiManager mMidiManager;
+
+    // Flags
+    private boolean mHasMIDI;
+
+    // Test "Modules"
+    private NDKMidiTestModule   mUSBTestModule;
+    private NDKMidiTestModule   mVirtTestModule;
+    private BTMidiTestModule    mBTTestModule;
+
+    // Widgets
+    private Button          mUSBTestBtn;
+    private Button          mVirtTestBtn;
+    private Button          mBTTestBtn;
+
+    private TextView        mUSBIInputDeviceLbl;
+    private TextView        mUSBOutputDeviceLbl;
+    private TextView        mUSBTestStatusTxt;
+
+    private TextView        mVirtInputDeviceLbl;
+    private TextView        mVirtOutputDeviceLbl;
+    private TextView        mVirtTestStatusTxt;
+
+    private TextView        mBTInputDeviceLbl;
+    private TextView        mBTOutputDeviceLbl;
+    private TextView        mBTTestStatusTxt;
+
+    private Intent          mMidiServiceIntent;
+    private ComponentName   mMidiService;
+
+    public NDKMidiActivity() {
+        super();
+
+        NativeMidiManager.loadNativeAPI();
+        NativeMidiManager.initN();
+    }
+
+    private boolean hasMIDI() {
+        // CDD Section C-1-4: android.software.midi
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
+    }
+
+    private void showConnectedMIDIPeripheral() {
+        // USB
+        mUSBIInputDeviceLbl.setText(mUSBTestModule.getInputName());
+        mUSBOutputDeviceLbl.setText(mUSBTestModule.getOutputName());
+        mUSBTestBtn.setEnabled(mUSBTestModule.isTestReady());
+
+        // Virtual MIDI
+        mVirtInputDeviceLbl.setText(mVirtTestModule.getInputName());
+        mVirtOutputDeviceLbl.setText(mVirtTestModule.getOutputName());
+        mVirtTestBtn.setEnabled(mVirtTestModule.isTestReady());
+
+        // Bluetooth
+        mBTInputDeviceLbl.setText(mBTTestModule.getInputName());
+        mBTOutputDeviceLbl.setText(mBTTestModule.getOutputName());
+        mBTTestBtn.setEnabled(mBTTestModule.isTestReady());
+    }
+
+    private boolean calcTestPassed() {
+        boolean hasPassed = false;
+        if (!mHasMIDI) {
+            // if it doesn't report MIDI support, then it doesn't have to pass the other tests.
+            hasPassed = true;
+        } else {
+            hasPassed = mUSBTestModule.hasTestPassed() &&
+                    mVirtTestModule.hasTestPassed() &&
+                    mBTTestModule.hasTestPassed();
+        }
+
+        getPassButton().setEnabled(hasPassed);
+        return hasPassed;
+    }
+
+    private void scanMidiDevices() {
+        if (DEBUG) {
+            Log.i(TAG, "scanMidiDevices()....");
+        }
+
+        MidiDeviceInfo[] devInfos = mMidiManager.getDevices();
+        mUSBTestModule.scanDevices(devInfos, MidiDeviceInfo.TYPE_USB);
+        mVirtTestModule.scanDevices(devInfos, MidiDeviceInfo.TYPE_VIRTUAL);
+        mBTTestModule.scanDevices(devInfos, MidiDeviceInfo.TYPE_BLUETOOTH);
+
+        showConnectedMIDIPeripheral();
+    }
+
+    //
+    // UI Updaters
+    //
+    public void enableTestButtons(boolean enable) {
+        if (DEBUG) {
+            Log.i(TAG, "enableTestButtons" + enable + ")");
+        }
+
+        runOnUiThread(new Runnable() {
+            public void run() {
+                if (enable) {
+                    // remember, a given test might not be enabled, so we can't just enable
+                    // all of the buttons
+                    showConnectedMIDIPeripheral();
+                } else {
+                    mUSBTestBtn.setEnabled(enable);
+                    mVirtTestBtn.setEnabled(enable);
+                    mBTTestBtn.setEnabled(enable);
+                }
+            }
+        });
+    }
+
+    private void showUSBTestStatus() {
+        mUSBTestStatusTxt.setText(mUSBTestModule.getTestStatusString());
+    }
+
+    private void showVirtTestStatus() {
+        mVirtTestStatusTxt.setText(mVirtTestModule.getTestStatusString());
+    }
+
+    private void showBTTestStatus() {
+        mBTTestStatusTxt.setText(mBTTestModule.getTestStatusString());
+    }
+
+    // Need this to update UI from MIDI read thread
+    public void updateTestStateUI() {
+        if (DEBUG) {
+            Log.i(TAG, "updateTestStateUI()");
+        }
+        runOnUiThread(new Runnable() {
+            public void run() {
+                calcTestPassed();
+                showUSBTestStatus();
+                showVirtTestStatus();
+                showBTTestStatus();
+            }
+        });
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (DEBUG) {
+            Log.i(TAG, "---- onCreate()");
+        }
+
+        // Start MIDI Echo service
+        mMidiServiceIntent = new Intent(this, MidiEchoTestService.class);
+        mMidiService = startService(mMidiServiceIntent);
+        if (DEBUG) {
+            Log.i(TAG, "---- mMidiService instantiated:" + mMidiService);
+        }
+
+        // Setup UI
+        setContentView(R.layout.ndk_midi_activity);
+
+        // Standard PassFailButtons.Activity initialization
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.midi_test, R.string.midi_info, -1);
+
+        // May as well calculate this right off the bat.
+        mHasMIDI = hasMIDI();
+        ((TextView)findViewById(R.id.midiHasMIDILbl)).setText("" + mHasMIDI);
+
+        // USB Test Widgets
+        mUSBIInputDeviceLbl = (TextView)findViewById(R.id.midiUSBInputLbl);
+        mUSBOutputDeviceLbl = (TextView)findViewById(R.id.midiUSBOutputLbl);
+        mUSBTestBtn = (Button)findViewById(R.id.midiTestUSBInterfaceBtn);
+        mUSBTestBtn.setOnClickListener(this);
+        mUSBTestStatusTxt = (TextView)findViewById(R.id.midiUSBTestStatusLbl);
+
+        // Virtual MIDI Test Widgets
+        mVirtInputDeviceLbl = (TextView)findViewById(R.id.midiVirtInputLbl);
+        mVirtOutputDeviceLbl = (TextView)findViewById(R.id.midiVirtOutputLbl);
+        mVirtTestBtn = (Button)findViewById(R.id.midiTestVirtInterfaceBtn);
+        mVirtTestBtn.setOnClickListener(this);
+        mVirtTestStatusTxt = (TextView)findViewById(R.id.midiVirtTestStatusLbl);
+
+        // Bluetooth MIDI Test Widgets
+        mBTInputDeviceLbl = (TextView)findViewById(R.id.midiBTInputLbl);
+        mBTOutputDeviceLbl = (TextView)findViewById(R.id.midiBTOutputLbl);
+        mBTTestBtn = (Button)findViewById(R.id.midiTestBTInterfaceBtn);
+        mBTTestBtn.setOnClickListener(this);
+        mBTTestStatusTxt = (TextView)findViewById(R.id.midiBTTestStatusLbl);
+
+        // Init MIDI Stuff
+        mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+
+        // Setup Test Modules
+        mUSBTestModule = new NDKMidiTestModule(this, mMidiManager);
+        mVirtTestModule = new NDKMidiTestModule(this, mMidiManager);
+        mBTTestModule = new BTMidiTestModule(this, mMidiManager);
+
+        // Initial MIDI Device Scan
+        scanMidiDevices();
+
+        // Plug in device connect/disconnect callback
+        mMidiManager.registerDeviceCallback(new MidiDeviceCallback(), new Handler(getMainLooper()));
+
+    }
+
+    @Override
+    protected void onPause () {
+        super.onPause();
+        if (DEBUG) {
+            Log.i(TAG, "---- onPause()");
+        }
+
+        boolean isFound = stopService(mMidiServiceIntent);
+        if (DEBUG) {
+            Log.i(TAG, "---- Stop Service: " + isFound);
+        }
+    }
+
+    /**
+     * Callback class for MIDI device connect/disconnect.
+     */
+    private class MidiDeviceCallback extends MidiManager.DeviceCallback {
+        private static final String TAG = "MidiDeviceCallback";
+
+        @Override
+        public void onDeviceAdded(MidiDeviceInfo device) {
+            scanMidiDevices();
+        }
+
+        @Override
+        public void onDeviceRemoved(MidiDeviceInfo device) {
+            scanMidiDevices();
+        }
+    } /* class MidiDeviceCallback */
+
+    //
+    // View.OnClickListener Override - Handles button clicks
+    //
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+        case R.id.midiTestUSBInterfaceBtn:
+            mUSBTestModule.startLoopbackTest();
+            break;
+
+        case R.id.midiTestVirtInterfaceBtn:
+            mVirtTestModule.startLoopbackTest();
+            break;
+
+        case R.id.midiTestBTInterfaceBtn:
+            mBTTestModule.startLoopbackTest();
+            break;
+
+        default:
+            assert false : "Unhandled button click";
+        }
+    }
+
+    /**
+     * A class to control and represent the state of a given test.
+     * It hold the data needed for IO, and the logic for sending, receiving and matching
+     * the MIDI data stream.
+     */
+    public class NDKMidiTestModule {
+        private static final String TAG = "NDKMidiTestModule";
+        private static final boolean DEBUG = true;
+
+        private NDKMidiActivity     mMidiActivity;
+
+        private MidiManager         mMidiManager;
+        private NativeMidiManager   mNativeMidiManager;
+
+        // Test State
+        private final Object        mTestLock = new Object();
+        private boolean             mTestRunning;
+
+        // Timeout handling
+        private static final int    TEST_TIMEOUT_MS = 1000;
+        private final Timer         mTimeoutTimer = new Timer();
+
+        // Test Peripheral
+        MidiIODevice                mIODevice = new MidiIODevice();
+
+        // Test Status
+        protected static final int TESTSTATUS_NOTRUN = 0;
+        protected static final int TESTSTATUS_PASSED = 1;
+        protected static final int TESTSTATUS_FAILED_MISMATCH = 2;
+        protected static final int TESTSTATUS_FAILED_TIMEOUT = 3;
+        protected static final int TESTSTATUS_FAILED_OVERRUN = 4;
+        protected static final int TESTSTATUS_FAILED_DEVICE = 5;
+        protected static final int TESTSTATUS_FAILED_JNI = 6;
+        protected int mTestStatus = TESTSTATUS_NOTRUN;
+
+        /**
+         * A class to hold the MidiDeviceInfo and ports objects associated
+         * with a MIDI I/O peripheral.
+         */
+        class MidiIODevice {
+            private static final String TAG = "MidiIODevice";
+
+            public MidiDeviceInfo   mSendDevInfo;
+            public MidiDeviceInfo   mReceiveDevInfo;
+
+            public MidiInputPort    mSendPort;
+            public MidiOutputPort   mReceivePort;
+
+            public void scanDevices(MidiDeviceInfo[] devInfos, int typeID) {
+                if (DEBUG) {
+                    Log.i(TAG, "---- scanDevices() typeID: " + typeID);
+                }
+
+                mSendDevInfo = null;
+                mReceiveDevInfo = null;
+                mSendPort = null;
+                mReceivePort = null;
+
+                for (MidiDeviceInfo devInfo : devInfos) {
+                    // Inputs?
+                    int numInPorts = devInfo.getInputPortCount();
+                    if (numInPorts <= 0) {
+                        continue; // none?
+                    }
+                    if (devInfo.getType() == typeID && mSendDevInfo == null) {
+                        mSendDevInfo = devInfo;
+                    }
+
+                    // Outputs?
+                    int numOutPorts = devInfo.getOutputPortCount();
+                    if (numOutPorts <= 0) {
+                        continue; // none?
+                    }
+                    if (devInfo.getType() == typeID && mReceiveDevInfo == null) {
+                        mReceiveDevInfo = devInfo;
+                    }
+
+                    if (mSendDevInfo != null && mReceiveDevInfo != null) {
+                        break;  // we have an in and out device, so we can stop scanning
+                    }
+                }
+
+                if (DEBUG) {
+                    if (mSendDevInfo != null) {
+                        Log.i(TAG, "---- mSendDevInfo: " + mSendDevInfo);
+                    }
+                    if (mReceiveDevInfo != null) {
+                        Log.i(TAG, "---- mReceiveDevInfo: " + mReceiveDevInfo);
+                    }
+                }
+            }
+
+            protected void openPorts(MidiDevice device, MidiReceiver receiver) {
+                if (DEBUG) {
+                    Log.i(TAG, "---- openPorts()");
+                }
+                MidiDeviceInfo deviceInfo = device.getInfo();
+                int numOutputs = deviceInfo.getOutputPortCount();
+                if (numOutputs > 0) {
+                    mReceivePort = device.openOutputPort(0);
+                    mReceivePort.connect(receiver);
+                }
+
+                int numInputs = deviceInfo.getInputPortCount();
+                if (numInputs != 0) {
+                    mSendPort = device.openInputPort(0);
+                }
+            }
+
+            public void closePorts() {
+                if (DEBUG) {
+                    Log.i(TAG, "---- closePorts()");
+                }
+                try {
+                    if (mSendPort != null) {
+                        mSendPort.close();
+                        mSendPort = null;
+                    }
+                    if (mReceivePort != null) {
+                        mReceivePort.close();
+                        mReceivePort = null;
+                    }
+                } catch (IOException ex) {
+                    Log.e(TAG, "IOException Closing MIDI ports: " + ex);
+                }
+            }
+
+            public String getInputName() {
+                return mReceiveDevInfo != null
+                        ? mReceiveDevInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME)
+                        : "";
+            }
+
+            public String getOutputName() {
+                return mSendDevInfo != null
+                        ? mSendDevInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME)
+                        : "";
+            }
+        }   /* class MidiIODevice */
+
+        public NDKMidiTestModule(NDKMidiActivity midiActivity, MidiManager midiManager) {
+            mMidiActivity = midiActivity;
+            mMidiManager = midiManager;
+            mNativeMidiManager = new NativeMidiManager();
+
+            // this call is just to keep the build from stripping out "endTest", because
+            // it is only called from JNI.
+            endTest(TESTSTATUS_NOTRUN);
+        }
+
+        // UI Helper
+        public String getTestStatusString() {
+            Resources appResources = mMidiActivity.getApplicationContext().getResources();
+            int status;
+            synchronized (mTestLock) {
+                status = mTestStatus;
+            }
+            switch (status) {
+            case TESTSTATUS_NOTRUN:
+                return appResources.getString(R.string.midiNotRunLbl);
+
+            case TESTSTATUS_PASSED:
+                return appResources.getString(R.string.midiPassedLbl);
+
+            case TESTSTATUS_FAILED_MISMATCH:
+                return appResources.getString(R.string.midiFailedMismatchLbl);
+
+            case TESTSTATUS_FAILED_TIMEOUT:
+                return appResources.getString(R.string.midiFailedTimeoutLbl);
+
+            case TESTSTATUS_FAILED_OVERRUN:
+                return appResources.getString(R.string.midiFailedOverrunLbl);
+
+            case TESTSTATUS_FAILED_DEVICE:
+                return appResources.getString(R.string.midiFailedDeviceLbl);
+
+            case TESTSTATUS_FAILED_JNI:
+                return appResources.getString(R.string.midiFailedJNILbl);
+
+            default:
+                return "Unknown Test Status.";
+            }
+        }
+
+        public void scanDevices(MidiDeviceInfo[] devInfos, int typeID) {
+            mIODevice.scanDevices(devInfos, typeID);
+        }
+
+        public void showTimeoutMessage() {
+            mMidiActivity.runOnUiThread(new Runnable() {
+                public void run() {
+                    synchronized (mTestLock) {
+                        if (mTestRunning) {
+                            if (DEBUG) {
+                                Log.i(TAG, "---- Test Failed - TIMEOUT");
+                            }
+                            mTestStatus = TESTSTATUS_FAILED_TIMEOUT;
+                            mMidiActivity.updateTestStateUI();
+                        }
+                    }
+                }
+            });
+        }
+
+        public void startLoopbackTest() {
+            synchronized (mTestLock) {
+                mTestRunning = true;
+                mMidiActivity.enableTestButtons(false);
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "---- startLoopbackTest()");
+            }
+
+            synchronized (mTestLock) {
+                mTestStatus = TESTSTATUS_NOTRUN;
+            }
+
+            if (mIODevice.mSendDevInfo != null) {
+                mMidiManager.openDevice(mIODevice.mSendDevInfo, new TestModuleOpenListener(), null);
+            }
+
+            // Start the timeout timer
+            TimerTask task = new TimerTask() {
+                @Override
+                public void run() {
+                    synchronized (mTestLock) {
+                        if (mTestRunning) {
+                            // Timeout
+                            showTimeoutMessage();
+                            mMidiActivity.enableTestButtons(true);
+                        }
+                    }
+                }
+            };
+            mTimeoutTimer.schedule(task, TEST_TIMEOUT_MS);
+        }
+
+        public String getInputName() {
+            return mIODevice.getInputName();
+        }
+
+        public String getOutputName() {
+            return mIODevice.getOutputName();
+        }
+
+        public boolean isTestReady() {
+            return mIODevice.mReceiveDevInfo != null && mIODevice.mSendDevInfo != null;
+        }
+
+        public boolean hasTestPassed() {
+            int status;
+            synchronized (mTestLock) {
+                status = mTestStatus;
+            }
+            return status == TESTSTATUS_PASSED;
+        }
+
+        public void endTest(int endCode) {
+            synchronized (mTestLock) {
+                mTestRunning = false;
+                mTestStatus = endCode;
+            }
+            if (endCode != TESTSTATUS_NOTRUN) {
+                mMidiActivity.updateTestStateUI();
+                mMidiActivity.enableTestButtons(true);
+            }
+        }
+
+        /**
+         * Listens for MIDI device opens. Opens I/O ports and sends out the apriori
+         * setup messages.
+         */
+        class TestModuleOpenListener implements MidiManager.OnDeviceOpenedListener {
+            //
+            // This is where the logical part of the test starts
+            //
+            @Override
+            public void onDeviceOpened(MidiDevice device) {
+                if (DEBUG) {
+                    Log.i(TAG, "---- onDeviceOpened()");
+                }
+                mNativeMidiManager.startTest(NDKMidiTestModule.this, device);
+            }
+        }
+    } /* class NDKMidiTestModule */
+
+    /**
+     * Test Module for Bluetooth Loopback.
+     * This is a specialization of NDKMidiTestModule (which has the connections for the BL device
+     * itself) with and added MidiIODevice object for the USB audio device which does the
+     * "looping back".
+     */
+    private class BTMidiTestModule extends NDKMidiTestModule {
+        private static final String TAG = "BTMidiTestModule";
+        private MidiIODevice mUSBLoopbackDevice = new MidiIODevice();
+
+        public BTMidiTestModule(NDKMidiActivity midiActivity, MidiManager midiManager) {
+            super(midiActivity, midiManager);
+        }
+
+        @Override
+        public void scanDevices(MidiDeviceInfo[] devInfos, int typeID) {
+            // (normal) Scan for BT MIDI device
+            super.scanDevices(devInfos, typeID);
+            // Find a USB Loopback Device
+            mUSBLoopbackDevice.scanDevices(devInfos, MidiDeviceInfo.TYPE_USB);
+        }
+
+        public void startLoopbackTest() {
+            if (DEBUG) {
+                Log.i(TAG, "---- startLoopbackTest()");
+            }
+            // Setup the USB Loopback Device
+            mUSBLoopbackDevice.closePorts();
+
+            if (mIODevice.mSendDevInfo != null) {
+                mMidiManager.openDevice(
+                        mUSBLoopbackDevice.mSendDevInfo, new USBLoopbackOpenListener(), null);
+            }
+
+            // Now start the test as usual
+            super.startLoopbackTest();
+        }
+
+        /**
+         * We need this OnDeviceOpenedListener to open the USB-Loopback device
+         */
+        private class USBLoopbackOpenListener implements MidiManager.OnDeviceOpenedListener {
+            @Override
+            public void onDeviceOpened(MidiDevice device) {
+                if (DEBUG) {
+                    Log.i("USBLoopbackOpenListener", "---- onDeviceOpened()");
+                }
+                mUSBLoopbackDevice.openPorts(device, new USBMidiEchoReceiver());
+            }
+        } /* class USBLoopbackOpenListener */
+
+        /**
+         * MidiReceiver subclass for BlueTooth Loopback Test
+         *
+         * This class receives bytes from the USB Interface (presumably coming from the
+         * Bluetooth MIDI peripheral) and echoes them back out (presumably to the Bluetooth
+         * MIDI peripheral).
+         */
+        private class USBMidiEchoReceiver extends MidiReceiver {
+            private int mTotalBytesEchoed;
+
+            @Override
+            public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+                mTotalBytesEchoed += count;
+                if (DEBUG) {
+                    Log.i(TAG, "---- USBMidiEchoReceiver.onSend() count:" + count +
+                            " total:" + mTotalBytesEchoed);
+                }
+                mUSBLoopbackDevice.mSendPort.onSend(msg, offset, count, timestamp);
+            }
+        } /* class USBMidiEchoReceiver */
+    } /* class BTMidiTestModule */
+} /* class NDKMidiActivity */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
new file mode 100644
index 0000000..30bc5fc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -0,0 +1,379 @@
+/*
+ * 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.verifier.audio;
+
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+import java.util.List;
+
+public class ProAudioActivity
+        extends PassFailButtons.Activity
+        implements View.OnClickListener {
+    private static final String TAG = ProAudioActivity.class.getName();
+    private static final boolean DEBUG = false;
+
+    // Flags
+    private boolean mClaimsLowLatencyAudio;    // CDD ProAudio section C-1-1
+    private boolean mClaimsMIDI;               // CDD ProAudio section C-1-4
+    private boolean mClaimsUSBHostMode;        // CDD ProAudio section C-1-3
+    private boolean mClaimsUSBPeripheralMode;  // CDD ProAudio section C-1-3
+    private boolean mClaimsHDMI;               // CDD ProAudio section C-1-3
+
+    // Values
+    private static final double LATENCY_MS_LIMIT = 20.0; // CDD ProAudio section C-1-2
+    private double mRoundTripLatency;
+    private static final double CONFIDENCE_LIMIT = 0.75; // TBD
+    private double mRoundTripConfidence;
+
+    // Peripheral(s)
+    AudioManager mAudioManager;
+    private boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
+    private AudioDeviceInfo mOutputDevInfo;
+    private AudioDeviceInfo mInputDevInfo;
+
+    private AudioDeviceInfo mHDMIDeviceInfo;
+
+    // Widgets
+    TextView mInputDeviceTxt;
+    TextView mOutputDeviceTxt;
+    TextView mRoundTripLatencyTxt;
+    TextView mRoundTripConfidenceTxt;
+    TextView mHDMISupportLbl;
+
+    CheckBox mClaimsHDMICheckBox;
+
+    public ProAudioActivity() {
+        super();
+    }
+
+    private boolean claimsLowLatencyAudio() {
+        // CDD Section C-1-1: android.hardware.audio.low_latency
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+    }
+
+    private boolean claimsMIDI() {
+        // CDD Section C-1-4: android.software.midi
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
+    }
+
+    private boolean claimsUSBHostMode() {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST);
+    }
+
+    private boolean claimsUSBPeripheralMode() {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
+    }
+
+    private void showConnectedAudioPeripheral() {
+        mInputDeviceTxt.setText(
+                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
+                        : "");
+        mOutputDeviceTxt.setText(
+                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
+                        : "");
+    }
+
+    // HDMI Stuff
+    private boolean isHDMIValid() {
+        if (mHDMIDeviceInfo == null) {
+            return false;
+        }
+
+        // MUST support output in stereo and eight channels...
+        boolean has2Chans = false;
+        boolean has8Chans = false;
+        int[] channelCounts = mHDMIDeviceInfo.getChannelCounts();
+        for (int count : channelCounts) {
+            if (count == 2) {
+                has2Chans = true;
+            } else if (count == 8) {
+                has8Chans = true;
+            }
+        }
+        if (!has2Chans || !has8Chans) {
+            return false;
+        }
+
+        // at 20-bit or 24-bit depth
+        boolean hasFloatEncoding = false;
+        int[] encodings = mHDMIDeviceInfo.getEncodings();
+        for (int encoding : encodings) {
+            if (encoding == AudioFormat.ENCODING_PCM_FLOAT) {
+                hasFloatEncoding = true;
+                break;
+            }
+        }
+        if (!hasFloatEncoding) {
+            return false;
+        }
+
+         // and 192 kHz
+        boolean has192K = false;
+        int[] sampleRates = mHDMIDeviceInfo.getSampleRates();
+        for (int rate : sampleRates) {
+            if (rate >= 192000) {
+                has192K = true;
+            }
+        }
+        if (!has192K) {
+            return false;
+        }
+
+        // without bit-depth loss or resampling (hmmmmm....).
+
+        return true;
+    }
+
+    private void calculatePass() {
+        boolean hasPassed =
+                mClaimsLowLatencyAudio && mClaimsMIDI &&
+                mClaimsUSBHostMode && mClaimsUSBPeripheralMode &&
+                (!mClaimsHDMI || isHDMIValid()) &&
+                mOutputDevInfo != null && mInputDevInfo != null &&
+                mRoundTripLatency != 0.0 && mRoundTripLatency <= LATENCY_MS_LIMIT &&
+                mRoundTripConfidence >= CONFIDENCE_LIMIT;
+        getPassButton().setEnabled(hasPassed);
+    }
+
+    //
+    // Loopback App Stuff
+    //
+    private final static String LOOPBACK_PACKAGE_NAME = "org.drrickorang.loopback";
+
+    // Test Intents
+    // From Loopback App LoobackActivity.java
+    private static final String INTENT_TEST_TYPE = "TestType";
+
+    // from Loopback App Constant.java
+    public static final int LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY = 222;
+
+    public boolean isLoopbackAppInstalled() {
+        try {
+            getPackageManager().getPackageInfo(
+                    LOOPBACK_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            // This indicates that the specified app (Loopback in this case) is NOT installed
+            // fall through...
+        }
+        return false;
+    }
+
+    // arbitrary request code
+    private static final int LATENCY_RESULTS_REQUEST_CODE = 1;
+    private static final String KEY_CTSINVOCATION = "CTS-Test";
+    private static final String KEY_ROUND_TRIP_TIME = "RoundTripTime";
+    private static final String KEY_ROUND_TRIP_CONFIDENCE = "Confidence";
+
+    // We may need to iterate and average round-trip measurements
+    // So add this plumbing though NOT USED.
+    private static final String KEY_NUMITERATIONS = "NumIterations";
+    private static final int NUM_ROUNDTRIPITERATIONS = 3;
+
+    private void runRoundTripTest() {
+        if (!isLoopbackAppInstalled()) {
+            Toast.makeText(this, "Loopback App not installed", Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        if (!mIsPeripheralAttached) {
+            Toast.makeText(this, "Please connect a USB audio peripheral with loopback cables" +
+                    " before running the latency test.",
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        mRoundTripLatency = 0.0;
+        mRoundTripConfidence = 0.0;
+        Intent intent = new Intent(Intent.CATEGORY_LAUNCHER);
+        intent.setComponent(
+            new ComponentName(LOOPBACK_PACKAGE_NAME,LOOPBACK_PACKAGE_NAME + ".LoopbackActivity"));
+
+        intent.putExtra(KEY_CTSINVOCATION, "CTS-Verifier Invocation");
+        intent.putExtra(INTENT_TEST_TYPE, LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY);
+        intent.putExtra(KEY_NUMITERATIONS, NUM_ROUNDTRIPITERATIONS);
+
+        startActivityForResult(intent, LATENCY_RESULTS_REQUEST_CODE);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Check which request we're responding to
+        if (resultCode == RESULT_OK) {
+            Toast.makeText(this, "Round Trip Test Complete.", Toast.LENGTH_SHORT).show();
+            if (requestCode == LATENCY_RESULTS_REQUEST_CODE) {
+                Bundle extras = data != null ? data.getExtras() : null;
+                if (extras != null) {
+                    mRoundTripLatency =  extras.getDouble(KEY_ROUND_TRIP_TIME);
+                    mRoundTripLatencyTxt.setText(String.format("%.2f ms", mRoundTripLatency));
+                    mRoundTripConfidence = extras.getDouble(KEY_ROUND_TRIP_CONFIDENCE);
+                    mRoundTripConfidenceTxt.setText(String.format("%.2f", mRoundTripConfidence));
+                }
+            }
+            calculatePass();
+        } else {
+            Toast.makeText(this, "Round Trip Test Canceled.", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pro_audio);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.proaudio_test, R.string.proaudio_info, -1);
+
+        mClaimsLowLatencyAudio = claimsLowLatencyAudio();
+        ((TextView)findViewById(R.id.proAudioHasLLALbl)).setText("" + mClaimsLowLatencyAudio);
+
+        mClaimsMIDI = claimsMIDI();
+        ((TextView)findViewById(R.id.proAudioHasMIDILbl)).setText("" + mClaimsMIDI);
+
+        mClaimsUSBHostMode = claimsUSBHostMode();
+        ((TextView)findViewById(R.id.proAudioMidiHasUSBHostLbl)).setText("" + mClaimsUSBHostMode);
+
+        mClaimsUSBPeripheralMode = claimsUSBPeripheralMode();
+        ((TextView)findViewById(
+                R.id.proAudioMidiHasUSBPeripheralLbl)).setText("" + mClaimsUSBPeripheralMode);
+
+        // Connected Device
+        mInputDeviceTxt = ((TextView)findViewById(R.id.proAudioInputLbl));
+        mOutputDeviceTxt = ((TextView)findViewById(R.id.proAudioOutputLbl));
+
+        // Round-trip Latency
+        mRoundTripLatencyTxt = (TextView)findViewById(R.id.proAudioRoundTripLbl);
+        mRoundTripConfidenceTxt = (TextView)findViewById(R.id.proAudioConfidenceLbl);
+        ((Button)findViewById(R.id.proAudio_runRoundtripBtn)).setOnClickListener(this);
+
+        // HDMI
+        mHDMISupportLbl = (TextView)findViewById(R.id.proAudioHDMISupportLbl);
+        mClaimsHDMICheckBox = (CheckBox)findViewById(R.id.proAudioHasHDMICheckBox);
+        mClaimsHDMICheckBox.setOnClickListener(this);
+
+        calculatePass();
+    }
+
+    private void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // CDD Section C-1-3: USB port, host-mode support
+
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        // Do we leave in the Headset test to support a USB-Dongle?
+        for (AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||     // USB Peripheral
+                devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET ||    // USB dongle+LBPlug
+                devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
+                devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            } else if (devInfo.isSink() && devInfo.getType() == AudioDeviceInfo.TYPE_HDMI) {
+                mHDMIDeviceInfo = devInfo;
+            }
+        }
+
+        mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
+        showConnectedAudioPeripheral();
+
+        if (mHDMIDeviceInfo != null) {
+            mClaimsHDMICheckBox.setChecked(true);
+            mHDMISupportLbl.setText(getResources().getString(
+                    isHDMIValid() ? R.string.pass_button_text : R.string.fail_button_text));
+        }
+        mHDMISupportLbl.setText(getResources().getString(R.string.audio_proaudio_NA));
+
+        calculatePass();
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+        case R.id.proAudio_runRoundtripBtn:
+            runRoundTripTest();
+            break;
+
+        case R.id.proAudioHasHDMICheckBox:
+            if (mClaimsHDMICheckBox.isChecked()) {
+                AlertDialog.Builder builder =
+                        new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
+                builder.setTitle(getResources().getString(R.string.proaudio_hdmi_infotitle));
+                builder.setMessage(getResources().getString(R.string.proaudio_hdmi_message));
+                builder.setPositiveButton(android.R.string.yes,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {}
+                 });
+                builder.setIcon(android.R.drawable.ic_dialog_alert);
+                builder.show();
+
+                mClaimsHDMI = true;
+                mHDMISupportLbl.setText(getResources().getString(R.string.audio_proaudio_pending));
+            } else {
+                mClaimsHDMI = false;
+                mHDMISupportLbl.setText(getResources().getString(R.string.audio_proaudio_NA));
+            }
+            break;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
index 7fdf403..140757d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -44,6 +44,8 @@
     protected AudioDeviceInfo mOutputDevInfo;
     protected AudioDeviceInfo mInputDevInfo;
 
+    protected final boolean mIsMandatedRequired;
+
     // This will be overriden...
     protected  int mSystemSampleRate = 48000;
 
@@ -53,9 +55,12 @@
 
     private TextView mPeripheralNameTx;
 
-    public USBAudioPeripheralActivity() {
+    public USBAudioPeripheralActivity(boolean mandatedRequired) {
         super();
 
+        // determine if to show "UNSUPPORTED" if the mandated peripheral is required.
+        mIsMandatedRequired = mandatedRequired;
+
         mProfileManager.loadProfiles();
     }
 
@@ -99,7 +104,7 @@
                 productName = mInputDevInfo.getProductName().toString();
             }
             String ctrlText;
-            if (mSelectedProfile == null) {
+            if (mSelectedProfile == null && mIsMandatedRequired) {
                 ctrlText = productName + " - UNSUPPORTED";
             } else {
                 ctrlText = productName;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
index f12de73..5029160 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
@@ -32,6 +32,10 @@
 
     private TextView mTestStatusTx;
 
+    public USBAudioPeripheralAttributesActivity() {
+        super(true); // Mandated peripheral is required
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
index 87b2149..de3ce7f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.audio;
 
+import android.app.AlertDialog;
+import android.content.DialogInterface;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.Bundle;
@@ -45,6 +47,23 @@
     private TextView mBtnBStatusTxt;
     private TextView mBtnCStatusTxt;
 
+    public USBAudioPeripheralButtonsActivity() {
+        super(false); // Mandated peripheral is NOT required
+    }
+
+    private void showDisableAssistantDialog() {
+        AlertDialog.Builder builder =
+                new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
+        builder.setTitle(getResources().getString(R.string.uapButtonsDisableAssistantTitle));
+        builder.setMessage(getResources().getString(R.string.uapButtonsDisableAssistant));
+        builder.setPositiveButton(android.R.string.yes,
+            new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {}
+         });
+        builder.setIcon(android.R.drawable.ic_dialog_alert);
+        builder.show();
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -62,12 +81,12 @@
 
         setPassFailButtonClickListeners();
         setInfoResources(R.string.usbaudio_buttons_test, R.string.usbaudio_buttons_info, -1);
+
+        showDisableAssistantDialog();
     }
 
     private void showButtonsState() {
-        int ctrlColor = mIsPeripheralAttached && mSelectedProfile != null
-                ? Color.WHITE
-                : Color.GRAY;
+        int ctrlColor = mIsPeripheralAttached ? Color.WHITE : Color.GRAY;
         mBtnALabelTxt.setTextColor(ctrlColor);
         mBtnAStatusTxt.setTextColor(ctrlColor);
         mBtnBLabelTxt.setTextColor(ctrlColor);
@@ -81,23 +100,13 @@
             mHasBtnB ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
         mBtnCStatusTxt.setText(getString(
             mHasBtnC ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+
+        calculateMatch();
     }
 
     private void calculateMatch() {
-        if (mIsPeripheralAttached && mSelectedProfile != null) {
-            ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
-            boolean match = mButtonAttributes != null;
-            boolean interceptedVolume = getResources().getBoolean(Resources.getSystem()
-                .getIdentifier("config_handleVolumeKeysInWindowManager", "bool", "android"));
-            if (match && mButtonAttributes.mHasBtnA != mHasBtnA) {
-                match = false;
-            }
-            if (match && mButtonAttributes.mHasBtnB != mHasBtnB && !interceptedVolume) {
-                match = false;
-            }
-            if (match && mButtonAttributes.mHasBtnC != mHasBtnC && !interceptedVolume) {
-                match = false;
-            }
+        if (mIsPeripheralAttached) {
+            boolean match = mHasBtnA && mHasBtnB && mHasBtnC;
             Log.i(TAG, "match:" + match);
             getPassButton().setEnabled(match);
         } else {
@@ -107,29 +116,27 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        Log.i(TAG, "onKeyDown(" + keyCode + ")");
-        if (mSelectedProfile != null) {
-            switch (keyCode) {
-            // Function A control event
-            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-                mHasBtnA = true;
-                break;
-    
-            // Function B control event
-            case KeyEvent.KEYCODE_VOLUME_UP:
-                mHasBtnB = true;
-                break;
-    
-            // Function C control event
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-                mHasBtnC = true;
-                break;
-            }
-    
-            showButtonsState();
-            calculateMatch();
+        // Log.i(TAG, "onKeyDown(" + keyCode + ")");
+        switch (keyCode) {
+        // Function A control event
+        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+            mHasBtnA = true;
+            break;
+
+        // Function B control event
+        case KeyEvent.KEYCODE_VOLUME_UP:
+            mHasBtnB = true;
+            break;
+
+        // Function C control event
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+            mHasBtnC = true;
+            break;
         }
 
+        showButtonsState();
+        calculateMatch();
+
         return super.onKeyDown(keyCode, event);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
index 640d489..de9016a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
@@ -31,6 +31,10 @@
     private Button mPlayBtn;
     private LocalClickListener mButtonClickListener = new LocalClickListener();
 
+    public USBAudioPeripheralPlayActivity() {
+        super(false); // Mandated peripheral is NOT required
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -52,8 +56,8 @@
     // USBAudioPeripheralActivity
     // Headset not publicly available, violates CTS Verifier additional equipment guidelines.
     public void updateConnectStatus() {
-        mPlayBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
-        getPassButton().setEnabled(mSelectedProfile != null && mOutputDevInfo != null);
+        mPlayBtn.setEnabled(mIsPeripheralAttached);
+        getPassButton().setEnabled(mIsPeripheralAttached);
     }
 
     public class LocalClickListener implements View.OnClickListener {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
index 33417d1..fc666aa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -41,6 +41,10 @@
 
     private static final int WAVBUFF_SIZE_IN_SAMPLES = 2048;
 
+    public USBAudioPeripheralPlayerActivity(boolean requiresMandatePeripheral) {
+        super(requiresMandatePeripheral); // Mandated peripheral is NOT required
+    }
+
     protected void setupPlayer() {
         mSystemBufferSize =
             StreamPlayer.calcNumBurstFrames((AudioManager)getSystemService(Context.AUDIO_SERVICE));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index 5772461..268201c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -49,6 +49,10 @@
 
     private WaveScopeView mWaveView = null;
 
+    public USBAudioPeripheralRecordActivity() {
+        super(false); // Mandated peripheral is NOT required
+    }
+
     private void connectWaveView() {
         // Log.i(TAG, "connectWaveView() rec:" + (mRecorder != null));
         if (mRecorder != null) {
@@ -73,7 +77,12 @@
             mRecorder.stop();
         }
 
+        // no reason to do more than 2
         int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mInputDevInfo);
+        if (numChans > 2) {
+            numChans = 2;
+        }
+        Log.i(TAG, "  numChans:" + numChans);
 
         if (mRecorder.open(numChans, mSystemSampleRate, mSystemBufferSize)) {
             connectWaveView();  // Setup the WaveView
@@ -136,9 +145,9 @@
     // USBAudioPeripheralActivity
     //
     public void updateConnectStatus() {
-        mRecordBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
-        mRecordLoopbackBtn.setEnabled(mIsPeripheralAttached && mSelectedProfile != null);
-        getPassButton().setEnabled(mSelectedProfile != null && mOutputDevInfo != null);
+        mRecordBtn.setEnabled(mIsPeripheralAttached);
+        mRecordLoopbackBtn.setEnabled(mIsPeripheralAttached);
+        getPassButton().setEnabled(mIsPeripheralAttached);
     }
 
     public class LocalClickListener implements View.OnClickListener {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
index 7190af9..55b7f9a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
@@ -32,9 +32,6 @@
     }
 
     public static int countToIndexMask(int chanCount) {
-        // From the documentation for AudioFormat:
-        // The canonical channel index masks by channel count are given by the formula
-        // (1 << channelCount) - 1.
         return (1 << chanCount) - 1;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
index 7cdff34..2ec742e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
@@ -120,19 +120,30 @@
         mNumChannels = numChans;
         mSampleRate = sampleRate;
 
-        int chanIndexMask = AudioUtils.countToIndexMask(numChans);
-        int bufferSizeInBytes = 2048;   // Some, non-critical value
+        final int frameSize =
+                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChannels);
+        final int bufferSizeInBytes = frameSize * 64;   // Some, non-critical value
+
+        AudioFormat.Builder formatBuilder = new AudioFormat.Builder();
+        formatBuilder.setEncoding(AudioFormat.ENCODING_PCM_FLOAT);
+        formatBuilder.setSampleRate(mSampleRate);
+
+        if (numChans <= 2) {
+            // There is currently a bug causing channel INDEX masks to fail.
+            // for channels counts of <= 2, use channel POSITION
+            final int chanPosMask = AudioUtils.countToInPositionMask(numChans);
+            formatBuilder.setChannelMask(chanPosMask);
+        } else {
+            // There are no INPUT channel-position masks for > 2 channels
+            final int chanIndexMask = AudioUtils.countToIndexMask(numChans);
+            formatBuilder.setChannelIndexMask(chanIndexMask);
+        }
+
+        AudioRecord.Builder builder = new AudioRecord.Builder();
+        builder.setAudioFormat(formatBuilder.build());
 
         try {
-            mAudioRecord = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
-                            .setSampleRate(mSampleRate)
-                            .setChannelIndexMask(chanIndexMask)
-                            .build())
-                    .setBufferSizeInBytes(bufferSizeInBytes)
-                    .build();
-
+            mAudioRecord = builder.build();
             return true;
         } catch (UnsupportedOperationException ex) {
             Log.e(TAG, "Couldn't open AudioRecord: " + ex);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/MidiEchoTestService.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/MidiEchoTestService.java
new file mode 100644
index 0000000..e5727f3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/MidiEchoTestService.java
@@ -0,0 +1,111 @@
+/*
+ * 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.verifier.audio.midilib;
+
+import android.content.Intent;
+
+import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiReceiver;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Virtual MIDI Device that copies its input to its output.
+ * This is used for loop-back testing of MIDI I/O.
+ */
+
+public class MidiEchoTestService extends MidiDeviceService {
+    private static final String TAG = "MidiEchoTestService";
+    private static final boolean DEBUG = false;
+
+    // Other apps will write to this port.
+    private MidiReceiver mInputReceiver = new MyReceiver();
+    // This app will copy the data to this port.
+    private MidiReceiver mOutputReceiver;
+    private static MidiEchoTestService mInstance;
+
+    // These are public so we can easily read them from CTS test.
+    public int statusChangeCount;
+    public boolean inputOpened;
+    public int outputOpenCount;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (DEBUG) {
+            Log.i(TAG, "#### onCreate()");
+        }
+        mInstance = this;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (DEBUG) {
+            Log.i(TAG, "#### onDestroy()");
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (DEBUG) {
+            Log.i(TAG, "#### onStartCommand()");
+        }
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (DEBUG) {
+            Log.i(TAG, "#### onBind()");
+        }
+        return super.onBind(intent);
+    }
+
+    // For CTS testing, so I can read test fields.
+    public static MidiEchoTestService getInstance() {
+        return mInstance;
+    }
+
+    @Override
+    public MidiReceiver[] onGetInputPortReceivers() {
+        return new MidiReceiver[] { mInputReceiver };
+    }
+
+    class MyReceiver extends MidiReceiver {
+        @Override
+        public void onSend(byte[] data, int offset, int count, long timestamp)
+                throws IOException {
+            if (mOutputReceiver == null) {
+                mOutputReceiver = getOutputPortReceivers()[0];
+            }
+            // Copy input to output.
+            mOutputReceiver.send(data, offset, count, timestamp);
+        }
+    }
+
+    @Override
+    public void onDeviceStatusChanged(MidiDeviceStatus status) {
+        statusChangeCount++;
+        inputOpened = status.isInputPortOpen(0);
+        outputOpenCount = status.getOutputPortOpenCount(0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/NativeMidiManager.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/NativeMidiManager.java
new file mode 100644
index 0000000..2cf8076
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/midilib/NativeMidiManager.java
@@ -0,0 +1,35 @@
+/*
+ * 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.verifier.audio.midilib;
+
+import android.media.midi.MidiDevice;
+
+import com.android.cts.verifier.audio.NDKMidiActivity;
+
+public class NativeMidiManager {
+    //
+    // Native API stuff
+    //
+    public static void loadNativeAPI() {
+        System.loadLibrary("ctsnativemidi_jni");
+    }
+
+    public static native void initN();
+
+    public native void startTest(NDKMidiActivity.NDKMidiTestModule testModule,
+            MidiDevice midiDevice);
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
index b4cb0b3..b5ff250 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
@@ -90,6 +90,11 @@
                 "<InputDevInfo ChanCounts=\"1\" ChanPosMasks=\"16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
             "</PeripheralProfile>" +
+            "<PeripheralProfile ProfileName=\"Libratone Q Adapt\" ProfileDescription=\"Libratone Q Adapt Headset\" ProductName=\"USB-Audio - Libratone_INEAR\">" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"48000\" />"+
+                "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
+            "</PeripheralProfile>" +
         "</ProfileList>";
 
     // XML Tags and Attributes
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java
new file mode 100644
index 0000000..31b43b4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/BiometricTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.verifier.biometrics;
+
+import android.Manifest;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Manual test for BiometricManager and BiometricPrompt. This tests two things currently.
+ * 1) When no biometrics are enrolled, BiometricManager and BiometricPrompt both return consistent
+ *    BIOMETRIC_ERROR_NONE_ENROLLED errors).
+ * 2) When biometrics are enrolled, BiometricManager returns BIOMETRIC_SUCCESS and BiometricPrompt
+ *    authentication can be successfully completed.
+ */
+public class BiometricTest extends PassFailButtons.Activity {
+
+    private static final String TAG = "BiometricTest";
+    private static final String BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
+    private static final int BIOMETRIC_PERMISSION_REQUEST_CODE = 0;
+
+    private static final int TEST_NONE_ENROLLED = 1;
+    private static final int TEST_ENROLLED = 2;
+
+    private BiometricManager mBiometricManager;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private CancellationSignal mCancellationSignal;
+    private int mExpectedError;
+    private int mCurrentTest;
+    private Button mButtonEnroll;
+    private Button mButtonTest1;
+    private Button mButtonTest2;
+
+    private Executor mExecutor = (runnable) -> {
+        mHandler.post(runnable);
+    };
+
+    private BiometricPrompt.AuthenticationCallback mAuthenticationCallback =
+            new BiometricPrompt.AuthenticationCallback() {
+        @Override
+        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+            if (mCurrentTest == TEST_NONE_ENROLLED) {
+                showToastAndLog("This should be impossible, please capture a bug report");
+            } else if (mCurrentTest == TEST_ENROLLED) {
+                showToastAndLog("Authenticated. You passed the test.");
+                getPassButton().setEnabled(true);
+            }
+        }
+
+        @Override
+        public void onAuthenticationError(int errorCode, CharSequence errString) {
+            if (mCurrentTest == TEST_NONE_ENROLLED) {
+                if (errorCode == mExpectedError) {
+                    mButtonTest1.setVisibility(View.INVISIBLE);
+                    mButtonTest2.setVisibility(View.VISIBLE);
+                    mButtonEnroll.setVisibility(View.VISIBLE);
+                    showToastAndLog("Please enroll a biometric and start the next test");
+                } else {
+                    showToastAndLog("Expected: " + mExpectedError +
+                            " Actual: " + errorCode + " " + errString);
+                }
+            } else if (mCurrentTest == TEST_ENROLLED) {
+                showToastAndLog(errString.toString() + " Please try again");
+            }
+        }
+    };
+
+    private DialogInterface.OnClickListener mBiometricPromptButtonListener = (dialog, which) -> {
+        showToastAndLog("Authentication canceled");
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.biometric_test_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.biometric_test, R.string.biometric_test_info, -1);
+        getPassButton().setEnabled(false);
+
+        mBiometricManager = getApplicationContext().getSystemService(BiometricManager.class);
+        mButtonEnroll = findViewById(R.id.biometric_enroll_button);
+        mButtonEnroll.setVisibility(View.INVISIBLE);
+        mButtonTest1 = findViewById(R.id.biometric_start_test1_button);
+        mButtonTest2 = findViewById(R.id.biometric_start_test2_button);
+        mButtonTest2.setVisibility(View.INVISIBLE);
+
+        PackageManager pm = getApplicationContext().getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+                || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            requestPermissions(new String[]{Manifest.permission.USE_BIOMETRIC},
+                    BIOMETRIC_PERMISSION_REQUEST_CODE);
+            mButtonTest1.setEnabled(false);
+            mButtonTest1.setOnClickListener((view) -> {
+                startTest(TEST_NONE_ENROLLED);
+            });
+            mButtonTest2.setOnClickListener((view) -> {
+                startTest(TEST_ENROLLED);
+            });
+            mButtonEnroll.setOnClickListener((view) -> {
+                final Intent intent = new Intent();
+                intent.setAction(BIOMETRIC_ENROLL);
+                startActivity(intent);
+            });
+        } else {
+            // NO biometrics available
+            mButtonTest1.setEnabled(false);
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+        if (requestCode == BIOMETRIC_PERMISSION_REQUEST_CODE &&
+                state[0] == PackageManager.PERMISSION_GRANTED) {
+            mButtonTest1.setEnabled(true);
+        }
+    }
+
+    private void startTest(int testType) {
+        mCurrentTest = testType;
+        int result = mBiometricManager.canAuthenticate();
+
+        if (testType == TEST_NONE_ENROLLED) {
+            if (result == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
+                mExpectedError = BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS;
+                showBiometricPrompt();
+            } else {
+                showToastAndLog("Error: " + result + " Please remove all biometrics and try again");
+            }
+        } else if (testType == TEST_ENROLLED) {
+            if (result == BiometricManager.BIOMETRIC_SUCCESS) {
+                mExpectedError = 0;
+                showBiometricPrompt();
+            } else {
+                showToastAndLog("Error: " + result +
+                        " Please ensure at least one biometric is enrolled and try again");
+            }
+        } else {
+            showToastAndLog("Unknown test type: " + testType);
+        }
+    }
+
+    private void showBiometricPrompt() {
+        BiometricPrompt.Builder builder = new BiometricPrompt.Builder(getApplicationContext())
+            .setTitle("Please authenticate")
+            .setNegativeButton("Cancel", mExecutor, mBiometricPromptButtonListener);
+        BiometricPrompt bp = builder.build();
+        mCancellationSignal = new CancellationSignal();
+        bp.authenticate(mCancellationSignal, mExecutor, mAuthenticationCallback);
+    }
+
+    private void showToastAndLog(String string) {
+        Toast.makeText(getApplicationContext(), string, Toast.LENGTH_SHORT).show();
+        Log.v(TAG, string);
+    }
+}
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 db45452..1b18a5a 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
@@ -101,6 +101,11 @@
 public class ItsService extends Service implements SensorEventListener {
     public static final String TAG = ItsService.class.getSimpleName();
 
+    // Version number to keep host/server communication in sync
+    // This string must be in sync with python side device.py
+    // Updated when interface between script and ItsService is changed
+    private final String ITS_SERVICE_VERSION = "1.0";
+
     private final int SERVICE_NOTIFICATION_ID = 37; // random int that is unique within app
     private NotificationChannel mChannel;
 
@@ -670,6 +675,8 @@
                     doGetCameraIds();
                 } else if ("doReprocessCapture".equals(cmdObj.getString("cmdName"))) {
                     doReprocessCapture(cmdObj);
+                } else if ("getItsVersion".equals(cmdObj.getString("cmdName"))) {
+                    mSocketRunnableObj.sendResponse("ItsVersion", ITS_SERVICE_VERSION);
                 } else {
                     throw new ItsException("Unknown command: " + cmd);
                 }
@@ -804,6 +811,8 @@
                         jsonSurface.put("format", "jpeg");
                     } else if (format == ImageFormat.YUV_420_888) {
                         jsonSurface.put("format", "yuv");
+                    } else if (format == ImageFormat.Y8) {
+                        jsonSurface.put("format", "y8");
                     } else {
                         throw new ItsException("Invalid format");
                     }
@@ -1288,6 +1297,9 @@
                         mCaptureRawIsStats = true;
                         mCaptureStatsGridWidth = surfaceObj.optInt("gridWidth");
                         mCaptureStatsGridHeight = surfaceObj.optInt("gridHeight");
+                    } else if ("y8".equals(sformat)) {
+                        outputFormats[i] = ImageFormat.Y8;
+                        sizes = ItsUtils.getY8OutputSizes(mCameraCharacteristics);
                     } else {
                         throw new ItsException("Unsupported format: " + sformat);
                     }
@@ -1784,6 +1796,12 @@
                             }
                         }
                     }
+                } else if (format == ImageFormat.Y8) {
+                    Logt.i(TAG, "Received Y8 capture");
+                    byte[] img = ItsUtils.getDataFromImage(capture, mSocketQueueQuota);
+                    ByteBuffer buf = ByteBuffer.wrap(img);
+                    mSocketRunnableObj.sendResponseCaptureBuffer(
+                            "y8Image"+physicalCameraId, buf);
                 } else {
                     throw new ItsException("Unsupported image format: " + format);
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index 65e4970..7c14064 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -119,6 +119,11 @@
         return getOutputSizes(ccs, ImageFormat.YUV_420_888);
     }
 
+    public static Size[] getY8OutputSizes(CameraCharacteristics ccs)
+            throws ItsException {
+        return getOutputSizes(ccs, ImageFormat.Y8);
+    }
+
     public static Size getMaxOutputSize(CameraCharacteristics ccs, int format)
             throws ItsException {
         return getMaxSize(getOutputSizes(ccs, format));
@@ -201,10 +206,11 @@
             }
             data = new byte[buffer.capacity()];
             buffer.get(data);
-            Logt.i(TAG, "Done reading jpeg image, format %d");
+            Logt.i(TAG, "Done reading jpeg image");
             return data;
         } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR
-                || format == ImageFormat.RAW10 || format == ImageFormat.RAW12) {
+                || format == ImageFormat.RAW10 || format == ImageFormat.RAW12
+                || format == ImageFormat.Y8) {
             int offset = 0;
             int dataSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
             if (quota != null) {
@@ -291,6 +297,7 @@
             case ImageFormat.RAW10:
             case ImageFormat.RAW12:
             case ImageFormat.JPEG:
+            case ImageFormat.Y8:
                 return 1 == planes.length;
             default:
                 return false;
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
new file mode 100644
index 0000000..5ac23da
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
@@ -0,0 +1,325 @@
+/*
+ * 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 com.android.cts.verifier.camera.performance;
+
+import com.android.compatibility.common.util.ReportLog.Metric;
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.DialogTestListActivity;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
+
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Instrumentation;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+import android.view.View;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.Result;
+import org.junit.runner.Runner;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.HashMap;
+import java.util.Enumeration;
+import java.util.Set;
+
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.cts.PerformanceTest;
+import android.hardware.cts.CameraTestCase;
+import android.hardware.cts.LegacyCameraPerformanceTest;
+
+/**
+ * This test checks the camera performance by running the respective CTS performance test cases
+ * and collecting the corresponding KPIs.
+ */
+public class CameraPerformanceActivity extends DialogTestListActivity {
+    private static final String TAG = "CameraPerformanceActivity";
+    private static final Class[] TEST_CLASSES =
+            { PerformanceTest.class, LegacyCameraPerformanceTest.class };
+
+    private ExecutorService mExecutorService;
+    private CameraTestInstrumentation mCameraInstrumentation = new CameraTestInstrumentation();
+    private Instrumentation mCachedInstrumentation;
+    private Bundle mCachedInstrumentationArgs;
+    private HashMap<String, TestCase> mTestCaseMap = new HashMap<String, TestCase>();
+    private ProgressDialog mSpinnerDialog;
+    private AlertDialog mResultDialog;
+    private ArrayList<Metric> mResults = new ArrayList<Metric>();
+
+    public CameraPerformanceActivity() {
+        super(R.layout.camera_performance, R.string.camera_performance_test,
+                R.string.camera_performance_test_info, R.string.camera_performance_test_info);
+    }
+
+    private void executeTest(TestCase testCase) {
+        JUnitCore testRunner = new JUnitCore();
+        testRunner.addListener(new CameraRunListener());
+        testRunner.run(testCase);
+    }
+
+    private class MetricListener implements CameraTestInstrumentation.MetricListener {
+        @Override
+        public void onResultMetric(Metric metric) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mResults.add(metric);
+                }
+            });
+        }
+    }
+
+    /**
+     * Basic {@link RunListener} implementation.
+     * It is only used to handle logging into the UI.
+     */
+    private class CameraRunListener extends RunListener {
+        private volatile boolean mCurrentTestReported;
+
+        @Override
+        public void testRunStarted(Description description) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mResults.clear();
+                    mSpinnerDialog.show();
+                }
+            });
+        }
+
+        @Override
+        public void testRunFinished(Result result) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mSpinnerDialog.dismiss();
+
+                    if (!mResults.isEmpty()) {
+                        StringBuilder message = new StringBuilder();
+                        for (Metric m : mResults) {
+                            message.append(String.format("%s : %5.2f %s\n",
+                                        m.getMessage().replaceAll("_", " "), m.getValues()[0],
+                                        m.getUnit()));
+                        }
+                        mResultDialog.setMessage(message);
+                        mResultDialog.show();
+                    }
+
+                    mResults.clear();
+                }
+            });
+        }
+
+        @Override
+        public void testStarted(Description description) {
+            mCurrentTestReported = false;
+        }
+
+        @Override
+        public void testFinished(Description description) {
+            if (!mCurrentTestReported) {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        setTestResult(description.getMethodName(), TestResult.TEST_RESULT_PASSED);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void testFailure(Failure failure) {
+            mCurrentTestReported = true;
+
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    setTestResult(failure.getDescription().getMethodName(),
+                            TestResult.TEST_RESULT_FAILED);
+                    mSpinnerDialog.dismiss();
+                    mResults.clear();
+                    String message = new String();
+                    String failureMessage = failure.getMessage();
+                    String failureTrace = failure.getTrace();
+                    if ((failureMessage != null) && (!failureMessage.isEmpty())) {
+                        message = failureMessage + "\n";
+                    } else if ((failureTrace != null) && (!failureTrace.isEmpty())) {
+                        message += failureTrace;
+                    }
+
+                    if (!message.isEmpty()) {
+                        mResultDialog.setMessage(message);
+                        mResultDialog.show();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            mCurrentTestReported = true;
+        }
+
+        @Override
+        public void testIgnored(Description description) {
+            mCurrentTestReported = true;
+        }
+    }
+
+    private void initializeTestCases(Context ctx) {
+        TestSuite suite = new TestSuite(TEST_CLASSES);
+        Enumeration<Test> testSuite = suite.tests();
+        while (testSuite.hasMoreElements()) {
+            Test s = testSuite.nextElement();
+            if (s instanceof TestSuite) {
+                Enumeration<Test> tests = ((TestSuite) s).tests();
+                while (tests.hasMoreElements()) {
+                    Test test = tests.nextElement();
+                    if (test instanceof Camera2AndroidTestCase) {
+                        Camera2AndroidTestCase testCase = (Camera2AndroidTestCase) test;
+
+                        // The base case class has one internal test that can
+                        // be ignored for the purpose of this test activity.
+                        try {
+                            Method method = testCase.getClass().getMethod(testCase.getName());
+                            Annotation an = method.getAnnotation(
+                                    android.test.suitebuilder.annotation.Suppress.class);
+                            if (an != null) {
+                                continue;
+                            }
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            continue;
+                        }
+
+                        testCase.setContext(ctx);
+                        mTestCaseMap.put(testCase.getName(), testCase);
+                    } else if (test instanceof CameraTestCase) {
+                        TestCase testCase = (CameraTestCase) test;
+                        mTestCaseMap.put(testCase.getName(), testCase);
+                    } else {
+                        Log.d(TAG, "Test is not instance of any known camera test cases");
+                    }
+                }
+            } else {
+                Log.d(TAG, "Test is not instance of TestSuite");
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Need to enumerate and initialize the available test cases first
+        // before calling the base 'onCreate' implementation.
+        initializeTestCases(getApplicationContext());
+
+        super.onCreate(savedInstanceState);
+
+        String spinnerMessage = (String) getResources().getString(
+                R.string.camera_performance_spinner_text);
+        String resultTitle = (String) getResources().getString(
+                R.string.camera_performance_result_title);
+        mSpinnerDialog = new ProgressDialog(this);
+        mSpinnerDialog.setIndeterminate(true);
+        mSpinnerDialog.setCancelable(false);
+        mSpinnerDialog.setMessage(spinnerMessage);
+        mResultDialog =
+                new AlertDialog.Builder(this).setCancelable(true).setTitle(resultTitle).create();
+
+    }
+
+    private class TestListItem extends DialogTestListActivity.DialogTestListItem {
+        private String mTestId;
+
+        public TestListItem(Context context, String nameId, String testId) {
+            super(context, nameId, testId);
+            mTestId = testId;
+        }
+
+        @Override
+        public void performTest(DialogTestListActivity activity) {
+            TestCase testCase = mTestCaseMap.get(mTestId);
+            if (testCase == null) {
+                Log.e(TAG, "Test case with name: " + mTestId + " not found!");
+                return;
+            }
+
+            mExecutorService.execute(new Runnable() {
+                @Override
+                public void run() {
+                    executeTest(testCase);
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void setupTests(ArrayTestListAdapter adapter) {
+        Set<String> testCaseNames = mTestCaseMap.keySet();
+        for (String testCaseName : testCaseNames) {
+            adapter.add(new TestListItem(this, testCaseName, testCaseName));
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mCameraInstrumentation.addMetricListener(new MetricListener());
+
+        try {
+            mCachedInstrumentation = InstrumentationRegistry.getInstrumentation();
+            mCachedInstrumentationArgs = InstrumentationRegistry.getArguments();
+        } catch (IllegalStateException e) {
+            // This is expected in case there was no prior instrumentation.
+        }
+        InstrumentationRegistry.registerInstance(mCameraInstrumentation, new Bundle());
+
+        mExecutorService = Executors.newSingleThreadExecutor();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        // Terminate any running test cases.
+        mExecutorService.shutdownNow();
+
+        // Restore any cached instrumentation.
+        if ((mCachedInstrumentation != null) && (mCachedInstrumentationArgs != null)) {
+            InstrumentationRegistry.registerInstance(mCachedInstrumentation,
+                    mCachedInstrumentationArgs);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraTestInstrumentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraTestInstrumentation.java
new file mode 100644
index 0000000..c7aae5b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraTestInstrumentation.java
@@ -0,0 +1,86 @@
+
+/*
+ * 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.verifier.camera.performance;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+import android.util.Log;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ReportLog.Metric;
+
+import java.util.Set;
+
+public class CameraTestInstrumentation extends Instrumentation {
+    private static final String TAG = "CameraTestInstrumentation";
+
+    private MetricListener mMetricListener;
+
+    public interface MetricListener {
+        public void onResultMetric(Metric metric);
+    }
+
+    public void addMetricListener(MetricListener listener) {
+        mMetricListener = listener;
+    }
+
+    @Override
+    public void sendStatus(int resultCode, Bundle results) {
+        super.sendStatus(resultCode, results);
+
+        if (results == null) {
+            return;
+        }
+
+        Set<String> keys = results.keySet();
+        if (keys.isEmpty()) {
+            Log.v(TAG,"Empty keys");
+            return;
+        }
+
+        for (String key : keys) {
+            ReportLog report;
+            try {
+                report = ReportLog.parse(results.getString(key));
+            } catch (Exception e) {
+                Log.e(TAG, "Failed parsing report log!");
+                return;
+            }
+
+            Metric metric = report.getSummary();
+            if (metric == null) {
+                Log.v(TAG, "Empty metric");
+                return;
+            }
+            String message = metric.getMessage();
+            double[] values = metric.getValues();
+            String source = metric.getSource();
+            if ((message == null) || (message.isEmpty()) || (values == null) ||
+                    (values.length == 0) || (source == null) || (source.isEmpty())) {
+                Log.v(TAG, "Metric has no valid entries");
+                return;
+            }
+
+            if (mMetricListener != null) {
+                mMetricListener.onResultMetric(metric);
+            }
+        }
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
index 67f82d0..6015242 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -27,8 +27,6 @@
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
 import android.widget.Toast;
 
 import com.android.cts.verifier.ArrayTestListAdapter;
@@ -58,6 +56,7 @@
             "com.android.cts.verifier.managedprovisioning.BYOD_TEST_RESULT";
     // Extra for ACTION_TEST_RESULT containing test result.
     public static final String EXTRA_RESULT = "extra-result";
+    protected static final String HELPER_APP_PATH = "/data/local/tmp/NotificationBot.apk";
 
     private static final String TAG = "ByodFlowTestActivity";
     private static ConnectivityManager mCm;
@@ -79,8 +78,7 @@
     private DialogTestListItem mCrossProfileIntentFiltersTestFromPersonal;
     private DialogTestListItem mCrossProfileIntentFiltersTestFromWork;
     private DialogTestListItem mAppLinkingTest;
-    private DialogTestListItem mDisableNonMarketTest;
-    private DialogTestListItem mEnableNonMarketTest;
+    private TestListItem mNonMarketAppsTest;
     private DialogTestListItem mWorkNotificationBadgedTest;
     private DialogTestListItem mWorkStatusBarIconTest;
     private DialogTestListItem mWorkStatusBarToastTest;
@@ -106,7 +104,9 @@
     private DialogTestListItem mPrimaryLocationWhenWorkDisabledTest;
     private DialogTestListItem mSelectWorkChallenge;
     private DialogTestListItem mConfirmWorkCredentials;
+    private DialogTestListItem mPatternWorkChallenge;
     private DialogTestListItem mParentProfilePassword;
+    private DialogTestListItem mPersonalRingtonesTest;
     private TestListItem mVpnTest;
     private TestListItem mKeyChainTest;
     private TestListItem mAlwaysOnVpnSettingsTest;
@@ -133,13 +133,9 @@
         mByodFlowTestHelper.setup();
 
         mPrepareTestButton.setText(R.string.provisioning_byod_start);
-        mPrepareTestButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Utils.provisionManagedProfile(ByodFlowTestActivity.this, mAdminReceiverComponent,
-                        REQUEST_MANAGED_PROVISIONING);
-            }
-        });
+        mPrepareTestButton.setOnClickListener(v -> Utils.provisionManagedProfile(
+                ByodFlowTestActivity.this, mAdminReceiverComponent,
+                REQUEST_MANAGED_PROVISIONING));
 
         // If we are started by managed provisioning (fresh managed provisioning after encryption
         // reboot), redirect the user back to the main test list. This is because the test result
@@ -247,6 +243,12 @@
                     R.string.provisioning_byod_confirm_work_credentials_description,
                     new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
 
+            mPatternWorkChallenge = new DialogTestListItem(this,
+                    R.string.provisioning_byod_pattern_work_challenge,
+                    "BYOD_PatternWorkChallenge",
+                    R.string.provisioning_byod_pattern_work_challenge_description,
+                    new Intent(ByodHelperActivity.ACTION_TEST_PATTERN_WORK_CHALLENGE));
+
             mWiFiDataUsageSettingsVisibleTest = new DialogTestListItem(this,
                     R.string.provisioning_byod_wifi_data_usage_settings,
                     "BYOD_WiFiDataUsageSettingsVisibleTest",
@@ -280,19 +282,10 @@
                 workStatusToast);
         */
 
-        mDisableNonMarketTest = new DialogTestListItem(this,
-                R.string.provisioning_byod_nonmarket_deny,
-                "BYOD_DisableNonMarketTest",
-                R.string.provisioning_byod_nonmarket_deny_info,
-                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
-                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
-
-        mEnableNonMarketTest = new DialogTestListItem(this,
-                R.string.provisioning_byod_nonmarket_allow,
-                "BYOD_EnableNonMarketTest",
-                R.string.provisioning_byod_nonmarket_allow_info,
-                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
-                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
+        mNonMarketAppsTest = TestListItem.newTest(this,
+                R.string.provisioning_byod_non_market_apps,
+                NonMarketAppsActivity.class.getName(),
+                new Intent(this, NonMarketAppsActivity.class), null);
 
         mProfileAccountVisibleTest = new DialogTestListItem(this,
                 R.string.provisioning_byod_profile_visible,
@@ -448,6 +441,12 @@
                 R.string.provisioning_byod_parent_profile_password_description,
                 new Intent(ByodHelperActivity.ACTION_TEST_PARENT_PROFILE_PASSWORD));
 
+        mPersonalRingtonesTest = new DialogTestListItem(this,
+                R.string.provisioning_byod_personal_ringtones,
+                "BYOD_PersonalRingtones",
+                R.string.provisioning_byod_personal_ringtones_instruction,
+                new Intent(Settings.ACTION_SOUND_SETTINGS));
+
         final Intent policyTransparencyTestIntent = new Intent(this,
                 PolicyTransparencyTestListActivity.class);
         policyTransparencyTestIntent.putExtra(
@@ -483,15 +482,15 @@
         adapter.add(mAppSettingsVisibleTest);
         adapter.add(mLocationSettingsVisibleTest);
         adapter.add(mPrintSettingsVisibleTest);
+        adapter.add(mPersonalRingtonesTest);
 
         adapter.add(mCrossProfileIntentFiltersTestFromPersonal);
         adapter.add(mCrossProfileIntentFiltersTestFromWork);
         /* Disable due to b/33571176
         adapter.add(mAppLinkingTest);
         */
-        adapter.add(mDisableNonMarketTest);
-        adapter.add(mEnableNonMarketTest);
         adapter.add(mIntentFiltersTest);
+        adapter.add(mNonMarketAppsTest);
         adapter.add(mPermissionLockdownTest);
         adapter.add(mKeyguardDisabledFeaturesTest);
         adapter.add(mAuthenticationBoundKeyTest);
@@ -501,6 +500,7 @@
         adapter.add(mSelectWorkChallenge);
         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             adapter.add(mConfirmWorkCredentials);
+            adapter.add(mPatternWorkChallenge);
         }
         adapter.add(mRecentsTest);
         adapter.add(mOrganizationInfoTest);
@@ -656,6 +656,18 @@
             adapter.add(mWidgetTest);
         }
 
+        adapter.add(new DialogTestListItem(this,
+                R.string.provisioning_byod_uninstall_work_app,
+                "BYOD_UninstallWorkApp",
+                R.string.provisioning_byod_uninstall_work_app_instruction,
+                createInstallWorkProfileAppIntent()));
+    }
+
+    private Intent createInstallWorkProfileAppIntent() {
+        // We place the APK file in /data/local/tmp to make it visible from the work profile.
+        return new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true)
+                .putExtra(ByodHelperActivity.EXTRA_PARAMETER_1, HELPER_APP_PATH);
     }
 
     // Return whether the intent can be resolved in the current profile
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index 9bb9cf8..e59e6d6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -17,6 +17,7 @@
 package com.android.cts.verifier.managedprovisioning;
 
 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES;
+import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
 
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -26,7 +27,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.net.Uri;
 import android.os.Bundle;
@@ -55,6 +55,7 @@
  */
 public class ByodHelperActivity extends LocationListenerActivity
         implements DialogCallback {
+
     static final String TAG = "ByodHelperActivity";
 
     // Primary -> managed intent: query if the profile owner has been set up.
@@ -94,6 +95,14 @@
     // Primary -> managed intent: set unknown sources restriction and install package
     public static final String ACTION_INSTALL_APK = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK";
     public static final String EXTRA_ALLOW_NON_MARKET_APPS = "allow_non_market_apps";
+    public static final String ACTION_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION";
+    public static final String EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE = "allow_non_market_apps_device_wide";
+
+    // Primary -> managed intent: set unknown sources globally restriction
+    public static final String ACTION_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION";
+    // Managed -> primary intent: install primary profile app with global unknown sources
+    // restriction.
+    public static final String ACTION_INSTALL_APK_IN_PRIMARY = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_IN_PRIMARY";
 
     // Primary -> managed intent: check if the required cross profile intent filters are set.
     public static final String ACTION_CHECK_INTENT_FILTERS =
@@ -133,6 +142,10 @@
     public static final String ACTION_TEST_SELECT_WORK_CHALLENGE =
             "com.android.cts.verifier.managedprovisioning.TEST_SELECT_WORK_CHALLENGE";
 
+    // Primary -> managed intent: Start the selection of a work challenge
+    public static final String ACTION_TEST_PATTERN_WORK_CHALLENGE =
+            "com.android.cts.verifier.managedprovisioning.TEST_PATTERN_WORK_CHALLENGE";
+
     // Primary -> managed intent: Start the selection of a parent profile password.
     public static final String ACTION_TEST_PARENT_PROFILE_PASSWORD =
             "com.android.cts.verifier.managedprovisioning.TEST_PARENT_PROFILE_PASSWORD";
@@ -151,7 +164,6 @@
     private static final int REQUEST_VIDEO_CAPTURE_WITH_EXTRA_OUTPUT = 4;
     private static final int REQUEST_VIDEO_CAPTURE_WITHOUT_EXTRA_OUTPUT = 5;
     private static final int REQUEST_AUDIO_CAPTURE = 6;
-    private static final int REQUEST_LOCATION_UPDATE = 7;
 
     private static final String ORIGINAL_RESTRICTIONS_NAME = "original restrictions";
 
@@ -240,26 +252,32 @@
             setResult(RESULT_OK, response);
         } else if (action.equals(ACTION_INSTALL_APK)) {
             boolean allowNonMarket = intent.getBooleanExtra(EXTRA_ALLOW_NON_MARKET_APPS, false);
-            boolean wasAllowed = !isUnknownSourcesRestrictionSet();
-
-            if (wasAllowed != allowNonMarket) {
-                setUnknownSourcesRestriction(!allowNonMarket);
-                mOriginalRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, !wasAllowed);
-            }
-            // Start the installer activity until this activity is rendered to workaround a glitch.
-            mMainThreadHandler.post(() -> {
-                // Request to install a non-market application- easiest way is to reinstall ourself
-                final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
-                        .setData(Uri.parse("package:" + getPackageName()))
-                        .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
-                        .putExtra(Intent.EXTRA_RETURN_RESULT, true);
-                startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
-
-            });
-            // Not yet ready to finish- wait until the result comes back
+            setRestrictionAndSaveOriginal(DISALLOW_INSTALL_UNKNOWN_SOURCES, !allowNonMarket);
+            startInstallerActivity(intent.getStringExtra(EXTRA_PARAMETER_1));
+            // Not yet ready to finish - wait until the result comes back
             return;
-            // Queried by CtsVerifier in the primary side using startActivityForResult.
+        } else if (action.equals(ACTION_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION)) {
+            // Save original unknown sources setting to be restored later and clear it for now.
+            setRestrictionAndSaveOriginal(DISALLOW_INSTALL_UNKNOWN_SOURCES, false);
+            boolean allowNonMarketGlobal = intent.getBooleanExtra(
+                    EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE, false);
+            setRestrictionAndSaveOriginal(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+                    !allowNonMarketGlobal);
+            startInstallerActivity(intent.getStringExtra(EXTRA_PARAMETER_1));
+            // Not yet ready to finish - wait until the result comes back
+            return;
+        } else if (action.equals(ACTION_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION)) {
+            boolean allowNonMarketGlobal = intent.getExtras().getBoolean(
+                    EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE, false);
+            setRestrictionAndSaveOriginal(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+                    !allowNonMarketGlobal);
+            setRestrictionAndSaveOriginal(DISALLOW_INSTALL_UNKNOWN_SOURCES, false);
+            Intent installPersonalProfileIntent = new Intent(ACTION_INSTALL_APK_IN_PRIMARY);
+            // Attempt to install an apk in the primary profile
+            startActivityForResult(installPersonalProfileIntent, REQUEST_INSTALL_PACKAGE);
+            return;
         } else if (action.equals(ACTION_CHECK_INTENT_FILTERS)) {
+            // Queried by CtsVerifier in the primary side using startActivityForResult.
             final boolean intentFiltersSetForManagedIntents =
                     new IntentFiltersTestHelper(this).checkCrossProfileIntentFilters(
                             IntentFiltersTestHelper.FLAG_INTENTS_FROM_MANAGED);
@@ -370,6 +388,9 @@
             } else {
                 showToast(R.string.provisioning_byod_no_secure_lockscreen);
             }
+        } else if (ACTION_TEST_PATTERN_WORK_CHALLENGE.equals(action)) {
+            startActivity(new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD));
+            // The remaining steps are manual.
         } else if (ACTION_SET_ORGANIZATION_INFO.equals(action)) {
             if(intent.hasExtra(OrganizationInfoTestActivity.EXTRA_ORGANIZATION_NAME)) {
                 final String organizationName = intent
@@ -387,6 +408,26 @@
         finish();
     }
 
+    private void startInstallerActivity(String pathToApk) {
+        // Start the installer activity until this activity is rendered to workaround a glitch.
+        mMainThreadHandler.post(() -> {
+            final Uri uri;
+            if (pathToApk == null) {
+                // By default we reinstall ourselves, e.g. request to install a non-market app
+                uri = Uri.parse("package:" + getPackageName());
+            } else {
+                uri = FileProvider.getUriForFile(
+                    this, Utils.FILE_PROVIDER_AUTHORITY, new File(pathToApk));
+            }
+            final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
+                .setData(uri)
+                .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
+                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                .putExtra(Intent.EXTRA_RETURN_RESULT, true);
+            startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
+        });
+    }
+
     @Override
     protected void onSaveInstanceState(final Bundle savedState) {
         super.onSaveInstanceState(savedState);
@@ -399,12 +440,9 @@
         switch (requestCode) {
             case REQUEST_INSTALL_PACKAGE: {
                 Log.w(TAG, "Received REQUEST_INSTALL_PACKAGE, resultCode = " + resultCode);
-                if (mOriginalRestrictions.containsKey(DISALLOW_INSTALL_UNKNOWN_SOURCES)) {
-                    // Restore original setting
-                    setUnknownSourcesRestriction(
-                            mOriginalRestrictions.getBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES));
-                    mOriginalRestrictions.remove(DISALLOW_INSTALL_UNKNOWN_SOURCES);
-                }
+                // Restore original settings for restrictions being changed before installs.
+                restoreOriginalRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES);
+                restoreOriginalRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
                 finish();
                 break;
             }
@@ -482,8 +520,8 @@
                 + File.separator + fileName);
         file.getParentFile().mkdirs(); //if the folder doesn't exists it is created
         mTempFiles.add(file);
-        return new Pair<>(file, FileProvider.getUriForFile(this,
-                    "com.android.cts.verifier.managedprovisioning.fileprovider", file));
+        return new Pair<>(file, FileProvider.getUriForFile(
+                this, Utils.FILE_PROVIDER_AUTHORITY, file));
     }
 
     private void cleanUpTempUris() {
@@ -497,20 +535,34 @@
                 mDevicePolicyManager.isProfileOwnerApp(mAdminReceiverComponent.getPackageName());
     }
 
-    private boolean isUnknownSourcesRestrictionSet() {
-        // We only care about restrictions set by Cts Verifier. In other cases, we cannot modify
-        // it and the test will fail anyway.
+    private boolean isRestrictionSet(String restriction) {
         Bundle restrictions = mDevicePolicyManager.getUserRestrictions(mAdminReceiverComponent);
-        return restrictions.getBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, false);
+        // This defaults to false if there is no value already there. If a restriction was true,
+        // the restriction would already be set.
+        return restrictions.getBoolean(restriction, false);
     }
 
-    private void setUnknownSourcesRestriction(boolean enabled) {
+    private void setRestriction(String restriction, boolean enabled) {
         if (enabled) {
-            mDevicePolicyManager.addUserRestriction(mAdminReceiverComponent,
-                    DISALLOW_INSTALL_UNKNOWN_SOURCES);
+            mDevicePolicyManager.addUserRestriction(mAdminReceiverComponent, restriction);
         } else {
-            mDevicePolicyManager.clearUserRestriction(mAdminReceiverComponent,
-                    DISALLOW_INSTALL_UNKNOWN_SOURCES);
+            mDevicePolicyManager.clearUserRestriction(mAdminReceiverComponent, restriction);
+        }
+    }
+
+    private void setRestrictionAndSaveOriginal(String restriction, boolean enabled) {
+        // Saves original restriction values in mOriginalRestrictions before changing its value.
+        boolean original = isRestrictionSet(restriction);
+        if (enabled != original) {
+            setRestriction(restriction, enabled);
+            mOriginalRestrictions.putBoolean(restriction, original);
+        }
+    }
+
+    public void restoreOriginalRestriction(String restriction) {
+        if (mOriginalRestrictions.containsKey(restriction)) {
+            setRestriction(restriction, mOriginalRestrictions.getBoolean(restriction));
+            mOriginalRestrictions.remove(restriction);
         }
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodPrimaryHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodPrimaryHelperActivity.java
new file mode 100644
index 0000000..27c2a41
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodPrimaryHelperActivity.java
@@ -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.cts.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+
+/** Cts Verifier helper activity that runs only in the primary profile.*/
+public class ByodPrimaryHelperActivity extends Activity {
+
+    private static final String TAG = "ByodPrimaryHlprActivity";
+    private static final int REQUEST_INSTALL_PACKAGE = 1;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (ByodHelperActivity.ACTION_INSTALL_APK_IN_PRIMARY.equals(getIntent().getAction())) {
+            final Uri uri = FileProvider.getUriForFile(this, Utils.FILE_PROVIDER_AUTHORITY,
+                    new File(ByodFlowTestActivity.HELPER_APP_PATH));
+            final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
+                    .setData(uri)
+                    .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
+                    .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                    .putExtra(Intent.EXTRA_RETURN_RESULT, true)
+                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_INSTALL_PACKAGE) {
+            Log.w(TAG, "Receiving installer result " + resultCode + ".");
+            finish();
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index e807d29..3ad2b82 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -210,9 +210,11 @@
                 case COMMAND_SET_AUTO_TIME_REQUIRED: {
                     mDpm.setAutoTimeRequired(mAdmin,
                             intent.getBooleanExtra(EXTRA_ENFORCED, false));
+                    break;
                 }
                 case COMMAND_SET_LOCK_SCREEN_INFO: {
                     mDpm.setDeviceOwnerLockScreenInfo(mAdmin, intent.getStringExtra(EXTRA_VALUE));
+                    break;
                 }
                 case COMMAND_SET_MAXIMUM_TO_LOCK: {
                     final long timeInSeconds = Long.parseLong(intent.getStringExtra(EXTRA_VALUE));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
index abad5b5..9cff9d2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -144,6 +145,8 @@
         filter.addAction(ByodHelperActivity.ACTION_REMOVE_MANAGED_PROFILE);
         filter.addAction(ByodHelperActivity.ACTION_CHECK_DISK_ENCRYPTION);
         filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK);
+        filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION);
+        filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION);
         filter.addAction(ByodHelperActivity.ACTION_CHECK_INTENT_FILTERS);
         filter.addAction(ByodHelperActivity.ACTION_CAPTURE_AND_CHECK_IMAGE);
         filter.addAction(ByodHelperActivity.ACTION_CAPTURE_AND_CHECK_VIDEO_WITH_EXTRA_OUTPUT);
@@ -170,6 +173,7 @@
         filter.addAction(AlwaysOnVpnSettingsTestActivity.ACTION_ALWAYS_ON_VPN_SETTINGS_TEST);
         filter.addAction(RecentsRedactionActivity.ACTION_RECENTS);
         filter.addAction(ByodHelperActivity.ACTION_TEST_SELECT_WORK_CHALLENGE);
+        filter.addAction(ByodHelperActivity.ACTION_TEST_PATTERN_WORK_CHALLENGE);
         filter.addAction(ByodHelperActivity.ACTION_LAUNCH_CONFIRM_WORK_CREDENTIALS);
         filter.addAction(ByodHelperActivity.ACTION_SET_ORGANIZATION_INFO);
         filter.addAction(ByodHelperActivity.ACTION_TEST_PARENT_PROFILE_PASSWORD);
@@ -184,6 +188,7 @@
         filter = new IntentFilter();
         filter.addAction(ByodHelperActivity.ACTION_PROFILE_OWNER_STATUS);
         filter.addAction(ByodHelperActivity.ACTION_DISK_ENCRYPTION_STATUS);
+        filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK_IN_PRIMARY);
         filter.addAction(ByodFlowTestActivity.ACTION_TEST_RESULT);
         filter.addAction(CrossProfileTestActivity.ACTION_CROSS_PROFILE_TO_PERSONAL);
         filter.addAction(LocationListenerActivity.ACTION_SET_LOCATION_AND_CHECK_UPDATES);
@@ -194,6 +199,11 @@
         intent.setAction(ByodHelperActivity.ACTION_PROFILE_PROVISIONED);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         context.startActivity(intent);
+        // Disable the work profile instance of this activity, because it is a helper activity for
+        // the work -> primary direction.
+        context.getPackageManager().setComponentEnabledSetting(
+                new ComponentName(context, ByodPrimaryHelperActivity.class.getName()),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
     }
 
     private void wipeIfNecessary(Context context, Intent intent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index ff4f540..72bc3ab 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -400,7 +400,7 @@
                                             UserManager.DISALLOW_USER_SWITCH, true)),
                             new ButtonInfo(
                                     R.string.device_owner_settings_go,
-                                    new Intent(Settings.ACTION_SETTINGS))}));
+                                    new Intent(Settings.ACTION_USER_SETTINGS))}));
 
             // DISALLOW_REMOVE_USER
             adapter.add(createInteractiveTestItem(this, DISALLOW_REMOVE_USER_TEST_ID,
@@ -416,7 +416,7 @@
                                             UserManager.DISALLOW_REMOVE_USER, true)),
                             new ButtonInfo(
                                     R.string.device_owner_settings_go,
-                                    new Intent(Settings.ACTION_SETTINGS))}));
+                                    new Intent(Settings.ACTION_USER_SETTINGS))}));
         }
 
         // Network logging UI
@@ -431,6 +431,13 @@
                                 R.string.device_owner_disable_network_logging_button,
                                 createDisableNetworkLoggingIntent())}));
 
+        // Customize lock screen message
+        adapter.add(TestListItem.newTest(this,
+                R.string.device_owner_customize_lockscreen_message,
+                LockscreenMessageTestActivity.class.getName(),
+                new Intent(this, LockscreenMessageTestActivity.class),
+                /* requiredFeatures */ null));
+
         // removeDeviceOwner
         adapter.add(createInteractiveTestItem(this, REMOVE_DEVICE_OWNER_TEST_ID,
                 R.string.device_owner_remove_device_owner_test,
@@ -440,7 +447,6 @@
                         createTearDownIntent())));
     }
 
-
     static TestListItem createTestItem(Activity activity, String id, int titleRes,
             Intent intent) {
         intent.putExtra(EXTRA_TEST_ID, id);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
index a59261c..fa88e32 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
@@ -228,7 +228,7 @@
         @Override
         public void onClick(View v) {
             Log.i(TAG, "Selecting certificate");
-            mLogView.setText("Waiting for prompt");
+            mLogView.setText("Prompt should not appear.");
             selectCertificate(this);
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockscreenMessageTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockscreenMessageTestActivity.java
new file mode 100644
index 0000000..5047c03
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockscreenMessageTestActivity.java
@@ -0,0 +1,69 @@
+/*
+ * 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.verifier.managedprovisioning;
+
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.COMMAND_SET_LOCK_SCREEN_INFO;
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.EXTRA_COMMAND;
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.EXTRA_VALUE;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Test class to verify setting a customized Lockscreen message.
+ */
+public class LockscreenMessageTestActivity extends PassFailButtons.Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.lockscreen_message);
+        setPassFailButtonClickListeners();
+        setButtonClickListeners();
+    }
+
+    private void setButtonClickListeners() {
+        findViewById(R.id.lockscreen_message_set_button)
+                .setOnClickListener(v -> setLockscreenMessage());
+        findViewById(R.id.go_button).setOnClickListener(v -> goToSettings());
+    }
+
+    private void setLockscreenMessage() {
+        TextView lockscreenMessageView = findViewById(R.id.lockscreen_message_edit_text);
+        String lockscreenMessage = lockscreenMessageView.getText().toString();
+        if (lockscreenMessage.isEmpty()) {
+            Toast.makeText(this, R.string.device_owner_lockscreen_message_cannot_be_empty,
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        Intent intent = new Intent(this, CommandReceiverActivity.class)
+                .putExtra(EXTRA_COMMAND, COMMAND_SET_LOCK_SCREEN_INFO)
+                .putExtra(EXTRA_VALUE, lockscreenMessage);
+        startActivity(intent);
+    }
+
+    private void goToSettings() {
+        Intent intent = new Intent(Settings.ACTION_SETTINGS);
+        startActivity(intent);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
index dd2a639..acfebfe 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
@@ -161,7 +161,7 @@
                                         UserManager.DISALLOW_REMOVE_USER, true)),
                         new ButtonInfo(
                                 R.string.device_owner_settings_go,
-                                new Intent(Settings.ACTION_SETTINGS))}));
+                                new Intent(Settings.ACTION_USER_SETTINGS))}));
 
         // Policy Transparency
         final Intent policyTransparencyTestIntent = new Intent(this,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NonMarketAppsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NonMarketAppsActivity.java
new file mode 100644
index 0000000..25b3b80
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/NonMarketAppsActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.managedprovisioning;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.DialogTestListActivity;
+import com.android.cts.verifier.R;
+
+public class NonMarketAppsActivity extends DialogTestListActivity {
+
+    protected DevicePolicyManager mDpm;
+
+    public NonMarketAppsActivity() {
+        super(R.layout.provisioning_byod,
+              R.string.provisioning_byod_non_market_apps,
+              R.string.provisioning_byod_non_market_apps_info,
+              R.string.provisioning_byod_non_market_apps_info);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Hiding button from default BYOD test layout, as it is not useful in this test.
+        mPrepareTestButton = findViewById(R.id.prepare_test_button);
+        mPrepareTestButton.setVisibility(View.INVISIBLE);
+        mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+    }
+
+    @Override
+    protected void setupTests(ArrayTestListAdapter adapter) {
+        DialogTestListItem disableNonMarketTest = new DialogTestListItem(this,
+                R.string.provisioning_byod_nonmarket_deny,
+                "BYOD_DisableNonMarketTest",
+                R.string.provisioning_byod_nonmarket_deny_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
+
+        DialogTestListItem enableNonMarketTest = new DialogTestListItem(this,
+                R.string.provisioning_byod_nonmarket_allow,
+                "BYOD_EnableNonMarketTest",
+                R.string.provisioning_byod_nonmarket_allow_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
+
+        DialogTestListItem disableNonMarketWorkProfileDeviceWideTest = new DialogTestListItem(
+                this,
+                R.string.provisioning_byod_nonmarket_deny_global,
+                "BYOD_DisableNonMarketDeviceWideTest",
+                R.string.provisioning_byod_nonmarket_deny_global_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE,
+                                false));
+
+        DialogTestListItem enableNonMarketWorkProfileDeviceWideTest = new DialogTestListItem(
+                this,
+                R.string.provisioning_byod_nonmarket_allow_global,
+                "BYOD_EnableNonMarketDeviceWideTest",
+                R.string.provisioning_byod_nonmarket_allow_global_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK_WORK_PROFILE_GLOBAL_RESTRICTION)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE,
+                                true));
+
+        DialogTestListItem disableNonMarketPrimaryUserDeviceWideTest = new DialogTestListItem(
+                this,
+                R.string.provisioning_byod_nonmarket_deny_global_primary,
+                "BYOD_DisableNonMarketPrimaryUserDeviceWideTest",
+                R.string.provisioning_byod_nonmarket_deny_global_primary_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE,
+                                false));
+
+        DialogTestListItem enableNonMarketPrimaryUserDeviceWideTest = new DialogTestListItem(
+                this,
+                R.string.provisioning_byod_nonmarket_allow_global_primary,
+                "BYOD_EnableNonMarketPrimaryUserDeviceWideTest",
+                R.string.provisioning_byod_nonmarket_allow_global_primary_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK_PRIMARY_PROFILE_GLOBAL_RESTRICTION)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS_DEVICE_WIDE,
+                                true));
+
+        adapter.add(disableNonMarketTest);
+        adapter.add(enableNonMarketTest);
+        adapter.add(disableNonMarketWorkProfileDeviceWideTest);
+        adapter.add(enableNonMarketWorkProfileDeviceWideTest);
+        adapter.add(disableNonMarketPrimaryUserDeviceWideTest);
+        adapter.add(enableNonMarketPrimaryUserDeviceWideTest);
+    }
+
+    protected ComponentName getAdminComponent() {
+        return DeviceAdminTestReceiver.getReceiverComponentName();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/RecentsRedactionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/RecentsRedactionActivity.java
index a49985d..cce6df0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/RecentsRedactionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/RecentsRedactionActivity.java
@@ -23,6 +23,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.view.View;
 
 import com.android.cts.verifier.ArrayTestListAdapter;
@@ -102,7 +103,7 @@
                 /* name */ R.string.provisioning_byod_recents_verify_not_redacted,
                 /* testId */ "BYOD_recents_verify_not_redacted",
                 /* intruction */ R.string.provisioning_byod_recents_verify_not_redacted_instruction,
-                /* action */ new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD));
+                /* action */ new Intent(Settings.ACTION_SECURITY_SETTINGS));
 
         adapter.add(mVerifyRedacted);
         adapter.add(mVerifyNotRedacted);
@@ -134,7 +135,7 @@
                 .setTitle(R.string.provisioning_byod_recents)
                 .setMessage(R.string.provisioning_byod_recents_remove_password)
                 .setPositiveButton(android.R.string.ok, (DialogInterface dialog, int which) -> {
-                    startActivity(new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD));
+                    startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
                 })
                 .show();
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
index 372ae67..3037d98 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
@@ -39,6 +39,8 @@
     private static final String TAG = "CtsVerifierByodUtils";
     static final int BUGREPORT_NOTIFICATION_ID = 12345;
     private static final String CHANNEL_ID = "BugReport";
+    static final String FILE_PROVIDER_AUTHORITY =
+            "com.android.cts.verifier.managedprovisioning.fileprovider";
 
     static TestListItem createInteractiveTestItem(Activity activity, String id, int titleRes,
             int infoRes, ButtonInfo[] buttonInfos) {
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 d510320..a3de878 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,7 @@
 
 package com.android.cts.verifier.notifications;
 
-import com.android.cts.verifier.R;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
@@ -26,11 +26,14 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.provider.Settings;
+import android.service.notification.ZenPolicy;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.cts.verifier.R;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -65,7 +68,9 @@
             tests.add(new IsEnabledTest());
             tests.add(new ServiceStartedTest());
             tests.add(new CreateAutomaticZenRuleTest());
+            tests.add(new CreateAutomaticZenRuleWithZenPolicyTest());
             tests.add(new UpdateAutomaticZenRuleTest());
+            tests.add(new UpdateAutomaticZenRuleWithZenPolicyTest());
             tests.add(new GetAutomaticZenRuleTest());
             tests.add(new GetAutomaticZenRulesTest());
             tests.add(new SubscribeAutomaticZenRuleTest());
@@ -282,6 +287,45 @@
         }
     }
 
+    private class CreateAutomaticZenRuleWithZenPolicyTest extends InteractiveTestCase {
+        private String id = null;
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.cp_create_rule_with_zen_policy);
+        }
+
+        @Override
+        protected void test() {
+            AutomaticZenRule ruleToCreate =
+                    createRuleWithZenPolicy("Rule", "value",
+                            new ZenPolicy.Builder().allowReminders(true).build());
+            id = mNm.addAutomaticZenRule(ruleToCreate);
+
+            if (!TextUtils.isEmpty(id)) {
+                AutomaticZenRule rule = mNm.getAutomaticZenRule(id);
+                if (Objects.equals(ruleToCreate, rule)) {
+                    status = PASS;
+                } else {
+                    logFail("created rule doesn't equal actual rule");
+                    status = FAIL;
+                }
+            } else {
+                logFail("rule wasn't created");
+                status = FAIL;
+            }
+            next();
+        }
+
+        @Override
+        protected void tearDown() {
+            if (id != null) {
+                mNm.removeAutomaticZenRule(id);
+            }
+            MockConditionProvider.getInstance().resetData();
+        }
+    }
+
     private class UpdateAutomaticZenRuleTest extends InteractiveTestCase {
         private String id = null;
 
@@ -329,6 +373,89 @@
         }
     }
 
+    private class UpdateAutomaticZenRuleWithZenPolicyTest extends InteractiveTestCase {
+        private String id1 = null; // no zen policy
+        private String id2 = null; // has zen policy
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.cp_update_rule_use_zen_policy);
+        }
+
+        @Override
+        protected void setUp() {
+            // create rule without a zen policy
+            id1 = mNm.addAutomaticZenRule(createRule("BeforeUpdate1",
+                    "beforeValue1", NotificationManager.INTERRUPTION_FILTER_ALARMS));
+            id2 = mNm.addAutomaticZenRule(createRuleWithZenPolicy(
+                    "BeforeUpdate2", "beforeValue2",
+                    new ZenPolicy.Builder().allowReminders(true).build()));
+            status = READY;
+            delay();
+        }
+
+        @Override
+        protected void test() {
+            ZenPolicy.Builder builder = new ZenPolicy.Builder().allowAlarms(true);
+
+            // update rule with zen policy
+            AutomaticZenRule updated1 = mNm.getAutomaticZenRule(id1);
+            updated1.setName("AfterUpdate1");
+            updated1.setConditionId(MockConditionProvider.toConditionId("afterValue1"));
+            updated1.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            updated1.setZenPolicy(builder.build());
+
+            AutomaticZenRule updated2 = mNm.getAutomaticZenRule(id2);
+            updated2.setName("AfterUpdate2");
+            updated2.setConditionId(MockConditionProvider.toConditionId("afterValue2"));
+            updated2.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            updated2.setZenPolicy(builder.build());
+
+            try {
+                boolean success1 = mNm.updateAutomaticZenRule(id1, updated1);
+                boolean success2 = mNm.updateAutomaticZenRule(id2, updated2);
+                if (success1 && success2) {
+                    boolean rule1UpdateSuccess =
+                            updated1.equals(mNm.getAutomaticZenRule(id1));
+                    boolean rule2UpdateSuccess =
+                            updated2.equals(mNm.getAutomaticZenRule(id2));
+                    if (rule1UpdateSuccess && rule2UpdateSuccess) {
+                        status = PASS;
+                    } else {
+                        if (!rule1UpdateSuccess) {
+                            logFail("Updated rule1 is not expected expected=" + updated1.toString()
+                                    + " actual=" + mNm.getAutomaticZenRule(id1));
+                        }
+                        if (!rule2UpdateSuccess) {
+                            logFail("Updated rule2 is not expected expected=" + updated2.toString()
+                                    + " actual=" + mNm.getAutomaticZenRule(id2));
+                        }
+                        status = FAIL;
+                    }
+                } else {
+                    logFail("Did not successfully update rules");
+                    status = FAIL;
+                }
+            } catch (Exception e) {
+                logFail("update failed", e);
+                status = FAIL;
+            }
+            next();
+        }
+
+        @Override
+        protected void tearDown() {
+            if (id1 != null) {
+                mNm.removeAutomaticZenRule(id1);
+            }
+
+            if (id2 != null) {
+                mNm.removeAutomaticZenRule(id2);
+            }
+            MockConditionProvider.getInstance().resetData();
+        }
+    }
+
     private class SubscribeAutomaticZenRuleTest extends InteractiveTestCase {
         private String id = null;
         private AutomaticZenRule ruleToCreate;
@@ -380,8 +507,10 @@
     }
 
     private class GetAutomaticZenRuleTest extends InteractiveTestCase {
-        private String id = null;
-        private AutomaticZenRule ruleToCreate;
+        private String id1 = null;
+        private String id2 = null;
+        private AutomaticZenRule ruleToCreate1; // no zen policy
+        private AutomaticZenRule ruleToCreate2; // has zen policy
 
         @Override
         protected View inflate(ViewGroup parent) {
@@ -390,21 +519,32 @@
 
         @Override
         protected void setUp() {
-            ruleToCreate = createRule("RuleGet", "valueGet",
+            ruleToCreate1 = createRule("RuleGet no zen policy", "valueGet",
                     NotificationManager.INTERRUPTION_FILTER_ALARMS);
-            id = mNm.addAutomaticZenRule(ruleToCreate);
+            ruleToCreate2 = createRuleWithZenPolicy("RuleGet zen policy", "valueGet",
+                    new ZenPolicy.Builder().allowReminders(true).build());
+            id1 = mNm.addAutomaticZenRule(ruleToCreate1);
+            id2 = mNm.addAutomaticZenRule(ruleToCreate2);
             status = READY;
             delay();
         }
 
         @Override
         protected void test() {
-            AutomaticZenRule queriedRule = mNm.getAutomaticZenRule(id);
-            if (queriedRule != null
-                    && ruleToCreate.getName().equals(queriedRule.getName())
-                    && ruleToCreate.getOwner().equals(queriedRule.getOwner())
-                    && ruleToCreate.getConditionId().equals(queriedRule.getConditionId())
-                    && ruleToCreate.isEnabled() == queriedRule.isEnabled()) {
+            AutomaticZenRule queriedRule1 = mNm.getAutomaticZenRule(id1);
+            AutomaticZenRule queriedRule2 = mNm.getAutomaticZenRule(id2);
+            if (queriedRule1 != null
+                    && ruleToCreate1.getName().equals(queriedRule1.getName())
+                    && ruleToCreate1.getOwner().equals(queriedRule1.getOwner())
+                    && ruleToCreate1.getConditionId().equals(queriedRule1.getConditionId())
+                    && ruleToCreate1.isEnabled() == queriedRule1.isEnabled()
+                    && Objects.equals(ruleToCreate1.getZenPolicy(), queriedRule1.getZenPolicy())
+                    && queriedRule2 != null
+                    && ruleToCreate2.getName().equals(queriedRule2.getName())
+                    && ruleToCreate2.getOwner().equals(queriedRule2.getOwner())
+                    && ruleToCreate2.getConditionId().equals(queriedRule2.getConditionId())
+                    && ruleToCreate2.isEnabled() == queriedRule2.isEnabled()
+                    && Objects.equals(ruleToCreate2.getZenPolicy(), queriedRule2.getZenPolicy())) {
                 status = PASS;
             } else {
                 logFail();
@@ -415,8 +555,12 @@
 
         @Override
         protected void tearDown() {
-            if (id != null) {
-                mNm.removeAutomaticZenRule(id);
+            if (id1 != null) {
+                mNm.removeAutomaticZenRule(id1);
+            }
+
+            if (id2 != null) {
+                mNm.removeAutomaticZenRule(id2);
             }
             MockConditionProvider.getInstance().resetData();
         }
@@ -424,8 +568,8 @@
 
     private class GetAutomaticZenRulesTest extends InteractiveTestCase {
         private List<String> ids = new ArrayList<>();
-        private AutomaticZenRule rule1;
-        private AutomaticZenRule rule2;
+        private AutomaticZenRule rule1; // no ZenPolicy
+        private AutomaticZenRule rule2; // has ZenPolicy
 
         @Override
         protected View inflate(ViewGroup parent) {
@@ -434,8 +578,10 @@
 
         @Override
         protected void setUp() {
-            rule1 = createRule("Rule1", "value1", NotificationManager.INTERRUPTION_FILTER_ALARMS);
-            rule2 = createRule("Rule2", "value2", NotificationManager.INTERRUPTION_FILTER_NONE);
+            rule1 = createRule("Rule without ZenPolicy", "value1",
+                    NotificationManager.INTERRUPTION_FILTER_ALARMS);
+            rule2 = createRuleWithZenPolicy("Rule with ZenPolicy", "value2",
+                    new ZenPolicy.Builder().allowReminders(true).build());
             ids.add(mNm.addAutomaticZenRule(rule1));
             ids.add(mNm.addAutomaticZenRule(rule2));
             status = READY;
@@ -474,7 +620,8 @@
     }
 
     private class DeleteAutomaticZenRuleTest extends InteractiveTestCase {
-        private String id = null;
+        private String id1 = null;
+        private String id2 = null;
 
         @Override
         protected View inflate(ViewGroup parent) {
@@ -483,13 +630,17 @@
 
         @Override
         protected void test() {
-            AutomaticZenRule ruleToCreate = createRule("RuleDelete", "Deletevalue",
-                    NotificationManager.INTERRUPTION_FILTER_ALARMS);
-            id = mNm.addAutomaticZenRule(ruleToCreate);
+            AutomaticZenRule ruleToCreate1 = createRule("RuleDelete without ZenPolicy",
+                    "Deletevalue", NotificationManager.INTERRUPTION_FILTER_ALARMS);
+            AutomaticZenRule ruleToCreate2 = createRule("RuleDelete with ZenPolicy",
+                    "Deletevalue", NotificationManager.INTERRUPTION_FILTER_ALARMS);
+            id1 = mNm.addAutomaticZenRule(ruleToCreate1);
+            id2 = mNm.addAutomaticZenRule(ruleToCreate2);
 
-            if (id != null) {
-                if (mNm.removeAutomaticZenRule(id)) {
-                    if (mNm.getAutomaticZenRule(id) == null) {
+            if (id1 != null && id2 != null) {
+                if (mNm.removeAutomaticZenRule(id1) && mNm.removeAutomaticZenRule(id2)) {
+                    if (mNm.getAutomaticZenRule(id1) == null
+                            && mNm.getAutomaticZenRule(id2) == null) {
                         status = PASS;
                     } else {
                         logFail();
@@ -528,7 +679,7 @@
         @Override
         protected void setUp() {
             ruleToCreate = createRule("RuleUnsubscribe", "valueUnsubscribe",
-                    NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+                    INTERRUPTION_FILTER_PRIORITY);
             id = mNm.addAutomaticZenRule(ruleToCreate);
             status = READY;
             delay();
@@ -648,9 +799,15 @@
     private AutomaticZenRule createRule(String name, String queryValue, int status) {
         return new AutomaticZenRule(name,
                 ComponentName.unflattenFromString(CP_PATH),
-                MockConditionProvider.toConditionId(queryValue),
-                status,
-                true);
+                MockConditionProvider.toConditionId(queryValue), status, true);
+    }
+
+    private AutomaticZenRule createRuleWithZenPolicy(String name, String queryValue,
+            ZenPolicy policy) {
+        return new AutomaticZenRule(name,
+                ComponentName.unflattenFromString(CP_PATH), null,
+                MockConditionProvider.toConditionId(queryValue), policy,
+                INTERRUPTION_FILTER_PRIORITY, true);
     }
 
     private boolean compareRules(AutomaticZenRule rule1, AutomaticZenRule rule2) {
@@ -658,7 +815,8 @@
                 && Objects.equals(rule1.getName(), rule2.getName())
                 && rule1.getInterruptionFilter() == rule2.getInterruptionFilter()
                 && Objects.equals(rule1.getConditionId(), rule2.getConditionId())
-                && Objects.equals(rule1.getOwner(), rule2.getOwner());
+                && Objects.equals(rule1.getOwner(), rule2.getOwner())
+                && Objects.equals(rule1.getZenPolicy(), rule2.getZenPolicy());
     }
 
     protected View createSettingsItem(ViewGroup parent, int messageId) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index 5dfee12..12e2b93 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -49,6 +49,7 @@
     public static final String JSON_MATCHES_ZEN_FILTER = "matches_zen_filter";
     public static final String JSON_REASON = "reason";
     public static final String JSON_STATS = "stats";
+    public static final String JSON_LAST_AUDIBLY_ALERTED = "last_audibly_alerted";
 
     ArrayList<String> mPosted = new ArrayList<String>();
     ArrayMap<String, JSONObject> mNotifications = new ArrayMap<>();
@@ -136,6 +137,7 @@
                     note.put(JSON_RANK, rank.getRank());
                     note.put(JSON_AMBIENT, rank.isAmbient());
                     note.put(JSON_MATCHES_ZEN_FILTER, rank.matchesInterruptionFilter());
+                    note.put(JSON_LAST_AUDIBLY_ALERTED, rank.getLastAudiblyAlertedMillis());
                 } catch (JSONException e) {
                     Log.e(TAG, "failed to pack up notification payload", e);
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 6a3678d..7db3d0d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -17,6 +17,7 @@
 package com.android.cts.verifier.notifications;
 
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
 import static android.provider.Settings.EXTRA_APP_PACKAGE;
@@ -25,6 +26,7 @@
 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
 import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
+import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED;
 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON;
 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS;
@@ -34,24 +36,23 @@
 
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
-import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.notification.StatusBarNotification;
-import androidx.core.app.NotificationCompat;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
 
+import androidx.core.app.NotificationCompat;
+
 import com.android.cts.verifier.R;
 
 import org.json.JSONException;
@@ -67,21 +68,26 @@
         implements Runnable {
     private static final String TAG = "NoListenerVerifier";
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
+    private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "Noisy";
     protected static final String PREFS = "listener_prefs";
     final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications()
 
     private String mTag1;
     private String mTag2;
     private String mTag3;
+    private String mTag4;
     private int mIcon1;
     private int mIcon2;
     private int mIcon3;
+    private int mIcon4;
     private int mId1;
     private int mId2;
     private int mId3;
+    private int mId4;
     private long mWhen1;
     private long mWhen2;
     private long mWhen3;
+    private long mWhen4;
     private int mFlag1;
     private int mFlag2;
     private int mFlag3;
@@ -111,6 +117,7 @@
             tests.add(new ServiceStartedTest());
             tests.add(new NotificationReceivedTest());
             tests.add(new DataIntactTest());
+            tests.add(new AudiblyAlertedTest());
             tests.add(new DismissOneTest());
             tests.add(new DismissOneWithReasonTest());
             tests.add(new DismissOneWithStatsTest());
@@ -134,14 +141,19 @@
         return tests;
     }
 
-    private void createChannel() {
+    private void createChannels() {
         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
                 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
+        NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID,
+                NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX);
+        noisyChannel.setVibrationPattern(new long[]{100, 0, 100});
         mNm.createNotificationChannel(channel);
+        mNm.createNotificationChannel(noisyChannel);
     }
 
-    private void deleteChannel() {
+    private void deleteChannels() {
         mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID);
     }
 
     @SuppressLint("NewApi")
@@ -202,6 +214,26 @@
         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
     }
 
+    private void sendNoisyNotification() {
+        mTag4 = UUID.randomUUID().toString();
+        Log.d(TAG, "Sending " + mTag4);
+
+        mWhen4 = System.currentTimeMillis() + 4;
+        mIcon4 = R.drawable.ic_stat_charlie;
+        mId4 = NOTIFICATION_ID + 4;
+        mPackageString = "com.android.cts.verifier";
+
+        Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("NoisyTest 1")
+                .setContentText(mTag4)
+                .setSmallIcon(mIcon4)
+                .setWhen(mWhen4)
+                .setDeleteIntent(makeIntent(4, mTag4))
+                .setCategory(Notification.CATEGORY_REMINDER)
+                .build();
+        mNm.notify(mTag4, mId4, n1);
+    }
+
     // Tests
     private class NotificationReceivedTest extends InteractiveTestCase {
         @Override
@@ -212,7 +244,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -221,7 +253,7 @@
         protected void tearDown() {
             mNm.cancelAll();
             MockListener.getInstance().resetData();
-            deleteChannel();
+            deleteChannels();
         }
 
         @Override
@@ -538,7 +570,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -604,7 +636,72 @@
         protected void tearDown() {
             mNm.cancelAll();
             MockListener.getInstance().resetData();
-            deleteChannel();
+            deleteChannels();
+        }
+    }
+
+    private class AudiblyAlertedTest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_audibly_alerted);
+        }
+
+        @Override
+        protected void setUp() {
+            createChannels();
+            sendNotifications();
+            sendNoisyNotification();
+            status = READY;
+        }
+
+        @Override
+        protected void test() {
+            List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+            Set<String> found = new HashSet<>();
+            if (result.size() == 0) {
+                status = FAIL;
+                return;
+            }
+            boolean pass = true;
+            for (JSONObject payload : result) {
+                try {
+                    String tag = payload.getString(JSON_TAG);
+                    if (mTag4.equals(tag)) {
+                        found.add(mTag4);
+                        boolean lastAudiblyAlertedSet
+                                = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
+                        if (!lastAudiblyAlertedSet) {
+                            logWithStack(
+                                    "noisy notification test: getLastAudiblyAlertedMillis not set");
+                        }
+                        pass &= lastAudiblyAlertedSet;
+                    } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) {
+                        found.add(tag);
+                        boolean lastAudiblyAlertedSet
+                                = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
+                        if (lastAudiblyAlertedSet) {
+                            logWithStack(
+                                    "noisy notification test: getLastAudiblyAlertedMillis set "
+                                            + "incorrectly");
+                        }
+                        pass &= !lastAudiblyAlertedSet;
+                    }
+                } catch (JSONException e) {
+                    pass = false;
+                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                }
+            }
+
+            pass &= found.size() >= 4;
+            status = pass ? PASS : FAIL;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            MockListener.getInstance().resetData();
+            deleteChannels();
         }
     }
 
@@ -616,7 +713,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -644,7 +741,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
         }
     }
@@ -659,7 +756,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -705,7 +802,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
         }
     }
@@ -720,7 +817,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -760,7 +857,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
         }
     }
@@ -773,7 +870,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -800,7 +897,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
         }
     }
@@ -875,7 +972,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
         }
@@ -899,7 +996,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             if (MockListener.getInstance() != null) {
                 MockListener.getInstance().resetData();
             }
@@ -1015,7 +1112,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
             state = READY_TO_SNOOZE;
@@ -1070,7 +1167,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
             delay();
         }
@@ -1096,7 +1193,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             tag = mTag1;
             status = READY;
@@ -1146,7 +1243,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
         }
     }
@@ -1165,7 +1262,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendNotifications();
             status = READY;
             state = READY_TO_SNOOZE;
@@ -1230,7 +1327,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             MockListener.getInstance().resetData();
             delay();
         }
@@ -1250,7 +1347,7 @@
 
         @Override
         protected void setUp() {
-            createChannel();
+            createChannels();
             sendMessagingNotification();
             status = READY;
         }
@@ -1258,7 +1355,7 @@
         @Override
         protected void tearDown() {
             mNm.cancelAll();
-            deleteChannel();
+            deleteChannels();
             delay();
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfig2gBandTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfig2gBandTestActivity.java
new file mode 100644
index 0000000..2b4e4f0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfig2gBandTestActivity.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.p2p;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.GoWithConfigTestCase;
+import com.android.cts.verifier.p2p.testcase.TestCase;
+
+/**
+ * Test activity that accepts a connection from p2p client.
+ */
+public class GoWithConfig2gBandTestActivity extends ResponderTestActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_accept_client,
+                R.string.p2p_accept_client_info, -1);
+    }
+
+    @Override
+    protected TestCase getTestCase(Context context) {
+        return new GoWithConfigTestCase(context, WifiP2pConfig.GROUP_OWNER_BAND_2GHZ);
+    }
+
+    @Override
+    protected int getReadyMsgId() {
+        return R.string.p2p_go_ready;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigFixedFrequencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigFixedFrequencyTestActivity.java
new file mode 100644
index 0000000..bec4994
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigFixedFrequencyTestActivity.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.verifier.p2p;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.GoWithConfigTestCase;
+import com.android.cts.verifier.p2p.testcase.TestCase;
+
+/**
+ * Test activity that accepts a connection from p2p client.
+ */
+public class GoWithConfigFixedFrequencyTestActivity extends ResponderTestActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_accept_client,
+                R.string.p2p_accept_client_info, -1);
+    }
+
+    @Override
+    protected TestCase getTestCase(Context context) {
+        return new GoWithConfigTestCase(context, 2447);
+    }
+
+    @Override
+    protected int getReadyMsgId() {
+        return R.string.p2p_go_ready;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigTestActivity.java
new file mode 100644
index 0000000..aa6a7fe
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/GoWithConfigTestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.verifier.p2p;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.GoWithConfigTestCase;
+import com.android.cts.verifier.p2p.testcase.TestCase;
+
+/**
+ * Test activity that accepts a connection from p2p client.
+ */
+public class GoWithConfigTestActivity extends ResponderTestActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_accept_client,
+                R.string.p2p_accept_client_info, -1);
+    }
+
+    @Override
+    protected TestCase getTestCase(Context context) {
+        return new GoWithConfigTestCase(context);
+    }
+
+    @Override
+    protected int getReadyMsgId() {
+        return R.string.p2p_go_ready;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/OWNERS
new file mode 100644
index 0000000..008649d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/OWNERS
@@ -0,0 +1,3 @@
+etancohen@google.com
+mplass@google.com
+satk@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestActivity.java
new file mode 100644
index 0000000..08d942d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.p2p;
+
+import android.content.Context;
+
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfig2gBandTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Test activity that tries to join an existing p2p group with config.
+ * This activity is invoked from JoinTestListActivity.
+ */
+public class P2pClientWithConfig2gBandTestActivity extends RequesterTestActivity {
+
+    /**
+     * Do not need peer discovery first.
+     * For joining a group with config, this device finds the peer by
+     * Network Name, says SSID in specified band or frequency.
+     */
+    @Override
+    protected boolean isNoPeerDiscoveryOnResume() {
+        return true;
+    }
+
+    @Override
+    protected ReqTestCase getTestCase(Context context, String testId) {
+        return P2pClientWithConfig2gBandTestSuite.getTestCase(context, testId);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestListActivity.java
new file mode 100644
index 0000000..29fc2f8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfig2gBandTestListActivity.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 com.android.cts.verifier.p2p;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfig2gBandTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Activity that lists all the joining group owner with config tests.
+ */
+public class P2pClientWithConfig2gBandTestListActivity extends RequesterTestListActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_join_go,
+                R.string.p2p_join_go_info, -1);
+    }
+
+    @Override
+    protected ArrayList<ReqTestCase> getTestSuite(Context context) {
+        return P2pClientWithConfig2gBandTestSuite.getTestSuite(context);
+    }
+
+    @Override
+    protected Class<?> getRequesterActivityClass() {
+        return P2pClientWithConfig2gBandTestActivity.class;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestActivity.java
new file mode 100644
index 0000000..57b3d74
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.p2p;
+
+import android.content.Context;
+
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfigFixedFrequencyTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Test activity that tries to join an existing p2p group with config.
+ * This activity is invoked from JoinTestListActivity.
+ */
+public class P2pClientWithConfigFixedFrequencyTestActivity extends RequesterTestActivity {
+
+    /**
+     * Do not need peer discovery first.
+     * For joining a group with config, this device finds the peer by
+     * Network Name, says SSID in specified band or frequency.
+     */
+    @Override
+    protected boolean isNoPeerDiscoveryOnResume() {
+        return true;
+    }
+
+    @Override
+    protected ReqTestCase getTestCase(Context context, String testId) {
+        return P2pClientWithConfigFixedFrequencyTestSuite.getTestCase(context, testId);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestListActivity.java
new file mode 100644
index 0000000..db0943f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigFixedFrequencyTestListActivity.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 com.android.cts.verifier.p2p;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfigFixedFrequencyTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Activity that lists all the joining group owner with config tests.
+ */
+public class P2pClientWithConfigFixedFrequencyTestListActivity extends RequesterTestListActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_join_go,
+                R.string.p2p_join_go_info, -1);
+    }
+
+    @Override
+    protected ArrayList<ReqTestCase> getTestSuite(Context context) {
+        return P2pClientWithConfigFixedFrequencyTestSuite.getTestSuite(context);
+    }
+
+    @Override
+    protected Class<?> getRequesterActivityClass() {
+        return P2pClientWithConfigFixedFrequencyTestActivity.class;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestActivity.java
new file mode 100644
index 0000000..e782a13
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestActivity.java
@@ -0,0 +1,43 @@
+/*
+ * 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.verifier.p2p;
+
+import android.content.Context;
+
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfigTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Test activity that tries to join an existing p2p group with config.
+ * This activity is invoked from JoinTestListActivity.
+ */
+public class P2pClientWithConfigTestActivity extends RequesterTestActivity {
+
+    /**
+     * Do not need to select a peer first.
+     * For joining a group with config, this device finds the peer by
+     * Network Name, says SSID, and do not need to select a peer first.
+     */
+    @Override
+    protected boolean isSearchOnlyOnResume() {
+        return true;
+    }
+
+    @Override
+    protected ReqTestCase getTestCase(Context context, String testId) {
+        return P2pClientWithConfigTestSuite.getTestCase(context, testId);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestListActivity.java
new file mode 100644
index 0000000..a3961d6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pClientWithConfigTestListActivity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.verifier.p2p;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.p2p.testcase.P2pClientWithConfigTestSuite;
+import com.android.cts.verifier.p2p.testcase.ReqTestCase;
+
+/**
+ * Activity that lists all the joining group owner with config tests.
+ */
+public class P2pClientWithConfigTestListActivity extends RequesterTestListActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.p2p_join_go,
+                R.string.p2p_join_go_info, -1);
+    }
+
+    @Override
+    protected ArrayList<ReqTestCase> getTestSuite(Context context) {
+        return P2pClientWithConfigTestSuite.getTestSuite(context);
+    }
+
+    @Override
+    protected Class<?> getRequesterActivityClass() {
+        return P2pClientWithConfigTestActivity.class;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pTestListActivity.java
index 5985be6..2e7ed74 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/P2pTestListActivity.java
@@ -87,6 +87,34 @@
                 P2pClientTestListActivity.class.getName(),
                 new Intent(this, P2pClientTestListActivity.class), null));
 
+        adapter.add(TestListItem.newCategory(this, R.string.p2p_join_with_config));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_owner_with_config_test,
+                GoWithConfigTestActivity.class.getName(),
+                new Intent(this, GoWithConfigTestActivity.class), null));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_client_with_config_test,
+                P2pClientWithConfigTestListActivity.class.getName(),
+                new Intent(this, P2pClientWithConfigTestListActivity.class), null));
+        adapter.add(TestListItem.newCategory(this, R.string.p2p_join_with_config_2g_band));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_owner_with_config_2g_band_test,
+                GoWithConfig2gBandTestActivity.class.getName(),
+                new Intent(this, GoWithConfig2gBandTestActivity.class), null));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_client_with_config_2g_band_test,
+                P2pClientWithConfig2gBandTestListActivity.class.getName(),
+                new Intent(this, P2pClientWithConfig2gBandTestListActivity.class), null));
+        adapter.add(TestListItem.newCategory(this, R.string.p2p_join_with_config_fixed_frequency));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_owner_with_config_fixed_frequency_test,
+                GoWithConfigFixedFrequencyTestActivity.class.getName(),
+                new Intent(this, GoWithConfigFixedFrequencyTestActivity.class), null));
+        adapter.add(TestListItem.newTest(this,
+                R.string.p2p_group_client_with_config_fixed_frequency_test,
+                P2pClientWithConfigFixedFrequencyTestListActivity.class.getName(),
+                new Intent(this, P2pClientWithConfigFixedFrequencyTestListActivity.class), null));
+
         adapter.add(TestListItem.newCategory(this, R.string.p2p_service_discovery));
         adapter.add(TestListItem.newTest(this,
                 R.string.p2p_service_discovery_responder_test,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
index aa24d55..542fb36 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
@@ -142,7 +142,7 @@
          * the target device list on the dialog.
          * After the user selection, the specified test will be executed.
          */
-        if (sTargetAddr == null) {
+        if (sTargetAddr == null && !isNoPeerDiscoveryOnResume()) {
             searchTarget();
             return;
         }
@@ -212,6 +212,21 @@
     }
 
     /**
+     * Do peer searching only, don't do peer selection.
+     * For requester test which do not need to select a peer first.
+     */
+    protected boolean isSearchOnlyOnResume() {
+        return false;
+    }
+
+    /** Do peer discovery or not
+     *  For requester test which do not need to discover peer first.
+     */
+    protected boolean isNoPeerDiscoveryOnResume() {
+        return false;
+    }
+
+    /**
      * Search devices and show the found devices on the dialog.
      * After user selection, the specified test will be executed.
      */
@@ -245,7 +260,11 @@
                                     mTextView.setText(
                                             R.string.p2p_target_not_found_error);
                                 } else {
-                                    showSelectTargetDialog(peers);
+                                    if (isSearchOnlyOnResume()) {
+                                        mTestCase.start(getTestCaseListener());
+                                    } else {
+                                        showSelectTargetDialog(peers);
+                                    }
                                 }
                             }
                         });
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/ConnectReqTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/ConnectReqTestCase.java
index 95c8d09..20ce5ac 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/ConnectReqTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/ConnectReqTestCase.java
@@ -20,6 +20,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.net.wifi.p2p.WifiP2pManager;
 
@@ -178,4 +179,88 @@
 
         return true;
     }
+
+    /**
+     * Tries to connect the target devices with config.
+     * @param config config for connecting a group.
+     * @return true if succeeded.
+     * @throws InterruptedException
+     */
+    protected boolean connectTest(WifiP2pConfig config) throws InterruptedException {
+        notifyTestMsg(R.string.p2p_searching_target);
+
+        /*
+         * Search target device and check its capability.
+         */
+        ActionListenerTest actionListener = new ActionListenerTest();
+        mP2pMgr.discoverPeers(mChannel, actionListener);
+        if (!actionListener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_discover_peers_error);
+            return false;
+        }
+
+        /*
+         * Try to connect the target device.
+         */
+        mP2pMgr.connect(mChannel, config, actionListener);
+        if (!actionListener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_connect_error);
+            return false;
+        }
+
+        /*
+         * Check if the connection broadcast is received.
+         */
+        notifyTestMsg(R.string.p2p_waiting_for_peer_to_connect);
+        WifiP2pInfo p2pInfo = mReceiverTest.waitConnectionNotice(TIMEOUT_FOR_USER_ACTION);
+        if (p2pInfo == null) {
+            mReason = mContext.getString(R.string.p2p_connection_error);
+            return false;
+        }
+
+        /*
+         * target MAC address is known until group is formed
+         */
+        WifiP2pGroup group = mReceiverTest.getWifiP2pGroup();
+        if (group != null) {
+            if (!group.isGroupOwner()) {
+                setTargetAddress(group.getOwner().deviceAddress);
+            } else {
+                mReason = mContext.getString(R.string.p2p_connection_error);
+                return false;
+            }
+        } else {
+            mReason = mContext.getString(R.string.p2p_connection_error);
+            return false;
+        }
+
+        /*
+         * Remove the p2p group manualy.
+         */
+        mP2pMgr.removeGroup(mChannel, actionListener);
+        if (!actionListener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_remove_group_error);
+            return false;
+        }
+
+        notifyTestMsg(R.string.p2p_waiting_for_peer_to_disconnect);
+
+        /*
+         * Check if p2p disconnection broadcast is received
+         */
+        p2pInfo = mReceiverTest.waitDisconnectionNotice(TIMEOUT);
+        if (p2pInfo == null) {
+            mReason = mContext.getString(R.string.p2p_connection_error);
+            return false;
+        }
+
+        /* Wait until peer gets marked disconnected */
+
+        if (mReceiverTest.waitPeerDisconnected(mTargetAddress, TIMEOUT) != true) {
+            mReason = mContext.getString(R.string.p2p_detect_disconnection_error);
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/GoWithConfigTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/GoWithConfigTestCase.java
new file mode 100644
index 0000000..5004e36
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/GoWithConfigTestCase.java
@@ -0,0 +1,167 @@
+/*
+ * 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.verifier.p2p.testcase;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pInfo;
+
+import com.android.cts.verifier.R;
+
+/**
+ * A test case which accepts a connection from p2p client.
+ *
+ * The requester device tries to join this device.
+ */
+public class GoWithConfigTestCase extends TestCase {
+
+    protected P2pBroadcastReceiverTest mReceiverTest;
+
+    private int mGroupOperatingBand = WifiP2pConfig.GROUP_OWNER_BAND_AUTO;
+    private int mGroupOperatingFrequency = 0;
+    private String mNetworkName = "DIRECT-XY-HELLO";
+
+    public GoWithConfigTestCase(Context context) {
+        super(context);
+    }
+
+    public GoWithConfigTestCase(Context context, int bandOrFrequency) {
+        super(context);
+        StringBuilder builder = new StringBuilder(mNetworkName);
+        switch(bandOrFrequency) {
+            case WifiP2pConfig.GROUP_OWNER_BAND_AUTO:
+                break;
+            case WifiP2pConfig.GROUP_OWNER_BAND_2GHZ:
+                builder.append("-2.4G");
+                mGroupOperatingBand = bandOrFrequency;
+                break;
+            case WifiP2pConfig.GROUP_OWNER_BAND_5GHZ:
+                builder.append("-5G");
+                mGroupOperatingBand = bandOrFrequency;
+                break;
+            default:
+                builder.append("-" + bandOrFrequency + "MHz");
+                mGroupOperatingFrequency = bandOrFrequency;
+        }
+        mNetworkName = builder.toString();
+    }
+
+    @Override
+    protected void setUp() {
+        super.setUp();
+        mReceiverTest = new P2pBroadcastReceiverTest(mContext);
+        mReceiverTest.init(mChannel);
+    }
+
+    @Override
+    protected boolean executeTest() throws InterruptedException {
+
+        ActionListenerTest listener = new ActionListenerTest();
+
+        /*
+         * Add renderer service
+         */
+        mP2pMgr.addLocalService(mChannel, LocalServices.createRendererService(),
+                listener);
+        if (!listener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_add_local_service_error);
+            return false;
+        }
+
+        /*
+         * Add IPP service
+         */
+        mP2pMgr.addLocalService(mChannel, LocalServices.createIppService(),
+                listener);
+        if (!listener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_add_local_service_error);
+            return false;
+        }
+
+        /*
+         * Add AFP service
+         */
+        mP2pMgr.addLocalService(mChannel, LocalServices.createAfpService(),
+                listener);
+        if (!listener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_add_local_service_error);
+            return false;
+        }
+
+        /*
+         * Start up an autonomous group owner.
+         */
+        WifiP2pConfig.Builder b = new WifiP2pConfig.Builder()
+                .setNetworkName(mNetworkName)
+                .setPassphrase("DEADBEEF");
+        if (mGroupOperatingBand > 0) {
+            b.setGroupOperatingBand(mGroupOperatingBand);
+        } else if (mGroupOperatingFrequency > 0) {
+            b.setGroupOperatingFrequency(mGroupOperatingFrequency);
+        }
+        WifiP2pConfig config = b.build();
+        mP2pMgr.createGroup(mChannel, config, listener);
+        if (!listener.check(ActionListenerTest.SUCCESS, TIMEOUT)) {
+            mReason = mContext.getString(R.string.p2p_ceate_group_error);
+            return false;
+        }
+
+        /*
+         * Check whether createGroup() is succeeded.
+         */
+        WifiP2pInfo info = mReceiverTest.waitConnectionNotice(TIMEOUT);
+        if (info == null || !info.isGroupOwner) {
+            mReason = mContext.getString(R.string.p2p_ceate_group_error);
+            return false;
+        }
+
+        // wait until p2p client is joining.
+        return true;
+    }
+
+    @Override
+    protected void tearDown() {
+
+        // wait until p2p client is joining.
+        synchronized (this) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        if (mP2pMgr != null) {
+            mP2pMgr.cancelConnect(mChannel, null);
+            mP2pMgr.removeGroup(mChannel, null);
+        }
+        if (mReceiverTest != null) {
+            mReceiverTest.close();
+        }
+        super.tearDown();
+    }
+
+    @Override
+    public String getTestName() {
+        String testName = "Accept client connection test";
+        if (mGroupOperatingBand > 0) {
+            testName += " with " + mGroupOperatingBand + "G band";
+        } else if (mGroupOperatingFrequency > 0) {
+            testName += " with " + mGroupOperatingFrequency + " MHz";
+        }
+        return testName;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pBroadcastReceiverTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pBroadcastReceiverTest.java
index 14c3ddb..5cc23f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pBroadcastReceiverTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pBroadcastReceiverTest.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.net.wifi.p2p.WifiP2pDevice;
 import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.net.wifi.p2p.WifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager.Channel;
@@ -43,6 +44,7 @@
 
     private WifiP2pDeviceList mPeers;
     private WifiP2pInfo mP2pInfo;
+    private WifiP2pGroup mP2pGroup;
 
     public P2pBroadcastReceiverTest(Context context) {
         this.mContext = context;
@@ -205,6 +207,8 @@
             synchronized(this) {
                 mP2pInfo = (WifiP2pInfo)intent.getParcelableExtra(
                         WifiP2pManager.EXTRA_WIFI_P2P_INFO);
+                mP2pGroup = (WifiP2pGroup) intent.getParcelableExtra(
+                        WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
                 notifyAll();
             }
         }
@@ -216,4 +220,8 @@
         mPeers = peers;
         notifyAll();
     }
+
+    public synchronized WifiP2pGroup getWifiP2pGroup() {
+        return mP2pGroup;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfig2gBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfig2gBandTestCase.java
new file mode 100644
index 0000000..b24b17a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfig2gBandTestCase.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.p2p.testcase;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Test case to join a p2p group with 2G band config.
+ */
+public class P2pClientConfig2gBandTestCase extends P2pClientConfigTestCase {
+
+    public P2pClientConfig2gBandTestCase(Context context) {
+        super(context, WifiP2pConfig.GROUP_OWNER_BAND_2GHZ);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigFixedFrequencyTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigFixedFrequencyTestCase.java
new file mode 100644
index 0000000..90336ad
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigFixedFrequencyTestCase.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.p2p.testcase;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Test case to join a p2p group with fixed frequency config.
+ */
+public class P2pClientConfigFixedFrequencyTestCase extends P2pClientConfigTestCase {
+
+    public P2pClientConfigFixedFrequencyTestCase(Context context, int freq) {
+        super(context, freq);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigTestCase.java
new file mode 100644
index 0000000..2aa6a4b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientConfigTestCase.java
@@ -0,0 +1,103 @@
+/*
+ * 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.verifier.p2p.testcase;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Test case to join a p2p group with wps push button.
+ */
+public class P2pClientConfigTestCase extends ConnectReqTestCase {
+
+    private int mGroupOperatingBand = WifiP2pConfig.GROUP_OWNER_BAND_AUTO;
+    private int mGroupOperatingFrequency = 0;
+    private String mNetworkName = "DIRECT-XY-HELLO";
+
+    public P2pClientConfigTestCase(Context context) {
+        super(context);
+    }
+
+    public P2pClientConfigTestCase(Context context, int bandOrFrequency) {
+        super(context);
+        StringBuilder builder = new StringBuilder(mNetworkName);
+        switch(bandOrFrequency) {
+            case WifiP2pConfig.GROUP_OWNER_BAND_AUTO:
+                break;
+            case WifiP2pConfig.GROUP_OWNER_BAND_2GHZ:
+                builder.append("-2.4G");
+                mGroupOperatingBand = bandOrFrequency;
+                break;
+            case WifiP2pConfig.GROUP_OWNER_BAND_5GHZ:
+                builder.append("-5G");
+                mGroupOperatingBand = bandOrFrequency;
+                break;
+            default:
+                builder.append("-" + bandOrFrequency + "MHz");
+                mGroupOperatingFrequency = bandOrFrequency;
+        }
+        mNetworkName = builder.toString();
+    }
+
+    @Override
+    protected boolean executeTest() throws InterruptedException {
+
+        WifiP2pConfig.Builder b = new WifiP2pConfig.Builder()
+                .setNetworkName(mNetworkName)
+                .setPassphrase("DEADBEEF");
+
+        if (mGroupOperatingBand > 0) {
+            b.setGroupOperatingBand(mGroupOperatingBand);
+        } else if (mGroupOperatingFrequency > 0) {
+            b.setGroupOperatingFrequency(mGroupOperatingFrequency);
+        }
+
+        WifiP2pConfig config = b.build();
+
+        return connectTest(config);
+    }
+
+    private String getListenerError(ListenerTest listener) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(mContext.getText(R.string.p2p_receive_invalid_response_error));
+        sb.append(listener.getReason());
+        return sb.toString();
+    }
+
+    @Override
+    public String getTestName() {
+        if (mGroupOperatingBand > 0) {
+            String bandName = "";
+            switch (mGroupOperatingBand) {
+                case WifiP2pConfig.GROUP_OWNER_BAND_2GHZ:
+                    bandName = "2.4G";
+                    break;
+                case WifiP2pConfig.GROUP_OWNER_BAND_5GHZ:
+                    bandName = "5G";
+                    break;
+            }
+            return "Join p2p group test (" + bandName + " config)";
+        } else if (mGroupOperatingFrequency > 0) {
+            String freqName = mGroupOperatingFrequency + " MHz";
+            return "Join p2p group test (" + freqName + " config)";
+        } else {
+            return "Join p2p group test (config)";
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfig2gBandTestSuite.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfig2gBandTestSuite.java
new file mode 100644
index 0000000..221cd7d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfig2gBandTestSuite.java
@@ -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.
+ */
+
+package com.android.cts.verifier.p2p.testcase;
+
+import android.content.Context;
+import android.net.wifi.p2p.WifiP2pConfig;
+
+import java.util.ArrayList;
+
+/**
+ * Test suite to join a p2p group with 2G band config.
+ */
+public class P2pClientWithConfig2gBandTestSuite {
+
+    private static ArrayList<ReqTestCase> sTestSuite = null;
+
+    /**
+     * Return test suite.
+     * @param context
+     * @return
+     */
+    public static ArrayList<ReqTestCase> getTestSuite(Context context) {
+        initialize(context);
+        return sTestSuite;
+    }
+
+    /**
+     * Return the specified test case.
+     * @param context
+     * @param testId
+     * @return
+     */
+    public static ReqTestCase getTestCase(Context context,
+            String testId) {
+        initialize(context);
+
+        for (ReqTestCase test: sTestSuite) {
+            if (test.getTestId().equals(testId)) {
+                return test;
+            }
+        }
+        return null;
+    }
+
+    private static void initialize(Context context) {
+        if (sTestSuite != null) {
+            return;
+        }
+
+        sTestSuite = new ArrayList<ReqTestCase>();
+        sTestSuite.add(new P2pClientConfig2gBandTestCase(context));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigFixedFrequencyTestSuite.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigFixedFrequencyTestSuite.java
new file mode 100644
index 0000000..35638fa
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigFixedFrequencyTestSuite.java
@@ -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.
+ */
+
+package com.android.cts.verifier.p2p.testcase;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+/**
+ * Test suite to join a p2p group with fixed frequency config.
+ */
+public class P2pClientWithConfigFixedFrequencyTestSuite {
+
+    private static ArrayList<ReqTestCase> sTestSuite = null;
+
+    /**
+     * Return test suite.
+     * @param context
+     * @return
+     */
+    public static ArrayList<ReqTestCase> getTestSuite(Context context) {
+        initialize(context);
+        return sTestSuite;
+    }
+
+    /**
+     * Return the specified test case.
+     * @param context
+     * @param testId
+     * @return
+     */
+    public static ReqTestCase getTestCase(Context context,
+            String testId) {
+        initialize(context);
+
+        for (ReqTestCase test: sTestSuite) {
+            if (test.getTestId().equals(testId)) {
+                return test;
+            }
+        }
+        return null;
+    }
+
+    private static void initialize(Context context) {
+        if (sTestSuite != null) {
+            return;
+        }
+
+        sTestSuite = new ArrayList<ReqTestCase>();
+        sTestSuite.add(new P2pClientConfigFixedFrequencyTestCase(context, 2447));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigTestSuite.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigTestSuite.java
new file mode 100644
index 0000000..5e6ae0b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/testcase/P2pClientWithConfigTestSuite.java
@@ -0,0 +1,66 @@
+/*
+ * 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.verifier.p2p.testcase;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+/**
+ * Test suite to join a p2p group.
+ */
+public class P2pClientWithConfigTestSuite {
+
+    private static ArrayList<ReqTestCase> sTestSuite = null;
+
+    /**
+     * Return test suite.
+     * @param context
+     * @return
+     */
+    public static ArrayList<ReqTestCase> getTestSuite(Context context) {
+        initialize(context);
+        return sTestSuite;
+    }
+
+    /**
+     * Return the specified test case.
+     * @param context
+     * @param testId
+     * @return
+     */
+    public static ReqTestCase getTestCase(Context context,
+            String testId) {
+        initialize(context);
+
+        for (ReqTestCase test: sTestSuite) {
+            if (test.getTestId().equals(testId)) {
+                return test;
+            }
+        }
+        return null;
+    }
+
+    private static void initialize(Context context) {
+        if (sTestSuite != null) {
+            return;
+        }
+
+        sTestSuite = new ArrayList<ReqTestCase>();
+        sTestSuite.add(new P2pClientConfigTestCase(context));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
index a11eb82..d1a480d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
@@ -17,18 +17,31 @@
 package com.android.cts.verifier.security;
 
 import android.content.DialogInterface;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.widget.Button;
+
+import com.android.cts.verifier.R;
 
 import java.util.concurrent.Executor;
 
+/**
+ * Test for {@link BiometricPrompt}. This test runs twice, once with confirmation required,
+ * once without. Both tests use crypto objects.
+ */
 public class BiometricPromptBoundKeysTest extends FingerprintBoundKeysTest {
 
+    private static final int STATE_TEST_REQUIRE_CONFIRMATION = 1; // confirmation required
+    private static final int STATE_TEST_NO_CONFIRMATION = 2; // no confirmation required
+
     private DialogCallback mDialogCallback;
     private BiometricPrompt mBiometricPrompt;
     private CancellationSignal mCancellationSignal;
+    private BiometricManager mBiometricManager;
+    private int mState = STATE_TEST_REQUIRE_CONFIRMATION;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -50,8 +63,15 @@
         @Override
         public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
             if (tryEncrypt()) {
-                showToast("Test passed.");
-                getPassButton().setEnabled(true);
+                if (mState == STATE_TEST_REQUIRE_CONFIRMATION) {
+                    mState = STATE_TEST_NO_CONFIRMATION;
+                    showToast("First test passed, run again to start the second test");
+                    Button startButton = findViewById(R.id.sec_start_test_button);
+                    startButton.setText("Run second test");
+                } else if (mState == STATE_TEST_NO_CONFIRMATION) {
+                    showToast("Test passed.");
+                    getPassButton().setEnabled(true);
+                }
             } else {
                 showToast("Test failed. Key not accessible after auth");
             }
@@ -59,21 +79,51 @@
     }
 
     @Override
+    protected void onPermissionsGranted() {
+        mBiometricManager = getSystemService(BiometricManager.class);
+        final int result = mBiometricManager.canAuthenticate();
+
+        if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
+            showToast("No biometric features, test passed.");
+            getPassButton().setEnabled(true);
+        } else if (result == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+            showToast("Biometric unavailable, something is wrong with your device");
+        } else if (result == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
+            showToast("Error: " + result + " Please ensure you have a biometric enrolled");
+            Button startTestButton = findViewById(R.id.sec_start_test_button);
+            startTestButton.setEnabled(false);
+        }
+    }
+
+    @Override
     protected void showAuthenticationScreen() {
         mCancellationSignal = new CancellationSignal();
         mDialogCallback = new DialogCallback();
+        final boolean requireConfirmation = mState == STATE_TEST_REQUIRE_CONFIRMATION
+                ? true : false;
         mBiometricPrompt = new BiometricPrompt.Builder(getApplicationContext())
-                .setTitle("Authenticate with fingerprint")
+                .setTitle("Authenticate with biometric")
                 .setNegativeButton("Cancel", mExecutor,
                         (DialogInterface dialogInterface, int which) -> {
                             if (which == DialogInterface.BUTTON_NEGATIVE) {
                                 mHandler.post(mNegativeButtonRunnable);
                             }
                         })
+                .setRequireConfirmation(requireConfirmation)
                 .build();
         mBiometricPrompt.authenticate(
                 new BiometricPrompt
                 .CryptoObject(getCipher()),
                 mCancellationSignal, mExecutor, mDialogCallback);
     }
+
+    @Override
+    protected int getTitleRes() {
+        return R.string.sec_biometric_prompt_bound_key_test;
+    }
+
+    @Override
+    protected int getDescriptionRes() {
+        return R.string.sec_biometric_prompt_bound_key_test_info;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
index 83b84e7..4ad8e09 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -16,9 +16,6 @@
 
 package com.android.cts.verifier.security;
 
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
 import android.Manifest;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -26,21 +23,23 @@
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.util.Log;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.UserNotAuthenticatedException;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.Toast;
 
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -67,7 +66,7 @@
     private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
     private static final int AUTHENTICATION_DURATION_SECONDS = 2;
     private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
-    private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
+    private static final int BIOMETRIC_REQUEST_PERMISSION_CODE = 0;
 
     protected boolean useStrongBox;
 
@@ -76,24 +75,32 @@
     private FingerprintAuthDialogFragment mFingerprintDialog;
     private Cipher mCipher;
 
+    protected int getTitleRes() {
+        return R.string.sec_fingerprint_bound_key_test;
+    }
+
+    protected int getDescriptionRes() {
+        return R.string.sec_fingerprint_bound_key_test_info;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.sec_screen_lock_keys_main);
         setPassFailButtonClickListeners();
-        setInfoResources(R.string.sec_fingerprint_bound_key_test, R.string.sec_fingerprint_bound_key_test_info, -1);
+        setInfoResources(getTitleRes(), getDescriptionRes(), -1);
         getPassButton().setEnabled(false);
-        requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
-                FINGERPRINT_PERMISSION_REQUEST_CODE);
+        requestPermissions(new String[]{Manifest.permission.USE_BIOMETRIC},
+                BIOMETRIC_REQUEST_PERMISSION_CODE);
     }
 
     @Override
     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
-        if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) {
+        if (requestCode == BIOMETRIC_REQUEST_PERMISSION_CODE && state[0] == PackageManager.PERMISSION_GRANTED) {
             useStrongBox = false;
             mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
-            mKeyguardManager = (KeyguardManager) getSystemService(KeyguardManager.class);
-            Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
+            mKeyguardManager = getSystemService(KeyguardManager.class);
+            Button startTestButton = findViewById(R.id.sec_start_test_button);
 
             if (!mKeyguardManager.isKeyguardSecure()) {
                 // Show a message that the user hasn't set up a lock screen.
@@ -101,13 +108,10 @@
                                 + "Go to 'Settings -> Security -> Screen lock' to set up a lock screen");
                 startTestButton.setEnabled(false);
                 return;
-            } else if (!mFingerprintManager.hasEnrolledFingerprints()) {
-                showToast("No fingerprints enrolled.\n"
-                                + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint");
-                startTestButton.setEnabled(false);
-                return;
             }
 
+            onPermissionsGranted();
+
             startTestButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -117,6 +121,19 @@
         }
     }
 
+    /**
+     * Fingerprint-specific check before allowing test to be started
+     */
+    protected void onPermissionsGranted() {
+        mFingerprintManager = getSystemService(FingerprintManager.class);
+        if (!mFingerprintManager.hasEnrolledFingerprints()) {
+            showToast("No fingerprints enrolled.\n"
+                    + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint");
+            Button startTestButton = findViewById(R.id.sec_start_test_button);
+            startTestButton.setEnabled(false);
+        }
+    }
+
     protected void startTest() {
         createKey(false /* hasValidityDuration */);
         prepareEncrypt();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/KeyChainTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/KeyChainTest.java
index 82a99e3..b13902e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/KeyChainTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/KeyChainTest.java
@@ -46,9 +46,11 @@
 import java.security.Key;
 import java.security.KeyFactory;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.PKCS8EncodedKeySpec;
@@ -63,6 +65,7 @@
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509TrustManager;
 
 import libcore.java.security.TestKeyStore;
 import libcore.javax.net.ssl.TestSSLContext;
@@ -282,6 +285,44 @@
         }
     }
 
+    static class CustomTrustManager implements X509TrustManager {
+        private final X509TrustManager mOther;
+        private final X509Certificate mDesiredIssuer;
+
+        CustomTrustManager(X509TrustManager other, X509Certificate desiredIssuer) {
+            mOther = other;
+            mDesiredIssuer = desiredIssuer;
+        }
+
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            mOther.checkClientTrusted(chain, authType);
+        }
+
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            mOther.checkServerTrusted(chain, authType);
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            // The issuers specified by the default X509TrustManager do not match the
+            // client certificate installed into KeyChain.
+            // Supply an issuers array that is guaranteed to match the issuer of the
+            // client certificate by using the issuer of the client certificate.
+            if (mDesiredIssuer != null) {
+                Log.w(TAG, "Returning certificate with subject "
+                        + mDesiredIssuer.getSubjectDN().getName());
+                return new X509Certificate[] { mDesiredIssuer };
+            }
+
+            X509Certificate[] issuers = mOther.getAcceptedIssuers();
+            for (X509Certificate issuer: issuers) {
+                Log.w(TAG, "From other: " + issuer.getSubjectDN().getName());
+            }
+            return issuers;
+        }
+    };
+
     private class TestHttpsRequestTask extends AsyncTask<Void, Void, Void> {
         @Override
         protected Void doInBackground(Void... params) {
@@ -315,8 +356,19 @@
                     KeyManagerFactory.getDefaultAlgorithm());
             kmf.init(mKeyStore, EMPTY_PASSWORD);
             SSLContext serverContext = SSLContext.getInstance("TLS");
+
+            X509Certificate desiredIssuer = null;
+            try {
+                desiredIssuer = (X509Certificate) mKeyStore.getCertificateChain(ALIAS)[1];
+            } catch (KeyStoreException e) {
+                log("Error getting client cert: " + e);
+            }
+            CustomTrustManager ctm = new CustomTrustManager(
+                    (X509TrustManager) mTrustManagerFactory.getTrustManagers()[0],
+                    desiredIssuer);
+
             serverContext.init(kmf.getKeyManagers(),
-                    mTrustManagerFactory.getTrustManagers(),
+                    new X509TrustManager[] { ctm },
                     null /* SecureRandom */);
             SSLSocketFactory sf = serverContext.getSocketFactory();
             SSLSocketFactory needsClientAuth = TestSSLContext.clientAuth(sf,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index b4aa606..d98b3df 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -29,6 +29,7 @@
 import android.hardware.cts.helpers.TestSensorManager;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
 import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
+import android.hardware.cts.helpers.sensorverification.OffsetVerification;
 import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
 
 /**
@@ -92,6 +93,37 @@
     }
 
     /**
+     * This test verifies that the norm of the sensor offset is less than the reference value.
+     * The units of the reference value are dependent on the type of sensor.
+     *
+     * The test takes a sample from the sensor under test and calculates the Euclidean Norm of the
+     * offset represented by the sampled data. It then compares it against the test expectations
+     * that are represented by a reference value.
+     *
+     * The assertion associated with the test provides the required data needed to identify any
+     * possible issue. It provides:
+     * - the thread id on which the failure occurred
+     * - the sensor type and sensor handle that caused the failure
+     * - the values representing the expectation of the test
+     * - the values sampled from the sensor
+     */
+    @SuppressWarnings("unused")
+    public String testOffset() throws Throwable {
+        getTestLogger().logMessage(R.string.snsr_mag_verify_offset);
+
+        TestSensorEnvironment environment = new TestSensorEnvironment(
+                getApplicationContext(),
+                Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+                SensorManager.SENSOR_DELAY_FASTEST);
+        TestSensorOperation verifyOffset =
+                TestSensorOperation.createOperation(environment, 100 /* event count */);
+
+        verifyOffset.addVerification(OffsetVerification.getDefault(environment));
+        verifyOffset.execute(getCurrentTestNode());
+        return null;
+    }
+
+    /**
      * This test verifies that the standard deviation of a set of sampled data from a particular
      * sensor falls into the expectations defined in the CDD. The verification applies to each axis
      * of the sampled data reported by the Sensor under test.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
index f4542db..29f78e1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
@@ -39,6 +39,7 @@
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbRequest;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.ArraySet;
 import android.util.Log;
@@ -133,6 +134,17 @@
                                 mStatus.setText(R.string.usb_device_test_step2);
                             }
 
+                            if (getApplicationContext().getApplicationInfo().targetSdkVersion
+                                    >= Build.VERSION_CODES.Q) {
+                                try {
+                                    device.getSerialNumber();
+                                    fail("Serial number could be read", null);
+                                    return;
+                                } catch (SecurityException expected) {
+                                    // expected as app cannot read serial number without permission
+                                }
+                            }
+
                             mUsbManager.requestPermission(device,
                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
                                             new Intent(ACTION_USB_PERMISSION), 0));
@@ -1943,7 +1955,7 @@
     /**
      * Enumerate all known devices and check basic relationship between the properties.
      */
-    private void enumerateDevices() throws Exception {
+    private void enumerateDevices(@NonNull UsbDevice companionDevice) throws Exception {
         Set<Integer> knownDeviceIds = new ArraySet<>();
 
         for (Map.Entry<String, UsbDevice> entry : mUsbManager.getDeviceList().entrySet()) {
@@ -1962,7 +1974,12 @@
             device.getManufacturerName();
             device.getProductName();
             device.getVersion();
-            device.getSerialNumber();
+
+            // We are only guaranteed to have permission to the companion device.
+            if (device.equals(companionDevice)) {
+                device.getSerialNumber();
+            }
+
             device.getVendorId();
             device.getProductId();
             device.getDeviceClass();
@@ -2056,7 +2073,7 @@
             assertNotNull("No \"Android Accessory Interface\" interface found in " + allInterfaces,
                     iface);
 
-            enumerateDevices();
+            enumerateDevices(device);
 
             UsbDeviceConnection connection = mUsbManager.openDevice(device);
             assertNotNull(connection);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
new file mode 100644
index 0000000..3d507cd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifi;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Base class for Wifi tests.
+ */
+public abstract class BaseTestActivity extends PassFailButtons.Activity implements
+        BaseTestCase.Listener {
+    /*
+     * Handles to GUI elements.
+     */
+    private TextView mWifiInfo;
+    private ProgressBar mWifiProgress;
+
+    /*
+     * Test case to be executed
+     */
+    private BaseTestCase mTestCase;
+
+    private Handler mHandler = new Handler();
+
+    protected abstract BaseTestCase getTestCase(Context context);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.wifi_main);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        // Get UI component.
+        mWifiInfo = (TextView) findViewById(R.id.wifi_info);
+        mWifiProgress = (ProgressBar) findViewById(R.id.wifi_progress);
+
+        // Initialize test components.
+        mTestCase = getTestCase(this);
+
+        // keep screen on while this activity is front view.
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mTestCase.start(this);
+        mWifiProgress.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mTestCase.stop();
+        mWifiProgress.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onTestStarted() {
+        // nop
+    }
+
+    @Override
+    public void onTestMsgReceived(String msg) {
+        if (msg == null) {
+            return;
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mWifiInfo.append(msg);
+                mWifiInfo.append("\n");
+            }
+        });
+    }
+
+    @Override
+    public void onTestSuccess() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                getPassButton().setEnabled(true);
+                mWifiInfo.append(getString(R.string.wifi_status_test_success));
+                mWifiInfo.append("\n");
+                mWifiProgress.setVisibility(View.GONE);
+            }
+        });
+    }
+
+    @Override
+    public void onTestFailed(String reason) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (reason != null) {
+                    mWifiInfo.append(reason);
+                }
+                getPassButton().setEnabled(false);
+                mWifiInfo.append(getString(R.string.wifi_status_test_failed));
+                mWifiInfo.append("\n");
+                mWifiProgress.setVisibility(View.GONE);
+            }
+        });
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java
new file mode 100644
index 0000000..8382903
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.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.verifier.wifi;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Base class for all Wifi test cases.
+ */
+public abstract class BaseTestCase {
+    protected Context mContext;
+    protected Resources mResources;
+    protected Listener mListener;
+
+    private Thread mThread;
+    private HandlerThread mHandlerThread;
+    protected Handler mHandler;
+
+    protected WifiManager mWifiManager;
+
+    public BaseTestCase(Context context) {
+        mContext = context;
+        mResources = mContext.getResources();
+    }
+
+    /**
+     * Set up the test case. Executed once before test starts.
+     */
+    protected void setUp() {
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    /**
+     * Tear down the test case. Executed after test finishes - whether on success or failure.
+     */
+    protected void tearDown() {
+        mWifiManager = null;
+    }
+
+    /**
+     * Execute test case.
+     *
+     * @return true on success, false on failure. In case of failure
+     */
+    protected abstract boolean executeTest() throws InterruptedException;
+
+    /**
+     * Returns a String describing the failure reason of the most recent test failure (not valid
+     * in other scenarios). Override to customize the failure string.
+     */
+    protected String getFailureReason() {
+        return mContext.getString(R.string.wifi_unexpected_error);
+    }
+
+    /**
+     * Start running the test case.
+     * <p>
+     * Test case is executed in another thread.
+     */
+    public void start(Listener listener) {
+        mListener = listener;
+
+        stop();
+        mHandlerThread = new HandlerThread("CtsVerifier-Wifi");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mThread = new Thread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mListener.onTestStarted();
+                        try {
+                            setUp();
+                        } catch (Exception e) {
+                            mListener.onTestFailed(mContext.getString(R.string.wifi_setup_error));
+                            return;
+                        }
+
+                        try {
+                            if (executeTest()) {
+                                mListener.onTestSuccess();
+                            } else {
+                                mListener.onTestFailed(getFailureReason());
+                            }
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            mListener.onTestFailed(
+                                    mContext.getString(R.string.aware_unexpected_error));
+                        } finally {
+                            tearDown();
+                        }
+                    }
+                });
+        mThread.start();
+    }
+
+    /**
+     * Stop the currently running test case.
+     */
+    public void stop() {
+        if (mThread != null) {
+            mThread.interrupt();
+            mThread = null;
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread = null;
+            mHandler = null;
+        }
+    }
+
+    /**
+     * Listener interface used to communicate the state and status of the test case. It should
+     * be implemented by any activity encompassing a test case.
+     */
+    public interface Listener {
+        /**
+         * This function is invoked when the test case starts.
+         */
+        void onTestStarted();
+
+        /**
+         * This function is invoked by the test to send a message to listener.
+         */
+        void onTestMsgReceived(String msg);
+
+        /**
+         * This function is invoked when the test finished successfully.
+         */
+        void onTestSuccess();
+
+        /**
+         * This function is invoked when the test failed (test is done).
+         */
+        void onTestFailed(String reason);
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/CallbackUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/CallbackUtils.java
new file mode 100644
index 0000000..b486852
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/CallbackUtils.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 com.android.cts.verifier.wifi;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.util.Pair;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Blocking callbacks for Wi-Fi and Connectivity Manager.
+ */
+public class CallbackUtils {
+    public static final int DEFAULT_CALLBACK_TIMEOUT_MS = 15_000;
+
+    /**
+     * Utility NetworkCallback- provides mechanism to block execution with the
+     * waitForAttach method.
+     */
+    public static class NetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final int mCallbackTimeoutInMs;
+
+        private CountDownLatch mBlocker = new CountDownLatch(1);
+        private CountDownLatch mOnLostBlocker = new CountDownLatch(1);
+        private Network mNetwork;
+
+        public NetworkCallback() {
+            mCallbackTimeoutInMs = DEFAULT_CALLBACK_TIMEOUT_MS;
+        }
+
+        public NetworkCallback(int callbackTimeoutInMs) {
+            mCallbackTimeoutInMs = callbackTimeoutInMs;
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            mNetwork = network;
+            mBlocker.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            mBlocker.countDown();
+        }
+
+        @Override
+        public void onLost(Network network) {
+            mNetwork = network;
+            mOnLostBlocker.countDown();
+        }
+
+        /**
+         * Wait (blocks) for {@link #onAvailable(Network)} or timeout.
+         *
+         * @return A pair of values: whether the callback was invoked and the Network object
+         * created when successful - null otherwise.
+         */
+        public Pair<Boolean, Network> waitForAvailable() throws InterruptedException {
+            if (mBlocker.await(mCallbackTimeoutInMs, TimeUnit.MILLISECONDS)) {
+                return Pair.create(true, mNetwork);
+            }
+            return Pair.create(false, null);
+        }
+
+        /**
+         * Wait (blocks) for {@link #onUnavailable()} or timeout.
+         *
+         * @return true whether the callback was invoked.
+         */
+        public boolean waitForUnavailable() throws InterruptedException {
+            if (mBlocker.await(mCallbackTimeoutInMs, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Wait (blocks) for {@link #onLost(Network)} or timeout.
+         *
+         * @return true whether the callback was invoked.
+         */
+        public boolean waitForLost() throws InterruptedException {
+            if (mOnLostBlocker.await(mCallbackTimeoutInMs, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestPatternNetworkSpecifierTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestPatternNetworkSpecifierTestActivity.java
new file mode 100644
index 0000000..b49f11d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestPatternNetworkSpecifierTestActivity.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 com.android.cts.verifier.wifi;
+
+import static com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase.NETWORK_SPECIFIER_PATTERN_SSID_BSSID;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase;
+
+/**
+ * Test activity for specifier with ssid/bssid pattern.
+ */
+public class NetworkRequestPatternNetworkSpecifierTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new NetworkRequestTestCase(context, NETWORK_SPECIFIER_PATTERN_SSID_BSSID);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.wifi_test_network_request_pattern,
+                R.string.wifi_test_network_request_pattern_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestSpecificNetworkSpecifierTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestSpecificNetworkSpecifierTestActivity.java
new file mode 100644
index 0000000..7d82e8d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestSpecificNetworkSpecifierTestActivity.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 com.android.cts.verifier.wifi;
+
+import static com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase.NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase;
+
+/**
+ * Test activity for specifier with specific ssid/bssid.
+ */
+public class NetworkRequestSpecificNetworkSpecifierTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new NetworkRequestTestCase(context, NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.wifi_test_network_request_specific,
+                R.string.wifi_test_network_request_specific_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestUnavailableNetworkSpecifierTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestUnavailableNetworkSpecifierTestActivity.java
new file mode 100644
index 0000000..c02c429
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/NetworkRequestUnavailableNetworkSpecifierTestActivity.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 com.android.cts.verifier.wifi;
+
+import static com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase.NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.testcase.NetworkRequestTestCase;
+
+/**
+ * Test activity for specifier with specific ssid/bssid not present in scan results.
+ */
+public class NetworkRequestUnavailableNetworkSpecifierTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new NetworkRequestTestCase(context, NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.wifi_test_network_request_unavailable,
+                R.string.wifi_test_network_request_unavailable_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/OWNERS
new file mode 100644
index 0000000..1848dfd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/OWNERS
@@ -0,0 +1,3 @@
+rpius@google.com
+etancohen@google.com
+satk@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.java
new file mode 100644
index 0000000..b4ccd3a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestListActivity.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 com.android.cts.verifier.wifi;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.DataSetObserver;
+import android.location.LocationManager;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListView;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter;
+
+/**
+ * Activity listing all Wi-Fi Wifi tests.
+ */
+public class TestListActivity extends PassFailButtons.TestListActivity {
+    private static final String TAG = "TestListActivity";
+
+    private WifiManager mWifiManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+        if (mWifiManager == null) {
+            Log.wtf(TAG,
+                    "Can't get WIFI_SERVICE. Should be gated by 'test_required_features'!?");
+            return;
+        }
+        setContentView(R.layout.pass_fail_list);
+        setInfoResources(R.string.wifi_test, R.string.wifi_test_info, 0);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        // Add the sub-test/categories
+        ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+        adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                R.string.wifi_test_network_request));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.wifi_test_network_request_specific,
+                NetworkRequestSpecificNetworkSpecifierTestActivity.class.getName(),
+                new Intent(this, NetworkRequestSpecificNetworkSpecifierTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.wifi_test_network_request_pattern,
+                NetworkRequestPatternNetworkSpecifierTestActivity.class.getName(),
+                new Intent(this, NetworkRequestPatternNetworkSpecifierTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.wifi_test_network_request_unavailable,
+                NetworkRequestUnavailableNetworkSpecifierTestActivity.class.getName(),
+                new Intent(this, NetworkRequestUnavailableNetworkSpecifierTestActivity.class),
+                null));
+
+        adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                updatePassButton();
+            }
+
+            @Override
+            public void onInvalidated() {
+                updatePassButton();
+            }
+        });
+
+        setTestListAdapter(adapter);
+    }
+
+    @Override
+    protected void handleItemClick(ListView listView, View view, int position, long id) {
+        LocationManager locationManager =
+                (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+        if (!mWifiManager.isWifiEnabled() || !locationManager.isLocationEnabled()) {
+            showWifiAndLocationEnableDialog();
+            return;
+        }
+        super.handleItemClick(listView, view, position, id);
+    }
+
+    /**
+     * Show the dialog to jump to system settings in order to enable WiFi & location.
+     */
+    private void showWifiAndLocationEnableDialog() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setIcon(android.R.drawable.ic_dialog_alert);
+        builder.setTitle(R.string.wifi_location_not_enabled);
+        builder.setMessage(R.string.wifi_location_not_enabled_message);
+        builder.setPositiveButton(R.string.wifi_settings,
+                new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
+                    }
+                });
+        builder.setPositiveButton(R.string.location_settings,
+                new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+                    }
+                });
+        builder.create().show();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
new file mode 100644
index 0000000..528f7d6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wifi.testcase;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.PatternMatcher;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifi.BaseTestCase;
+import com.android.cts.verifier.wifi.CallbackUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test case for all {@link NetworkRequest} requests with specifier built using
+ * {@link WifiNetworkSpecifier.Builder#build()}.
+ */
+public class NetworkRequestTestCase extends BaseTestCase {
+    private static final String TAG = "NetworkRequestTestCase";
+    private static final boolean DBG = true;
+
+    private static final String UNAVAILABLE_SSID = "blahblahblah";
+    private static final int NETWORK_REQUEST_TIMEOUT_MS = 30_000;
+    private static final int CALLBACK_TIMEOUT_MS = 40_000;
+    private static final int SCAN_TIMEOUT_MS = 30_000;
+
+    public static final int NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID = 0;
+    public static final int NETWORK_SPECIFIER_PATTERN_SSID_BSSID = 1;
+    public static final int NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID = 2;
+
+    @IntDef(prefix = { "NETWORK_SPECIFIER_" }, value = {
+            NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID,
+            NETWORK_SPECIFIER_PATTERN_SSID_BSSID,
+            NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NetworkSpecifierType{}
+
+    private final Object mLock = new Object();
+    private final @NetworkSpecifierType int mNetworkSpecifierType;
+
+    private ConnectivityManager mConnectivityManager;
+    private NetworkRequest mNetworkRequest;
+    private CallbackUtils.NetworkCallback mNetworkCallback;
+    private BroadcastReceiver mBroadcastReceiver;
+    private String mFailureReason;
+
+    public NetworkRequestTestCase(Context context, @NetworkSpecifierType int networkSpecifierType) {
+        super(context);
+        mNetworkSpecifierType = networkSpecifierType;
+    }
+
+    // Create a network specifier based on the test type.
+    private NetworkSpecifier createNetworkSpecifier(@NonNull ScanResult scanResult)
+            throws InterruptedException {
+        WifiNetworkSpecifier.Builder configBuilder = new WifiNetworkSpecifier.Builder();
+        switch (mNetworkSpecifierType) {
+            case NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID:
+                configBuilder.setSsid(scanResult.SSID);
+                configBuilder.setBssid(MacAddress.fromString(scanResult.BSSID));
+                break;
+            case NETWORK_SPECIFIER_PATTERN_SSID_BSSID:
+                String ssidPrefix = scanResult.SSID.substring(0, scanResult.SSID.length() - 1);
+                MacAddress bssidMask = MacAddress.fromString("ff:ff:ff:ff:ff:00");
+                configBuilder.setSsidPattern(
+                        new PatternMatcher(ssidPrefix, PatternMatcher.PATTERN_PREFIX));
+                configBuilder.setBssidPattern(MacAddress.fromString(scanResult.BSSID), bssidMask);
+                break;
+            case NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID:
+                String ssid = UNAVAILABLE_SSID;
+                MacAddress bssid = MacAddress.createRandomUnicastAddress();
+                if (findNetworkInScanResultsResults(ssid, bssid.toString())) {
+                    Log.e(TAG, "The specifiers chosen match a network in scan results."
+                            + "Test will fail");
+                    return null;
+                }
+                configBuilder.setSsid(UNAVAILABLE_SSID);
+                configBuilder.setBssid(bssid);
+                break;
+            default:
+                throw new IllegalStateException("Unknown specifier type specifier");
+        }
+        return configBuilder.build();
+    }
+
+    private boolean startScanAndWaitForResults() throws InterruptedException {
+        IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        // Scan Results available broadcast receiver.
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (DBG) Log.v(TAG, "Broadcast onReceive " + intent);
+                if (!intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) return;
+                if (!intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) return;
+                if (DBG) Log.v(TAG, "Scan results received");
+                countDownLatch.countDown();
+            }
+        };
+        // Register the receiver for scan results broadcast.
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+
+        // Start scan.
+        if (DBG) Log.v(TAG, "Starting scan");
+        mListener.onTestMsgReceived(mContext.getString(R.string.wifi_status_initiating_scan));
+        if (!mWifiManager.startScan()) {
+            Log.e(TAG, "Failed to start scan");
+            setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
+            return false;
+        }
+        // Wait for scan results.
+        if (DBG) Log.v(TAG, "Wait for scan results");
+        if (!countDownLatch.await(SCAN_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            Log.e(TAG, "No new scan results available");
+            setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
+            return false;
+        }
+        return true;
+    }
+
+    // Helper to check if the scan result corresponds to an open network.
+    private static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) {
+        String capabilities = scanResult.capabilities;
+        return !capabilities.contains("PSK") && !capabilities.contains("EAP")
+                && !capabilities.contains("WEP") && !capabilities.contains("SAE")
+                && !capabilities.contains("SUITE-B-192") && !capabilities.contains("OWE");
+    }
+
+    private @Nullable ScanResult startScanAndFindAnyOpenNetworkInResults()
+            throws InterruptedException {
+        // Start scan and wait for new results.
+        if (!startScanAndWaitForResults()) {
+            return null;
+        }
+        // Filter results to find an open network.
+        List<ScanResult> scanResults = mWifiManager.getScanResults();
+        for (ScanResult scanResult : scanResults) {
+            if (!TextUtils.isEmpty(scanResult.SSID)
+                    && !TextUtils.isEmpty(scanResult.BSSID)
+                    && isScanResultForOpenNetwork(scanResult)) {
+                if (DBG) Log.v(TAG, "Found open network " + scanResult);
+                return scanResult;
+            }
+        }
+        Log.e(TAG, "No open networks found in scan results");
+        setFailureReason(mContext.getString(R.string.wifi_status_open_network_not_found));
+        return null;
+    }
+
+    private boolean findNetworkInScanResultsResults(@NonNull String ssid, @NonNull String bssid)
+            throws InterruptedException {
+        List<ScanResult> scanResults = mWifiManager.getScanResults();
+        for (ScanResult scanResult : scanResults) {
+            if (TextUtils.equals(scanResult.SSID, ssid)
+                    && TextUtils.equals(scanResult.BSSID, bssid)) {
+                if (DBG) Log.v(TAG, "Found network " + scanResult);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void setFailureReason(String reason) {
+        synchronized (mLock) {
+            mFailureReason = reason;
+        }
+    }
+
+    @Override
+    protected boolean executeTest() throws InterruptedException {
+        // Step 1: Scan and find any open network around.
+        if (DBG) Log.v(TAG, "Scan and find an open network");
+        ScanResult openNetwork = startScanAndFindAnyOpenNetworkInResults();
+        if (openNetwork == null) return false;
+
+        // Step 2: Create a specifier for the chosen open network depending on the type of test.
+        NetworkSpecifier wns = createNetworkSpecifier(openNetwork);
+        if (wns == null) return false;
+
+        // Step 3: Create a network request with specifier.
+        mNetworkRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setNetworkSpecifier(wns)
+                .removeCapability(NET_CAPABILITY_INTERNET)
+                .build();
+
+        // Step 4: Send the network request
+        if (DBG) Log.v(TAG, "Request network using " + mNetworkRequest);
+        mNetworkCallback = new CallbackUtils.NetworkCallback(CALLBACK_TIMEOUT_MS);
+        mListener.onTestMsgReceived(
+                mContext.getString(R.string.wifi_status_initiating_network_request));
+        mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback,
+                NETWORK_REQUEST_TIMEOUT_MS);
+
+        // Step 5: Wait for the network available/unavailable callback.
+        if (mNetworkSpecifierType == NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID) {
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_network_wait_for_unavailable));
+            if (DBG) Log.v(TAG, "Waiting for network unavailable callback");
+            boolean cbStatusForUnavailable = mNetworkCallback.waitForUnavailable();
+            if (!cbStatusForUnavailable) {
+                Log.e(TAG, "Failed to get network unavailable callback");
+                setFailureReason(mContext.getString(R.string.wifi_status_network_cb_timeout));
+                return false;
+            }
+            mListener.onTestMsgReceived(
+                    mContext.getString(R.string.wifi_status_network_unavailable));
+            // All done!
+            return true;
+        }
+        mListener.onTestMsgReceived(
+                mContext.getString(R.string.wifi_status_network_wait_for_available));
+        if (DBG) Log.v(TAG, "Waiting for network available callback");
+        Pair<Boolean, Network> cbStatusForAvailable = mNetworkCallback.waitForAvailable();
+        if (!cbStatusForAvailable.first) {
+            Log.e(TAG, "Failed to get network available callback");
+            setFailureReason(mContext.getString(R.string.wifi_status_network_cb_timeout));
+            return false;
+        }
+        mListener.onTestMsgReceived(
+                mContext.getString(R.string.wifi_status_network_available));
+
+        mListener.onTestMsgReceived(
+                mContext.getString(R.string.wifi_status_network_wait_for_lost));
+        // Step 6: Ensure we don't disconnect from the network as long as the request is alive.
+        if (DBG) Log.v(TAG, "Ensuring network lost callback is not invoked");
+        boolean cbStatusForLost = mNetworkCallback.waitForLost();
+        if (cbStatusForLost) {
+            Log.e(TAG, "Disconnected from the network even though the request is active");
+            setFailureReason(mContext.getString(R.string.wifi_status_network_lost));
+            return false;
+        }
+        // All done!
+        return true;
+    }
+
+    @Override
+    protected String getFailureReason() {
+        synchronized (mLock) {
+            return mFailureReason;
+        }
+    }
+
+    @Override
+    protected void setUp() {
+        super.setUp();
+        mConnectivityManager = ConnectivityManager.from(mContext);
+    }
+
+    @Override
+    protected void tearDown() {
+        if (mBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        }
+        if (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        super.tearDown();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/CallbackUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/CallbackUtils.java
index 2bf4614..b1bd228 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/CallbackUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/CallbackUtils.java
@@ -18,6 +18,7 @@
 
 import android.net.ConnectivityManager;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.wifi.aware.AttachCallback;
 import android.net.wifi.aware.DiscoverySessionCallback;
 import android.net.wifi.aware.IdentityChangedListener;
@@ -123,17 +124,18 @@
      */
     public static class NetworkCb extends ConnectivityManager.NetworkCallback {
         private CountDownLatch mBlocker = new CountDownLatch(1);
-        private boolean mNetworkAvailable = false;
+        private NetworkCapabilities mNetworkCapabilities = null;
 
         @Override
-        public void onAvailable(Network network) {
-            mNetworkAvailable = true;
+        public void onUnavailable() {
+            mNetworkCapabilities = null;
             mBlocker.countDown();
         }
 
         @Override
-        public void onUnavailable() {
-            mNetworkAvailable = false;
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            mNetworkCapabilities = networkCapabilities;
             mBlocker.countDown();
         }
 
@@ -142,11 +144,11 @@
          *
          * @return true if Available, false otherwise (Unavailable or timeout).
          */
-        public boolean waitForNetwork() throws InterruptedException {
+        public NetworkCapabilities waitForNetwork() throws InterruptedException {
             if (mBlocker.await(CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)) {
-                return mNetworkAvailable;
+                return mNetworkCapabilities;
             }
-            return false;
+            return null;
         }
     }
 
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 fc8a3ef..2f9bf4c 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
@@ -141,13 +141,18 @@
         mListener.onTestMsgReceived(
                 mContext.getString(R.string.aware_status_network_requested));
         if (DBG) Log.d(TAG, "executeTestSubscriber: requested network");
-        boolean networkAvailable = networkCb.waitForNetwork();
+        NetworkCapabilities nc = networkCb.waitForNetwork();
         cm.unregisterNetworkCallback(networkCb);
-        if (!networkAvailable) {
+        if (nc == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestSubscriber: network request rejected - ON_UNAVAILABLE");
             return false;
         }
+        if (nc.getNetworkSpecifier() != null) {
+            setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
+            Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
+            return false;
+        }
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
         if (DBG) Log.d(TAG, "executeTestSubscriber: network request granted - AVAILABLE");
 
@@ -200,13 +205,18 @@
         }
 
         // 6. wait for network
-        boolean networkAvailable = networkCb.waitForNetwork();
+        NetworkCapabilities nc = networkCb.waitForNetwork();
         cm.unregisterNetworkCallback(networkCb);
-        if (!networkAvailable) {
+        if (nc == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestPublisher: request network rejected - ON_UNAVAILABLE");
             return false;
         }
+        if (nc.getNetworkSpecifier() != null) {
+            setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
+            Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
+            return false;
+        }
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
         if (DBG) Log.d(TAG, "executeTestPublisher: network request granted - AVAILABLE");
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
index 0f81d2c..be198e1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
@@ -284,13 +284,18 @@
         cm.requestNetwork(nr, networkCb, CALLBACK_TIMEOUT_SEC * 1000);
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_requested));
         if (DBG) Log.d(TAG, "executeTestResponder: requested network");
-        boolean networkAvailable = networkCb.waitForNetwork();
+        NetworkCapabilities nc = networkCb.waitForNetwork();
         cm.unregisterNetworkCallback(networkCb);
-        if (!networkAvailable) {
+        if (nc == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestResponder: network request rejected - ON_UNAVAILABLE");
             return false;
         }
+        if (nc.getNetworkSpecifier() != null) {
+            setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
+            Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
+            return false;
+        }
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
         if (DBG) Log.d(TAG, "executeTestResponder: network request granted - AVAILABLE");
 
@@ -418,13 +423,18 @@
         cm.requestNetwork(nr, networkCb, CALLBACK_TIMEOUT_SEC * 1000);
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_requested));
         if (DBG) Log.d(TAG, "executeTestInitiator: requested network");
-        boolean networkAvailable = networkCb.waitForNetwork();
+        NetworkCapabilities nc = networkCb.waitForNetwork();
         cm.unregisterNetworkCallback(networkCb);
-        if (!networkAvailable) {
+        if (nc == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestInitiator: network request rejected - ON_UNAVAILABLE");
             return false;
         }
+        if (nc.getNetworkSpecifier() != null) {
+            setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
+            Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
+            return false;
+        }
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
         if (DBG) Log.d(TAG, "executeTestInitiator: network request granted - AVAILABLE");
 
diff --git a/apps/CtsVerifierUSBCompanion/Android.mk b/apps/CtsVerifierUSBCompanion/Android.mk
index 2e08142..c505b13 100644
--- a/apps/CtsVerifierUSBCompanion/Android.mk
+++ b/apps/CtsVerifierUSBCompanion/Android.mk
@@ -28,6 +28,7 @@
 LOCAL_PACKAGE_NAME := CtsVerifierUSBCompanion
 
 LOCAL_SDK_VERSION := 25
+#LOCAL_MIN_SDK_VERSION := 12
 
 LOCAL_DEX_PREOPT := false
 
diff --git a/apps/EmptyDeviceAdmin/Android.mk b/apps/EmptyDeviceAdmin/Android.mk
index 1d68fbb..ec4c4a3 100644
--- a/apps/EmptyDeviceAdmin/Android.mk
+++ b/apps/EmptyDeviceAdmin/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 12
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/apps/EmptyDeviceOwner/Android.mk b/apps/EmptyDeviceOwner/Android.mk
index 714e996..0d85bb7 100644
--- a/apps/EmptyDeviceOwner/Android.mk
+++ b/apps/EmptyDeviceOwner/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 12
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/apps/NotificationBot/Android.mk b/apps/NotificationBot/Android.mk
index 9d9c9f9..8a89b1a 100644
--- a/apps/NotificationBot/Android.mk
+++ b/apps/NotificationBot/Android.mk
@@ -28,6 +28,7 @@
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 19
 
 LOCAL_DEX_PREOPT := false
 
diff --git a/apps/PermissionApp/Android.mk b/apps/PermissionApp/Android.mk
index cf4186d..026c803 100644
--- a/apps/PermissionApp/Android.mk
+++ b/apps/PermissionApp/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_PACKAGE_NAME := CtsPermissionApp
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 23
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/apps/VpnApp/latest/AndroidManifest.xml b/apps/VpnApp/latest/AndroidManifest.xml
index ff9ee84..418726a 100644
--- a/apps/VpnApp/latest/AndroidManifest.xml
+++ b/apps/VpnApp/latest/AndroidManifest.xml
@@ -18,7 +18,6 @@
         package="com.android.cts.vpnfirewall">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/app">
diff --git a/common/device-side/device-info/Android.bp b/common/device-side/device-info/Android.bp
index 583bc85..f4c80ba 100644
--- a/common/device-side/device-info/Android.bp
+++ b/common/device-side/device-info/Android.bp
@@ -25,17 +25,7 @@
 
     libs: [
         "android.test.base.stubs",
-        "framework-stub-for-compatibility-device-info",
     ],
 
     sdk_version: "test_current",
 }
-
-java_library {
-    // This stub library provides some internal APIs (SystemProperties, VintfObject, etc.)
-    // to compatibility-device-info library.
-    name: "framework-stub-for-compatibility-device-info",
-    srcs: ["src_stub/**/*.java"],
-
-    sdk_version: "current",
-}
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
index c48d16f..0268e86 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
@@ -83,14 +83,20 @@
         } else if ((dir = makeResultDir()) == null) {
             failed("Cannot create directory for device info files");
         } else {
+            File jsonFile;
             try {
-                File jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
+                jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
                 jsonFile.createNewFile();
                 mResultFilePath = jsonFile.getAbsolutePath();
-                DeviceInfoStore store = new DeviceInfoStore(jsonFile);
-                store.open();
-                collectDeviceInfo(store);
-                store.close();
+                try (DeviceInfoStore store = new DeviceInfoStore(jsonFile)) {
+                    store.open();
+                    collectDeviceInfo(store);
+                } finally {
+                    if (jsonFile != null && jsonFile.exists() &&
+                            jsonFile.length() == 0) {
+                        jsonFile.delete();
+                    }
+                }
                 if (mResultCode == ResultCode.STARTED) {
                     mResultCode = ResultCode.COMPLETED;
                 }
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
index 424a2ba..0a6a2b2 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
@@ -31,6 +31,7 @@
 import com.android.compatibility.common.deviceinfo.DeviceInfo;
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.DeviceInfoStore;
+import com.android.compatibility.common.util.ShellIdentityUtils;
 
 /**
  * Generic device info collector.
@@ -75,7 +76,8 @@
         store.addResult(BUILD_ABI, Build.CPU_ABI);
         store.addResult(BUILD_ABI2, Build.CPU_ABI2);
         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.O)) {
-            store.addResult(BUILD_SERIAL, Build.getSerial()); // added in O
+            store.addResult(BUILD_SERIAL,
+                    ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));
         } else {
             store.addResult(BUILD_SERIAL, Build.SERIAL); // deprecated in O
         }
diff --git a/common/device-side/device-info/src_stub/android/os/SystemProperties.java b/common/device-side/device-info/src_stub/android/os/SystemProperties.java
deleted file mode 100644
index 2f8a051..0000000
--- a/common/device-side/device-info/src_stub/android/os/SystemProperties.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * This is a stub library for the runtime class at
- * frameworks/base/core/java/android/os/SystemProperties.java
- */
-package android.os;
-
-public class SystemProperties {
-    public static String get(String key, String def) { return null; }
-}
diff --git a/common/device-side/device-info/src_stub/android/os/VintfObject.java b/common/device-side/device-info/src_stub/android/os/VintfObject.java
deleted file mode 100644
index b8da52e..0000000
--- a/common/device-side/device-info/src_stub/android/os/VintfObject.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * This is a stub library for the runtime class at
- * frameworks/base/core/java/android/os/VintfObject.java
- */
-
-package android.os;
-
-import java.util.Map;
-
-public class VintfObject {
-    public static Map<String, String[]> getVndkSnapshots() { return null; }
-    public static String getSepolicyVersion() { return null; }
-    public static String[] getHalNamesAndVersions() { return null; }
-    public static String[] report() { return null; }
-    public static Long getTargetFrameworkCompatibilityMatrixVersion() { return null; }
-}
diff --git a/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java b/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java
deleted file mode 100644
index 20fa94c..0000000
--- a/common/device-side/device-info/src_stub/android/os/VintfRuntimeInfo.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * This is a stub library for the runtime class at
- * frameworks/base/core/java/android/os/VintfRuntimeInfo.java
- */
-package android.os;
-
-public class VintfRuntimeInfo {
-    public static String getBootAvbVersion() { return null; }
-    public static String getBootVbmetaAvbVersion() { return null; }
-    public static String getCpuInfo() { return null; }
-    public static String getHardwareId() { return null; }
-    public static String getKernelVersion() { return null; }
-    public static String getNodeName() { return null; }
-    public static String getOsName() { return null; }
-    public static String getOsRelease() { return null; }
-    public static String getOsVersion() { return null; }
-}
diff --git a/common/device-side/util/Android.bp b/common/device-side/util/Android.bp
index 2000cb8..be5e520 100644
--- a/common/device-side/util/Android.bp
+++ b/common/device-side/util/Android.bp
@@ -17,7 +17,7 @@
     sdk_version: "test_current",
 
     srcs: [
-       "src/**/*.java",
+        "src/**/*.java",
         "src/**/*.aidl",
     ],
 
@@ -33,4 +33,6 @@
         "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/protobuf-jarjar-rules.txt b/common/device-side/util/protobuf-jarjar-rules.txt
new file mode 100644
index 0000000..9914809
--- /dev/null
+++ b/common/device-side/util/protobuf-jarjar-rules.txt
@@ -0,0 +1,3 @@
+rule com.google.protobuf.** com.google.compatibility.device.util.com.google.protobuf.@1
+rule android.**.nano.** com.google.compatibility.device.util.android.@1.nano.@2
+rule **.android.**.nano.** com.google.compatibility.device.util.@1.android.@2.nano.@3
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
new file mode 100644
index 0000000..23e22c6
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
@@ -0,0 +1,62 @@
+/*
+ * 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 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(Runnable)}
+ * or {@link SystemUtil#callWithShellPermissionIdentity(java.util.concurrent.Callable)} instead.
+ */
+public class AdoptShellPermissionsRule implements TestRule {
+
+    private final UiAutomation mUiAutomation;
+
+    public AdoptShellPermissionsRule() {
+        this(InstrumentationRegistry.getInstrumentation().getUiAutomation());
+    }
+
+    public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation) {
+        mUiAutomation = uiAutomation;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                mUiAutomation.adoptShellPermissionIdentity();
+                try {
+                    base.evaluate();
+                } finally {
+                    mUiAutomation.dropShellPermissionIdentity();
+                }
+            }
+        };
+    }
+}
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
index eb94b60..6eeaae2 100644
--- 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
@@ -59,7 +59,6 @@
     public static boolean isAppStandbyEnabledAtRuntime() {
         final String result =
                 SystemUtil.runShellCommand("settings get global app_standby_enabled").trim();
-        // framework considers null value as enabled.
         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
index 792db40..ba357a16 100644
--- 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
@@ -18,12 +18,15 @@
 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";
 
@@ -103,4 +106,15 @@
         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/ColorUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java
index a2439c7..c0da13d 100644
--- 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
@@ -16,23 +16,102 @@
 
 package com.android.compatibility.common.util;
 
-import static org.junit.Assert.assertEquals;
 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) {
-        String s = "expected " + Integer.toHexString(expected)
-                + ", observed " + Integer.toHexString(observed)
-                + ", tolerated channel error " + tolerance;
-        assertEquals(s, Color.red(expected), Color.red(observed), tolerance);
-        assertEquals(s, Color.green(expected), Color.green(observed), tolerance);
-        assertEquals(s, Color.blue(expected), Color.blue(observed), tolerance);
-        assertEquals(s, Color.alpha(expected), Color.alpha(observed), 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/CtsMouseUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
index 99228fe..e53eb08 100644
--- 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
@@ -22,6 +22,8 @@
 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;
@@ -63,6 +65,36 @@
         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;
 
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
index 966ac1a..03f69fa 100644
--- 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
@@ -54,7 +54,7 @@
      * Closes the writer.
      */
     @Override
-    public void close() throws IOException {
+    public void close() throws Exception {
         mJsonWriter.endObject();
         mJsonWriter.flush();
         mJsonWriter.close();
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java b/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java
deleted file mode 100644
index 5567ec6..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/EvaluateJsResultPollingCheck.java
+++ /dev/null
@@ -1,51 +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.webkit.ValueCallback;
-
-import junit.framework.Assert;
-
-public class EvaluateJsResultPollingCheck extends PollingCheck
-        implements ValueCallback<String> {
-    private String mActualResult;
-    private String mExpectedResult;
-    private boolean mGotResult;
-
-    public EvaluateJsResultPollingCheck(String expected) {
-        mExpectedResult = expected;
-    }
-
-    @Override
-    public synchronized boolean check() {
-        return mGotResult;
-    }
-
-    @Override
-    public void run() {
-        super.run();
-        synchronized (this) {
-            Assert.assertEquals(mExpectedResult, mActualResult);
-        }
-    }
-
-    @Override
-    public synchronized void onReceiveValue(String result) {
-        mGotResult = true;
-        mActualResult = result;
-    }
-}
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
new file mode 100644
index 0000000..eee05e2
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
@@ -0,0 +1,75 @@
+/*
+ * 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();
+            }
+        };
+    }
+
+    private 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/SafeCleanerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java
index 055a6a1..9c218d2 100644
--- 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
@@ -16,9 +16,11 @@
 
 package com.android.compatibility.common.util;
 
-import androidx.annotation.NonNull;
 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;
@@ -34,7 +36,6 @@
  * 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
  */
-// TODO: move to common CTS code
 public final class SafeCleanerRule implements TestRule {
 
     private static final String TAG = "SafeCleanerRule";
@@ -55,7 +56,7 @@
     /**
      * Adds exceptions directly.
      *
-     * <p>Typically used when exceptions were caught asychronously during the test execution.
+     * <p>Typically used for exceptions caught asychronously during the test execution.
      */
     public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
         mExtraThrowables.add(exceptions);
@@ -63,6 +64,17 @@
     }
 
     /**
+     * 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) {
@@ -79,8 +91,8 @@
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    Log.w(TAG, "Adding exception from main test");
-                    mThrowables.add(t);
+                    Log.w(TAG, "Adding exception from main test at index 0: " + t);
+                    mThrowables.add(0, t);
                 }
 
                 // Then the cleanup runners
@@ -102,6 +114,9 @@
                     }
                 }
 
+                // Ignore all instances of AssumptionViolatedExceptions
+                mThrowables.removeIf(t -> t instanceof AssumptionViolatedException);
+
                 // Finally, throw up!
                 if (mThrowables.isEmpty()) return;
 
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
new file mode 100644
index 0000000..f3438ee
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
@@ -0,0 +1,125 @@
+/*
+ * 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/SystemUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java
index 9c75975..fa7f046 100644
--- 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
@@ -22,14 +22,18 @@
 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 {
@@ -118,7 +122,7 @@
     }
 
     /**
-     * Run a command and print the result on logcat.
+     * Runs a command and print the result on logcat.
      */
     public static void runCommandAndPrintOnLogcat(String logtag, String cmd) {
         Log.i(logtag, "Executing: " + cmd);
@@ -129,7 +133,7 @@
     }
 
     /**
-     * Run a command and return the section matching the patterns.
+     * Runs a command and return the section matching the patterns.
      *
      * @see TextUtils#extractSection
      */
@@ -139,4 +143,64 @@
         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/TestUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
index 1702875..3b82cb7 100644
--- 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
@@ -20,6 +20,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import java.util.function.BooleanSupplier;
+
 public class TestUtils {
     private static final String TAG = "CtsTestUtils";
 
@@ -93,5 +95,36 @@
             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
index 639dc9c..cca2652 100644
--- 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
@@ -63,4 +63,15 @@
         }
         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
new file mode 100644
index 0000000..a61ffc1
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java
@@ -0,0 +1,50 @@
+/*
+ * 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/ThrowingRunnable.java b/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java
new file mode 100644
index 0000000..0588cff
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/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 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/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
index 798e016..99ea3c0 100644
--- 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
@@ -25,6 +25,7 @@
 
 import com.google.common.collect.ImmutableList;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
@@ -147,6 +148,21 @@
     }
 
     @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)
@@ -191,6 +207,7 @@
         final SafeCleanerRule rule = new SafeCleanerRule()
                 .run(mGoodGuyRunner1)
                 .add(mGoodGuyExtraExceptions1)
+                .add(mRuntimeException)
                 .add(() -> {
                     return ImmutableList.of(extra1, extra2);
                 })
@@ -210,7 +227,8 @@
                 SafeCleanerRule.MultipleExceptions.class,
                 () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
         assertThat(actualException.getThrowables())
-                .containsExactly(testException, error1, error2, extra1, extra2, extra3)
+                .containsExactly(testException, mRuntimeException, error1, error2, extra1, extra2,
+                        extra3)
                 .inOrder();
         verify(mGoodGuyRunner1).run();
         verify(mGoodGuyRunner2).run();
@@ -218,6 +236,27 @@
     }
 
     @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");
diff --git a/hostsidetests/angle/Android.mk b/hostsidetests/angle/Android.mk
new file mode 100644
index 0000000..e54985d
--- /dev/null
+++ b/hostsidetests/angle/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := assets/
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_MODULE := CtsAngleIntegrationHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/angle/AndroidTest.xml b/hostsidetests/angle/AndroidTest.xml
new file mode 100644
index 0000000..c72618a
--- /dev/null
+++ b/hostsidetests/angle/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<configuration description="Config for CtsAngleIntegrationHostTestCases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="graphics" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAngleDriverTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsAngleIntegrationHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/angle/app/Android.mk b/hostsidetests/angle/app/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/angle/app/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/angle/app/common/Android.mk b/hostsidetests/angle/app/common/Android.mk
new file mode 100644
index 0000000..1399707
--- /dev/null
+++ b/hostsidetests/angle/app/common/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := AngleIntegrationTestCommon
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as test artifact for gts, ats
+LOCAL_COMPATIBILITY_SUITE := gts ats
+LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
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
new file mode 100644
index 0000000..e167176
--- /dev/null
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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
new file mode 100644
index 0000000..d0fbe58
--- /dev/null
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
@@ -0,0 +1,129 @@
+/*
+ * 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 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;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+/**
+ * GLES View Class to get access to GLES20.glGetString()
+ */
+
+@TargetApi(VERSION_CODES.GINGERBREAD)
+public class GlesView extends SurfaceView implements SurfaceHolder.Callback2 {
+
+    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();
+    }
+
+    @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
+    private void createEGL() {
+        int[] version = new int[2];
+        display = EGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        EGL.eglInitialize(display, version);
+
+        int[] numConfigs = new int[1];
+        EGL.eglGetConfigs(display, null, 0, numConfigs);
+        EGLConfig[] allConfigs = new EGLConfig[numConfigs[0]];
+        EGL.eglGetConfigs(display, allConfigs, numConfigs[0], numConfigs);
+
+        int[] configAttrib =
+            new int[] {
+                EGL10.EGL_RENDERABLE_TYPE,
+                EGL14.EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_SURFACE_TYPE,
+                EGL10.EGL_WINDOW_BIT,
+                EGL10.EGL_RED_SIZE,
+                8,
+                EGL10.EGL_GREEN_SIZE,
+                8,
+                EGL10.EGL_BLUE_SIZE,
+                8,
+                EGL10.EGL_ALPHA_SIZE,
+                8,
+                EGL10.EGL_DEPTH_SIZE,
+                16,
+                EGL_STENCIL_SIZE,
+                0,
+                EGL10.EGL_NONE
+            };
+
+        EGLConfig[] selectedConfig = new EGLConfig[1];
+        EGL.eglChooseConfig(display, configAttrib, selectedConfig, 1, numConfigs);
+        if (selectedConfig[0] != null) {
+            eglConfig = selectedConfig[0];
+            Log.i(TAG, "Found matching EGL config");
+        } else {
+            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);
+        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));
+        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;
+    }
+}
diff --git a/hostsidetests/angle/app/driverTest/Android.mk b/hostsidetests/angle/app/driverTest/Android.mk
new file mode 100644
index 0000000..37a1140
--- /dev/null
+++ b/hostsidetests/angle/app/driverTest/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-java-files-under, ../common)
+
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsAngleDriverTestCases
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts
+
+LOCAL_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test AngleIntegrationTestCommon
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/angle/app/driverTest/AndroidManifest.xml b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
new file mode 100755
index 0000000..be800a1
--- /dev/null
+++ b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.angleIntegrationTest.driverTest">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.angleIntegrationTest.driverTest" />
+
+</manifest>
+
+
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
new file mode 100644
index 0000000..ca81946
--- /dev/null
+++ b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.driverTest;
+
+import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
+import com.android.angleIntegrationTest.common.GlesView;
+
+import static org.junit.Assert.fail;
+import android.content.Context;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.Override;
+
+@RunWith(AndroidJUnit4.class)
+public class AngleDriverTestActivity {
+
+    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();
+
+        while(renderer.length() == 0) {
+            renderer = glesView.getRenderer();
+        }
+
+        if (angleEnabled) {
+            if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+                fail("Failure - ANGLE was not loaded: '" + renderer + "'");
+            }
+        } else {
+            if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+                fail("Failure - ANGLE was loaded: '" + renderer + "'");
+            }
+        }
+
+    }
+
+    @Test
+    public void testUseDefaultDriver() throws Exception {
+        // The rules file does not enable ANGLE for this app
+        validateDeveloperOption(false);
+    }
+
+    @Test
+    public void testUseAngleDriver() throws Exception {
+        validateDeveloperOption(true);
+    }
+
+    @Test
+    public void testUseNativeDriver() throws Exception {
+        validateDeveloperOption(false);
+    }
+}
diff --git a/hostsidetests/angle/app/driverTestSecondary/Android.mk b/hostsidetests/angle/app/driverTestSecondary/Android.mk
new file mode 100644
index 0000000..d361192
--- /dev/null
+++ b/hostsidetests/angle/app/driverTestSecondary/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-java-files-under, ../common)
+
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsAngleDriverTestCasesSecondary
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts
+
+LOCAL_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test AngleIntegrationTestCommon
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
new file mode 100755
index 0000000..bd2739d
--- /dev/null
+++ b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.angleIntegrationTest.driverTestSecondary">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary" />
+
+</manifest>
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
new file mode 100644
index 0000000..0ecd766
--- /dev/null
+++ b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.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.angleIntegrationTest.driverTestSecondary;
+
+import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
+import com.android.angleIntegrationTest.common.GlesView;
+
+import static org.junit.Assert.fail;
+import android.content.Context;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.Override;
+
+@RunWith(AndroidJUnit4.class)
+public class AngleDriverTestActivity {
+
+    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();
+
+        if (angleEnabled) {
+            if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+                fail("Failure - ANGLE was not loaded: '" + renderer + "'");
+            }
+        } else {
+            if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+                fail("Failure - ANGLE was loaded: '" + renderer + "'");
+            }
+        }
+
+    }
+
+    @Test
+    public void testUseDefaultDriver() throws Exception {
+        // The rules file does not enable ANGLE for this app
+        validateDeveloperOption(false);
+    }
+
+    @Test
+    public void testUseAngleDriver() throws Exception {
+        validateDeveloperOption(true);
+    }
+
+    @Test
+    public void testUseNativeDriver() throws Exception {
+        validateDeveloperOption(false);
+    }
+}
diff --git a/hostsidetests/angle/assets/emptyRules.json b/hostsidetests/angle/assets/emptyRules.json
new file mode 100644
index 0000000..77fa0c0
--- /dev/null
+++ b/hostsidetests/angle/assets/emptyRules.json
@@ -0,0 +1,8 @@
+{
+  "Rules":[
+    {
+      "Rule":"Default Rule (i.e. use native driver)",
+      "UseANGLE":false
+    }
+  ]
+}
diff --git a/hostsidetests/angle/assets/enableAngleRules.json b/hostsidetests/angle/assets/enableAngleRules.json
new file mode 100644
index 0000000..90f4893
--- /dev/null
+++ b/hostsidetests/angle/assets/enableAngleRules.json
@@ -0,0 +1,17 @@
+{
+  "Rules":[
+    {
+      "Rule":"Default Rule (i.e. use native driver)",
+      "UseANGLE":false
+    },
+    {
+      "Rule":"Supported application(s) (e.g. Maps on Google devices)",
+      "UseANGLE":true,
+      "Applications":[
+        {
+          "AppName":"com.android.angleIntegrationTest.driverTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
new file mode 100644
index 0000000..a5d2262
--- /dev/null
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -0,0 +1,126 @@
+/*
+ * 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.angle.cts;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.PackageInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CtsAngleCommon {
+    // 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";
+    static final String SETTINGS_GLOBAL_DRIVER_VALUES = "angle_gl_driver_selection_values";
+    static final String SETTINGS_GLOBAL_WHITELIST = "angle_whitelist";
+
+    // System Properties
+    static final String PROPERTY_GFX_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
+    static final String PROPERTY_BUILD_TYPE = "ro.build.type";
+    static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
+    static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
+    static final String PROPERTY_TEMP_RULES_FILE = "debug.angle.rules";
+
+    // Rules File
+    static final String DEVICE_TEMP_RULES_FILE_DIRECTORY = "/data/local/tmp";
+    static final String DEVICE_TEMP_RULES_FILE_FILENAME = "a4a_rules.json";
+    static final String DEVICE_TEMP_RULES_FILE_PATH = DEVICE_TEMP_RULES_FILE_DIRECTORY + "/" + DEVICE_TEMP_RULES_FILE_FILENAME;
+
+    // ANGLE
+    static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleIntegrationTest.driverTest";
+    static final String ANGLE_DRIVER_TEST_SEC_PKG = "com.android.angleIntegrationTest.driverTestSecondary";
+    static final String ANGLE_DRIVER_TEST_CLASS = "AngleDriverTestActivity";
+    static final String ANGLE_DRIVER_TEST_DEFAULT_METHOD = "testUseDefaultDriver";
+    static final String ANGLE_DRIVER_TEST_ANGLE_METHOD = "testUseAngleDriver";
+    static final String ANGLE_DRIVER_TEST_NATIVE_METHOD = "testUseNativeDriver";
+    static final String ANGLE_DRIVER_TEST_APP = "CtsAngleDriverTestCases.apk";
+    static final String ANGLE_DRIVER_TEST_SEC_APP = "CtsAngleDriverTestCasesSecondary.apk";
+    static final String ANGLE_DRIVER_TEST_ACTIVITY =
+            ANGLE_DRIVER_TEST_PKG + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
+    static final String ANGLE_DRIVER_TEST_SEC_ACTIVITY =
+            ANGLE_DRIVER_TEST_SEC_PKG + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
+    static final String ANGLE_MAIN_ACTIVTY = "android.app.action.ANGLE_FOR_ANDROID";
+
+    enum OpenGlDriverChoice {
+        DEFAULT,
+        NATIVE,
+        ANGLE
+    }
+
+    static final Map<OpenGlDriverChoice, String> sDriverGlobalSettingMap = buildDriverGlobalSettingMap();
+    static Map<OpenGlDriverChoice, String> buildDriverGlobalSettingMap() {
+        Map<OpenGlDriverChoice, String> map = new HashMap<>();
+        map.put(OpenGlDriverChoice.DEFAULT, "default");
+        map.put(OpenGlDriverChoice.ANGLE, "angle");
+        map.put(OpenGlDriverChoice.NATIVE, "native");
+
+        return map;
+    }
+
+    static final Map<OpenGlDriverChoice, String> sDriverTestMethodMap = buildDriverTestMethodMap();
+    static Map<OpenGlDriverChoice, String> buildDriverTestMethodMap() {
+        Map<OpenGlDriverChoice, String> map = new HashMap<>();
+        map.put(OpenGlDriverChoice.DEFAULT, ANGLE_DRIVER_TEST_DEFAULT_METHOD);
+        map.put(OpenGlDriverChoice.ANGLE, ANGLE_DRIVER_TEST_ANGLE_METHOD);
+        map.put(OpenGlDriverChoice.NATIVE, ANGLE_DRIVER_TEST_NATIVE_METHOD);
+
+        return map;
+    }
+
+    static void clearSettings(ITestDevice device) throws Exception {
+        device.setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_ALL_USE_ANGLE, "0");
+        device.setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS, "\"\"");
+        device.setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES, "\"\"");
+        device.setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_WHITELIST, "\"\"");
+        CtsAngleCommon.setProperty(device, CtsAngleCommon.PROPERTY_TEMP_RULES_FILE, "\"\"");
+    }
+
+    static boolean isAngleLoadable(ITestDevice device) throws Exception {
+        String angleSupported = device.getProperty(PROPERTY_GFX_ANGLE_SUPPORTED);
+        String propDisablePreloading = device.getProperty(PROPERTY_DISABLE_OPENGL_PRELOADING);
+        String propGfxDriver = device.getProperty(PROPERTY_GFX_DRIVER);
+
+        // Make sure ANGLE exists on the device
+        if((angleSupported == null) || (angleSupported.equals("false"))) {
+            return false;
+        }
+
+        // This logic is attempting to mimic ZygoteInit.java::ZygoteInit#preloadOpenGL()
+        if (((propDisablePreloading != null) && propDisablePreloading.equals("false")) &&
+                ((propGfxDriver == null) || propGfxDriver.isEmpty())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    static void startActivity(ITestDevice device, String action) throws Exception {
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        device.executeShellCommand("am start -S -W -a \"" + action + "\"");
+    }
+
+    static void stopPackage(ITestDevice device, String pkgName) throws Exception {
+        device.executeShellCommand("am force-stop " + pkgName);
+    }
+
+    /**
+     * Work around the fact that INativeDevice.enableAdbRoot() is not supported.
+     */
+    static void setProperty(ITestDevice device, String property, String value) throws Exception {
+        device.executeShellCommand("setprop " + property + " " + value);
+    }
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
new file mode 100644
index 0000000..385da2a
--- /dev/null
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -0,0 +1,457 @@
+/*
+ * 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.angle.cts;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import com.android.ddmlib.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests ANGLE Developer Option Opt-In/Out functionality.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsAngleDeveloperOptionHostTest extends BaseHostJUnit4Test implements IDeviceTest {
+
+    private static final String TAG = CtsAngleDeveloperOptionHostTest.class.getSimpleName();
+
+    private String getDevOption(String devOption) throws Exception {
+        return getDevice().getSetting("global", devOption);
+    }
+
+    private void setAndValidateAngleDevOptionPkgDriver(String pkgName, String driverValue) throws Exception {
+        CLog.logAndDisplay(LogLevel.INFO, "Updating Global.Settings: pkgName = '" +
+                pkgName + "', driverValue = '" + driverValue + "'");
+
+        getDevice().setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS, pkgName);
+        getDevice().setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES, driverValue);
+
+        String devOption = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+        Assert.assertEquals(
+                "Developer option '" + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS +
+                        "' was not set correctly: '" + devOption + "'",
+                pkgName, devOption);
+
+        devOption = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+        Assert.assertEquals(
+                "Developer option '" + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES +
+                        "' was not set correctly: '" + driverValue + "'",
+                driverValue, devOption);
+    }
+
+    private void setAndValidatePkgDriver(String pkgName, CtsAngleCommon.OpenGlDriverChoice driver) throws Exception {
+        CtsAngleCommon.stopPackage(getDevice(), pkgName);
+
+        setAndValidateAngleDevOptionPkgDriver(pkgName, CtsAngleCommon.sDriverGlobalSettingMap.get(driver));
+
+        CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+        CLog.logAndDisplay(LogLevel.INFO, "Validating driver selection (" +
+                driver + ") with method '" + CtsAngleCommon.sDriverTestMethodMap.get(driver) + "'");
+
+        runDeviceTests(
+                pkgName,
+                pkgName + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.sDriverTestMethodMap.get(driver));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        CtsAngleCommon.clearSettings(getDevice());
+
+        CtsAngleCommon.stopPackage(getDevice(), CtsAngleCommon.ANGLE_DRIVER_TEST_PKG);
+        CtsAngleCommon.stopPackage(getDevice(), CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CtsAngleCommon.clearSettings(getDevice());
+    }
+
+    /**
+     * Test ANGLE is loaded when the 'Use ANGLE for all' Developer Option is enabled.
+     */
+    @Test
+    public void testEnableAngleForAll() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.DEFAULT));
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.DEFAULT));
+
+        getDevice().setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_ALL_USE_ANGLE, "1");
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+    }
+
+    /**
+     * Test ANGLE is not loaded when the Developer Option is set to 'default'.
+     */
+    @Test
+    public void testUseDefaultDriver() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.DEFAULT));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_DEFAULT_METHOD);
+    }
+
+    /**
+     * Test ANGLE is loaded when the Developer Option is set to 'angle'.
+     */
+    @Test
+    public void testUseAngleDriver() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+    }
+
+    /**
+     * Test ANGLE is not loaded when the Developer Option is set to 'native'.
+     */
+    @Test
+    public void testUseNativeDriver() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.NATIVE));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_NATIVE_METHOD);
+    }
+
+    /**
+     * Test ANGLE is not loaded for any apps when the Developer Option list lengths mismatch.
+     */
+    @Test
+    public void testSettingsLengthMismatch() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," +
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_DEFAULT_METHOD);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_DEFAULT_METHOD);
+    }
+
+    /**
+     * Test ANGLE is not loaded when the Developer Option is invalid.
+     */
+    @Test
+    public void testUseInvalidDriver() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG, "timtim");
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_DEFAULT_METHOD);
+    }
+
+    /**
+     * Test the Developer Options can be updated to/from each combination.
+     */
+    @Test
+    public void testUpdateDriverValues() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        for (CtsAngleCommon.OpenGlDriverChoice firstDriver : CtsAngleCommon.OpenGlDriverChoice.values()) {
+            for (CtsAngleCommon.OpenGlDriverChoice secondDriver : CtsAngleCommon.OpenGlDriverChoice.values()) {
+                CLog.logAndDisplay(LogLevel.INFO, "Testing updating Global.Settings from '" +
+                        firstDriver + "' to '" + secondDriver + "'");
+
+                setAndValidatePkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG, firstDriver);
+                setAndValidatePkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG, secondDriver);
+            }
+        }
+    }
+
+    /**
+     * Test different PKGs can have different developer option values.
+     * Primary: ANGLE
+     * Secondary: Native
+     */
+    @Test
+    public void testMultipleDevOptionsAngleNative() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," +
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE) + "," +
+                        CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.NATIVE));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_NATIVE_METHOD);
+    }
+
+    /**
+     * Test the Developer Options for a second PKG can be updated to/from each combination.
+     */
+    @Test
+    public void testMultipleUpdateDriverValues() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        // Set the first PKG to always use ANGLE
+        setAndValidatePkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG, CtsAngleCommon.OpenGlDriverChoice.ANGLE);
+
+        for (CtsAngleCommon.OpenGlDriverChoice firstDriver : CtsAngleCommon.OpenGlDriverChoice.values()) {
+            for (CtsAngleCommon.OpenGlDriverChoice secondDriver : CtsAngleCommon.OpenGlDriverChoice.values()) {
+                CLog.logAndDisplay(LogLevel.INFO, "Testing updating Global.Settings from '" +
+                        firstDriver + "' to '" + secondDriver + "'");
+
+                setAndValidateAngleDevOptionPkgDriver(
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," + CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                        CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE) + "," +
+                                CtsAngleCommon.sDriverGlobalSettingMap.get(firstDriver));
+
+                CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+                CLog.logAndDisplay(LogLevel.INFO, "Validating driver selection (" +
+                        firstDriver + ") with method '" + CtsAngleCommon.sDriverTestMethodMap.get(firstDriver) + "'");
+
+                runDeviceTests(
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                        CtsAngleCommon.sDriverTestMethodMap.get(firstDriver));
+
+                setAndValidateAngleDevOptionPkgDriver(
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," + CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                        CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE) + "," +
+                                CtsAngleCommon.sDriverGlobalSettingMap.get(secondDriver));
+
+                CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+                CLog.logAndDisplay(LogLevel.INFO, "Validating driver selection (" +
+                        secondDriver + ") with method '" + CtsAngleCommon.sDriverTestMethodMap.get(secondDriver) + "'");
+
+                runDeviceTests(
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                        CtsAngleCommon.sDriverTestMethodMap.get(secondDriver));
+
+                // Make sure the first PKG's driver value was not modified
+                CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+                String devOptionPkg = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+                String devOptionValue = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+                CLog.logAndDisplay(LogLevel.INFO, "Validating: PKG name = '" +
+                        devOptionPkg + "', driver value = '" + devOptionValue + "'");
+
+                runDeviceTests(
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                        CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+            }
+        }
+    }
+
+    /**
+     * Test setting a driver to 'default' does not keep the value in the settings when the ANGLE
+     * activity runs and cleans things up.
+     */
+    @Test
+    public void testDefaultNotInSettings() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.DEFAULT));
+
+        // Install the package so the setting isn't removed because the package isn't present.
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+        String devOptionPkg = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+        String devOptionValue = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+        CLog.logAndDisplay(LogLevel.INFO, "Validating: PKG name = '" +
+                devOptionPkg + "', driver value = '" + devOptionValue + "'");
+
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS + " = '" + devOptionPkg + "'",
+                "", devOptionPkg);
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES + " = '" + devOptionValue + "'",
+                "", devOptionValue);
+    }
+
+    /**
+     * Test uninstalled PKGs have their settings removed.
+     */
+    @Test
+    public void testUninstalledPkgsNotInSettings() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        uninstallPackage(getDevice(), CtsAngleCommon.ANGLE_DRIVER_TEST_PKG);
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.NATIVE));
+
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+        String devOptionPkg = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+        String devOptionValue = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+        CLog.logAndDisplay(LogLevel.INFO, "Validating: PKG name = '" +
+                devOptionPkg + "', driver value = '" + devOptionValue + "'");
+
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS + " = '" + devOptionPkg + "'",
+                "", devOptionPkg);
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES + " = '" + devOptionValue + "'",
+                "", devOptionValue);
+    }
+
+    /**
+     * Test different PKGs can have different developer option values.
+     * Primary: ANGLE
+     * Secondary: Default
+     *
+     * Verify the PKG set to 'default' is removed from the settings.
+     */
+    @Test
+    public void testMultipleDevOptionsAngleDefault() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," + CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE) + "," +
+                        CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.DEFAULT));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+        String devOption = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS + " = '" + devOption + "'",
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG, devOption);
+
+        devOption = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES + " = '" + devOption + "'",
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE), devOption);
+    }
+
+    /**
+     * Test different PKGs can have different developer option values.
+     * Primary: ANGLE
+     * Secondary: Default
+     *
+     * Verify the uninstalled PKG is removed from the settings.
+     */
+    @Test
+    public void testMultipleDevOptionsAngleNativeUninstall() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        setAndValidateAngleDevOptionPkgDriver(CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," + CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.ANGLE) + "," +
+                        CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.NATIVE));
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        // Run the ANGLE activity so it'll clear up any 'default' settings.
+        CtsAngleCommon.startActivity(getDevice(), CtsAngleCommon.ANGLE_MAIN_ACTIVTY);
+
+        String devOptionPkg = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS);
+        String devOptionValue = getDevOption(CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES);
+        CLog.logAndDisplay(LogLevel.INFO, "Validating: PKG name = '" +
+                devOptionPkg + "', driver value = '" + devOptionValue + "'");
+
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_PKGS + " = '" + devOptionPkg + "'",
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG, devOptionPkg);
+        Assert.assertEquals(
+                "Invalid developer option: " + CtsAngleCommon.SETTINGS_GLOBAL_DRIVER_VALUES + " = '" + devOptionValue + "'",
+                CtsAngleCommon.sDriverGlobalSettingMap.get(CtsAngleCommon.OpenGlDriverChoice.NATIVE), devOptionValue);
+    }
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
new file mode 100644
index 0000000..87281b9
--- /dev/null
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.angle.cts;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/**
+ * Tests ANGLE Rules File Opt-In/Out functionality.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsAngleRulesFileTest extends BaseHostJUnit4Test {
+
+    private final String TAG = this.getClass().getSimpleName();
+
+    File mRulesFile;
+
+    // Rules Files
+    private static final String RULES_FILE_EMPTY = "emptyRules.json";
+    private static final String RULES_FILE_ENABLE_ANGLE = "enableAngleRules.json";
+
+    private void pushRulesFile(String hostFilename) throws Exception {
+        byte[] rulesFileBytes =
+                ByteStreams.toByteArray(getClass().getResourceAsStream("/" + hostFilename));
+
+        Assert.assertTrue("Loaded empty rules file", rulesFileBytes.length > 0); // sanity check
+        mRulesFile = File.createTempFile("rulesFile", "tempRules.json");
+        Files.write(rulesFileBytes, mRulesFile);
+
+        Assert.assertTrue(getDevice().pushFile(mRulesFile, CtsAngleCommon.DEVICE_TEMP_RULES_FILE_PATH));
+
+        CtsAngleCommon.setProperty(getDevice(), CtsAngleCommon.PROPERTY_TEMP_RULES_FILE,
+                CtsAngleCommon.DEVICE_TEMP_RULES_FILE_PATH);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        CtsAngleCommon.clearSettings(getDevice());
+
+        // Application must be whitelisted to load temp rules
+        getDevice().setSetting("global", CtsAngleCommon.SETTINGS_GLOBAL_WHITELIST,
+            CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "," +
+            CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        CtsAngleCommon.clearSettings(getDevice());
+
+        FileUtil.deleteFile(mRulesFile);
+    }
+
+    /**
+     * Test ANGLE is not loaded when an empty rules file is used.
+     */
+    @Test
+    public void testEmptyRulesFile() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        pushRulesFile(RULES_FILE_EMPTY);
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_NATIVE_METHOD);
+    }
+
+    /**
+     * Test ANGLE is loaded for only the PKG the rules file specifies.
+     */
+    @Test
+    public void testEnableAngleRulesFile() throws Exception {
+        Assume.assumeTrue(CtsAngleCommon.isAngleLoadable(getDevice()));
+
+        pushRulesFile(RULES_FILE_ENABLE_ANGLE);
+
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_APP, new String[0]);
+        installPackage(CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_APP, new String[0]);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_ANGLE_METHOD);
+
+        runDeviceTests(
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_SEC_PKG + "." + CtsAngleCommon.ANGLE_DRIVER_TEST_CLASS,
+                CtsAngleCommon.ANGLE_DRIVER_TEST_NATIVE_METHOD);
+    }
+}
diff --git a/hostsidetests/appbinding/Android.mk b/hostsidetests/appbinding/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/hostsidetests/appbinding/Android.mk
@@ -0,0 +1,17 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appbinding/app/Android.mk b/hostsidetests/appbinding/app/Android.mk
new file mode 100644
index 0000000..94a0e94
--- /dev/null
+++ b/hostsidetests/appbinding/app/Android.mk
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+include $(call all-subdir-makefiles)
diff --git a/hostsidetests/appbinding/app/app1/Android.mk b/hostsidetests/appbinding/app/app1/Android.mk
new file mode 100644
index 0000000..05e8920
--- /dev/null
+++ b/hostsidetests/appbinding/app/app1/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app1/AndroidManifest.xml b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
new file mode 100644
index 0000000..b3b55e8
--- /dev/null
+++ b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
@@ -0,0 +1,86 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service. -->
+        <service
+            android:name=".MyService"
+            android:exported="true"
+            android:process=":persistent"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE">
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/app2/Android.mk b/hostsidetests/appbinding/app/app2/Android.mk
new file mode 100644
index 0000000..8246f03
--- /dev/null
+++ b/hostsidetests/appbinding/app/app2/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app2/AndroidManifest.xml b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
new file mode 100644
index 0000000..7890c6f
--- /dev/null
+++ b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
@@ -0,0 +1,86 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service. -->
+        <service
+            android:name=".MyService2"
+            android:exported="false"
+            android:process=":persistent"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/app3/Android.mk b/hostsidetests/appbinding/app/app3/Android.mk
new file mode 100644
index 0000000..682f31d
--- /dev/null
+++ b/hostsidetests/appbinding/app/app3/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app3/AndroidManifest.xml b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
new file mode 100644
index 0000000..ec7a4bf
--- /dev/null
+++ b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
@@ -0,0 +1,85 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service. -->
+        <service
+            android:name=".MyService"
+            android:exported="false"
+            android:process=":persistent">
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/app4/Android.mk b/hostsidetests/appbinding/app/app4/Android.mk
new file mode 100644
index 0000000..a43d031
--- /dev/null
+++ b/hostsidetests/appbinding/app/app4/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app4/AndroidManifest.xml b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
new file mode 100644
index 0000000..5df5fa8
--- /dev/null
+++ b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
@@ -0,0 +1,95 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service. -->
+        <service
+            android:name=".MyService"
+            android:exported="true"
+            android:process=":persistent"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+        <service
+            android:name=".MyService2"
+            android:exported="true"
+            android:process=":persistent"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/app5/Android.mk b/hostsidetests/appbinding/app/app5/Android.mk
new file mode 100644
index 0000000..063777a
--- /dev/null
+++ b/hostsidetests/appbinding/app/app5/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService5
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app5/AndroidManifest.xml b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
new file mode 100644
index 0000000..ccfcf22
--- /dev/null
+++ b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- No target services. -->
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/app6/Android.mk b/hostsidetests/appbinding/app/app6/Android.mk
new file mode 100644
index 0000000..007de00
--- /dev/null
+++ b/hostsidetests/appbinding/app/app6/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingService6
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/app6/AndroidManifest.xml b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
new file mode 100644
index 0000000..abea148
--- /dev/null
+++ b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
@@ -0,0 +1,85 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service found, but doesn't have :process. -->
+        <service
+            android:name=".MyService2"
+            android:exported="false"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name=".MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name=".sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name=".sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/appb/Android.mk b/hostsidetests/appbinding/app/appb/Android.mk
new file mode 100644
index 0000000..dd4c5b0
--- /dev/null
+++ b/hostsidetests/appbinding/app/appb/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppBindingServiceB
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/appbinding/app/appb/AndroidManifest.xml b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
new file mode 100644
index 0000000..3a1c5d3
--- /dev/null
+++ b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
@@ -0,0 +1,86 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.appbinding.app.b" >
+
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- Target to-be-bound service. -->
+        <service
+            android:name="com.android.cts.appbinding.app.MyService"
+            android:exported="true"
+            android:process=":persistent"
+            android:permission="android.permission.BIND_SMS_APP_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telephony.action.SMS_APP_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <!-- Components needed to be an SMS app -->
+        <activity android:name="com.android.cts.appbinding.app.MySendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
+        </activity>
+
+        <receiver android:name="com.android.cts.appbinding.app.sms.MySmsReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="com.android.cts.appbinding.app.sms.MyMmsReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+
+        </receiver>
+
+        <service android:name="com.android.cts.appbinding.app.sms.MyRespondService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadminservice" />
+</manifest>
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService.java
new file mode 100644
index 0000000..5df2fa9
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.appbinding.app;
+
+import android.app.SmsAppService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class MyService extends SmsAppService {
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+
+        if (args.length > 0 && "crash".equals(args[0])) {
+            // Trigger app crash
+            writer.println("Crashing...");
+            (new Thread(() -> {throw new RuntimeException();})).start();
+            return;
+        }
+        writer.print("Package=[" + getPackageName() + "]");
+        writer.println("Class=[" + this.getClass().getName() + "]");
+    }
+}
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService2.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService2.java
new file mode 100644
index 0000000..c37580d
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/MyService2.java
@@ -0,0 +1,19 @@
+/*
+ * 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.appbinding.app;
+
+public class MyService2 extends MyService {
+}
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyMmsReceiver.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyMmsReceiver.java
new file mode 100644
index 0000000..92a9c5c
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyMmsReceiver.java
@@ -0,0 +1,27 @@
+/*
+ * 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.appbinding.app.sms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class MyMmsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+    }
+}
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyRespondService.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyRespondService.java
new file mode 100644
index 0000000..b092e67
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MyRespondService.java
@@ -0,0 +1,27 @@
+/*
+ * 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.appbinding.app.sms;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class MyRespondService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySendToActivity.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySendToActivity.java
new file mode 100644
index 0000000..e96c443
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySendToActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.appbinding.app.sms;
+
+import android.app.Activity;
+
+public class MySendToActivity extends Activity {
+}
diff --git a/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySmsReceiver.java b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySmsReceiver.java
new file mode 100644
index 0000000..bdd88e8
--- /dev/null
+++ b/hostsidetests/appbinding/app/src/com/android/cts/appbinding/app/sms/MySmsReceiver.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 com.android.cts.appbinding.app.sms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class MySmsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+    }
+}
diff --git a/hostsidetests/appbinding/hostside/Android.mk b/hostsidetests/appbinding/hostside/Android.mk
new file mode 100644
index 0000000..aa4a7dc
--- /dev/null
+++ b/hostsidetests/appbinding/hostside/Android.mk
@@ -0,0 +1,48 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsAppBindingHostTestCases
+
+LOCAL_CTS_TEST_PACKAGE := android.appbinding
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := \
+    tools-common-prebuilt \
+    cts-tradefed \
+    tradefed \
+    compatibility-host-util \
+    guava
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_TARGET_REQUIRED_MODULES := \
+    CtsAppBindingService1 \
+    CtsAppBindingService2 \
+    CtsAppBindingService3 \
+    CtsAppBindingService4 \
+    CtsAppBindingService5 \
+    CtsAppBindingService6
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appbinding/hostside/AndroidTest.xml b/hostsidetests/appbinding/hostside/AndroidTest.xml
new file mode 100644
index 0000000..4d3b91a
--- /dev/null
+++ b/hostsidetests/appbinding/hostside/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<configuration description="Config for CTS Incident host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am wait-for-broadcast-idle" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsAppBindingHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/appbinding/hostside/src/com/android/cts/appbinding/AppBindingHostTest.java b/hostsidetests/appbinding/hostside/src/com/android/cts/appbinding/AppBindingHostTest.java
new file mode 100644
index 0000000..dd4c438
--- /dev/null
+++ b/hostsidetests/appbinding/hostside/src/com/android/cts/appbinding/AppBindingHostTest.java
@@ -0,0 +1,525 @@
+/*
+ * 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.appbinding;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class AppBindingHostTest extends DeviceTestCase implements IBuildReceiver {
+
+    private static final boolean SKIP_UNINSTALL = false;
+
+    private static final String APK_1 = "CtsAppBindingService1.apk";
+    private static final String APK_2 = "CtsAppBindingService2.apk";
+    private static final String APK_3 = "CtsAppBindingService3.apk";
+    private static final String APK_4 = "CtsAppBindingService4.apk";
+    private static final String APK_5 = "CtsAppBindingService5.apk";
+    private static final String APK_6 = "CtsAppBindingService6.apk";
+    private static final String APK_B = "CtsAppBindingServiceB.apk";
+
+    private static final String PACKAGE_A = "com.android.cts.appbinding.app";
+    private static final String PACKAGE_B = "com.android.cts.appbinding.app.b";
+
+    private static final String PACKAGE_A_PROC = PACKAGE_A + ":persistent";
+
+    private static final String APP_BINDING_SETTING = "app_binding_constants";
+
+    private static final String SERVICE_1 = "com.android.cts.appbinding.app.MyService";
+    private static final String SERVICE_2 = "com.android.cts.appbinding.app.MyService2";
+
+    private IBuildInfo mCtsBuild;
+
+    private static final int USER_SYSTEM = 0;
+
+    private static final int DEFAULT_TIMEOUT_SEC = 30;
+
+    private interface ThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    private void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
+            throws Exception {
+        CLog.d("Installing app " + appFileName + " for user " + userId);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        String result = getDevice().installPackageForUser(
+                buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t");
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    private String runCommand(String command) throws Exception {
+        return runCommand(command, "", true);
+    }
+
+    private String runCommand(String command, String expectedOutputPattern) throws Exception {
+        return runCommand(command, expectedOutputPattern, true);
+    }
+
+    private String runCommandAndNotMatch(String command, String expectedOutputPattern)
+            throws Exception {
+        return runCommand(command, expectedOutputPattern, false);
+    }
+
+    private String runCommand(String command, String expectedOutputPattern,
+            boolean shouldMatch) throws Exception {
+        CLog.d("Executing command: " + command);
+        final String output = getDevice().executeShellCommand(command);
+
+        CLog.d("Output:\n"
+                + "====================\n"
+                + output
+                + "====================");
+
+        final Pattern pat = Pattern.compile(
+                expectedOutputPattern, Pattern.MULTILINE | Pattern.COMMENTS);
+        if (pat.matcher(output.trim()).find() != shouldMatch) {
+            fail("Output from \"" + command + "\" "
+                    + (shouldMatch ? "didn't match" : "unexpectedly matched")
+                    + " \"" + expectedOutputPattern + "\"");
+        }
+        return output;
+    }
+
+    private String runCommandAndExtract(String command,
+            String startPattern, boolean startInclusive,
+            String endPattern, boolean endInclusive) throws Exception {
+        final String[] output = runCommand(command).split("\\n");
+        final StringBuilder sb = new StringBuilder();
+
+        final Pattern start = Pattern.compile(startPattern, Pattern.COMMENTS);
+        final Pattern end = Pattern.compile(endPattern, Pattern.COMMENTS);
+
+        boolean in = false;
+        for (String s : output) {
+            if (in) {
+                if (end.matcher(s.trim()).find()) {
+                    if (endInclusive) {
+                        sb.append(s);
+                        sb.append("\n");
+                    }
+                    break;
+                }
+                sb.append(s);
+                sb.append("\n");
+            } else {
+                if (start.matcher(s.trim()).find()) {
+                    if (startInclusive) {
+                        sb.append(s);
+                        sb.append("\n");
+                    }
+                    continue;
+                }
+                in = true;
+            }
+        }
+
+        return sb.toString();
+    }
+
+    private void updateConstants(String settings) throws Exception {
+        runCommand("settings put global " + APP_BINDING_SETTING + " '" + settings + "'");
+    }
+
+    private void setSmsApp(String pkg, int userId) throws Exception {
+        runCommand("cmd phone sms set-default-app --user " + userId + " " + pkg,
+                "^SMS \\s app \\s set \\s to \\s " + Pattern.quote(pkg) + "$");
+    }
+
+    private void uninstallTestApps() throws Exception {
+        if (SKIP_UNINSTALL) {
+            return;
+        }
+        getDevice().uninstallPackage(PACKAGE_A);
+        getDevice().uninstallPackage(PACKAGE_B);
+    }
+
+    private void runWithRetries(int timeoutSeconds, ThrowingRunnable r) throws Throwable {
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
+        Throwable lastThrowable = null;
+
+        int sleep = 200;
+        while (System.currentTimeMillis() < timeout) {
+            try {
+                r.run();
+                return;
+            } catch (Throwable th) {
+                lastThrowable = th;
+            }
+            Thread.sleep(sleep);
+            sleep = Math.min(1000, sleep * 2);
+        }
+        throw lastThrowable;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Reset to the default setting.
+        updateConstants(",");
+
+        uninstallTestApps();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallTestApps();
+
+        // Reset to the default setting.
+        updateConstants(",");
+
+        super.tearDown();
+    }
+
+    private void installAndCheckBound(String apk, String packageName,
+            String serviceClass, int userId) throws Throwable {
+        // Install
+        installAppAsUser(apk, true, userId);
+
+        // Set as the default app
+        setSmsApp(packageName, userId);
+
+        checkBound(packageName, serviceClass, userId);
+    }
+
+    private void checkBound(String packageName, String serviceClass, int userId) throws Throwable {
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys activity service " + packageName + "/" + serviceClass,
+                    Pattern.quote("[" + packageName + "]") + " .* "
+                    + Pattern.quote("[" + serviceClass + "]"));
+        });
+
+        // This should contain:
+        // "conn,0,[Default SMS app],PACKAGE,CLASS,bound,connected"
+
+        // The binding information is propagated asynchronously, so we need a retry here too.
+        // (Even though the activity manager said it's already bound.)
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^" + Pattern.quote("conn,[Default SMS app]," + userId + "," + packageName + ","
+                            + serviceClass + ",bound,connected,"));
+        });
+    }
+
+    private void installAndCheckNotBound(String apk, String packageName, int userId,
+            String expectedErrorPattern) throws Throwable {
+        // Install
+        installAppAsUser(apk, true, userId);
+
+        // Set as the default app
+        setSmsApp(packageName, userId);
+
+        checkNotBound(packageName, userId, expectedErrorPattern);
+    }
+
+    private void checkNotBound(String packageName, int userId,
+            String expectedErrorPattern) throws Throwable {
+        // This should contain:
+        // "finder,0,[Default SMS app],PACKAGE,null,ERROR-MESSAGE"
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^" + Pattern.quote("finder,[Default SMS app]," + userId + ","
+                            + packageName + ",null,") + ".*"
+                            + Pattern.quote(expectedErrorPattern) + ".*$");
+        });
+    }
+
+    private void assertOomAdjustment(String packageName, String processName, int oomAdj)
+            throws Exception {
+        final String output = runCommandAndExtract("dumpsys activity -a p " + packageName,
+                "\\sProcessRecord\\{.*\\:" + Pattern.quote(processName) + "\\/", false,
+                "^\\s*oom:", true);
+        /* Example:
+ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
+  All known processes:
+  *APP* UID 10196 ProcessRecord{ef7dd8f 29993:com.android.cts.appbinding.app:persistent/u0a196}
+    user #0 uid=10196 gids={50196, 20196, 9997}
+    mRequiredAbi=arm64-v8a instructionSet=null
+    dir=/data/app/com.android.cts.appbinding.app-zvJ1Z44jYKxm-K0HLBRtLA==/base.apk publicDir=/da...
+    packageList={com.android.cts.appbinding.app}
+    compat={560dpi}
+    thread=android.app.IApplicationThread$Stub$Proxy@a5181c
+    pid=29993 starting=false
+    lastActivityTime=-14s282ms lastPssTime=-14s316ms pssStatType=0 nextPssTime=+5s718ms
+    adjSeq=35457 lruSeq=0 lastPss=0.00 lastSwapPss=0.00 lastCachedPss=0.00 lastCachedSwapPss=0.00
+    procStateMemTracker: best=4 () / pending state=2 highest=2 1.0x
+    cached=false empty=true
+    oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
+    mCurSchedGroup=2 setSchedGroup=2 systemNoUi=false trimMemoryLevel=0
+    curProcState=4 mRepProcState=4 pssProcState=19 setProcState=4 lastStateTime=-14s282ms
+    reportedInteraction=true time=-14s284ms
+    startSeq=369
+    lastRequestedGc=-14s283ms lastLowMemory=-14s283ms reportLowMemory=false
+     Configuration={1.0 ?mcc?mnc [en_US] ldltr sw411dp w411dp h746dp 560dpi nrml long widecg ...
+     OverrideConfiguration={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ...
+     mLastReportedConfiguration={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ...
+    Services:
+      - ServiceRecord{383eb86 u0 com.android.cts.appbinding.app/.MyService}
+    Connected Providers:
+      - 54bfc25/com.android.providers.settings/.SettingsProvider->29993:com.android.cts....
+
+  Process LRU list (sorted by oom_adj, 50 total, non-act at 4, non-svc at 4):
+    Proc #10: prcp  F/ /BFGS trm: 0 29993:com.android.cts.appbinding.app:persistent/u0a196 (service)
+        com.android.cts.appbinding.app/.MyService<=Proc{1332:system/1000}
+         */
+        final Pattern pat = Pattern.compile("\\soom:\\s.* set=(\\d+)$", Pattern.MULTILINE);
+        final Matcher m = pat.matcher(output);
+        if (!m.find()) {
+            fail("Unable to fild the oom: line for process " + processName);
+        }
+        final String oom = m.group(1);
+        assertEquals("Unexpected oom adjustment:", String.valueOf(oomAdj), oom);
+    }
+
+    /**
+     * Install APK 1 and make it the default SMS app and make sure the service gets bound.
+     */
+    public void testSimpleBind1() throws Throwable {
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+    }
+
+    /**
+     * Install APK 2 and make it the default SMS app and make sure the service gets bound.
+     */
+    public void testSimpleBind2() throws Throwable {
+        installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, USER_SYSTEM);
+    }
+
+    /**
+     * Install APK B and make it the default SMS app and make sure the service gets bound.
+     */
+    public void testSimpleBindB() throws Throwable {
+        installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, USER_SYSTEM);
+    }
+
+    /**
+     * APK 3 doesn't have a valid service to be bound.
+     */
+    public void testSimpleNotBound3() throws Throwable {
+        installAndCheckNotBound(APK_3, PACKAGE_A, USER_SYSTEM,
+                "must be protected with android.permission.BIND_SMS_APP_SERVICE");
+    }
+
+    /**
+     * APK 4 doesn't have a valid service to be bound.
+     */
+    public void testSimpleNotBound4() throws Throwable {
+        installAndCheckNotBound(APK_4, PACKAGE_A, USER_SYSTEM, "More than one");
+    }
+
+    /**
+     * APK 5 doesn't have a valid service to be bound.
+     */
+    public void testSimpleNotBound5() throws Throwable {
+        installAndCheckNotBound(APK_5, PACKAGE_A, USER_SYSTEM,
+                "Service with android.telephony.action.SMS_APP_SERVICE not found");
+    }
+
+    /**
+     * APK 6's service doesn't have android:process.
+     */
+    public void testSimpleNotBound6() throws Throwable {
+        installAndCheckNotBound(APK_6, PACKAGE_A, USER_SYSTEM,
+                "Service must not run on the main process");
+    }
+
+    /**
+     * Make sure when the SMS app gets updated, the service still gets bound correctly.
+     */
+    public void testUpgrade() throws Throwable {
+        // Replace existing package without uninstalling.
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+        installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, USER_SYSTEM);
+        installAndCheckNotBound(APK_3, PACKAGE_A, USER_SYSTEM,
+                "must be protected with android.permission.BIND_SMS_APP_SERVICE");
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+        installAndCheckNotBound(APK_4, PACKAGE_A, USER_SYSTEM, "More than one");
+    }
+
+    /**
+     * Make sure when the SMS app changes, the service still gets bound correctly.
+     */
+    public void testSwitchDefaultApp() throws Throwable {
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+        installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, USER_SYSTEM);
+        installAndCheckBound(APK_2, PACKAGE_A, SERVICE_2, USER_SYSTEM);
+    }
+
+    private void assertUserHasNoConnection(int userId) throws Throwable {
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommandAndNotMatch("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\]," + userId + ",");
+        });
+    }
+
+    private void assertUserHasNoFinder(int userId) throws Throwable {
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommandAndNotMatch("dumpsys app_binding -s",
+                    "^finder,\\[Default\\sSMS\\sapp\\]," + userId + ",");
+        });
+    }
+
+    public void testSecondaryUser() throws Throwable {
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+
+        final int userId = getDevice().createUser("test-user");
+        try {
+            getDevice().startUser(userId);
+
+            // Install SMS app on the secondary user.
+            installAndCheckBound(APK_B, PACKAGE_B, SERVICE_1, userId);
+
+            // Package A should still be bound on user-0.
+            checkBound(PACKAGE_A, SERVICE_1, USER_SYSTEM);
+
+            // Replace the app on the primary user with an invalid one.
+            installAndCheckNotBound(APK_3, PACKAGE_A, USER_SYSTEM,
+                    "must be protected with android.permission.BIND_SMS_APP_SERVICE");
+
+            // Secondary user should still have a valid connection.
+            checkBound(PACKAGE_B, SERVICE_1, userId);
+
+            // Stop the secondary user, now the binding should be gone.
+            getDevice().stopUser(userId);
+
+            // Now the connection should be removed.
+            assertUserHasNoConnection(userId);
+
+            // Start the secondary user again.
+            getDevice().startUser(userId);
+
+            // Now the binding should recover.
+            runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+                checkBound(PACKAGE_B, SERVICE_1, userId);
+            });
+
+        } finally {
+            getDevice().removeUser(userId);
+        }
+        assertUserHasNoConnection(userId);
+        assertUserHasNoFinder(userId);
+    }
+
+    public void testCrashAndAutoRebind() throws Throwable {
+
+        updateConstants(
+                "service_reconnect_backoff_sec=5"
+                + ",service_reconnect_backoff_increase=2"
+                + ",service_reconnect_max_backoff_sec=1000"
+                + ",service_stable_connection_threshold_sec=10");
+
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+
+        // Ensure the expected status.
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,bound,connected"
+                    + ",\\#con=1,\\#dis=0,\\#died=0,backoff=5000");
+        });
+
+        // Let the service crash.
+        runCommand("dumpsys activity service " + PACKAGE_A + "/" + SERVICE_1 + " crash");
+
+        // Now the connection disconnected and re-connected, so the counters increase.
+        // In this case, because binder-died isn't called, so backoff won't increase.
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,bound,connected"
+                    + ",\\#con=2,\\#dis=1,\\#died=0,backoff=5000");
+        });
+
+        // Force-stop the app.
+        runCommand("am force-stop " + PACKAGE_A);
+
+        // Force-stop causes a disconnect and a binder-died. Then it doubles the backoff.
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,not-bound,not-connected"
+                    + ",\\#con=2,\\#dis=2,\\#died=1,backoff=10000");
+        });
+
+        Thread.sleep(5000);
+
+        // It should re-bind.
+        runWithRetries(10, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,bound,connected"
+                            + ",\\#con=3,\\#dis=2,\\#died=1,backoff=10000");
+        });
+
+        // Force-stop again.
+        runCommand("am force-stop " + PACKAGE_A);
+
+        runWithRetries(10, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,not-bound,not-connected"
+                            + ",\\#con=3,\\#dis=3,\\#died=2,backoff=20000");
+        });
+
+        Thread.sleep(10000);
+
+        runWithRetries(10, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,bound,connected"
+                            + ",\\#con=4,\\#dis=3,\\#died=2,backoff=20000");
+        });
+
+        // If the connection lasts more than service_stable_connection_threshold_sec seconds,
+        // the backoff resets.
+        Thread.sleep(10000);
+
+        runWithRetries(10, () -> {
+            runCommand("dumpsys app_binding -s",
+                    "^conn,\\[Default\\sSMS\\sapp\\],0,.*,bound,connected"
+                            + ",\\#con=4,\\#dis=3,\\#died=2,backoff=5000");
+        });
+    }
+
+    /**
+     * Test the feature flag.
+     */
+    public void testFeatureDisabled() throws Throwable {
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+
+        updateConstants("sms_service_enabled=false");
+
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            checkNotBound("null", USER_SYSTEM, "feature disabled");
+        });
+
+        updateConstants("sms_service_enabled=true");
+
+        runWithRetries(DEFAULT_TIMEOUT_SEC, () -> {
+            checkBound(PACKAGE_A, SERVICE_1, USER_SYSTEM);
+        });
+    }
+
+    public void testOomAdjustment() throws Throwable {
+        installAndCheckBound(APK_1, PACKAGE_A, SERVICE_1, USER_SYSTEM);
+        assertOomAdjustment(PACKAGE_A, PACKAGE_A_PROC, 200);
+    }
+}
diff --git a/hostsidetests/appsecurity/Android.mk b/hostsidetests/appsecurity/Android.mk
index e0470a7..392ccb6 100644
--- a/hostsidetests/appsecurity/Android.mk
+++ b/hostsidetests/appsecurity/Android.mk
@@ -32,6 +32,11 @@
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+LOCAL_REQUIRED_MODULES := \
+	CtsCorruptApkTests_b71360999 \
+	CtsCorruptApkTests_b71361168 \
+	CtsCorruptApkTests_b79488511
+
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 # Build the test APKs using their own makefiles
diff --git a/hostsidetests/appsecurity/AndroidTest.xml b/hostsidetests/appsecurity/AndroidTest.xml
index 2109a47..f06bac4 100644
--- a/hostsidetests/appsecurity/AndroidTest.xml
+++ b/hostsidetests/appsecurity/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for the CTS App Security host tests">
     <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" />
     <target_preparer class="android.appsecurity.cts.AppSecurityPreparer" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsAppSecurityHostTestCases.jar" />
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AccessSerialNumberTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AccessSerialNumberTest.java
new file mode 100644
index 0000000..8f53d2a
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AccessSerialNumberTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+public class AccessSerialNumberTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String APK_ACCESS_SERIAL_LEGACY = "CtsAccessSerialLegacy.apk";
+    private static final String APK_ACCESS_SERIAL_MODERN = "CtsAccessSerialModern.apk";
+    private static final String ACCESS_SERIAL_PKG = "android.os.cts";
+
+    private CompatibilityBuildHelper mBuildHelper;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+    }
+
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+            throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Utils.prepareSingleUser(getDevice());
+        assertNotNull(mBuildHelper);
+
+        getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
+    }
+
+    public void testSerialAccessPolicy() throws Exception {
+        // Verify legacy app behavior
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+                APK_ACCESS_SERIAL_LEGACY), false, false));
+        runDeviceTests(ACCESS_SERIAL_PKG,
+                "android.os.cts.AccessSerialLegacyTest",
+                "testAccessSerialNoPermissionNeeded");
+
+        // Verify modern app behavior
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+                APK_ACCESS_SERIAL_MODERN), true, false));
+        runDeviceTests(ACCESS_SERIAL_PKG,
+                "android.os.cts.AccessSerialModernTest",
+                "testAccessSerialPermissionNeeded");
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index 72958fe..d8a280e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -25,6 +25,8 @@
 
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -42,6 +44,7 @@
  * Set of tests that verify behavior of adopted storage media, if supported.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Instant applications can only be installed on internal storage")
 public class AdoptableHostTest extends BaseHostJUnit4Test {
 
     public static final String FEATURE_ADOPTABLE_STORAGE = "feature:android.software.adoptable_storage";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 02e62d3..490bb18 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -16,31 +16,27 @@
 
 package android.appsecurity.cts;
 
-import static org.junit.Assert.assertEquals;
+import static android.appsecurity.cts.Utils.waitForBootCompleted;
+
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+
 import com.android.ddmlib.Log;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.AbiUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-
 /**
  * Set of tests that verify various security checks involving multiple apps are
  * properly enforced.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class AppSecurityTests extends BaseHostJUnit4Test {
+public class AppSecurityTests extends BaseAppSecurityTest {
 
     // testSharedUidDifferentCerts constants
     private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
@@ -71,7 +67,9 @@
     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
     private static final String INSTRUMENT_DIFF_CERT_APK = "CtsInstrumentationAppDiffCert.apk";
     private static final String INSTRUMENT_DIFF_CERT_PKG =
-        "com.android.cts.instrumentationdiffcertapp";
+            "com.android.cts.instrumentationdiffcertapp";
+    private static final String INSTRUMENT_DIFF_CERT_CLASS =
+            "com.android.cts.instrumentationdiffcertapp.InstrumentationFailToRunTest";
 
     // testPermissionDiffCert constants
     private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk";
@@ -83,12 +81,12 @@
     private static final String PERMISSION_DIFF_CERT_PKG =
         "com.android.cts.usespermissiondiffcertapp";
 
-    private static final String LOG_TAG = "AppSecurityTests";
+    private static final String DUPLICATE_DECLARE_PERMISSION_APK =
+            "CtsDuplicatePermissionDeclareApp.apk";
+    private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
+            "com.android.cts.duplicatepermissiondeclareapp";
 
-    private File getTestAppFile(String fileName) throws FileNotFoundException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        return buildHelper.getTestFile(fileName);
-    }
+    private static final String LOG_TAG = "AppSecurityTests";
 
     @Before
     public void setUp() throws Exception {
@@ -101,24 +99,16 @@
      * if it is signed with a different certificate.
      */
     @Test
+    @AppModeFull(reason = "Instant applications can't define shared UID")
     public void testSharedUidDifferentCerts() throws Exception {
         Log.i(LOG_TAG, "installing apks with shared uid, but different certs");
         try {
-            // cleanup test apps that might be installed from previous partial test run
             getDevice().uninstallPackage(SHARED_UI_PKG);
             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_APK),
-                    false, options);
-            assertNull(String.format("failed to install shared uid app, Reason: %s", installResult),
-                    installResult);
-            installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_DIFF_CERT_APK),
-                    false, options);
-            assertNotNull("shared uid app with different cert than existing app installed " +
-                    "successfully", installResult);
-            assertEquals("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE",
-                    installResult.substring(0, installResult.indexOf(':')));
+            new InstallMultiple().addApk(SHARED_UI_APK).run();
+            new InstallMultiple().addApk(SHARED_UI_DIFF_CERT_APK)
+                    .runExpectingFailure("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE");
         } finally {
             getDevice().uninstallPackage(SHARED_UI_PKG);
             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
@@ -130,25 +120,27 @@
      * certificate.
      */
     @Test
-    public void testAppUpgradeDifferentCerts() throws Exception {
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAppUpgradeDifferentCerts_full() throws Exception {
+        testAppUpgradeDifferentCerts(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAppUpgradeDifferentCerts_instant() throws Exception {
+        testAppUpgradeDifferentCerts(true);
+    }
+    private void testAppUpgradeDifferentCerts(boolean instant) throws Exception {
         Log.i(LOG_TAG, "installing app upgrade with different certs");
         try {
-            // cleanup test app that might be installed from previous partial test run
             getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_APK),
-                    false, options);
-            assertNull(String.format("failed to install simple app. Reason: %s", installResult),
-                    installResult);
-            installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_DIFF_CERT_APK),
-                    true /* reinstall */, options);
-            assertNotNull("app upgrade with different cert than existing app installed " +
-                    "successfully", installResult);
-            assertEquals("INSTALL_FAILED_UPDATE_INCOMPATIBLE",
-                    installResult.substring(0, installResult.indexOf(':')));
+            new InstallMultiple(instant).addApk(SIMPLE_APP_APK).run();
+            new InstallMultiple(instant).addApk(SIMPLE_APP_DIFF_CERT_APK)
+                    .runExpectingFailure("INSTALL_FAILED_UPDATE_INCOMPATIBLE");
         } finally {
             getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
         }
     }
 
@@ -156,27 +148,27 @@
      * Test that an app cannot access another app's private data.
      */
     @Test
-    public void testAppFailAccessPrivateData() throws Exception {
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAppFailAccessPrivateData_full() throws Exception {
+        testAppFailAccessPrivateData(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAppFailAccessPrivateData_instant() throws Exception {
+        testAppFailAccessPrivateData(true);
+    }
+    private void testAppFailAccessPrivateData(boolean instant)
+            throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
         try {
-            // cleanup test app that might be installed from previous partial test run
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
-                    false, options);
-            assertNull(String.format("failed to install app with data. Reason: %s", installResult),
-                    installResult);
-            // run appwithdata's tests to create private data
+            new InstallMultiple().addApk(APP_WITH_DATA_APK).run();
             runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
 
-            installResult = getDevice().installPackage(getTestAppFile(APP_ACCESS_DATA_APK),
-                    false, options);
-            assertNull(String.format("failed to install app access data. Reason: %s",
-                    installResult), installResult);
-            // run appaccessdata's tests which attempt to access appwithdata's private data
-            runDeviceTests(APP_ACCESS_DATA_PKG);
+            new InstallMultiple(instant).addApk(APP_ACCESS_DATA_APK).run();
+            runDeviceTests(APP_ACCESS_DATA_PKG, null, null, instant);
         } finally {
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
@@ -187,29 +179,29 @@
      * Test that uninstall of an app removes its private data.
      */
     @Test
-    public void testUninstallRemovesData() throws Exception {
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testUninstallRemovesData_full() throws Exception {
+        testUninstallRemovesData(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testUninstallRemovesData_instant() throws Exception {
+        testUninstallRemovesData(true);
+    }
+    private void testUninstallRemovesData(boolean instant) throws Exception {
         Log.i(LOG_TAG, "Uninstalling app, verifying data is removed.");
         try {
-            // cleanup test app that might be installed from previous partial test run
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
-                    false, options);
-            assertNull(String.format("failed to install app with data. Reason: %s", installResult),
-                    installResult);
-            // run appwithdata's tests to create private data
-            runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
+            new InstallMultiple(instant).addApk(APP_WITH_DATA_APK).run();
+            runDeviceTests(
+                    APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
 
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
 
-            installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
-                    false, options);
-            assertNull(String.format("failed to install app with data second time. Reason: %s",
-                    installResult), installResult);
-            // run appwithdata's 'check if file exists' test
-            runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS,
-                    APP_WITH_DATA_CHECK_NOEXIST_METHOD);
+            new InstallMultiple(instant).addApk(APP_WITH_DATA_APK).run();
+            runDeviceTests(
+                    APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD);
         } finally {
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
         }
@@ -219,29 +211,35 @@
      * Test that an app cannot instrument another app that is signed with different certificate.
      */
     @Test
-    public void testInstrumentationDiffCert() throws Exception {
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstrumentationDiffCert_full() throws Exception {
+        testInstrumentationDiffCert(false, false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstrumentationDiffCert_instant() throws Exception {
+        testInstrumentationDiffCert(false, true);
+        testInstrumentationDiffCert(true, false);
+        testInstrumentationDiffCert(true, true);
+    }
+    private void testInstrumentationDiffCert(boolean targetInstant, boolean instrumentInstant)
+            throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
         try {
             // cleanup test app that might be installed from previous partial test run
             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(
-                    getTestAppFile(TARGET_INSTRUMENT_APK), false, options);
-            assertNull(String.format("failed to install target instrumentation app. Reason: %s",
-                    installResult), installResult);
+            new InstallMultiple(targetInstant).addApk(TARGET_INSTRUMENT_APK).run();
+            new InstallMultiple(instrumentInstant).addApk(INSTRUMENT_DIFF_CERT_APK).run();
 
-            // the app will install, but will get error at runtime when starting instrumentation
-            installResult = getDevice().installPackage(getTestAppFile(INSTRUMENT_DIFF_CERT_APK),
-                    false, options);
-            assertNull(String.format(
-                    "failed to install instrumentation app with diff cert. Reason: %s",
-                    installResult), installResult);
-            // run INSTRUMENT_DIFF_CERT_PKG tests
-            // this test will attempt to call startInstrumentation directly and verify
-            // SecurityException is thrown
-            runDeviceTests(INSTRUMENT_DIFF_CERT_PKG);
+            // if we've installed either the instrumentation or target as an instant application,
+            // starting an instrumentation will just fail instead of throwing a security exception
+            // because neither the target nor instrumentation packages can see one another
+            final String methodName = (targetInstant|instrumentInstant)
+                    ? "testInstrumentationNotAllowed_fail"
+                    : "testInstrumentationNotAllowed_exception";
+            runDeviceTests(INSTRUMENT_DIFF_CERT_PKG, INSTRUMENT_DIFF_CERT_CLASS, methodName);
         } finally {
             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
@@ -253,6 +251,7 @@
      * certificate than the app that declared the permission.
      */
     @Test
+    @AppModeFull(reason = "Only the platform can define permissions obtainable by instant applications")
     public void testPermissionDiffCert() throws Exception {
         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
         try {
@@ -261,24 +260,11 @@
             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
 
-            String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
-            String installResult = getDevice().installPackage(
-                    getTestAppFile(DECLARE_PERMISSION_APK), false, options);
-            assertNull(String.format("failed to install declare permission app. Reason: %s",
-                    installResult), installResult);
+            new InstallMultiple().addApk(DECLARE_PERMISSION_APK).run();
+            new InstallMultiple().addApk(DECLARE_PERMISSION_COMPAT_APK).run();
 
-            installResult = getDevice().installPackage(
-                    getTestAppFile(DECLARE_PERMISSION_COMPAT_APK), false, options);
-            assertNull(String.format("failed to install declare permission compat app. Reason: %s",
-                    installResult), installResult);
-
-            // the app will install, but will get error at runtime
-            installResult = getDevice().installPackage(getTestAppFile(PERMISSION_DIFF_CERT_APK),
-                    false, options);
-            assertNull(String.format("failed to install permission app with diff cert. Reason: %s",
-                    installResult), installResult);
-            // run PERMISSION_DIFF_CERT_PKG tests which try to access the permission
-            runDeviceTests(PERMISSION_DIFF_CERT_PKG);
+            new InstallMultiple().addApk(PERMISSION_DIFF_CERT_APK).run();
+            runDeviceTests(PERMISSION_DIFF_CERT_PKG, null);
         } finally {
             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
@@ -287,16 +273,44 @@
     }
 
     /**
+     * Test what happens if an app tried to take a permission away from another
+     */
+    @Test
+    public void rebootWithDuplicatePermission() throws Exception {
+        try {
+            new InstallMultiple(false).addApk(DECLARE_PERMISSION_APK).run();
+            new InstallMultiple(false).addApk(DUPLICATE_DECLARE_PERMISSION_APK).run();
+
+            runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
+
+            // make sure behavior is preserved after reboot
+            getDevice().reboot();
+            waitForBootCompleted(getDevice());
+            runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
+        } finally {
+            getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DUPLICATE_DECLARE_PERMISSION_PKG);
+        }
+    }
+
+    /**
      * Tests that an arbitrary file cannot be installed using the 'cmd' command.
      */
     @Test
-    public void testAdbInstallFile() throws Exception {
-        String output = getDevice().executeShellCommand(
-                "cmd package install -S 1024 /data/local/tmp/foo.apk");
-        assertTrue("Error text", output.contains("Error"));
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testAdbInstallFile_full() throws Exception {
+        testAdbInstallFile(false);
     }
-
-    private void runDeviceTests(String packageName) throws DeviceNotAvailableException {
-        runDeviceTests(packageName, null);
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testAdbInstallFile_instant() throws Exception {
+        testAdbInstallFile(true);
+    }
+    private void testAdbInstallFile(boolean instant) throws Exception {
+        String output = getDevice().executeShellCommand(
+                "cmd package install"
+                        + (instant ? " --instant" : " --full")
+                        + " -S 1024 /data/local/tmp/foo.apk");
+        assertTrue("Error text", output.contains("Error"));
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
index aac5bd1..a6d2139 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
@@ -74,10 +74,6 @@
         installTestAppForUser(TINY_APK, installUserId);
         installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
 
-        final String grantCmd = "pm grant"
-                + " com.android.cts.applicationvisibility"
-                + " android.permission.INTERACT_ACROSS_USERS";
-        getDevice().executeShellCommand(grantCmd);
         Utils.runDeviceTests(
                 getDevice(),
                 TEST_WITH_PERMISSION_PKG,
@@ -134,10 +130,6 @@
         installTestAppForUser(TINY_APK, installUserId);
         installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
 
-        final String grantCmd = "pm grant"
-                + " com.android.cts.applicationvisibility"
-                + " android.permission.INTERACT_ACROSS_USERS";
-        getDevice().executeShellCommand(grantCmd);
         Utils.runDeviceTests(
                 getDevice(),
                 TEST_WITH_PERMISSION_PKG,
@@ -184,10 +176,6 @@
         installTestAppForUser(TINY_APK, installUserId);
         installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
 
-        final String grantCmd = "pm grant"
-                + " com.android.cts.applicationvisibility"
-                + " android.permission.INTERACT_ACROSS_USERS";
-        getDevice().executeShellCommand(grantCmd);
         Utils.runDeviceTests(
                 getDevice(),
                 TEST_WITH_PERMISSION_PKG,
@@ -244,10 +232,6 @@
         installTestAppForUser(TINY_APK, installUserId);
         installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
 
-        final String grantCmd = "pm grant"
-                + " com.android.cts.applicationvisibility"
-                + " android.permission.INTERACT_ACROSS_USERS";
-        getDevice().executeShellCommand(grantCmd);
         Utils.runDeviceTests(
                 getDevice(),
                 TEST_WITH_PERMISSION_PKG,
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
index 5e2a97b..d7b812f 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
@@ -16,7 +16,6 @@
 
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -24,6 +23,7 @@
 import org.junit.Before;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 
 /**
  * Base class.
@@ -38,7 +38,7 @@
     private ArrayList<Integer> mFixedUsers;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUpBaseAppSecurityTest() throws Exception {
         Assert.assertNotNull(getBuild()); // ensure build has been set before test is run.
 
         mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
@@ -52,7 +52,7 @@
         getDevice().switchUser(mPrimaryUserId);
     }
 
-    private boolean checkIfSplitSystemUser() throws DeviceNotAvailableException {
+    private boolean checkIfSplitSystemUser() throws Exception {
         final String commandOuput = getDevice().executeShellCommand(
                 "getprop ro.fw.system_user_split");
         return "y".equals(commandOuput) || "yes".equals(commandOuput)
@@ -61,19 +61,49 @@
     }
 
     protected void installTestAppForUser(String apk, int userId) throws Exception {
+        installTestAppForUser(apk, false, userId);
+    }
+
+    protected void installTestAppForUser(String apk, boolean instant, int userId) throws Exception {
         if (userId < 0) {
             userId = mPrimaryUserId;
         }
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        Assert.assertNull(getDevice().installPackageForUser(
-                buildHelper.getTestFile(apk), true, false, userId, "-t"));
+        new InstallMultiple(instant)
+                .addApk(apk)
+                .allowTest()
+                .forUser(userId)
+                .run();
+    }
+
+    // TODO: We should be able to set test arguments from the BaseHostJUnit4Test methods
+    protected void runDeviceTests(String packageName, String testClassName,
+            String testMethodName, boolean instant) throws DeviceNotAvailableException {
+        final HashMap<String, String> testArgs;
+        if (instant) {
+            testArgs = new HashMap<>();
+            testArgs.put("is_instant", Boolean.TRUE.toString());
+        } else {
+            testArgs = null;
+        }
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName,
+                testArgs);
     }
 
     protected boolean isAppVisibleForUser(String packageName, int userId,
-            boolean matchUninstalled) throws DeviceNotAvailableException {
+            boolean matchUninstalled) throws Exception {
         String command = "cmd package list packages --user " + userId;
         if (matchUninstalled) command += " -u";
         String output = getDevice().executeShellCommand(command);
         return output.contains(packageName);
     }
+
+    protected class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+        public InstallMultiple() {
+            this(false);
+        }
+        public InstallMultiple(boolean instant) {
+            super(getDevice(), getBuild(), getAbi());
+            addArg(instant ? "--instant" : "");
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
index cc87584..8acda6d 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
@@ -47,6 +47,7 @@
 
     private final List<String> mArgs = new ArrayList<>();
     private final List<File> mApks = new ArrayList<>();
+    private final List<String> mSplits = new ArrayList<>();
     private boolean mUseNaturalAbi;
 
     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
@@ -67,6 +68,11 @@
         return (T) this;
     }
 
+    T removeSplit(String split) {
+        mSplits.add(split);
+        return (T) this;
+    }
+
     T inheritFrom(String packageName) {
         addArg("-r");
         addArg("-p " + packageName);
@@ -78,6 +84,11 @@
         return (T) this;
     }
 
+    T allowTest() {
+        addArg("-t");
+        return (T) this;
+    }
+
     T locationAuto() {
         addArg("--install-location 0");
         return (T) this;
@@ -98,15 +109,24 @@
         return (T) this;
     }
 
+    T forUser(int userId) {
+        addArg("--user " + userId);
+        return (T) this;
+    }
+
     void run() throws DeviceNotAvailableException {
-        run(true);
+        run(true, null);
     }
 
     void runExpectingFailure() throws DeviceNotAvailableException {
-        run(false);
+        run(false, null);
     }
 
-    private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
+    void runExpectingFailure(String failure) throws DeviceNotAvailableException {
+        run(false, failure);
+    }
+
+    private void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
         final ITestDevice device = mDevice;
 
         // Create an install session
@@ -154,16 +174,32 @@
             TestCase.assertTrue(result, result.startsWith("Success"));
         }
 
+        for (int i = 0; i < mSplits.size(); i++) {
+            final String split = mSplits.get(i);
+
+            cmd.setLength(0);
+            cmd.append("pm install-remove");
+            cmd.append(' ').append(sessionId);
+            cmd.append(' ').append(split);
+
+            result = device.executeShellCommand(cmd.toString());
+            TestCase.assertTrue(result, result.startsWith("Success"));
+        }
+
         // Everything staged; let's pull trigger
         cmd.setLength(0);
         cmd.append("pm install-commit");
         cmd.append(' ').append(sessionId);
 
-        result = device.executeShellCommand(cmd.toString());
-        if (expectingSuccess) {
-            TestCase.assertTrue(result, result.startsWith("Success"));
+        result = device.executeShellCommand(cmd.toString()).trim();
+        if (failure == null) {
+            if (expectingSuccess) {
+                TestCase.assertTrue(result, result.startsWith("Success"));
+            } else {
+                TestCase.assertFalse(result, result.startsWith("Success"));
+            }
         } else {
-            TestCase.assertFalse(result, result.startsWith("Success"));
+            TestCase.assertTrue(result, result.contains(failure));
         }
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
index f4f6d9e..4e54bc4 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
@@ -15,9 +15,9 @@
  */
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.Before;
@@ -25,7 +25,7 @@
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class ClassloaderSplitsTest extends BaseHostJUnit4Test implements IBuildReceiver {
+public class ClassloaderSplitsTest extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.classloadersplitapp";
     private static final String TEST_CLASS = PKG + ".SplitAppTest";
 
@@ -57,36 +57,63 @@
     }
 
     @Test
-    public void testBaseClassLoader() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).run();
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testBaseClassLoader_full() throws Exception {
+        testBaseClassLoader(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testBaseClassLoader_instant() throws Exception {
+        testBaseClassLoader(true);
+    }
+    private void testBaseClassLoader(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).run();
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
     }
 
     @Test
-    public void testFeatureAClassLoader() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).run();
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testFeatureAClassLoader_full() throws Exception {
+        testFeatureAClassLoader(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testFeatureAClassLoader_instant() throws Exception {
+        testFeatureAClassLoader(true);
+    }
+    private void testFeatureAClassLoader(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).run();
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
     }
 
     @Test
-    public void testFeatureBClassLoader() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testFeatureBClassLoader_full() throws Exception {
+        testFeatureBClassLoader(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testFeatureBClassLoader_instant() throws Exception {
+        testFeatureBClassLoader(true);
+    }
+    private void testFeatureBClassLoader(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureBClassLoader");
     }
 
     @Test
-    public void testReceiverClassLoaders() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
+    @AppModeFull(reason = "b/109878606; instant applications can't send broadcasts to manifest receivers")
+    public void testReceiverClassLoaders_full() throws Exception {
+        testReceiverClassLoaders(false);
+    }
+    private void testReceiverClassLoaders(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
         runDeviceTests(getDevice(), PKG, TEST_CLASS, "testAllReceivers");
     }
-
-    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
-        public InstallMultiple() {
-            super(getDevice(), getBuild(), null);
-        }
-    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
index d09c525..8516c73 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
@@ -15,18 +15,24 @@
  */
 package android.appsecurity.cts;
 
-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.fail;
 
 import android.platform.test.annotations.AppModeFull;
+import com.android.ddmlib.Log.LogLevel;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 
 import java.io.File;
@@ -35,104 +41,115 @@
  * Set of tests that verify that corrupt APKs are properly rejected by PackageManager and
  * do not cause the system to crash.
  */
-@AppModeFull // TODO: Needs porting to instant
+@AppModeFull(reason = "the corrupt APKs were provided as-is and we cannot modify them to comply with instant mode")
 public class CorruptApkTests extends DeviceTestCase implements IBuildReceiver {
-    private final String B71360999_PKG = "com.android.appsecurity.b71360999";
-    private final String B71361168_PKG = "com.android.appsecurity.b71361168";
-    private final String B79488511_PKG = "com.android.appsecurity.b79488511";
 
     private IBuildInfo mBuildInfo;
 
+    /** A container for information about the system_server process. */
+    private class SystemServerInformation {
+        final long mPid;
+        final long mStartTime;
+
+        SystemServerInformation(long pid, long startTime) {
+            this.mPid = pid;
+            this.mStartTime = startTime;
+        }
+
+        @Override
+        public boolean equals(Object actual) {
+            return (actual instanceof SystemServerInformation)
+                && mPid == ((SystemServerInformation) actual).mPid
+                && mStartTime == ((SystemServerInformation) actual).mStartTime;
+        }
+    }
+
+    /** Retrieves the process id and elapsed run time of system_server. */
+    private SystemServerInformation retrieveInfo() throws DeviceNotAvailableException {
+        ITestDevice device = getDevice();
+
+        // Retrieve the process id of system_server
+        String pidResult = device.executeShellCommand("pidof system_server").trim();
+        assertNotNull("Failed to retrieve pid of system_server", pidResult);
+        long pid = 0;
+        try {
+            pid = Long.parseLong(pidResult);
+        } catch (NumberFormatException | IndexOutOfBoundsException e) {
+            fail("Unable to parse pid of system_server '" + pidResult + "'");
+        }
+
+        // Retrieve the start time of system_server
+        long startTime = 0;
+        String pidStats = device.executeShellCommand("cat /proc/" + pid + "/stat");
+        assertNotNull("Failed to retrieve stat of system_server with pid '" + pid + "'", pidStats);
+        try {
+            String startTimeJiffies = pidStats.split("\\s+")[21];
+            startTime = Long.parseLong(startTimeJiffies);
+        } catch (NumberFormatException | IndexOutOfBoundsException e) {
+            fail("Unable to parse system_server stat file '" + pidStats + "'");
+        }
+
+        return new SystemServerInformation(pid, startTime);
+    }
+
     @Override
     public void setBuild(IBuildInfo buildInfo) {
         mBuildInfo = buildInfo;
     }
 
+   /** Uninstall any test APKs already present on device. */
+    private void uninstallApks() throws DeviceNotAvailableException {
+        ITestDevice device = getDevice();
+        device.uninstallPackage("com.android.appsecurity.b71360999");
+        device.uninstallPackage("com.android.appsecurity.b71361168");
+        device.uninstallPackage("com.android.appsecurity.b79488511");
+    }
+
     @Before
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        uninstall(B71360999_PKG);
-        uninstall(B71361168_PKG);
-        uninstall(B79488511_PKG);
+        uninstallApks();
     }
 
     @After
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
-        uninstall(B71360999_PKG);
-        uninstall(B71361168_PKG);
-        uninstall(B79488511_PKG);
-    }
-
-    /** Uninstall the apk if the test failed previously. */
-    public void uninstall(String pkg) throws Exception {
-        ITestDevice device = getDevice();
-        if (device.getInstalledPackageNames().contains(pkg)) {
-            device.uninstallPackage(pkg);
-        }
+        uninstallApks();
     }
 
     /**
-     * Tests that apks described in b/71360999 do not install successfully.
+     * Asserts that installing the application does not cause a native error causing system_server
+     * to crash (typically the result of a buffer overflow or an out-of-bounds read).
      */
-    public void testFailToInstallCorruptStringPoolHeader_b71360999() throws Exception {
-        final String APK_PATH = "CtsCorruptApkTests_b71360999.apk";
-        assertInstallNoFatalError(APK_PATH, B71360999_PKG);
-    }
+    private void assertInstallDoesNotCrashSystem(String apk) throws Exception {
+        SystemServerInformation beforeInfo = retrieveInfo();
 
-    /**
-     * Tests that apks described in b/71361168 do not install successfully.
-     */
-    public void testFailToInstallCorruptStringPoolHeader_b71361168() throws Exception {
-        final String APK_PATH = "CtsCorruptApkTests_b71361168.apk";
-        assertInstallNoFatalError(APK_PATH, B71361168_PKG);
-    }
-
-    /**
-     * Tests that apks described in b/79488511 do not install successfully.
-     */
-    public void testFailToInstallCorruptStringPoolHeader_b79488511() throws Exception {
-        final String APK_PATH = "CtsCorruptApkTests_b79488511.apk";
-        assertInstallNoFatalError(APK_PATH, B79488511_PKG);
-    }
-
-    /**
-     * Assert that installing the app does not cause a native error caused by a buffer overflow
-     * or an out-of-bounds read.
-     **/
-    private void assertInstallNoFatalError(String filename, String pkg) throws Exception {
-        ITestDevice device = getDevice();
-        device.clearLogcat();
-
-        final String result = device.installPackage(
-                new CompatibilityBuildHelper(mBuildInfo).getTestFile(filename),
-                true /*reinstall*/);
-
-        // Starting from P, corrupt apks should always fail to install
-        if (device.getApiLevel() >= 28) {
-            assertThat(result).isNotNull();
-            assertThat(result).isNotEmpty();
-            assertThat(device.getInstalledPackageNames()).doesNotContain(pkg);
+        final String result = getDevice().installPackage(
+                new CompatibilityBuildHelper(mBuildInfo).getTestFile(apk),
+                false /*reinstall*/);
+        CLog.logAndDisplay(LogLevel.INFO, "Result: '" + result + "'");
+        if (result != null) {
+            assertFalse("Install package segmentation faulted",
+                result.toLowerCase().contains("segmentation fault"));
         }
 
-        // This catches if the device fails to install the app because a segmentation fault
-        // or out of bounds read created by the bug occurs
-        File tmpTxtFile = null;
-        InputStreamSource source = device.getLogcat(200 * 1024);
-        try {
-            assertNotNull(source);
-            tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
-            FileUtil.writeToFile(source.createInputStream(), tmpTxtFile);
-            String s = FileUtil.readStringFromFile(tmpTxtFile);
-            assertFalse(s.contains("SIGSEGV"));
-            assertFalse(s.contains("==ERROR"));
-        } finally {
-            source.close();
-            if (tmpTxtFile != null) {
-                FileUtil.deleteFile(tmpTxtFile);
-            }
-        }
+        assertEquals("system_server restarted", beforeInfo, retrieveInfo());
     }
-}
+
+    /** Tests that installing the APK described in b/71360999 does not crash the device. */
+    public void testSafeInstallOfCorruptAPK_b71360999() throws Exception {
+        assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b71360999.apk");
+    }
+
+    /** Tests that installing the APK described in b/71361168 does not crash the device. */
+    public void testSafeInstallOfCorruptAPK_b71361168() throws Exception {
+        assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b71361168.apk");
+    }
+
+    /** Tests that installing the APK described in b/79488511 does not crash the device. */
+    public void testSafeInstallOfCorruptAPK_b79488511() throws Exception {
+        assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b79488511.apk");
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index 559380d..6cdc9fd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -16,11 +16,13 @@
 
 package android.appsecurity.cts;
 
+import static android.appsecurity.cts.Utils.waitForBootCompleted;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.ddmlib.AdbCommandRejectedException;
-import com.android.ddmlib.CollectingOutputReceiver;
+import android.platform.test.annotations.RequiresDevice;
+
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -112,6 +114,7 @@
      * If device doesn't have native FBE, enable emulation and verify lifecycle.
      */
     @Test
+    @RequiresDevice
     public void testDirectBootEmulated() throws Exception {
         if (!isSupportedDevice()) {
             Log.v(TAG, "Device not supported; skipping test");
@@ -170,7 +173,7 @@
             } else {
                 getDevice().rebootUntilOnline();
             }
-            waitForBootCompleted();
+            waitForBootCompleted(getDevice());
 
             if (doTest) {
                 if (MODE_NONE.equals(mode)) {
@@ -212,21 +215,6 @@
         return getDevice().executeShellCommand("sm get-fbe-mode").trim();
     }
 
-    private boolean isBootCompleted() throws Exception {
-        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        try {
-            getDevice().getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
-        } catch (AdbCommandRejectedException e) {
-            // do nothing: device might be temporarily disconnected
-            Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
-        }
-        String output = receiver.getOutput();
-        if (output != null) {
-            output = output.trim();
-        }
-        return "1".equals(output);
-    }
-
     private boolean isSupportedDevice() throws Exception {
         return getDevice().hasFeature(FEATURE_DEVICE_ADMIN);
     }
@@ -235,21 +223,6 @@
         return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
     }
 
-    private void waitForBootCompleted() throws Exception {
-        for (int i = 0; i < 45; i++) {
-            if (isBootCompleted()) {
-                Log.d(TAG, "Yay, system is ready!");
-                // or is it really ready?
-                // guard against potential USB mode switch weirdness at boot
-                Thread.sleep(10 * 1000);
-                return;
-            }
-            Log.d(TAG, "Waiting for system ready...");
-            Thread.sleep(1000);
-        }
-        throw new AssertionError("System failed to become ready!");
-    }
-
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
         public InstallMultiple() {
             super(getDevice(), getBuild(), getAbi());
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 6f3b8cd..97988dd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -66,6 +66,10 @@
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testGetContent");
     }
 
+    public void testGetContentWithQueryContent() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testGetContentWithQueryContent");
+    }
+
     public void testTransferDocument() throws Exception {
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testTransferDocument");
     }
@@ -82,6 +86,11 @@
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testOpenDocumentTreeAtInitialLocation");
     }
 
+    public void testOpenRootWithoutRootIdAtInitialLocation() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                "testOpenRootWithoutRootIdAtInitialLocation");
+    }
+
     public void testCreateDocumentAtInitialLocation() throws Exception {
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testCreateDocumentAtInitialLocation");
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index cdc412a..89b249c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -16,14 +16,19 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.AppModeFull;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
-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.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -33,8 +38,9 @@
 /**
  * Tests for ephemeral packages.
  */
-public class EphemeralTest extends DeviceTestCase
-        implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Already handles instant installs when needed")
+public class EphemeralTest extends BaseAppSecurityTest {
 
     // a normally installed application
     private static final String NORMAL_APK = "CtsEphemeralTestsNormalApp.apk";
@@ -105,314 +111,374 @@
     }
 
     private String mOldVerifierValue;
-    private IAbi mAbi;
-    private IBuildInfo mBuildInfo;
+    private Boolean mIsUnsupportedDevice;
 
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
-
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        if (isDeviceUnsupported()) {
+        mIsUnsupportedDevice = isDeviceUnsupported();
+        if (mIsUnsupportedDevice) {
             return;
         }
 
         Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mBuildInfo);
+        assertNotNull(getAbi());
+        assertNotNull(getBuild());
 
         uninstallTestPackages();
         installTestPackages();
     }
 
+    @After
     public void tearDown() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         uninstallTestPackages();
-        super.tearDown();
     }
 
+    @Test
     public void testNormalQuery() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(NORMAL_PKG, TEST_CLASS, "testQuery");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS, "testQuery");
     }
 
+    @Test
     public void testNormalStartNormal() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(NORMAL_PKG, TEST_CLASS, "testStartNormal");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS, "testStartNormal");
     }
 
+    @Test
     public void testNormalStartEphemeral() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(NORMAL_PKG, TEST_CLASS, "testStartEphemeral");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "testStartEphemeral");
     }
 
+    @Test
     public void testEphemeralQuery() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testQuery");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS, "testQuery");
     }
 
+    @Test
     public void testEphemeralStartNormal() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartNormal");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartNormal");
     }
 
     // each connection to an exposed component needs to run in its own test to
     // avoid sharing state. once an instant app is exposed to a component, it's
     // exposed until the device restarts or the instant app is removed.
+    @Test
     public void testEphemeralStartExposed01() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed01");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed01");
     }
+    @Test
     public void testEphemeralStartExposed02() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed02");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed02");
     }
+    @Test
     public void testEphemeralStartExposed03() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed03");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed03");
     }
+    @Test
     public void testEphemeralStartExposed04() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed04");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed04");
     }
+    @Test
     public void testEphemeralStartExposed05() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed05");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed05");
     }
+    @Test
     public void testEphemeralStartExposed06() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed06");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed06");
     }
+    @Test
     public void testEphemeralStartExposed07() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed07");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed07");
     }
+    @Test
     public void testEphemeralStartExposed08() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed08");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed08");
     }
+    @Test
     public void testEphemeralStartExposed09() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed09");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed09");
     }
+    @Test
     public void testEphemeralStartExposed10() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed10");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartExposed10");
     }
 
+    @Test
     public void testEphemeralStartEphemeral() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartEphemeral");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartEphemeral");
     }
 
+    @Test
     public void testEphemeralGetInstaller01() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         installEphemeralApp(EPHEMERAL_1_APK, "com.android.cts.normalapp");
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller01");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetInstaller01");
     }
+    @Test
     public void testEphemeralGetInstaller02() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         installApp(NORMAL_APK, "com.android.cts.normalapp");
         installEphemeralApp(EPHEMERAL_1_APK, "com.android.cts.normalapp");
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller02");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetInstaller02");
     }
+    @Test
     public void testEphemeralGetInstaller03() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         installApp(NORMAL_APK, "com.android.cts.normalapp");
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetInstaller03");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetInstaller03");
     }
 
+    @Test
     public void testExposedSystemActivities() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         for (Map<String, String> testArgs : EXPECTED_EXPOSED_INTENTS) {
             final boolean exposed = isIntentExposed(testArgs);
             if (exposed) {
-                runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testExposedActivity", testArgs);
+                Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                        "testExposedActivity", testArgs);
             } else {
                 CLog.w("Skip intent; " + dumpArgs(testArgs));
             }
         }
     }
 
+    @Test
     public void testBuildSerialUnknown() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testBuildSerialUnknown");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testBuildSerialUnknown");
     }
 
+    @Test
     public void testPackageInfo() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testPackageInfo");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testPackageInfo");
     }
 
+    @Test
     public void testActivityInfo() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testActivityInfo");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testActivityInfo");
     }
 
+    @Test
     public void testWebViewLoads() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, WEBVIEW_TEST_CLASS, "testWebViewLoads");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, WEBVIEW_TEST_CLASS,
+                "testWebViewLoads");
     }
 
+    @Test
     public void testInstallPermissionNotGranted() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInstallPermissionNotGranted");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testInstallPermissionNotGranted");
     }
 
+    @Test
     public void testInstallPermissionGranted() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInstallPermissionGranted");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testInstallPermissionGranted");
     }
 
     /** Test for android.permission.INSTANT_APP_FOREGROUND_SERVICE */
-    public void testStartForegrondService() throws Exception {
-        if (isDeviceUnsupported()) {
+    @Test
+    public void testStartForegroundService() throws Exception {
+        if (mIsUnsupportedDevice) {
             return;
         }
         // Make sure the test package does not have INSTANT_APP_FOREGROUND_SERVICE
         getDevice().executeShellCommand("cmd package revoke " + EPHEMERAL_1_PKG
-                        + " android.permission.INSTANT_APP_FOREGROUND_SERVICE");
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartForegroundService");
+                + " android.permission.INSTANT_APP_FOREGROUND_SERVICE");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testStartForegroundService");
     }
 
     /** Test for android.permission.RECORD_AUDIO */
+    @Test
     public void testRecordAudioPermission() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testRecordAudioPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testRecordAudioPermission");
     }
 
     /** Test for android.permission.CAMERA */
+    @Test
     public void testCameraPermission() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testCameraPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testCameraPermission");
     }
 
     /** Test for android.permission.READ_PHONE_NUMBERS */
+    @Test
     public void testReadPhoneNumbersPermission() throws Exception {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testReadPhoneNumbersPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testReadPhoneNumbersPermission");
     }
 
     /** Test for android.permission.ACCESS_COARSE_LOCATION */
+    @Test
     public void testAccessCoarseLocationPermission() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testAccessCoarseLocationPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testAccessCoarseLocationPermission");
     }
 
     /** Test for android.permission.NETWORK */
+    @Test
     public void testInternetPermission() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInternetPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testInternetPermission");
     }
 
     /** Test for android.permission.VIBRATE */
+    @Test
     public void testVibratePermission() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testVibratePermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testVibratePermission");
     }
 
     /** Test for android.permission.WAKE_LOCK */
+    @Test
     public void testWakeLockPermission() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testWakeLockPermission");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testWakeLockPermission");
     }
 
     /** Test for search manager */
+    @Test
     public void testGetSearchableInfo() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
-        runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testGetSearchableInfo");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetSearchableInfo");
     }
 
     /** Test for upgrade from instant --> full */
+    @Test
     public void testInstantAppUpgrade() throws Throwable {
-        if (isDeviceUnsupported()) {
+        if (mIsUnsupportedDevice) {
             return;
         }
         installEphemeralApp(UPGRADED_APK);
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testInstantApplicationWritePreferences");
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testInstantApplicationWriteFile");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testInstantApplicationWritePreferences");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testInstantApplicationWriteFile");
         installFullApp(UPGRADED_APK);
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testFullApplicationReadPreferences");
-        runDeviceTests(UPGRADED_PKG, TEST_CLASS, "testFullApplicationReadFile");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testFullApplicationReadPreferences");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), UPGRADED_PKG, TEST_CLASS,
+                "testFullApplicationReadFile");
     }
 
     private static final HashMap<String, String> makeArgs(
@@ -445,7 +511,7 @@
     }
 
     private boolean isIntentExposed(Map<String, String> testArgs)
-            throws DeviceNotAvailableException {
+            throws Exception {
         final StringBuffer command = new StringBuffer();
         command.append("cmd package query-activities");
         command.append(" -a ").append(testArgs.get("action"));
@@ -471,59 +537,54 @@
         return dump.toString();
     }
 
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName,
-                getDevice().getCurrentUser());
-    }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName,
-            Map<String, String> testArgs) throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName,
-                getDevice().getCurrentUser(), testArgs);
-    }
-
     private void installApp(String apk) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
+        new InstallMultiple(false /* instant */)
+                .addApk(apk)
+                .run();
     }
 
     private void installApp(String apk, String installer) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false,
-                "-i", installer));
+        new InstallMultiple(false /* instant */)
+                .addApk(apk)
+                .addArg("-i " + installer)
+                .run();
     }
 
     private void installEphemeralApp(String apk) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false, "--ephemeral"));
+        new InstallMultiple(true /* instant */)
+                .addApk(apk)
+                .run();
     }
 
     private void installEphemeralApp(String apk, String installer) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false,
-                "--ephemeral", "-i", installer));
+        new InstallMultiple(true /* instant */)
+                .addApk(apk)
+                .addArg("-i " + installer)
+                .run();
     }
 
     private void installFullApp(String apk) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), true, "--full"));
+        new InstallMultiple(false /* instant */)
+                .addApk(apk)
+                .addArg("--full")
+                .run();
     }
 
     private void installTestPackages() throws Exception {
         installApp(NORMAL_APK);
         installApp(UNEXPOSED_APK);
         installApp(IMPLICIT_APK);
+
         installEphemeralApp(EPHEMERAL_1_APK);
         installEphemeralApp(EPHEMERAL_2_APK);
     }
 
     private void uninstallTestPackages() throws Exception {
-        getDevice().uninstallPackage(NORMAL_PKG);
-        getDevice().uninstallPackage(UNEXPOSED_PKG);
-        getDevice().uninstallPackage(IMPLICIT_PKG);
-        getDevice().uninstallPackage(EPHEMERAL_1_PKG);
-        getDevice().uninstallPackage(EPHEMERAL_2_PKG);
-        getDevice().uninstallPackage(UPGRADED_PKG);
+        uninstallPackage(NORMAL_PKG);
+        uninstallPackage(UNEXPOSED_PKG);
+        uninstallPackage(IMPLICIT_PKG);
+        uninstallPackage(EPHEMERAL_1_PKG);
+        uninstallPackage(EPHEMERAL_2_PKG);
+        uninstallPackage(UPGRADED_PKG);
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 3ba96ea..ec66cd1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -28,6 +28,8 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.AbiUtils;
 
+import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,6 +61,18 @@
     private static final String MULTIUSER_APK = "CtsMultiUserStorageApp.apk";
     private static final String MULTIUSER_PKG = "com.android.cts.multiuserstorageapp";
     private static final String MULTIUSER_CLASS = MULTIUSER_PKG + ".MultiUserStorageTest";
+    private static final String MEDIA_APK = "CtsMediaStorageApp.apk";
+    private static final String MEDIA_PKG = "com.android.cts.mediastorageapp";
+    private static final String MEDIA_CLASS = MEDIA_PKG + ".MediaStorageTest";
+
+    private static final String PKG_A = "com.android.cts.storageapp_a";
+    private static final String PKG_B = "com.android.cts.storageapp_b";
+    private static final String APK_A = "CtsStorageAppA.apk";
+    private static final String APK_B = "CtsStorageAppB.apk";
+    private static final String CLASS = "com.android.cts.storageapp.StorageTest";
+
+    private static final String PERM_READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
+    private static final String ROLE_GALLERY = "android.app.role.GALLERY";
 
     private int[] mUsers;
 
@@ -74,11 +88,27 @@
         assertNotNull(getBuild());
     }
 
+    @Before
+    @After
+    public void cleanUp() throws DeviceNotAvailableException {
+        getDevice().uninstallPackage(NONE_PKG);
+        getDevice().uninstallPackage(READ_PKG);
+        getDevice().uninstallPackage(WRITE_PKG);
+        getDevice().uninstallPackage(MULTIUSER_PKG);
+        getDevice().uninstallPackage(PKG_A);
+        getDevice().uninstallPackage(PKG_B);
+
+        wipePrimaryExternalStorage();
+    }
+
     /**
      * Verify that app with no external storage permissions works correctly.
      */
     @Test
     public void testExternalStorageNone() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -102,6 +132,9 @@
      */
     @Test
     public void testExternalStorageRead() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -125,6 +158,9 @@
      */
     @Test
     public void testExternalStorageWrite() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -147,6 +183,9 @@
      */
     @Test
     public void testExternalStorageGifts() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -159,14 +198,14 @@
             // verify that the daemon correctly invalidates any caches.
             assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
             for (int user : mUsers) {
-                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", "testGifts", user);
             }
 
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
             for (int user : mUsers) {
-                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", user);
-                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", "testGifts", user);
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testGifts", user);
             }
         } finally {
             getDevice().uninstallPackage(NONE_PKG);
@@ -175,6 +214,99 @@
         }
     }
 
+    @Test
+    public void testExternalStorageObbGifts() throws Exception {
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(NONE_PKG);
+            getDevice().uninstallPackage(WRITE_PKG);
+            final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
+
+            // We purposefully delay the installation of the reading apps to
+            // verify that the daemon correctly invalidates any caches.
+            assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
+            for (int user : mUsers) {
+                updateAppOp(WRITE_PKG, user, "android:request_install_packages", true);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", "testObbGifts", user);
+            }
+
+            assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
+            for (int user : mUsers) {
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testObbGifts", user);
+            }
+
+            Assume.assumeTrue(hasIsolatedStorage());
+            for (int user : mUsers) {
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testRemoveObbGifts", user);
+            }
+
+            for (int user : mUsers) {
+                updateAppOp(WRITE_PKG, user, "android:request_install_packages", false);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", "testObbGifts", user);
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testNoObbGifts", user);
+            }
+        } finally {
+            getDevice().uninstallPackage(NONE_PKG);
+            getDevice().uninstallPackage(WRITE_PKG);
+        }
+    }
+
+    /**
+     * Test isolated external storage, ensuring that two apps are running in
+     * complete isolation.
+     */
+    @Test
+    public void testExternalStorageIsolated() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(PKG_A);
+            getDevice().uninstallPackage(PKG_B);
+
+            installPackage(APK_A);
+            installPackage(APK_B);
+
+            for (int user : mUsers) {
+                runDeviceTests(PKG_A, CLASS, "testExternalStorageIsolatedWrite", user);
+                runDeviceTests(PKG_B, CLASS, "testExternalStorageIsolatedRead", user);
+            }
+        } finally {
+            getDevice().uninstallPackage(PKG_A);
+            getDevice().uninstallPackage(PKG_B);
+        }
+    }
+
+    /**
+     * Test isolated external storage, ensuring that legacy apps behave as
+     * expected.
+     */
+    @Test
+    public void testExternalStorageIsolatedLegacy() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        final int owner = mUsers[0];
+        try {
+            wipePrimaryExternalStorage();
+            getDevice().executeShellCommand("touch /sdcard/cts_top");
+
+            getDevice().uninstallPackage(PKG_A);
+            installPackage(APK_A);
+
+            updateAppOp(PKG_A, owner, "android:legacy_storage", true);
+            runDeviceTests(PKG_A, CLASS, "testExternalStorageIsolatedLegacy", owner);
+
+            updateAppOp(PKG_A, owner, "android:legacy_storage", false);
+            runDeviceTests(PKG_A, CLASS, "testExternalStorageIsolatedNonLegacy", owner);
+        } finally {
+            getDevice().uninstallPackage(PKG_A);
+        }
+    }
+
     /**
      * Test multi-user emulated storage environment, ensuring that each user has
      * isolated storage.
@@ -227,6 +359,9 @@
      */
     @Test
     public void testMultiViewMoveConsistency() throws Exception {
+        // TODO: remove this test once isolated storage is always enabled
+        Assume.assumeFalse(hasIsolatedStorage());
+
         try {
             wipePrimaryExternalStorage();
 
@@ -281,7 +416,7 @@
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
 
             for (int user : mUsers) {
-                enableWriteSettings(WRITE_PKG, user);
+                updateAppOp(WRITE_PKG, user, "android:write_settings", true);
                 runDeviceTests(
                         WRITE_PKG, WRITE_PKG + ".ChangeDefaultUris", "testChangeDefaultUris", user);
 
@@ -332,29 +467,97 @@
         }
     }
 
+    @Test
+    public void testMediaNone() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        installPackage(MEDIA_APK);
+        for (int user : mUsers) {
+            updatePermission(MEDIA_PKG, user, PERM_READ_MEDIA_IMAGES, false);
+            updateRole(MEDIA_PKG, user, ROLE_GALLERY, false);
+
+            runDeviceTests(MEDIA_PKG, MEDIA_CLASS, "testMediaNone", user);
+        }
+    }
+
+    @Test
+    public void testMediaRead() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        installPackage(MEDIA_APK);
+        for (int user : mUsers) {
+            updatePermission(MEDIA_PKG, user, PERM_READ_MEDIA_IMAGES, true);
+            updateRole(MEDIA_PKG, user, ROLE_GALLERY, false);
+
+            runDeviceTests(MEDIA_PKG, MEDIA_CLASS, "testMediaRead", user);
+        }
+    }
+
+    @Test
+    public void testMediaWrite() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        installPackage(MEDIA_APK);
+        for (int user : mUsers) {
+            updatePermission(MEDIA_PKG, user, PERM_READ_MEDIA_IMAGES, true);
+            updateRole(MEDIA_PKG, user, ROLE_GALLERY, true);
+
+            runDeviceTests(MEDIA_PKG, MEDIA_CLASS, "testMediaWrite", user);
+        }
+    }
+
+    @Test
+    public void testMediaEscalation() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(hasIsolatedStorage());
+
+        installPackage(MEDIA_APK);
+        for (int user : mUsers) {
+            updatePermission(MEDIA_PKG, user, PERM_READ_MEDIA_IMAGES, true);
+            updateRole(MEDIA_PKG, user, ROLE_GALLERY, false);
+
+            runDeviceTests(MEDIA_PKG, MEDIA_CLASS, "testMediaEscalation", user);
+        }
+    }
+
     private boolean access(String path) throws DeviceNotAvailableException {
         final long nonce = System.nanoTime();
         return getDevice().executeShellCommand("ls -la " + path + " && echo " + nonce)
                 .contains(Long.toString(nonce));
     }
 
-    private void enableWriteSettings(String packageName, int userId)
-            throws DeviceNotAvailableException {
-        StringBuilder cmd = new StringBuilder();
-        cmd.append("appops set --user ");
-        cmd.append(userId);
-        cmd.append(" ");
-        cmd.append(packageName);
-        cmd.append(" android:write_settings allow");
-        getDevice().executeShellCommand(cmd.toString());
-        try {
-            Thread.sleep(2200);
-        } catch (InterruptedException e) {
-        }
+    private void updatePermission(String packageName, int userId, String permission, boolean grant)
+            throws Exception {
+        final String verb = grant ? "grant" : "revoke";
+        getDevice().executeShellCommand(
+                "cmd package " + verb + " --user " + userId + " " + packageName + " " + permission);
+    }
+
+    private void updateAppOp(String packageName, int userId, String appOp, boolean allow)
+            throws Exception {
+        final String verb = allow ? "allow" : "default";
+        getDevice().executeShellCommand(
+                "cmd appops set --user " + userId + " " + packageName + " " + appOp + " " + verb);
+    }
+
+    private void updateRole(String packageName, int userId, String role, boolean add)
+            throws Exception {
+        final String verb = add ? "add-role-holder" : "remove-role-holder";
+        getDevice().executeShellCommand(
+                "cmd role " + verb + " --user " + userId + " " + role + " " + packageName);
+    }
+
+    private boolean hasIsolatedStorage() throws DeviceNotAvailableException {
+        return getDevice().executeShellCommand("getprop sys.isolated_storage_snapshot")
+                .contains("true");
     }
 
     private void wipePrimaryExternalStorage() throws DeviceNotAvailableException {
-        getDevice().executeShellCommand("rm -rf /sdcard/Android");
+        // Can't delete everything under /sdcard as that's going to remove the mounts.
+        getDevice().executeShellCommand("find /sdcard -type f -delete");
         getDevice().executeShellCommand("rm -rf /sdcard/DCIM");
         getDevice().executeShellCommand("rm -rf /sdcard/MUST_*");
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
index 4609e8a..9de71b1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
@@ -16,21 +16,39 @@
 
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
-import java.util.ArrayList;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+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;
 
 /**
  * Tests for ephemeral packages.
  */
-public class InstantAppUserTest extends DeviceTestCase
-        implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Already handles instant installs when needed")
+public class InstantAppUserTest extends BaseHostJUnit4Test {
+
+    // a normally installed application
+    private static final String NORMAL_APK = "CtsEphemeralTestsNormalApp.apk";
+    private static final String NORMAL_PKG = "com.android.cts.normalapp";
+
+    // a normally installed application with implicitly exposed components
+    private static final String IMPLICIT_APK = "CtsEphemeralTestsImplicitApp.apk";
+    private static final String IMPLICIT_PKG = "com.android.cts.implicitapp";
+
+    // the first ephemerally installed application
+    private static final String EPHEMERAL_1_APK = "CtsEphemeralTestsEphemeralApp1.apk";
+    private static final String EPHEMERAL_1_PKG = "com.android.cts.ephemeralapp1";
 
     // an application to verify instant/full app per user
     private static final String USER_APK = "CtsEphemeralTestsUserApp.apk";
@@ -41,149 +59,182 @@
 
     private static final String TEST_CLASS = ".ClientTest";
 
-    private static final boolean MATCH_UNINSTALLED = true;
-    private static final boolean MATCH_NORMAL = false;
-
-    private static final int USER_SYSTEM = 0; // From the UserHandle class.
-
-    private String mOldVerifierValue;
-    private IAbi mAbi;
-    private IBuildInfo mBuildInfo;
     private boolean mSupportsMultiUser;
     private int mPrimaryUserId;
-    /** Users we shouldn't delete in the tests */
-    private ArrayList<Integer> mFixedUsers;
-    private int[] mTestUser = new int[2];
+    private int mSecondaryUserId;
 
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
-
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        assertNotNull(mAbi);
-        assertNotNull(mBuildInfo);
-
-        // This test only runs when we have at least 3 users to work with
-        final int[] users = Utils.prepareMultipleUsers(getDevice(), 3);
-        mSupportsMultiUser = (users.length == 3);
-        if (mSupportsMultiUser) {
-            mPrimaryUserId = getDevice().getPrimaryUserId();
-            mFixedUsers = new ArrayList<>();
-            mFixedUsers.add(mPrimaryUserId);
-            if (mPrimaryUserId != USER_SYSTEM) {
-                mFixedUsers.add(USER_SYSTEM);
-            }
-            getDevice().switchUser(mPrimaryUserId);
-
-            mTestUser[0] = users[1];
-            mTestUser[1] = users[2];
-
-            uninstallTestPackages();
-            installTestPackages();
+        // This test only runs when we have at least 2 users to work with
+        final int[] users = Utils.prepareMultipleUsers(getDevice(), 2);
+        mSupportsMultiUser = (users.length == 2);
+        if (!mSupportsMultiUser) {
+            return;
         }
+        mPrimaryUserId = getDevice().getPrimaryUserId();
+        mSecondaryUserId = users[1];
+        getDevice().switchUser(mSecondaryUserId);
+        uninstallTestPackages();
+        installTestPackages();
     }
 
+    @After
     public void tearDown() throws Exception {
-        if (mSupportsMultiUser) {
-            uninstallTestPackages();
+        if (!mSupportsMultiUser) {
+            return;
         }
-        super.tearDown();
+        uninstallTestPackages();
+        getDevice().switchUser(mPrimaryUserId);
     }
 
+    // each connection to an exposed component needs to run in its own test to
+    // avoid sharing state. once an instant app is exposed to a component, it's
+    // exposed until the device restarts or the instant app is removed.
+    @Test
+    public void testStartExposed01() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed01", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed02() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed02", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed03() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed03", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed04() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed04", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed05() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed05", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed06() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed06", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed07() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed07", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed08() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed08", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed09() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed09", mSecondaryUserId);
+    }
+    @Test
+    public void testStartExposed10() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+        runDeviceTestsAsUser(EPHEMERAL_1_PKG, TEST_CLASS, "testStartExposed10", mSecondaryUserId);
+    }
+
+    @Test
     public void testInstallInstant() throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
         installInstantApp(USER_APK);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mSecondaryUserId);
     }
 
+    @Test
     public void testInstallFull() throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
         installApp(USER_APK);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mSecondaryUserId);
     }
 
+    @Test
     public void testInstallMultiple() throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
         installAppAsUser(USER_APK, mPrimaryUserId);
-        installExistingInstantAppAsUser(USER_PKG, mTestUser[0]);
-        installExistingFullAppAsUser(USER_PKG, mTestUser[1]);
+        installExistingInstantAppAsUser(USER_PKG, mSecondaryUserId);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mSecondaryUserId);
     }
 
+    @Test
     public void testUpgradeExisting() throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
         installInstantApp(USER_APK);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mSecondaryUserId);
 
-        installExistingFullAppAsUser(USER_PKG, mTestUser[0]);
+        installExistingFullAppAsUser(USER_PKG, mSecondaryUserId);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[1]);
-
-        installExistingFullAppAsUser(USER_PKG, mTestUser[1]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mSecondaryUserId);
     }
 
+    @Test
     public void testReplaceExisting() throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
         installInstantApp(USER_APK);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mSecondaryUserId);
 
-        replaceFullAppAsUser(USER_APK, mTestUser[0]);
+        replaceFullAppAsUser(USER_APK, mSecondaryUserId);
         runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mTestUser[1]);
-
-        replaceFullAppAsUser(USER_APK, mTestUser[1]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryInstant", mPrimaryUserId);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[0]);
-        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mTestUser[1]);
+        runDeviceTestsAsUser(USER_TEST_PKG, TEST_CLASS, "testQueryFull", mSecondaryUserId);
     }
 
     private void installTestPackages() throws Exception {
+        installApp(NORMAL_APK);
+        installApp(IMPLICIT_APK);
+        installInstantApp(EPHEMERAL_1_APK);
         installApp(USER_TEST_APK);
     }
 
     private void uninstallTestPackages() throws Exception {
+        getDevice().uninstallPackage(NORMAL_PKG);
+        getDevice().uninstallPackage(IMPLICIT_APK);
+        getDevice().uninstallPackage(EPHEMERAL_1_PKG);
         getDevice().uninstallPackage(USER_TEST_PKG);
         getDevice().uninstallPackage(USER_PKG);
     }
 
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
-
     private void runDeviceTestsAsUser(String packageName, String testClassName,
             String testMethodName, int userId)
             throws DeviceNotAvailableException {
@@ -191,22 +242,22 @@
     }
 
     private void installApp(String apk) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
     }
 
     private void installInstantApp(String apk) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false, "--instant"));
     }
 
     private void installAppAsUser(String apk, int userId) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        assertNull(getDevice().installPackageForUser(buildHelper.getTestFile(apk), false, userId));
     }
 
     private void replaceFullAppAsUser(String apk, int userId) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         assertNull(getDevice().installPackageForUser(
                 buildHelper.getTestFile(apk), true, userId, "--full"));
     }
@@ -214,7 +265,6 @@
     private void installExistingInstantAppAsUser(String packageName, int userId) throws Exception {
         final String installString =
                 "Package " + packageName + " installed for user: " + userId + "\n";
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
         assertEquals(installString, getDevice().executeShellCommand(
                 "cmd package install-existing --instant"
                         + " --user " + Integer.toString(userId)
@@ -224,7 +274,6 @@
     private void installExistingFullAppAsUser(String packageName, int userId) throws Exception {
         final String installString =
                 "Package " + packageName + " installed for user: " + userId + "\n";
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
         assertEquals(installString, getDevice().executeShellCommand(
                 "cmd package install-existing --full"
                         + " --user " + Integer.toString(userId)
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
index 97658eb..65c2881 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
@@ -16,108 +16,105 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
+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;
+
+import java.io.File;
+import java.io.FileNotFoundException;
 
 /**
  * Tests for the instant cookie APIs
  */
-public class InstantCookieHostTest extends DeviceTestCase implements IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Already handles instant installs when needed")
+public class InstantCookieHostTest extends BaseHostJUnit4Test {
     private static final String INSTANT_COOKIE_APP_APK = "CtsInstantCookieApp.apk";
     private static final String INSTANT_COOKIE_APP_PKG = "test.instant.cookie";
 
     private static final String INSTANT_COOKIE_APP_APK_2 = "CtsInstantCookieApp2.apk";
     private static final String INSTANT_COOKIE_APP_PKG_2 = "test.instant.cookie";
 
-    // Wait time in msec to make sure the cookie is stored on disk.
-    private static final int WAIT_COOKIE_STORED = 1000;
-
-    private CompatibilityBuildHelper mBuildHelper;
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
         uninstallPackage(INSTANT_COOKIE_APP_PKG);
         clearAppCookieData();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         uninstallPackage(INSTANT_COOKIE_APP_PKG);
         clearAppCookieData();
     }
 
+    @Test
     public void testCookieUpdateAndRetrieval() throws Exception {
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookieUpdateAndRetrieval");
     }
 
+    @Test
     public void testCookiePersistedAcrossInstantInstalls() throws Exception {
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookiePersistedAcrossInstantInstalls1");
         uninstallPackage(INSTANT_COOKIE_APP_PKG);
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
-        Thread.sleep(WAIT_COOKIE_STORED);
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookiePersistedAcrossInstantInstalls2");
     }
 
+    @Test
     public void testCookiePersistedUpgradeFromInstant() throws Exception {
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookiePersistedUpgradeFromInstant1");
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
-        Thread.sleep(WAIT_COOKIE_STORED);
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookiePersistedUpgradeFromInstant2");
     }
 
+    @Test
     public void testCookieResetOnNonInstantReinstall() throws Exception {
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, false));
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookieResetOnNonInstantReinstall1");
         uninstallPackage(INSTANT_COOKIE_APP_PKG);
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
-        Thread.sleep(WAIT_COOKIE_STORED);
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookieResetOnNonInstantReinstall2");
     }
 
-    public void testCookieValidWhenSingedWithTwoCerts() throws Exception {
+    @Test
+    public void testCookieValidWhenSignedWithTwoCerts() throws Exception {
         assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
         runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
                 "testCookiePersistedAcrossInstantInstalls1");
         uninstallPackage(INSTANT_COOKIE_APP_PKG);
         assertNull(installPackage(INSTANT_COOKIE_APP_APK_2, true, true));
-        Thread.sleep(WAIT_COOKIE_STORED);
         runDeviceTests(INSTANT_COOKIE_APP_PKG_2, "test.instant.cookie.CookieTest",
                 "testCookiePersistedAcrossInstantInstalls2");
     }
 
     private String installPackage(String apk, boolean replace, boolean instant) throws Exception {
-        return getDevice().installPackage(mBuildHelper.getTestFile(apk), replace,
+        return getDevice().installPackage(getTestAppFile(apk), replace,
                 instant ? "--instant" : "--full");
     }
 
-    private String uninstallPackage(String packageName) throws DeviceNotAvailableException {
-        return getDevice().uninstallPackage(packageName);
-    }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+    private File getTestAppFile(String fileName) throws FileNotFoundException {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        return buildHelper.getTestFile(fileName);
     }
 
     private void clearAppCookieData() throws Exception {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index 8795fe8..3bc1654 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -15,11 +15,17 @@
  */
 package android.appsecurity.cts;
 
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-public class IsolatedSplitsTests extends DeviceTestCase implements IBuildReceiver {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class IsolatedSplitsTests extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.isolatedsplitapp";
     private static final String TEST_CLASS = PKG + ".SplitAppTest";
 
@@ -38,68 +44,129 @@
     private static final String APK_FEATURE_C = "CtsIsolatedSplitAppFeatureC.apk";
     private static final String APK_FEATURE_C_pl = "CtsIsolatedSplitAppFeatureC_pl.apk";
 
-    private IBuildInfo mBuildInfo;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
         getDevice().uninstallPackage(PKG);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
     }
 
-    public void testInstallBase() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallBase_full() throws Exception {
+        testInstallBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallBase_instant() throws Exception {
+        testInstallBase(true);
+    }
+    private void testInstallBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadDefault");
     }
 
-    public void testInstallBaseAndConfigSplit() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_BASE_pl).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallBaseAndConfigSplit_full() throws Exception {
+        testInstallBaseAndConfigSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallBaseAndConfigSplit_instant() throws Exception {
+        testInstallBaseAndConfigSplit(true);
+    }
+    private void testInstallBaseAndConfigSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_BASE_pl).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadPolishLocale");
     }
 
-    public void testInstallMissingDependency() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_B).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallMissingDependency_full() throws Exception {
+        testInstallMissingDependency(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallMissingDependency_instant() throws Exception {
+        testInstallMissingDependency(true);
+    }
+    private void testInstallMissingDependency(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_B).runExpectingFailure();
     }
 
-    public void testInstallOneFeatureSplit() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).run();
+    @Test
+    @AppModeFull(reason = "b/109878606; instant applications can't send broadcasts to manifest receivers")
+    public void testInstallOneFeatureSplit_full() throws Exception {
+        testInstallOneFeatureSplit(false);
+    }
+    private void testInstallOneFeatureSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadDefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureADefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureAReceivers");
     }
 
-    public void testInstallOneFeatureSplitAndConfigSplits() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_BASE_pl)
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallOneFeatureSplitAndConfigSplits_full() throws Exception {
+        testInstallOneFeatureSplitAndConfigSplits(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallOneFeatureSplitAndConfigSplits_instant() throws Exception {
+        testInstallOneFeatureSplitAndConfigSplits(true);
+    }
+    private void testInstallOneFeatureSplitAndConfigSplits(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_BASE_pl)
                 .addApk(APK_FEATURE_A_pl).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadPolishLocale");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureAPolishLocale");
     }
 
-    public void testInstallDependentFeatureSplits() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
+    @Test
+    @AppModeFull(reason = "b/109878606; instant applications can't send broadcasts to manifest receivers")
+    public void testInstallDependentFeatureSplits_full() throws Exception {
+        testInstallDependentFeatureSplits(false);
+    }
+    private void testInstallDependentFeatureSplits(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadDefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureADefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureBDefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureAAndBReceivers");
     }
 
-    public void testInstallDependentFeatureSplitsAndConfigSplits() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallDependentFeatureSplitsAndConfigSplits_full() throws Exception {
+        testInstallDependentFeatureSplitsAndConfigSplits(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallDependentFeatureSplitsAndConfigSplits_instant() throws Exception {
+        testInstallDependentFeatureSplitsAndConfigSplits(true);
+    }
+    private void testInstallDependentFeatureSplitsAndConfigSplits(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
                 .addApk(APK_BASE_pl).addApk(APK_FEATURE_A_pl).addApk(APK_FEATURE_B_pl).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadPolishLocale");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureAPolishLocale");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureBPolishLocale");
     }
 
-    public void testInstallAllFeatureSplits() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
+    @Test
+    @AppModeFull(reason = "b/109878606; instant applications can't send broadcasts to manifest receivers")
+    public void testInstallAllFeatureSplits_full() throws Exception {
+        testInstallAllFeatureSplits(false);
+    }
+    private void testInstallAllFeatureSplits(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
                 .addApk(APK_FEATURE_C).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadDefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureADefault");
@@ -108,8 +175,18 @@
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureAAndBAndCReceivers");
     }
 
-    public void testInstallAllFeatureSplitsAndConfigSplits() throws Exception {
-        new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallAllFeatureSplitsAndConfigSplits_full() throws Exception {
+        testInstallAllFeatureSplitsAndConfigSplits(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallAllFeatureSplitsAndConfigSplits_instant() throws Exception {
+        testInstallAllFeatureSplitsAndConfigSplits(true);
+    }
+    private void testInstallAllFeatureSplitsAndConfigSplits(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B)
                 .addApk(APK_FEATURE_C).addApk(APK_BASE_pl).addApk(APK_FEATURE_A_pl)
                 .addApk(APK_FEATURE_C_pl).run();
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadDefault");
@@ -117,15 +194,4 @@
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureBDefault");
         Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "shouldLoadFeatureCDefault");
     }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildInfo = buildInfo;
-    }
-
-    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
-        public InstallMultiple() {
-            super(getDevice(), mBuildInfo, null);
-        }
-    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ListeningPortsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ListeningPortsTest.java
new file mode 100644
index 0000000..3e78032
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ListeningPortsTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+/**
+ * Runs the n portion of the listening ports test. With /proc/net access removed for third
+ * party apps the device side test could no longer directly parse the file for listening ports. This
+ * class pulls the /proc/net files from the device and passes their contents as a parameter to the
+ * device side test that will then check for any listening ports and perform any necessary
+ * localhost connection tests.
+ */
+public class ListeningPortsTest extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String DEVICE_TEST_APK = "CtsListeningPortsTest.apk";
+    private static final String DEVICE_TEST_PKG = "android.appsecurity.cts.listeningports";
+    private static final String DEVICE_TEST_CLASS = DEVICE_TEST_PKG + ".ListeningPortsTest";
+    private static final String DEVICE_TEST_METHOD = "testNoAccessibleListeningPorts";
+
+    private static final String PROC_FILE_CONTENTS_PARAM = "procFileContents";
+    private static final String IS_TCP_PARAM = "isTcp";
+    private static final String LOOPBACK_PARAM = "loopback";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Utils.prepareSingleUser(getDevice());
+        assertNotNull(mCtsBuild);
+        installDeviceTestPkg();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            uninstallDeviceTestPackage();
+        } catch (DeviceNotAvailableException ignored) {
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    private void installDeviceTestPkg() throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        File apk = buildHelper.getTestFile(DEVICE_TEST_APK);
+        String result = getDevice().installPackage(apk, true);
+        assertNull("failed to install " + DEVICE_TEST_APK + ", Reason: " + result, result);
+    }
+
+    private String uninstallDeviceTestPackage() throws DeviceNotAvailableException {
+        return getDevice().uninstallPackage(DEVICE_TEST_PKG);
+    }
+
+    /**
+     * Remotely accessible ports are often used by attackers to gain
+     * unauthorized access to computers systems without user knowledge or
+     * awareness.
+     */
+    public void testNoRemotelyAccessibleListeningTcpPorts() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/tcp", true, false);
+    }
+
+    /**
+     * Remotely accessible ports are often used by attackers to gain
+     * unauthorized access to computers systems without user knowledge or
+     * awareness.
+     */
+    public void testNoRemotelyAccessibleListeningTcp6Ports() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/tcp6", true, false);
+    }
+
+    /**
+     * Remotely accessible ports are often used by attackers to gain
+     * unauthorized access to computers systems without user knowledge or
+     * awareness.
+     */
+    public void testNoRemotelyAccessibleListeningUdpPorts() throws Exception {
+        assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp", false);
+    }
+
+    /**
+     * Remotely accessible ports are often used by attackers to gain
+     * unauthorized access to computers systems without user knowledge or
+     * awareness.
+     */
+    /*
+    public void testNoRemotelyAccessibleListeningUdp6Ports() throws Exception {
+        assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp6", false);
+    }
+    */
+
+    /**
+     * Locally accessible ports are often targeted by malicious locally
+     * installed programs to gain unauthorized access to program data or
+     * cause system corruption.
+     *
+     * In all cases, a local listening IP port can be replaced by a UNIX domain
+     * socket. Unix domain sockets can be protected with unix filesystem
+     * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
+     * determine if a program is authorized to connect to your socket.
+     *
+     * Please convert loopback IP connections to unix domain sockets.
+     */
+    public void testNoListeningLoopbackTcpPorts() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/tcp", true, true);
+    }
+
+    /**
+     * Locally accessible ports are often targeted by malicious locally
+     * installed programs to gain unauthorized access to program data or
+     * cause system corruption.
+     *
+     * In all cases, a local listening IP port can be replaced by a UNIX domain
+     * socket. Unix domain sockets can be protected with unix filesystem
+     * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
+     * determine if a program is authorized to connect to your socket.
+     *
+     * Please convert loopback IP connections to unix domain sockets.
+     */
+    public void testNoListeningLoopbackTcp6Ports() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/tcp6", true, true);
+    }
+
+    /**
+     * Locally accessible ports are often targeted by malicious locally
+     * installed programs to gain unauthorized access to program data or
+     * cause system corruption.
+     *
+     * In all cases, a local listening IP port can be replaced by a UNIX domain
+     * socket. Unix domain sockets can be protected with unix filesystem
+     * permission.  Alternately, or you can use setsockopt(SO_PASSCRED) to
+     * send credentials, and recvmsg to retrieve the passed credentials.
+     *
+     * Please convert loopback IP connections to unix domain sockets.
+     */
+    public void testNoListeningLoopbackUdpPorts() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/udp", false, true);
+    }
+
+    /**
+     * Locally accessible ports are often targeted by malicious locally
+     * installed programs to gain unauthorized access to program data or
+     * cause system corruption.
+     *
+     * In all cases, a local listening IP port can be replaced by a UNIX domain
+     * socket. Unix domain sockets can be protected with unix filesystem
+     * permission.  Alternately, or you can use setsockopt(SO_PASSCRED) to
+     * send credentials, and recvmsg to retrieve the passed credentials.
+     *
+     * Please convert loopback IP connections to unix domain sockets.
+     */
+    public void testNoListeningLoopbackUdp6Ports() throws Exception {
+        assertNoAccessibleListeningPorts("/proc/net/udp6", false, true);
+    }
+
+    private static final int RETRIES_MAX = 6;
+
+    /**
+     * UDP tests can be flaky due to DNS lookups.  Compensate.
+     */
+    private void assertNoRemotelyAccessibleListeningUdpPorts(String procFilePath, boolean loopback)
+            throws Exception {
+        for (int i = 0; i < RETRIES_MAX; i++) {
+            try {
+                assertNoAccessibleListeningPorts(procFilePath, false, loopback);
+                return;
+            } catch (AssertionError e) {
+                // If an AssertionError is caught then a listening UDP port was detected on the
+                // device; since this could be due to a DNS lookup sleep and retry the test. If
+                // the test continues to fail then report the error.
+                if (i == RETRIES_MAX - 1) {
+                    throw e;
+                }
+                Thread.sleep(2 * 1000 * i);
+            }
+        }
+        throw new IllegalStateException("unreachable");
+    }
+
+
+    /**
+     * Passes the contents of the /proc/net file and the boolean arguments to the device side test
+     * which performs the necessary checks to determine if the port is listening and if it is
+     * accessible from the localhost.
+     */
+    private void assertNoAccessibleListeningPorts(String procFilePath, boolean isTcp,
+            boolean loopback) throws Exception {
+        Map<String, String> args = new HashMap<>();
+        String procFileContents = parse(procFilePath);
+        args.put(PROC_FILE_CONTENTS_PARAM, procFileContents);
+        args.put(IS_TCP_PARAM, String.valueOf(isTcp));
+        args.put(LOOPBACK_PARAM, String.valueOf(loopback));
+        Utils.runDeviceTests(getDevice(), DEVICE_TEST_PKG, DEVICE_TEST_CLASS, DEVICE_TEST_METHOD,
+                args);
+    }
+
+    /**
+     * Since the device side test cannot directly access the /proc/net files this method pulls the
+     * file from the device and returns a String with its contents which can then be passed as an
+     * argument to the device side test.
+     */
+    private String parse(String procFilePath) throws IOException, DeviceNotAvailableException {
+        File procFile = File.createTempFile("ListeningPortsTest", "tmp");
+        getDevice().pullFile(procFilePath, procFile);
+        procFile.deleteOnExit();
+
+        Scanner scanner = null;
+        StringBuilder contents = new StringBuilder();
+        // When passing the file contents as an argument to the device side method a 'No
+        // instrumentation found' error was reported due to spaces in the String. To prevent this
+        // the entire contents of the file need to be quoted before passing it as an argument.
+        contents.append("'");
+        try {
+            scanner = new Scanner(procFile);
+            while (scanner.hasNextLine()) {
+                contents.append(scanner.nextLine() + "\n");
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        contents.append("'");
+        return contents.toString();
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
index 15c3d3c..ec113cb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
@@ -16,118 +16,125 @@
 
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-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 static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Test that install of apps using major version codes is being handled properly.
  */
-public class MajorVersionTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MajorVersionTest extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.majorversion";
     private static final String APK_000000000000ffff = "CtsMajorVersion000000000000ffff.apk";
     private static final String APK_00000000ffffffff = "CtsMajorVersion00000000ffffffff.apk";
     private static final String APK_000000ff00000000 = "CtsMajorVersion000000ff00000000.apk";
     private static final String APK_000000ffffffffff = "CtsMajorVersion000000ffffffffff.apk";
 
-    private IAbi mAbi;
-    private CompatibilityBuildHelper mBuildHelper;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mBuildHelper);
-
         getDevice().uninstallPackage(PKG);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
     }
 
-    public void testInstallMinorVersion() throws Exception {
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallMinorVersion_full() throws Exception {
+        testInstallMinorVersion(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallMinorVersion_instant() throws Exception {
+        testInstallMinorVersion(true);
+    }
+    private void testInstallMinorVersion(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_000000000000ffff).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        getDevice().uninstallPackage(PKG);
     }
 
-    public void testInstallMajorVersion() throws Exception {
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000ff00000000), false, false));
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallMajorVersion_full() throws Exception {
+        testInstallMajorVersion(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallMajorVersion_instant() throws Exception {
+        testInstallMajorVersion(true);
+    }
+    private void testInstallMajorVersion(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_000000ff00000000).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        getDevice().uninstallPackage(PKG);
     }
 
-    public void testInstallUpdateAcrossMinorMajorVersion() throws Exception {
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallUpdateAcrossMinorMajorVersion_full() throws Exception {
+        testInstallUpdateAcrossMinorMajorVersion(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallUpdateAcrossMinorMajorVersion_instant() throws Exception {
+        testInstallUpdateAcrossMinorMajorVersion(true);
+    }
+    private void testInstallUpdateAcrossMinorMajorVersion(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_000000000000ffff).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+        new InstallMultiple(instant).addApk(APK_00000000ffffffff).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+        new InstallMultiple(instant).addApk(APK_000000ff00000000).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000ffffffffff), true, false));
+        new InstallMultiple(instant).addApk(APK_000000ffffffffff).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        getDevice().uninstallPackage(PKG);
     }
 
-    public void testInstallDowngradeAcrossMajorMinorVersion() throws Exception {
-        assertNull(getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000ffffffffff), false, false));
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallDowngradeAcrossMajorMinorVersion_full() throws Exception {
+        testInstallDowngradeAcrossMajorMinorVersion(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallDowngradeAcrossMajorMinorVersion_instant() throws Exception {
+        testInstallDowngradeAcrossMajorMinorVersion(true);
+    }
+    private void testInstallDowngradeAcrossMajorMinorVersion(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_000000ffffffffff).run();
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+        new InstallMultiple(instant).addApk(APK_00000000ffffffff)
+                .runExpectingFailure("INSTALL_FAILED_VERSION_DOWNGRADE");
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+        new InstallMultiple(instant).addApk(APK_000000ff00000000)
+                .runExpectingFailure("INSTALL_FAILED_VERSION_DOWNGRADE");
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
-                mBuildHelper.getTestFile(APK_000000000000ffff), true, false));
+        new InstallMultiple(instant).addApk(APK_000000000000ffff)
+                .runExpectingFailure("INSTALL_FAILED_VERSION_DOWNGRADE");
         assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
         runVersionDeviceTests("testCheckVersion");
-        getDevice().uninstallPackage(PKG);
     }
 
-    private void runVersionDeviceTests(String testMethodName)
-            throws DeviceNotAvailableException {
+    private void runVersionDeviceTests(String testMethodName) throws Exception {
         runDeviceTests(PKG, PKG + ".VersionTest", testMethodName);
     }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
index cf903db..0a2c5ce 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -15,38 +15,38 @@
  */
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
+import static org.junit.Assert.assertFalse;
 
-public class OverlayHostTest extends DeviceTestCase implements IBuildReceiver {
+import android.platform.test.annotations.AppModeFull;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull(reason = "Overlays cannot be instant apps")
+public class OverlayHostTest extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.overlayapp";
     private static final String APK = "CtsOverlayApp.apk";
-    private CompatibilityBuildHelper mBuildHelper;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         getDevice().uninstallPackage(PKG);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
-        super.tearDown();
     }
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-    }
-
+    @Test
     public void testInstallingOverlayHasNoEffect() throws Exception {
         assertFalse(getDevice().getInstalledPackageNames().contains(PKG));
 
         // Try to install the overlay, but expect an error.
-        assertNotNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
+        new InstallMultiple().addApk(APK).runExpectingFailure();
 
         // The install should have failed.
         assertFalse(getDevice().getInstalledPackageNames().contains(PKG));
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java
index 680798a..e081d62 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageResolutionHostTest.java
@@ -16,11 +16,8 @@
 
 package android.appsecurity.cts;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -38,12 +35,10 @@
     private static final String TINY_PKG = "android.appsecurity.cts.orderedactivity";
 
     private String mOldVerifierValue;
-    private CompatibilityBuildHelper mBuildHelper;
 
     @Before
     public void setUp() throws Exception {
         getDevice().uninstallPackage(TINY_PKG);
-        mBuildHelper = new CompatibilityBuildHelper(getBuild());
     }
 
     @After
@@ -52,26 +47,56 @@
     }
 
     @Test
-    public void testResolveOrderedActivity() throws Exception {
-        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testResolveOrderedActivity_full() throws Exception {
+        testResolveOrderedActivity(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testResolveOrderedActivity_instant() throws Exception {
+        testResolveOrderedActivity(true);
+    }
+    private void testResolveOrderedActivity(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(TINY_APK)
+                .run();
         Utils.runDeviceTests(getDevice(), TINY_PKG,
                 ".PackageResolutionTest", "queryActivityOrdered");
-        getDevice().uninstallPackage(TINY_PKG);
     }
 
     @Test
-    public void testResolveOrderedService() throws Exception {
-        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testResolveOrderedService_full() throws Exception {
+        testResolveOrderedService(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testResolveOrderedService_instant() throws Exception {
+        testResolveOrderedService(true);
+    }
+    private void testResolveOrderedService(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(TINY_APK)
+                .run();
         Utils.runDeviceTests(getDevice(), TINY_PKG,
                 ".PackageResolutionTest", "queryServiceOrdered");
-        getDevice().uninstallPackage(TINY_PKG);
     }
 
     @Test
-    public void testResolveOrderedReceiver() throws Exception {
-        getDevice().installPackage(mBuildHelper.getTestFile(TINY_APK), true);
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testResolveOrderedReceiver_full() throws Exception {
+        testResolveOrderedReceiver(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testResolveOrderedReceiver_instant() throws Exception {
+        testResolveOrderedReceiver(true);
+    }
+    private void testResolveOrderedReceiver(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addApk(TINY_APK)
+                .run();
         Utils.runDeviceTests(getDevice(), TINY_PKG,
                 ".PackageResolutionTest", "queryReceiverOrdered");
-        getDevice().uninstallPackage(TINY_PKG);
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
index 5bb70d1..c8c1e67 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
@@ -19,7 +19,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -66,7 +67,16 @@
     }
 
     @Test
-    public void testUninstalledPackageVisibility() throws Exception {
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testUninstalledPackageVisibility_full() throws Exception {
+        testUninstalledPackageVisibility(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testUninstalledPackageVisibility_instant() throws Exception {
+        testUninstalledPackageVisibility(true);
+    }
+    private void testUninstalledPackageVisibility(boolean instant) throws Exception {
         if (!mSupportsMultiUser) {
             return;
         }
@@ -135,8 +145,7 @@
         getDevice().uninstallPackage(TEST_PKG);
     }
 
-    protected void uninstallWithKeepDataForUser(String packageName, int userId)
-            throws DeviceNotAvailableException {
+    private void uninstallWithKeepDataForUser(String packageName, int userId) throws Exception {
         final String command = "pm uninstall -k --user " + userId + " " + packageName;
         getDevice().executeShellCommand(command);
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index e4b0f09..3f76502 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -16,6 +16,7 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -35,9 +36,12 @@
     private static final String ESCALATE_PERMISSION_PKG = "com.android.cts.escalate.permission";
 
     private static final String APK_22 = "CtsUsePermissionApp22.apk";
+    private static final String APK_22_ONLY_CALENDAR = "RequestsOnlyCalendarApp22.apk";
     private static final String APK_23 = "CtsUsePermissionApp23.apk";
     private static final String APK_25 = "CtsUsePermissionApp25.apk";
     private static final String APK_26 = "CtsUsePermissionApp26.apk";
+    private static final String APK_28 = "CtsUsePermissionApp28.apk";
+    private static final String APK_P0 = "CtsUsePermissionAppP0.apk";
     private static final String APK_Latest = "CtsUsePermissionAppLatest.apk";
 
     private static final String APK_PERMISSION_POLICY_25 = "CtsPermissionPolicyTest25.apk";
@@ -48,9 +52,10 @@
     private static final String APK_ESCLATE_TO_RUNTIME_PERMISSIONS =
             "CtsEscalateToRuntimePermissions.apk";
 
-    private static final String APK_ACCESS_SERIAL_LEGACY = "CtsAccessSerialLegacy.apk";
-    private static final String APK_ACCESS_SERIAL_MODERN = "CtsAccessSerialModern.apk";
-    private static final String ACCESS_SERIAL_PKG = "android.os.cts";
+    private static final String REVIEW_HELPER_APK = "ReviewPermissionHelper.apk";
+    private static final String REVIEW_HELPER_PKG = "com.android.cts.reviewpermissionhelper";
+    private static final String REVIEW_HELPER_TEST_CLASS = REVIEW_HELPER_PKG
+            + ".ReviewPermissionsTest";
 
     private static final String SCREEN_OFF_TIMEOUT_NS = "system";
     private static final String SCREEN_OFF_TIMEOUT_KEY = "screen_off_timeout";
@@ -69,6 +74,16 @@
         mBuildHelper = new CompatibilityBuildHelper(buildInfo);
     }
 
+    /**
+     * Approve the review permission prompt
+     */
+    private void approveReviewPermissionDialog() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS, "approveReviewPermissions");
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -80,7 +95,6 @@
         getDevice().uninstallPackage(USES_PERMISSION_PKG);
         getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
         getDevice().uninstallPackage(PERMISSION_POLICY_25_PKG);
-        getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
 
         // Set screen timeout to 30 min to not timeout while waiting for UI to change
         mScreenTimeoutBeforeTest = getDevice().getSetting(SCREEN_OFF_TIMEOUT_NS,
@@ -102,7 +116,6 @@
         getDevice().uninstallPackage(USES_PERMISSION_PKG);
         getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
         getDevice().uninstallPackage(PERMISSION_POLICY_25_PKG);
-        getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
     }
 
     public void testFail() throws Exception {
@@ -135,14 +148,22 @@
         }
     }
 
+    @AppModeFull(reason = "Instant applications must be at least SDK 26")
     public void testCompatDefault22() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false, false));
+
+        approveReviewPermissionDialog();
+
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
                 "testCompatDefault");
     }
 
+    @AppModeFull(reason = "Instant applications must be at least SDK 26")
     public void testCompatRevoked22() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false, false));
+
+        approveReviewPermissionDialog();
+
         boolean didThrow = false;
         try {
             runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
@@ -157,8 +178,12 @@
                 "testCompatRevoked_part2");
     }
 
+    @AppModeFull(reason = "Instant applications must be at least SDK 26")
     public void testNoRuntimePrompt22() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false, false));
+
+        approveReviewPermissionDialog();
+
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
                 "testNoRuntimePrompt");
     }
@@ -282,6 +307,9 @@
 
     public void testUpgradeKeepsPermissions() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false, false));
+
+        approveReviewPermissionDialog();
+
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
                 "testAllPermissionsGrantedByDefault");
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), true, false));
@@ -314,6 +342,9 @@
 
     public void testRevokePropagatedOnUpgradeOldToNewModel() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false, false));
+
+        approveReviewPermissionDialog();
+
         boolean didThrow = false;
         try {
             runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
@@ -382,21 +413,117 @@
                 "testInvalidPermission");
     }
 
+    public void testPermissionSplit28() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_28), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest28",
+                "testLocationPermissionWasSplit");
+    }
 
-    public void testSerialAccessPolicy() throws Exception {
-        // Verify legacy app behavior
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
-                APK_ACCESS_SERIAL_LEGACY), false, false));
-        runDeviceTests(ACCESS_SERIAL_PKG,
-                "android.os.cts.AccessSerialLegacyTest",
-                "testAccessSerialNoPermissionNeeded");
+    public void testPermissionNotSplitP0() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "locationPermissionIsNotSplit");
+    }
 
-        // Verify modern app behavior
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
-                APK_ACCESS_SERIAL_MODERN), true, false));
-        runDeviceTests(ACCESS_SERIAL_PKG,
-                "android.os.cts.AccessSerialModernTest",
-                "testAccessSerialPermissionNeeded");
+    public void testRequestOnlyBackgroundNotPossible() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "requestOnlyBackgroundNotPossible");
+    }
+
+    public void testRequestBoth() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "requestBoth");
+    }
+
+    public void testRequestBothInSequence() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "requestBothInSequence");
+    }
+
+    public void testRequestBothButGrantInSequence() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "requestBothButGrantInSequence");
+    }
+
+    public void testDenyBackgroundWithPrejudice() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_P0), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "denyBackgroundWithPrejudice");
+    }
+
+    public void testPermissionNotSplitLatest() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_Latest), false, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTestP0",
+                "locationPermissionIsNotSplit");
+    }
+
+    public void testDenyCalendarDuringReview() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22_ONLY_CALENDAR), false,
+                false));
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS, "denyCalendarPermissions");
+
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
+                "testAssertNoCalendarAccess");
+    }
+
+    public void testDenyGrantCalendarDuringReview() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22_ONLY_CALENDAR), false,
+                false));
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS, "denyGrantCalendarPermissions");
+
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
+                "testAssertCalendarAccess");
+    }
+
+    public void testDenyGrantDenyCalendarDuringReview() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22_ONLY_CALENDAR), false,
+                false));
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS,
+                "denyGrantDenyCalendarPermissions");
+
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
+                "testAssertNoCalendarAccess");
+    }
+
+    public void testCancelReview() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22_ONLY_CALENDAR), false,
+                false));
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        // Start APK_22_ONLY_CALENDAR, but cancel review
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS,
+                "cancelReviewPermissions");
+
+        // Start APK_22_ONLY_CALENDAR again, now approve review
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS, "approveReviewPermissions");
+
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS,
+                "assertNoReviewPermissionsNeeded");
+    }
+
+    public void testReviewPermissionWhenServiceIsBound() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), false,
+                false));
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(REVIEW_HELPER_APK), true,
+                true));
+
+        // Check if service of APK_22 has permissions
+        runDeviceTests(REVIEW_HELPER_PKG, REVIEW_HELPER_TEST_CLASS,
+                "reviewPermissionWhenServiceIsBound");
     }
 
     private void runDeviceTests(String packageName, String testClassName, String testMethodName)
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
index 66cee05..aa28a97 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
@@ -16,6 +16,7 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.AppModeFull;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
@@ -31,6 +32,7 @@
 /**
  * Tests that verify intent filters.
  */
+@AppModeFull(reason="Instant applications can never be system or privileged")
 public class PrivilegedUpdateTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
     private static final String TAG = "PrivilegedUpdateTests";
     private static final String SHIM_PKG = "com.android.cts.priv.ctsshim";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
index 96992bf..552b298 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
@@ -17,7 +17,7 @@
 package android.appsecurity.cts;
 
 /**
- * Set of tests that verify behavior of the Scoped Directory access API.
+ * Set of tests that verify behavior of the deprecated Scoped Directory access API.
  */
 public class ScopedDirectoryAccessTest extends DocumentsTestCase {
 
@@ -25,45 +25,8 @@
         runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testInvalidPath");
     }
 
-    public void testUserRejects() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testUserRejects");
-    }
-
-    public void testUserAccepts() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testUserAccepts");
-    }
-
-    public void testUserAcceptsNewDirectory() throws Exception {
+    public void testActivityFailsForAllVolumesAndDirectories() throws Exception {
         runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
-                "testUserAcceptsNewDirectory");
-    }
-
-    public void testNotAskedAgain() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testNotAskedAgain");
-    }
-
-    public void testDeniesOnceForAllClearedWhenPackageRemoved() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
-                "testRemovePackageStep1UserDenies");
-        reinstallClientPackage();
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
-                "testRemovePackageStep2UserAcceptsDoNotClear");
-    }
-
-    public void testDeniesOnceButAllowsAskingAgain() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
-                "testDeniesOnceButAllowsAskingAgain");
-    }
-
-    public void testDeniesOnceForAll() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testDeniesOnceForAll");
-    }
-
-    public void testResetDoNotAskAgain() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testResetDoNotAskAgain");
-    }
-
-    public void testResetGranted() throws Exception {
-        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testResetGranted");
+                "testActivityFailsForAllVolumesAndDirectories");
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index acec1fb..b1128a9 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -16,23 +16,32 @@
 
 package android.appsecurity.cts;
 
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-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 static org.junit.Assert.assertNotNull;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+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.HashMap;
 
 /**
  * Tests that verify installing of various split APKs from host side.
  */
-public class SplitTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SplitTests extends BaseAppSecurityTest {
     static final String PKG_NO_RESTART = "com.android.cts.norestart";
     static final String APK_NO_RESTART_BASE = "CtsNoRestartBase.apk";
     static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
 
+    static final String APK_NEED_SPLIT_BASE = "CtsNeedSplitApp.apk";
+    static final String APK_NEED_SPLIT_FEATURE = "CtsNeedSplitFeature.apk";
+    static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
+
     static final String PKG = "com.android.cts.splitapp";
     static final String CLASS = PKG + ".SplitAppTest";
 
@@ -79,50 +88,61 @@
         ABI_TO_APK.put("mips", APK_mips);
     }
 
-    private IAbi mAbi;
-    private IBuildInfo mCtsBuild;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mCtsBuild);
-
-        getDevice().uninstallPackage(PKG);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
         getDevice().uninstallPackage(PKG);
         getDevice().uninstallPackage(PKG_NO_RESTART);
     }
 
-    public void testSingleBase() throws Exception {
-        new InstallMultiple().addApk(APK).run();
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(PKG);
+        getDevice().uninstallPackage(PKG_NO_RESTART);
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testSingleBase_full() throws Exception {
+        testSingleBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testSingleBase_instant() throws Exception {
+        testSingleBase(true);
+    }
+    private void testSingleBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
         runDeviceTests(PKG, CLASS, "testSingleBase");
     }
 
-    public void testDensitySingle() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDensitySingle_full() throws Exception {
+        testDensitySingle(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDensitySingle_instant() throws Exception {
+        testDensitySingle(true);
+    }
+    private void testDensitySingle(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_mdpi).run();
         runDeviceTests(PKG, CLASS, "testDensitySingle");
     }
 
-    public void testDensityAll() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_mdpi).addApk(APK_hdpi).addApk(APK_xhdpi)
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDensityAll_full() throws Exception {
+        testDensityAll(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDensityAll_instant() throws Exception {
+        testDensityAll(true);
+    }
+    private void testDensityAll(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_mdpi).addApk(APK_hdpi).addApk(APK_xhdpi)
                 .addApk(APK_xxhdpi).run();
         runDeviceTests(PKG, CLASS, "testDensityAll");
     }
@@ -131,12 +151,22 @@
      * Install first with low-resolution resources, then add a split that offers
      * higher-resolution resources.
      */
-    public void testDensityBest() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDensityBest_full() throws Exception {
+        testDensityBest(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDensityBest_instant() throws Exception {
+        testDensityBest(true);
+    }
+    private void testDensityBest(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_mdpi).run();
         runDeviceTests(PKG, CLASS, "testDensityBest1");
 
         // Now splice in an additional split which offers better resources
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_xxhdpi).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_xxhdpi).run();
         runDeviceTests(PKG, CLASS, "testDensityBest2");
     }
 
@@ -144,13 +174,33 @@
      * Verify that an API-based split can change enabled/disabled state of
      * manifest elements.
      */
-    public void testApi() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_v7).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testApi_full() throws Exception {
+        testApi(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testApi_instant() throws Exception {
+        testApi(true);
+    }
+    private void testApi(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_v7).run();
         runDeviceTests(PKG, CLASS, "testApi");
     }
 
-    public void testLocale() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_de).addApk(APK_fr).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testLocale_full() throws Exception {
+        testLocale(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testLocale_instant() throws Exception {
+        testLocale(true);
+    }
+    private void testLocale(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_de).addApk(APK_fr).run();
         runDeviceTests(PKG, CLASS, "testLocale");
     }
 
@@ -158,12 +208,22 @@
      * Install test app with <em>single</em> split that exactly matches the
      * currently active ABI. This also explicitly forces ABI when installing.
      */
-    public void testNativeSingle() throws Exception {
-        final String abi = mAbi.getName();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeSingle_full() throws Exception {
+        testNativeSingle(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeSingle_instant() throws Exception {
+        testNativeSingle(true);
+    }
+    private void testNativeSingle(boolean instant) throws Exception {
+        final String abi = getAbi().getName();
         final String apk = ABI_TO_APK.get(abi);
         assertNotNull("Failed to find APK for ABI " + abi, apk);
 
-        new InstallMultiple().addApk(APK).addApk(apk).run();
+        new InstallMultiple(instant).addApk(APK).addApk(apk).run();
         runDeviceTests(PKG, CLASS, "testNative");
     }
 
@@ -173,12 +233,22 @@
      * installing, instead exercising the system's ability to choose the ABI
      * through inspection of the installed app.
      */
-    public void testNativeSingleNatural() throws Exception {
-        final String abi = mAbi.getName();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeSingleNatural_full() throws Exception {
+        testNativeSingleNatural(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeSingleNatural_instant() throws Exception {
+        testNativeSingleNatural(true);
+    }
+    private void testNativeSingleNatural(boolean instant) throws Exception {
+        final String abi = getAbi().getName();
         final String apk = ABI_TO_APK.get(abi);
         assertNotNull("Failed to find APK for ABI " + abi, apk);
 
-        new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
+        new InstallMultiple(instant).useNaturalAbi().addApk(APK).addApk(apk).run();
         runDeviceTests(PKG, CLASS, "testNative");
     }
 
@@ -186,8 +256,18 @@
      * Install test app with <em>all</em> possible ABI splits. This also
      * explicitly forces ABI when installing.
      */
-    public void testNativeAll() throws Exception {
-        final InstallMultiple inst = new InstallMultiple().addApk(APK);
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeAll_full() throws Exception {
+        testNativeAll(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeAll_instant() throws Exception {
+        testNativeAll(true);
+    }
+    private void testNativeAll(boolean instant) throws Exception {
+        final InstallMultiple inst = new InstallMultiple(instant).addApk(APK);
         for (String apk : ABI_TO_APK.values()) {
             inst.addApk(apk);
         }
@@ -201,8 +281,18 @@
      * system's ability to choose the ABI through inspection of the installed
      * app.
      */
-    public void testNativeAllNatural() throws Exception {
-        final InstallMultiple inst = new InstallMultiple().useNaturalAbi().addApk(APK);
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeAllNatural_full() throws Exception {
+        testNativeAllNatural(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeAllNatural_instant() throws Exception {
+        testNativeAllNatural(true);
+    }
+    private void testNativeAllNatural(boolean instant) throws Exception {
+        final InstallMultiple inst = new InstallMultiple(instant).useNaturalAbi().addApk(APK);
         for (String apk : ABI_TO_APK.values()) {
             inst.addApk(apk);
         }
@@ -210,104 +300,310 @@
         runDeviceTests(PKG, CLASS, "testNative");
     }
 
-    public void testDuplicateBase() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDuplicateBase_full() throws Exception {
+        testDuplicateBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDuplicateBase_instant() throws Exception {
+        testDuplicateBase(true);
+    }
+    private void testDuplicateBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK).runExpectingFailure();
     }
 
-    public void testDuplicateSplit() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_v7).addApk(APK_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDuplicateSplit_full() throws Exception {
+        testDuplicateSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDuplicateSplit_instant() throws Exception {
+        testDuplicateSplit(true);
+    }
+    private void testDuplicateSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_v7).addApk(APK_v7).runExpectingFailure();
     }
 
-    public void testDiffCert() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffCert_full() throws Exception {
+        testDiffCert(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffCert_instant() throws Exception {
+        testDiffCert(true);
+    }
+    private void testDiffCert(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
     }
 
-    public void testDiffCertInherit() throws Exception {
-        new InstallMultiple().addApk(APK).run();
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffCertInherit_full() throws Exception {
+        testDiffCertInherit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffCertInherit_instant() throws Exception {
+        testDiffCertInherit(true);
+    }
+    private void testDiffCertInherit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
     }
 
-    public void testDiffVersion() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffVersion_full() throws Exception {
+        testDiffVersion(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffVersion_instant() throws Exception {
+        testDiffVersion(true);
+    }
+    private void testDiffVersion(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
     }
 
-    public void testDiffVersionInherit() throws Exception {
-        new InstallMultiple().addApk(APK).run();
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffVersionInherit_full() throws Exception {
+        testDiffVersionInherit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffVersionInherit_instant() throws Exception {
+        testDiffVersionInherit(true);
+    }
+    private void testDiffVersionInherit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
     }
 
-    public void testDiffRevision() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffRevision_full() throws Exception {
+        testDiffRevision(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffRevision_instant() throws Exception {
+        testDiffRevision(true);
+    }
+    private void testDiffRevision(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
         runDeviceTests(PKG, CLASS, "testRevision0_12");
     }
 
-    public void testDiffRevisionInheritBase() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_v7).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffRevisionInheritBase_full() throws Exception {
+        testDiffRevisionInheritBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffRevisionInheritBase_instant() throws Exception {
+        testDiffRevisionInheritBase(true);
+    }
+    private void testDiffRevisionInheritBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_v7).run();
         runDeviceTests(PKG, CLASS, "testRevision0_0");
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
         runDeviceTests(PKG, CLASS, "testRevision0_12");
     }
 
-    public void testDiffRevisionInheritSplit() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_v7).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffRevisionInheritSplit_full() throws Exception {
+        testDiffRevisionInheritSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffRevisionInheritSplit_instant() throws Exception {
+        testDiffRevisionInheritSplit(true);
+    }
+    private void testDiffRevisionInheritSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_v7).run();
         runDeviceTests(PKG, CLASS, "testRevision0_0");
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
         runDeviceTests(PKG, CLASS, "testRevision12_0");
     }
 
-    public void testDiffRevisionDowngrade() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
-        new InstallMultiple().inheritFrom(PKG).addApk(APK_v7).runExpectingFailure();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testDiffRevisionDowngrade_full() throws Exception {
+        testDiffRevisionDowngrade(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testDiffRevisionDowngrade_instant() throws Exception {
+        testDiffRevisionDowngrade(true);
+    }
+    private void testDiffRevisionDowngrade(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addApk(APK_v7).runExpectingFailure();
     }
 
-    public void testFeatureBase() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_FEATURE).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testFeatureBase_full() throws Exception {
+        testFeatureBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testFeatureBase_instant() throws Exception {
+        testFeatureBase(true);
+    }
+    private void testFeatureBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_FEATURE).run();
         runDeviceTests(PKG, CLASS, "testFeatureBase");
     }
 
-    public void testFeatureApi() throws Exception {
-        new InstallMultiple().addApk(APK).addApk(APK_FEATURE).addApk(APK_FEATURE_v7).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testFeatureApi_full() throws Exception {
+        testFeatureApi(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testFeatureApi_instant() throws Exception {
+        testFeatureApi(true);
+    }
+    private void testFeatureApi(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).addApk(APK_FEATURE).addApk(APK_FEATURE_v7).run();
         runDeviceTests(PKG, CLASS, "testFeatureApi");
     }
 
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
     public void testInheritUpdatedBase() throws Exception {
         // TODO: flesh out this test
     }
 
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
     public void testInheritUpdatedSplit() throws Exception {
         // TODO: flesh out this test
     }
 
-    public void testFeatureWithoutRestart() throws Exception {
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testFeatureWithoutRestart_full() throws Exception {
+        testFeatureWithoutRestart(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testFeatureWithoutRestart_instant() throws Exception {
+        testFeatureWithoutRestart(true);
+    }
+    private void testFeatureWithoutRestart(boolean instant) throws Exception {
+        // always install as a full app; we're testing that the instant app can be
+        // updated without restarting and need a broadcast receiver to ensure the
+        // correct behaviour. So, this component must be visible to instant apps.
         new InstallMultiple().addApk(APK).run();
-        new InstallMultiple().addApk(APK_NO_RESTART_BASE).run();
-        runDeviceTests(PKG, CLASS, "testBaseInstalled");
-        new InstallMultiple()
+
+        new InstallMultiple(instant).addApk(APK_NO_RESTART_BASE).run();
+        runDeviceTests(PKG, CLASS, "testBaseInstalled", instant);
+        new InstallMultiple(instant)
                 .addArg("--dont-kill")
                 .inheritFrom(PKG_NO_RESTART)
                 .addApk(APK_NO_RESTART_FEATURE)
                 .run();
-        runDeviceTests(PKG, CLASS, "testFeatureInstalled");
+        runDeviceTests(PKG, CLASS, "testFeatureInstalled", instant);
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitMissing_full() throws Exception {
+        testSingleBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitMissing_instant() throws Exception {
+        testSingleBase(true);
+    }
+    private void testRequiredSplitMissing(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_NEED_SPLIT_BASE)
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitInstalledFeature_full() throws Exception {
+        testSingleBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitInstalledFeature_instant() throws Exception {
+        testSingleBase(true);
+    }
+    private void testRequiredSplitInstalledFeature(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_NEED_SPLIT_BASE).addApk(APK_NEED_SPLIT_FEATURE)
+                .run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitInstalledConfig_full() throws Exception {
+        testSingleBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitInstalledConfig_instant() throws Exception {
+        testSingleBase(true);
+    }
+    private void testRequiredSplitInstalledConfig(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK_NEED_SPLIT_BASE).addApk(APK_NEED_SPLIT_CONFIG)
+                .run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitRemoved_full() throws Exception {
+        testSingleBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitRemoved_instant() throws Exception {
+        testSingleBase(true);
+    }
+    private void testRequiredSplitRemoved(boolean instant) throws Exception {
+        // start with a base and two splits
+        new InstallMultiple(instant)
+                .addApk(APK_NEED_SPLIT_BASE)
+                .addApk(APK_NEED_SPLIT_FEATURE)
+                .addApk(APK_NEED_SPLIT_CONFIG)
+                .run();
+        // it's okay to remove one of the splits
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("split_feature").run();
+        // but, not to remove all of them
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("split_config.xxhdpi")
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
     }
 
     /**
      * Verify that installing a new version of app wipes code cache.
      */
-    public void testClearCodeCache() throws Exception {
-        new InstallMultiple().addApk(APK).run();
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testClearCodeCache_full() throws Exception {
+        testClearCodeCache(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testClearCodeCache_instant() throws Exception {
+        testClearCodeCache(true);
+    }
+    private void testClearCodeCache(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
         runDeviceTests(PKG, CLASS, "testCodeCacheWrite");
-        new InstallMultiple().addArg("-r").addApk(APK_DIFF_VERSION).run();
+        new InstallMultiple(instant).addArg("-r").addApk(APK_DIFF_VERSION).run();
         runDeviceTests(PKG, CLASS, "testCodeCacheRead");
     }
-
-    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
-        public InstallMultiple() {
-            super(getDevice(), mCtsBuild, mAbi);
-        }
-    }
-
-    public void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
-    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 38f7347..cc8410a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -23,6 +23,7 @@
 import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
 import junit.framework.AssertionFailedError;
 
@@ -140,14 +141,14 @@
     @Test
     public void testVerifyStatsExternal() throws Exception {
         for (int user : mUsers) {
-            runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", user);
+            runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", user, true);
         }
     }
 
     @Test
     public void testVerifyStatsExternalConsistent() throws Exception {
         for (int user : mUsers) {
-            runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user);
+            runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user, true);
         }
     }
 
@@ -191,46 +192,24 @@
         getDevice().executeShellCommand("pm trim-caches 4096G");
         getDevice().executeShellCommand("rm -rf /sdcard/*");
 
-        // We're interested in any crashes while disk full
-        final String lastEvent = getDevice().executeShellCommand("logcat -d -b events -t 1");
-        final String sinceTime = lastEvent.trim().substring(0, 18);
-
         try {
             // Try our hardest to fill up the entire disk
-            Utils.runDeviceTests(getDevice(), PKG_A, CLASS, "testFullDisk");
+            Utils.runDeviceTests(getDevice(), PKG_B, CLASS, "testFullDisk");
         } catch (Throwable t) {
-            // If we had trouble filling the disk, don't bother going any
-            // further; we failed because we either don't have quota support, or
-            // because disk was more than 10% full.
-            return;
+            if (t.getMessage().contains("Skipping")) {
+                // If the device doens't have resgid support, there's nothing
+                // for this test to verify
+                return;
+            } else {
+                throw new AssertionFailedError(t.getMessage());
+            }
         }
 
         // Tweak something that causes PackageManager to persist data
         Utils.runDeviceTests(getDevice(), PKG_A, CLASS, "testTweakComponent");
 
-        // Try poking around a couple of settings apps
-        getDevice().executeShellCommand("input keyevent KEY_HOME");
-        Thread.sleep(1000);
-        getDevice().executeShellCommand("am start -a android.settings.SETTINGS");
-        Thread.sleep(2000);
-        getDevice().executeShellCommand("input keyevent KEY_BACK");
-        Thread.sleep(1000);
-        getDevice().executeShellCommand("am start -a android.os.storage.action.MANAGE_STORAGE");
-        Thread.sleep(2000);
-        getDevice().executeShellCommand("input keyevent KEY_BACK");
-        Thread.sleep(1000);
-
-        // Our misbehaving app above shouldn't have caused anything else to
-        // think the disk was full
-        String troubleLogs = getDevice().executeShellCommand(
-                "logcat -d -t '" + sinceTime + "' -e '(ENOSPC|No space left on device)'");
-
-        if (troubleLogs == null) troubleLogs = "";
-        troubleLogs = troubleLogs.trim().replaceAll("\\-+ beginning of [a-z]+", "");
-
-        if (troubleLogs.length() > 4) {
-            throw new AssertionFailedError("Unexpected crashes while disk full: " + troubleLogs);
-        }
+        // Verify that Settings can free space used by abusive app
+        Utils.runDeviceTests(getDevice(), PKG_A, CLASS, "testClearSpace");
     }
 
     public void waitForIdle() throws Exception {
@@ -243,7 +222,19 @@
 
     public void runDeviceTests(String packageName, String testClassName, String testMethodName,
             int userId) throws DeviceNotAvailableException {
-        if (!runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, 20 * 60 * 1000L)) {
+        runDeviceTests(packageName, testClassName, testMethodName, userId, false);
+    }
+
+    public void runDeviceTests(String packageName, String testClassName, String testMethodName,
+            int userId, boolean disableIsolatedStorage) throws DeviceNotAvailableException {
+        final DeviceTestRunOptions options = new DeviceTestRunOptions(packageName);
+        options.setDevice(getDevice());
+        options.setTestClassName(testClassName);
+        options.setTestMethodName(testMethodName);
+        options.setUserId(userId);
+        options.setTestTimeoutMs(20 * 60 * 1000L);
+        options.setDisableIsolatedStorage(disableIsolatedStorage);
+        if (!runDeviceTests(options)) {
             TestRunResult res = getLastDeviceRunResults();
             if (res != null) {
                 StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/UsesLibraryHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/UsesLibraryHostTest.java
index 4afeb9bf..41ce7a1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/UsesLibraryHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/UsesLibraryHostTest.java
@@ -16,72 +16,67 @@
 
 package android.appsecurity.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-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 android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Set of tests that verify behavior of runtime permissions, including both
  * dynamic granting and behavior of legacy apps.
  */
-public class UsesLibraryHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@AppModeFull(reason = "TODO verify whether or not these should run in instant mode")
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UsesLibraryHostTest extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.useslibrary";
 
     private static final String APK = "CtsUsesLibraryApp.apk";
     private static final String APK_COMPAT = "CtsUsesLibraryAppCompat.apk";
 
-    private IAbi mAbi;
-    private CompatibilityBuildHelper mBuildHelper;
 
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
-        assertNotNull(mAbi);
-        assertNotNull(mBuildHelper);
-
         getDevice().uninstallPackage(PKG);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
+    @After
+    public void tearDown() throws Exception {
         getDevice().uninstallPackage(PKG);
     }
 
-    public void testUsesLibrary() throws Exception {
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
-        runDeviceTests(PKG, ".UsesLibraryTest", "testUsesLibrary");
+    @Test
+    @AppModeFull
+    public void testUsesLibrary_full() throws Exception {
+        testUsesLibrary(false);
+    }
+    private void testUsesLibrary(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
+        Utils.runDeviceTests(getDevice(), PKG, ".UsesLibraryTest", "testUsesLibrary");
     }
 
-    public void testMissingLibrary() throws Exception {
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
-        runDeviceTests(PKG, ".UsesLibraryTest", "testMissingLibrary");
+    @Test
+    @AppModeFull
+    public void testMissingLibrary_full() throws Exception {
+        testMissingLibrary(false);
+    }
+    public void testMissingLibrary(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
+        Utils.runDeviceTests(getDevice(), PKG, ".UsesLibraryTest", "testMissingLibrary");
     }
 
-    public void testDuplicateLibrary() throws Exception {
-        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK), false, false));
-        runDeviceTests(PKG, ".UsesLibraryTest", "testDuplicateLibrary");
+    @Test
+    @AppModeFull
+    public void testDuplicateLibrary_full() throws Exception {
+        testDuplicateLibrary(false);
     }
-
-    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+    public void testDuplicateLibrary(boolean instant) throws Exception {
+        new InstallMultiple(instant).addApk(APK).run();
+        Utils.runDeviceTests(getDevice(), PKG, ".UsesLibraryTest", "testDuplicateLibrary");
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
index c63720f..c7621e3 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
@@ -16,6 +16,9 @@
 
 package android.appsecurity.cts;
 
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -26,12 +29,28 @@
 import com.android.tradefed.result.TestRunResult;
 
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 public class Utils {
+    private static final String LOG_TAG = Utils.class.getSimpleName();
+
     public static final int USER_SYSTEM = 0;
 
+    public static void runDeviceTestsAsCurrentUser(ITestDevice device, String packageName,
+            String testClassName, String testMethodName) throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, testClassName, testMethodName, device.getCurrentUser(),
+                null);
+    }
+
+    public static void runDeviceTestsAsCurrentUser(ITestDevice device, String packageName,
+            String testClassName, String testMethodName, Map<String, String> testArgs)
+                    throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, testClassName, testMethodName, device.getCurrentUser(),
+                testArgs);
+    }
+
     public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
         runDeviceTests(device, packageName, testClassName, testMethodName, USER_SYSTEM, null);
@@ -156,4 +175,35 @@
         }
         return users;
     }
+
+    public static void waitForBootCompleted(ITestDevice device) throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted(device)) {
+                Log.d(LOG_TAG, "Yay, system is ready!");
+                // or is it really ready?
+                // guard against potential USB mode switch weirdness at boot
+                Thread.sleep(10 * 1000);
+                return;
+            }
+            Log.d(LOG_TAG, "Waiting for system ready...");
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+
+    private static boolean isBootCompleted(ITestDevice device) throws Exception {
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            device.getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
+        } catch (AdbCommandRejectedException e) {
+            // do nothing: device might be temporarily disconnected
+            Log.d(LOG_TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
+        }
+        String output = receiver.getOutput();
+        if (output != null) {
+            output = output.trim();
+        }
+        return "1".equals(output);
+    }
+
 }
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
index bfb88f0..a85305d 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
@@ -26,6 +26,7 @@
 
 LOCAL_PACKAGE_NAME := CtsAccessSerialLegacy
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 27
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
index 4bdb346..dd24d5b 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
@@ -27,6 +27,7 @@
 
 LOCAL_PACKAGE_NAME := CtsAccessSerialModern
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 27
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
index acbb30c..6bfbc15 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
@@ -16,6 +16,7 @@
 
 package android.os.cts;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -25,7 +26,8 @@
 import org.junit.Test;
 
 /**
- * Test that legacy apps can access the device serial without the phone permission.
+ * Test that modern apps cannot access the device serial number even with the phone permission as
+ * 3P apps are now restricted from accessing persistent device identifiers.
  */
 public class AccessSerialModernTest {
     @Test
@@ -51,12 +53,18 @@
         assertTrue("Build.SERIAL must not work for modern apps",
                 Build.UNKNOWN.equals(Build.SERIAL));
 
-        // We have the READ_PHONE_STATE permission, so this should not throw
+        // To prevent breakage an app targeting pre-Q with the READ_PHONE_STATE permission will
+        // receive Build.UNKNOWN; once the app is targeting Q+ a SecurityException will be thrown
+        // even if the app has the READ_PHONE_STATE permission.
         try {
-            assertTrue("Build.getSerial() must work for apps holding READ_PHONE_STATE",
-                    !Build.UNKNOWN.equals(Build.getSerial()));
+            assertEquals("Build.getSerial() must return " + Build.UNKNOWN
+                            + " for an app targeting pre-Q with the READ_PHONE_STATE permission",
+                    Build.UNKNOWN, Build.getSerial());
         } catch (SecurityException e) {
-            fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+            fail("Build.getSerial() must return " + Build.UNKNOWN
+                    + " for an app targeting pre-Q with the READ_PHONE_STATE permission, caught "
+                    + "SecurityException: "
+                    + e);
         }
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
index 6a846fc..6fb5ce5 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.appaccessdata">
+       package="com.android.cts.appaccessdata"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple app to test that other apps cannot access another app's private data.
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
index 7a64583..54e3946 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
@@ -16,6 +16,10 @@
 
 package com.android.cts.appaccessdata;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -24,7 +28,11 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -41,7 +49,8 @@
  *
  * Assumes that {@link #APP_WITH_DATA_PKG} has already created the private and public data.
  */
-public class AccessPrivateDataTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessPrivateDataTest {
 
     /**
      * The Android package name of the application that owns the data
@@ -68,6 +77,7 @@
      * and detailed traffic stats.
      * @throws IOException
      */
+    @Test
     public void testAccessPrivateData() throws IOException {
         try {
             // construct the absolute file path to the app's private file
@@ -84,7 +94,7 @@
 
     private ApplicationInfo getApplicationInfo(String packageName) {
         try {
-            return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+            return InstrumentationRegistry.getContext().getPackageManager().getApplicationInfo(packageName, 0);
         } catch (PackageManager.NameNotFoundException e) {
             throw new IllegalStateException("Expected package not found: " + e);
         }
@@ -94,6 +104,7 @@
      * Tests that another app's public file can be accessed
      * @throws IOException
      */
+    @Test
     public void testAccessPublicData() throws IOException {
         try {
             // construct the absolute file path to the other app's public file
@@ -108,6 +119,7 @@
         }
     }
 
+    @Test
     public void testAccessProcQtaguidTrafficStatsFailed() {
         // For untrusted app with SDK P or above, proc/net/xt_qtaguid files are no long readable.
         // They can only read their own stats from TrafficStats API. The test for TrafficStats API
@@ -116,10 +128,11 @@
             new File(QTAGUID_STATS_FILE).canRead());
     }
 
+    @Test
     public void testAccessPrivateTrafficStats() {
         int otherAppUid = -1;
         try {
-            otherAppUid = getContext()
+            otherAppUid = InstrumentationRegistry.getContext()
                     .createPackageContext(APP_WITH_DATA_PKG, 0 /*flags*/)
                     .getApplicationInfo().uid;
         } catch (NameNotFoundException e) {
@@ -133,7 +146,14 @@
         assertEquals(UNSUPPORTED, TrafficStats.getUidTxPackets(otherAppUid));
     }
 
+    @Test
     public void testTrafficStatsStatsUidSelf() throws Exception {
+        final boolean isInstant = Boolean.parseBoolean(
+                InstrumentationRegistry.getArguments().getString("is_instant"));
+        // test not applicable for instant applications; they cannot shift blame
+        if (isInstant) {
+            return;
+        }
         final int uid = android.os.Process.myUid();
         final long rxb = TrafficStats.getUidRxBytes(uid);
         final long rxp = TrafficStats.getUidRxPackets(uid);
@@ -141,7 +161,7 @@
         final long txp = TrafficStats.getUidTxPackets(uid);
 
         // Start remote server
-        final int port = getContext().getContentResolver().call(PRIVATE_TARGET, "start", null, null)
+        final int port = InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "start", null, null)
                 .getInt("port");
 
         // Try talking to them, but shift blame
@@ -151,7 +171,7 @@
 
             Bundle extras = new Bundle();
             extras.putParcelable("fd", ParcelFileDescriptor.fromSocket(socket));
-            getContext().getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
+            InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
 
             socket.connect(new InetSocketAddress("localhost", port));
 
@@ -163,7 +183,7 @@
         } catch (IOException e) {
             throw new RuntimeException(e);
         } finally {
-            getContext().getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
+            InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
         }
 
         SystemClock.sleep(1000);
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index 2accec1..435e636 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.appwithdata">
+       package="com.android.cts.appwithdata"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple app to test that other apps cannot access another app's private data.
@@ -30,7 +31,8 @@
         <provider
             android:name="com.android.cts.appwithdata.MyProvider"
             android:authorities="com.android.cts.appwithdata"
-            android:exported="true" />
+            android:exported="true"
+            android:visibleToInstantApps="true" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
index 1737817..90b7f83 100644
--- a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
+++ b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
@@ -20,11 +20,13 @@
 import static org.junit.Assert.fail;
 import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
 
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.os.Process;
 import android.support.test.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -67,6 +69,7 @@
     public void testPackageVisibility_anyUserCrossUserNoGrant() throws Exception {
         final PackageManager pm = mContext.getPackageManager();
         try {
+            ungrantAcrossUsersPermission();
             final List<PackageInfo> packageList =
                     pm.getInstalledPackagesAsUser(MATCH_KNOWN_PACKAGES, mContext.getUserId());
             fail("Should have received a security exception");
@@ -87,6 +90,7 @@
     public void testPackageVisibility_otherUserNoGrant() throws Exception {
         final PackageManager pm = mContext.getPackageManager();
         try {
+            ungrantAcrossUsersPermission();
             final List<PackageInfo> packageList =
                     pm.getInstalledPackagesAsUser(0, getTestUser());
             fail("Should have received a security exception");
@@ -116,6 +120,7 @@
     public void testApplicationVisibility_anyUserCrossUserNoGrant() throws Exception {
         final PackageManager pm = mContext.getPackageManager();
         try {
+            ungrantAcrossUsersPermission();
             final List<ApplicationInfo> applicationList =
                     pm.getInstalledApplicationsAsUser(MATCH_KNOWN_PACKAGES, mContext.getUserId());
             fail("Should have received a security exception");
@@ -136,6 +141,7 @@
     public void testApplicationVisibility_otherUserNoGrant() throws Exception {
         final PackageManager pm = mContext.getPackageManager();
         try {
+            ungrantAcrossUsersPermission();
             final List<ApplicationInfo> applicationList =
                     pm.getInstalledApplicationsAsUser(0, getTestUser());
             fail("Should have received a security exception");
@@ -171,4 +177,18 @@
         }
         return mContext.getUserId();
     }
+
+    private static void ungrantAcrossUsersPermission() {
+        final Context context = InstrumentationRegistry.getContext();
+        final PackageManager pm = context.getPackageManager();
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            pm.revokeRuntimePermission(context.getPackageName(),
+                    "android.permission.INTERACT_ACROSS_USERS", Process.myUserHandle());
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/AndroidManifest.xml
index 05f4573..cdaa469 100644
--- a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.classloadersplitapp"
-    android:isolatedSplits="true">
+    android:isolatedSplits="true"
+    android:targetSandboxVersion="2">
 
     <application android:label="ClassloaderSplitApp"
                  android:classLoader="dalvik.system.PathClassLoader">
diff --git a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_a/AndroidManifest.xml
index a334acf..96807d6 100644
--- a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_a/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_a">
+        featureSplit="feature_a"
+        android:targetSandboxVersion="2">
 
     <application android:classLoader="dalvik.system.DelegateLastClassLoader">
         <activity android:name=".feature_a.FeatureAActivity">
diff --git a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_b/AndroidManifest.xml
index 8d9ac52..fa975ad 100644
--- a/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ClassLoaderSplitApp/feature_b/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_b">
+        featureSplit="feature_b"
+        android:targetSandboxVersion="2">
 
     <uses-split android:name="feature_a" />
 
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 e85e167..2f503e3 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
@@ -16,8 +16,6 @@
 
 package com.android.cts.documentclient;
 
-import static org.junit.Assert.assertNotEquals;
-
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -73,6 +71,13 @@
         new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
     }
 
+    private UiObject findSearchViewTextField() {
+        final UiSelector selector = new UiSelector().resourceId(
+                "com.android.documentsui:id/option_menu_search").childSelector(
+                new UiSelector().resourceId("com.android.documentsui:id/search_src_text"));
+        return mDevice.findObject(selector);
+    }
+
     private UiObject findRoot(String label) throws UiObjectNotFoundException {
         final UiSelector rootsList = findRootListSelector();
         revealRoot(rootsList, label);
@@ -80,16 +85,16 @@
         return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
     }
 
-    private UiObject findEjectIcon(String rootLabel) throws UiObjectNotFoundException {
+    private UiObject findActionIcon(String rootLabel) throws UiObjectNotFoundException {
         final UiSelector rootsList = findRootListSelector();
         revealRoot(rootsList, rootLabel);
 
         final UiScrollable rootsListObject = new UiScrollable(rootsList);
         final UiObject rootItem = rootsListObject.getChildByText(
                 new UiSelector().className("android.widget.LinearLayout"), rootLabel, false);
-        final UiSelector ejectIcon =
-                new UiSelector().resourceId("com.android.documentsui:id/eject_icon");
-        return new UiObject(rootItem.getSelector().childSelector(ejectIcon));
+        final UiSelector actionIcon =
+                new UiSelector().resourceId("com.android.documentsui:id/action_icon_area");
+        return new UiObject(rootItem.getSelector().childSelector(actionIcon));
     }
 
     private UiObject findDocument(String label) throws UiObjectNotFoundException {
@@ -271,6 +276,8 @@
         findDocument("DIR2").click();
         mDevice.waitForIdle();
         findSaveButton().click();
+        mDevice.waitForIdle();
+        findPositiveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -339,19 +346,40 @@
         mActivity.startActivityForResult(intent, REQUEST_CODE);
 
         // Look around, we should be able to see both DocumentsProviders and
-        // other GET_CONTENT sources.
+        // other GET_CONTENT sources. If the DocumentsProvider and GetContent
+        // root has the same package, they will be combined as one root item.
         mDevice.waitForIdle();
         assertTrue("CtsLocal root", findRoot("CtsLocal").exists());
         assertTrue("CtsCreate root", findRoot("CtsCreate").exists());
-        assertTrue("CtsGetContent root", findRoot("CtsGetContent").exists());
+        assertFalse("CtsGetContent root", findRoot("CtsGetContent").exists());
 
         mDevice.waitForIdle();
-        findRoot("CtsGetContent").click();
-
-        final Result result = mActivity.getResult();
+        // Both CtsLocal and CtsLocal have action icon and have the same action.
+        findActionIcon("CtsCreate");
+        findActionIcon("CtsLocal").click();
+        Result result = mActivity.getResult();
         assertEquals("ReSuLt", result.data.getAction());
     }
 
+    public void testGetContentWithQueryContent() throws Exception {
+        if (!supportedHardware()) return;
+
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("*/*");
+        final String queryString = "FILE2";
+        intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryString);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        mDevice.waitForIdle();
+
+        assertTrue(findDocument(queryString).exists());
+
+        UiObject textField = findSearchViewTextField();
+        assertTrue(textField.exists());
+        assertEquals(queryString, textField.getText());
+    }
+
     public void testTransferDocument() throws Exception {
         if (!supportedHardware()) return;
 
@@ -371,6 +399,8 @@
         findDocument("DIR2").click();
         mDevice.waitForIdle();
         findSaveButton().click();
+        mDevice.waitForIdle();
+        findPositiveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -478,6 +508,8 @@
         findDocument("DIR2").click();
         mDevice.waitForIdle();
         findSaveButton().click();
+        mDevice.waitForIdle();
+        findPositiveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -543,6 +575,22 @@
         assertTrue(findDocument("FILE4").exists());
     }
 
+    public void testOpenRootWithoutRootIdAtInitialLocation() 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 Uri rootsUri = DocumentsContract.buildRootsUri(PROVIDER_PACKAGE);
+        final Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(rootsUri, "vnd.android.document/root");
+        mActivity.startActivity(intent);
+        mDevice.waitForIdle();
+
+        assertTrue(findDocument("DIR1").exists());
+    }
+
     public void testCreateDocumentAtInitialLocation() throws Exception {
         if (!supportedHardware()) return;
 
@@ -618,7 +666,7 @@
                 Document.MIME_TYPE_DIR);
         mActivity.startActivity(intent);
 
-        findEjectIcon("eject").click();
+        findActionIcon("eject").click();
 
         try {
             findRoot("eject").click();
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
index 135a0f7..8826c5b 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
@@ -26,32 +26,16 @@
 import static android.os.Environment.DIRECTORY_PICTURES;
 import static android.os.Environment.DIRECTORY_PODCASTS;
 import static android.os.Environment.DIRECTORY_RINGTONES;
-import static android.test.MoreAsserts.assertContainsRegex;
-import static android.test.MoreAsserts.assertNotContainsRegex;
-import static android.test.MoreAsserts.assertNotEqual;
-import android.content.ContentResolver;
+
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
-import android.provider.Settings;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
 
 import java.util.List;
 
 /**
- * Set of tests that verify behavior of the Scoped Directory Access API.
+ * Set of tests that verify behavior of the deprecated Scoped Directory Access API.
  */
 public class ScopedDirectoryAccessClientTest extends DocumentsClientTestCase {
     private static final String TAG = "ScopedDirectoryAccessClientTest";
@@ -80,7 +64,7 @@
         clearDocumentsUi();
     }
 
-    public void testInvalidPath() throws Exception {
+    public void testInvalidPath() {
         if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
@@ -92,360 +76,29 @@
         openExternalDirectoryInvalidPath(getPrimaryVolume(), DIRECTORY_ROOT);
     }
 
-    public void testUserRejects() throws Exception {
+    public void testActivityFailsForAllVolumesAndDirectories() {
         if (!supportedHardwareForScopedDirectoryAccess()) return;
 
         for (StorageVolume volume : getVolumes()) {
             // Tests user clicking DENY button, for all valid directories.
             for (String dir : STANDARD_DIRECTORIES) {
-                userRejectsTest(volume, dir);
+                sendOpenExternalDirectoryIntent(volume, dir);
+                assertActivityFailed();
             }
             if (!volume.isPrimary()) {
                 // Also test root
-                userRejectsTest(volume, DIRECTORY_ROOT);
-            }
-            // Also test user clicking back button - one directory is enough.
-            openExternalDirectoryValidPath(volume, DIRECTORY_PICTURES);
-            mDevice.pressBack();
-            assertActivityFailed();
-        }
-    }
-
-    private void userRejectsTest(StorageVolume volume, String dir) throws Exception {
-        final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
-        dialog.noButton.click();
-        assertActivityFailed();
-    }
-
-    public void testUserAccepts() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        for (StorageVolume volume : getVolumes()) {
-            userAcceptsTest(volume, DIRECTORY_PICTURES);
-            if (!volume.isPrimary()) {
-                userAcceptsTest(volume, DIRECTORY_ROOT);
-            }
-        }
-    }
-
-    public void testUserAcceptsNewDirectory() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        // TODO: figure out a better way to remove the directory.
-        final String command = "rm -rf /sdcard/" + DIRECTORY_PICTURES;
-        final String output = executeShellCommand(command);
-        if (!output.isEmpty()) {
-            fail("Command '" + command + "' failed: '" + output + "'");
-        }
-        userAcceptsTest(getPrimaryVolume(), DIRECTORY_PICTURES);
-    }
-
-    public void testNotAskedAgain() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        for (StorageVolume volume : getVolumes()) {
-            final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
-            final Uri grantedUri = userAcceptsTest(volume, DIRECTORY_PICTURES);
-
-            // Calls it again - since the permission has been granted, it should return right
-            // away, without popping up the permissions dialog.
-            sendOpenExternalDirectoryIntent(volume, DIRECTORY_PICTURES);
-            final Intent newData = assertActivitySucceeded("should have already granted "
-                    + "permission to " + volumeDesc + " and " + DIRECTORY_PICTURES);
-            assertEquals(grantedUri, newData.getData());
-
-            // Make sure other directories still require user permission.
-            final Uri grantedUri2 = userAcceptsTest(volume, DIRECTORY_ALARMS);
-            assertNotEqual(grantedUri, grantedUri2);
-        }
-    }
-
-    public void testNotAskedAgainOnRoot() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        for (StorageVolume volume : getVolumes()) {
-            if (volume.isPrimary()) continue;
-            final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
-            final Uri grantedRootUri = userAcceptsTest(volume, DIRECTORY_ROOT);
-
-            // Calls it again - since the permission has been granted, it should return right
-            // away, without popping up the permissions dialog.
-            sendOpenExternalDirectoryIntent(volume, DIRECTORY_ROOT);
-            final Intent rootData = assertActivitySucceeded("should have already granted "
-                    + "permission to " + volumeDesc + " and root dir");
-            assertEquals(grantedRootUri, rootData.getData());
-
-            // Make sure other directories don't permission neither.
-            for (String dir : STANDARD_DIRECTORIES) {
-                sendOpenExternalDirectoryIntent(volume, dir);
-                final Intent childData = assertActivitySucceeded("should have already granted "
-                        + "permission to " + volumeDesc + " and " + dir);
-                assertNotNull(childData);
-                final Uri grantedChildUri = childData.getData();
-                assertFalse("received root URI (" + grantedRootUri + ") for child request",
-                        grantedRootUri.equals(grantedChildUri));
-            }
-        }
-    }
-
-    public void testDeniesOnceButAllowsAskingAgain() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess())return;
-
-        final String[] dirs = { DIRECTORY_DCIM, DIRECTORY_ROOT };
-        for (StorageVolume volume : getVolumes()) {
-            for (String dir : dirs) {
-                if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
-                // Rejects the first attempt...
-                UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
-                dialog.assertDoNotAskAgainVisibility(false);
-                dialog.noButton.click();
+                sendOpenExternalDirectoryIntent(volume, DIRECTORY_ROOT);
                 assertActivityFailed();
-
-                // ...and the second.
-                dialog = openExternalDirectoryValidPath(volume, dir);
-                dialog.assertDoNotAskAgainVisibility(true);
-                dialog.noButton.click();
-                assertActivityFailed();
-
-                // Third time is a charm...
-                userAcceptsTest(volume, dir);
             }
         }
     }
 
-    public void testDeniesOnceForAll() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        final String[] dirs = {DIRECTORY_PICTURES, DIRECTORY_ROOT};
-        for (StorageVolume volume : getVolumes()) {
-            for (String dir : dirs) {
-                if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
-                deniesOnceForAllTest(volume, dir);
-            }
-        }
-    }
-
-    private void deniesOnceForAllTest(StorageVolume volume, String dir) throws Exception {
-        // Rejects the first attempt...
-        UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
-        dialog.assertDoNotAskAgainVisibility(false);
-        dialog.noButton.click();
-        assertActivityFailed();
-
-        // ...and the second, checking the box
-        dialog = openExternalDirectoryValidPath(volume, dir);
-        UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
-        assertTrue("checkbox should not be checkable", checkbox.isCheckable());
-        assertFalse("checkbox should not be checked", checkbox.isChecked());
-        checkbox.click();
-        assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
-        assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
-
-        dialog.noButton.click();
-        assertActivityFailed();
-
-        // Third strike out...
-        sendOpenExternalDirectoryIntent(volume, dir);
-        assertActivityFailed();
-    }
-
-    public void testRemovePackageStep1UserDenies() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        deniesOnceForAllTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
-    }
-
-    public void testRemovePackageStep2UserAcceptsDoNotClear() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        userAcceptsTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
-    }
-
-    private Uri userAcceptsTest(StorageVolume volume, String directoryName) throws Exception {
-        // Asserts dialog contain the proper message.
-        final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, directoryName);
-        final String message = dialog.messageText.getText();
-        Log.v(TAG, "request permission message: " + message);
-        final Context context = getInstrumentation().getContext();
-        final String appLabel = context.getPackageManager().getApplicationLabel(
-                context.getApplicationInfo()).toString();
-        assertContainsRegex("missing app label", appLabel, message);
-        final String volumeLabel = volume.getDescription(context);
-        if (volume.isPrimary()) {
-            assertNotContainsRegex("should not have volume label on primary", volumeLabel, message);
-        } else {
-            assertContainsRegex("missing volume label", volumeLabel, message);
-        }
-        if (directoryName != null) {
-            assertContainsRegex("missing folder", directoryName, message);
-        } else {
-            assertNotContainsRegex("should not have folder for root", "null", message);
-        }
-
-        // Call API...
-        dialog.yesButton.click();
-
-        // ...and get its response.
-        final String volumeDesc = volume.getDescription(context);
-        final Intent data = assertActivitySucceeded("should have already granted "
-                + "permission to " + volumeDesc + " and " + directoryName);
-
-        return assertPermissionGranted(data);
-    }
-
-    /**
-     * Asserts a permission was effectively granted by using it to write docs.
-     *
-     * @return the granted URI.
-     */
-    private Uri assertPermissionGranted(Intent data) throws Exception {
-        final Context context = getInstrumentation().getContext();
-        final Uri grantedUri = data.getData();
-
-        // Test granted permission directly by persisting it...
-        final ContentResolver resolver = context.getContentResolver();
-        final int modeFlags = data.getFlags()
-                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        resolver.takePersistableUriPermission(grantedUri, modeFlags);
-
-        // ...and indirectly by creating some documents
-        final Uri doc = DocumentsContract.buildDocumentUriUsingTree(grantedUri,
-                DocumentsContract.getTreeDocumentId(grantedUri));
-        assertNotNull("could not get tree URI", doc);
-        final Uri pic = DocumentsContract.createDocument(resolver, doc, "image/png", "pic.png");
-        assertNotNull("could not create file (pic.png) on tree root", pic);
-        final Uri dir = DocumentsContract.createDocument(resolver, doc, Document.MIME_TYPE_DIR,
-                "my dir");
-        assertNotNull("could not create child dir (my dir)", pic);
-        final Uri dirPic = DocumentsContract.createDocument(resolver, dir, "image/png", "pic2.png");
-        assertNotNull("could not create file (pic.png) on child dir (my dir)", dirPic);
-
-        writeFully(pic, "pic".getBytes());
-        writeFully(dirPic, "dirPic".getBytes());
-
-        // Clean up created documents.
-        assertTrue("delete", DocumentsContract.deleteDocument(resolver, pic));
-        assertTrue("delete", DocumentsContract.deleteDocument(resolver, dirPic));
-        assertTrue("delete", DocumentsContract.deleteDocument(resolver, dir));
-
-        return grantedUri;
-    }
-
-    public void testResetDoNotAskAgain() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        final StorageVolume volume = getPrimaryVolume();
-        // TODO: ideally it should test all directories, but that would be too slow, and would
-        // require a more sophisticated way to get the toggle object (for example, it might require
-        // scrolling on devices with small screens)
-        final String dir = DIRECTORY_DOCUMENTS;
-
-        // First, triggers it.
-        deniesOnceForAllTest(volume, dir);
-
-        // Then reset it using settings.
-
-        // Launch screen.
-        launchDirectoryAccessSettingsScreenAndSelectApp();
-
-        // Toggle permission for dir.
-        final boolean gotIt = mDevice.wait(Until.hasObject(By.text(dir)), TIMEOUT);
-        assertTrue("object with text'(" + dir + "') not visible yet", gotIt);
-        // TODO: find a better way to get the toggle rather then getting all
-        UiObject2 toggle = getUniqueToggle();
-        assertFalse("toggle for '" + dir + "' should not be checked", toggle.isChecked());
-        toggle.click();
-        toggle = getUniqueToggle();
-        assertTrue("toggle for '" + dir + "' should be checked", toggle.isChecked());
-
-        // Close app screen.
-        mDevice.pressBack();
-        if (!isTelevision()) {
-            // Close main screen.
-            mDevice.pressBack();
-        }
-
-        // Finally, make sure it's reset by requesting it again.
-        sendOpenExternalDirectoryIntent(volume, dir);
-        final Intent newData = assertActivitySucceeded("should have already granted "
-                + "permission to " + volume+ " and " + dir);
-        assertPermissionGranted(newData);
-    }
-
-    public void testResetGranted() throws Exception {
-        if (!supportedHardwareForScopedDirectoryAccess()) return;
-
-        final StorageVolume volume = getPrimaryVolume();
-        // TODO: ideally it should test all directories, but that would be too slow, and would
-        // require a more sophisticated way to get the toggle object (for example, it might require
-        // scrolling on devices with small screens)
-        final String dir = DIRECTORY_DOCUMENTS;
-
-        // First, grants it
-        userAcceptsTest(volume, dir);
-
-        // Then revoke it using settings.
-
-        // Launch screen.
-        launchDirectoryAccessSettingsScreenAndSelectApp();
-
-        // Toggle permission for dir.
-        final boolean gotIt = mDevice.wait(Until.hasObject(By.text(dir)), TIMEOUT);
-        assertTrue("object with text'(" + dir + "') not visible yet", gotIt);
-        // TODO: find a better way to get the toggle rather then getting all
-        UiObject2 toggle = getUniqueToggle();
-        assertTrue("toggle for '" + dir + "' should be checked", toggle.isChecked());
-        toggle.click();
-        toggle = getUniqueToggle();
-        assertFalse("toggle for '" + dir + "' should not be checked", toggle.isChecked());
-
-        // Close app screen.
-        mDevice.pressBack();
-        if (!isTelevision()) {
-            // Close main screen.
-            mDevice.pressBack();
-        }
-
-        // Then tries again - should be denied.
-        sendOpenExternalDirectoryIntent(volume, dir);
-        assertActivityFailed();
-    }
-
-    private UiObject2 getUniqueToggle() {
-        List<UiObject2> toggles = mDevice.findObjects(By.res("android:id/switch_widget"));
-        assertEquals("should have just one toggle: " + toggles, 1, toggles.size());
-        return toggles.get(0);
-    }
-
-    private void launchDirectoryAccessSettingsScreenAndSelectApp() {
-        final Intent intent = new Intent(Settings.ACTION_STORAGE_VOLUME_ACCESS_SETTINGS)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );
-        final Context context = getInstrumentation().getContext();
-        context.startActivity(intent);
-
-        // Select app.
-        final String appLabel = context.getPackageManager().getApplicationLabel(
-                context.getApplicationInfo()).toString();
-        final BySelector byAppLabel = By.text(appLabel);
-        boolean gotIt = mDevice.wait(Until.hasObject(byAppLabel), TIMEOUT);
-        assertTrue("object with text'(" + appLabel + "') not visible yet", gotIt);
-        final UiObject2 appRow = mDevice.findObject(byAppLabel);
-        appRow.click();
-    }
-
     private void openExternalDirectoryInvalidPath(StorageVolume volume, String directoryName) {
         final Intent intent = volume.createAccessIntent(directoryName);
         assertNull("should not get intent for volume '" + volume + "' and directory '"
                 + directoryName + "'", intent);
     }
 
-    private UiAlertDialog openExternalDirectoryValidPath(StorageVolume volume, String path)
-            throws UiObjectNotFoundException {
-        sendOpenExternalDirectoryIntent(volume, path);
-        return new UiAlertDialog(volume, path);
-    }
-
     private void sendOpenExternalDirectoryIntent(StorageVolume volume, String directoryName) {
         final Intent intent = volume.createAccessIntent(directoryName);
         assertNotNull("no intent for '" + volume + "' and directory " + directoryName, intent);
@@ -466,48 +119,4 @@
                 getInstrumentation().getTargetContext().getSystemService(Context.STORAGE_SERVICE);
         return sm.getPrimaryStorageVolume();
     }
-
-    private final class UiAlertDialog {
-        final UiObject dialog;
-        final UiObject messageText;
-        final UiObject yesButton;
-        final UiObject noButton;
-        final String volumeDesc;
-        final String directory;
-
-        UiAlertDialog(StorageVolume volume, String path) throws UiObjectNotFoundException {
-            volumeDesc = volume.getDescription(getInstrumentation().getContext());
-            directory = path;
-
-            final String id = "android:id/parentPanel";
-            boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), TIMEOUT);
-            assertTrue("object with id '(" + id + "') not visible yet for "
-                    + volumeDesc + " and " + path, gotIt);
-            dialog = mDevice.findObject(new UiSelector().resourceId(id));
-            assertTrue("object with id '(" + id + "') doesn't exist", dialog.exists());
-            messageText = dialog.getChild(
-                    new UiSelector().resourceId("com.android.documentsui:id/message"));
-            yesButton = dialog.getChild(new UiSelector().resourceId("android:id/button1"));
-            noButton  = dialog.getChild(new UiSelector().resourceId("android:id/button2"));
-        }
-
-        private UiObject getDoNotAskAgainCheckBox() throws UiObjectNotFoundException {
-            return dialog.getChild(
-                    new UiSelector().resourceId("com.android.documentsui:id/do_not_ask_checkbox"));
-        }
-
-        UiObject assertDoNotAskAgainVisibility(boolean expectVisible) {
-            UiObject checkbox = null;
-            try {
-                checkbox = getDoNotAskAgainCheckBox();
-                assertEquals("Wrong value for 'DoNotAskAgain.exists()",
-                        expectVisible, checkbox.exists());
-            } catch (UiObjectNotFoundException e) {
-                if (expectVisible) {
-                    fail("'Do Not Ask Again' not found");
-                }
-            }
-            return checkbox;
-        }
-    }
 }
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 91abb89..9a0b9ae 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
@@ -33,6 +33,7 @@
 import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
@@ -86,7 +87,7 @@
 
         RowBuilder row = result.newRow();
         row.add(Root.COLUMN_ROOT_ID, "local");
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH);
         row.add(Root.COLUMN_TITLE, "CtsLocal");
         row.add(Root.COLUMN_SUMMARY, "CtsLocalSummary");
         row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
@@ -334,6 +335,20 @@
     }
 
     @Override
+    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        final String lowerCaseQuery = query.toLowerCase();
+        for (Doc doc : mDocs.values()) {
+            if (!TextUtils.isEmpty(doc.displayName) && doc.displayName.toLowerCase().contains(
+                    lowerCaseQuery)) {
+                doc.include(result);
+            }
+        }
+        return result;
+    }
+
+    @Override
     public Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk
new file mode 100644
index 0000000..ce49914
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.mk
@@ -0,0 +1,36 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := CtsDuplicatePermissionDeclareApp
+
+# Use the same cert as the app that also defined the permission
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/AndroidManifest.xml
new file mode 100644
index 0000000..0c8b2d6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.duplicatepermissiondeclareapp">
+
+    <permission android:name="com.android.cts.permissionNormal"/>
+    <uses-permission android:name="com.android.cts.permissionNormal" />
+
+    <permission-tree android:name="com.android.cts" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.duplicatepermissiondeclareapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java
new file mode 100644
index 0000000..3584876
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/src/com/android/cts/duplicatepermissiondeclareapp/VerifyDuplicatePermissionTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.duplicatepermissiondeclareapp;
+
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VerifyDuplicatePermissionTest {
+    private final static String APP_THAT_WAS_INSTALLED_FIRST =
+            "com.android.cts.permissiondeclareapp";
+    private final static String APP_THAT_WAS_INSTALLED_AFTER =
+            "com.android.cts.duplicatepermissiondeclareapp";
+    private final static String PERM = "com.android.cts.permissionNormal";
+
+    private final static PackageManager sPm =
+            InstrumentationRegistry.getTargetContext().getPackageManager();
+
+    private PermissionInfo getDeclaredPermission(String pkg, String permission) throws Exception {
+        for (PermissionInfo permInfo : sPm.getPackageInfo(pkg, GET_PERMISSIONS).permissions) {
+            if (permInfo.name.equals(permission)) {
+                return permInfo;
+            }
+        }
+
+        return null;
+    }
+
+    @Test
+    public void verifyDuplicatePermission() throws Exception {
+        // The other app was first. The second app could not steal the permission. Hence the
+        // permission belongs to the first app.
+        assertEquals(APP_THAT_WAS_INSTALLED_FIRST, sPm.getPermissionInfo(PERM, 0).packageName);
+
+        // The first app declared the permission
+        assertEquals(APP_THAT_WAS_INSTALLED_FIRST,
+                getDeclaredPermission(APP_THAT_WAS_INSTALLED_FIRST, PERM).packageName);
+
+        // The second app declared the permission too, but the permission is owned by first app.
+        // Hence we end up with a permission info that is different from the one read from the
+        // system.
+        assertEquals(APP_THAT_WAS_INSTALLED_AFTER,
+                getDeclaredPermission(APP_THAT_WAS_INSTALLED_AFTER, PERM).packageName);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
index 9734112..e6fb2af 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
@@ -19,8 +19,13 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    truth-prebuilt \
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 1d0f83e..4673bb3 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -19,6 +19,9 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -28,8 +31,13 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Environment;
+import android.os.StrictMode;
+import android.os.StrictMode.ViolationInfo;
 import android.os.SystemClock;
 import android.os.UserManager;
+import android.os.strictmode.CredentialProtectedWhileLockedViolation;
+import android.os.strictmode.ImplicitDirectBootViolation;
+import android.os.strictmode.Violation;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
@@ -39,7 +47,9 @@
 
 import java.io.File;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 public class EncryptionAppTest extends InstrumentationTestCase {
     private static final String TAG = "EncryptionAppTest";
@@ -217,6 +227,22 @@
             assertEquals(expected, mCe.getExternalCacheDir());
             assertEquals(expected, mDe.getExternalCacheDir());
         }
+
+        assertViolation(
+                new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot()
+                        .penaltyLog().build(),
+                ImplicitDirectBootViolation.class,
+                () -> {
+                    final Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
+                    mCe.getPackageManager().queryBroadcastReceivers(intent, 0);
+                });
+
+        final File ceFile = getTestFile(mCe);
+        assertViolation(
+                new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked()
+                        .penaltyLog().build(),
+                CredentialProtectedWhileLockedViolation.class,
+                ceFile::exists);
     }
 
     public void assertUnlocked() throws Exception {
@@ -260,6 +286,20 @@
             assertCanonicalEquals(expected, mCe.getExternalCacheDir());
             assertCanonicalEquals(expected, mDe.getExternalCacheDir());
         }
+
+        assertNoViolation(
+                new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot()
+                        .penaltyLog().build(),
+                () -> {
+                    final Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
+                    mCe.getPackageManager().queryBroadcastReceivers(intent, 0);
+                });
+
+        final File ceFile = getTestFile(mCe);
+        assertNoViolation(
+                new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked()
+                        .penaltyLog().build(),
+                ceFile::exists);
     }
 
     private void assertQuery(int count, int flags) throws Exception {
@@ -351,4 +391,36 @@
         }
         throw new AssertionError("Failed to find " + probe);
     }
+
+    public interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+
+    private static void assertViolation(StrictMode.VmPolicy policy,
+            Class<? extends Violation> expected, ThrowingRunnable r) throws Exception {
+        inspectViolation(policy, r,
+                info -> assertThat(info.getViolationClass()).isAssignableTo(expected));
+    }
+
+    private static void assertNoViolation(StrictMode.VmPolicy policy, ThrowingRunnable r)
+            throws Exception {
+        inspectViolation(policy, r,
+                info -> assertWithMessage("Unexpected violation").that(info).isNull());
+    }
+
+    private static void inspectViolation(StrictMode.VmPolicy policy, ThrowingRunnable violating,
+            Consumer<ViolationInfo> consume) throws Exception {
+        final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>();
+        StrictMode.setViolationLogger(violations::add);
+
+        final StrictMode.VmPolicy original = StrictMode.getVmPolicy();
+        try {
+            StrictMode.setVmPolicy(policy);
+            violating.run();
+            consume.accept(violations.poll(5, TimeUnit.SECONDS));
+        } finally {
+            StrictMode.setVmPolicy(original);
+            StrictMode.setViolationLogger(null);
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
index 90b7866..eb8a172 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
@@ -37,4 +37,6 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
+LOCAL_MIN_SDK_VERSION := 25
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index 2dc1b02..93bf970 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -15,10 +15,9 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.ephemeralapp1"
-    android:targetSandboxVersion="2">
+  package="com.android.cts.ephemeralapp1" >
     <uses-sdk
-        android:minSdkVersion="25" />
+        android:minSdkVersion="24" android:targetSdkVersion="26" />
 
     <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTest.java
index 177583e..a2a57ea 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/WebViewTest.java
@@ -40,7 +40,7 @@
         }
         final WebViewTestActivity activity = getActivity();
         mWebView = activity.getWebView();
-        mOnUiThread = new WebViewOnUiThread(this, mWebView);
+        mOnUiThread = new WebViewOnUiThread(mWebView);
     }
 
     @Override
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.mk
index 78c7970..d13d8a1 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.mk
@@ -33,6 +33,7 @@
 
 LOCAL_PACKAGE_NAME := CtsEphemeralTestsEphemeralApp2
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
index 0350cc6..c0fe68f 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
@@ -15,10 +15,9 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.ephemeralapp2"
-    android:targetSandboxVersion="2">
+  package="com.android.cts.ephemeralapp2" >
     <uses-sdk
-        android:minSdkVersion="24" />
+        android:minSdkVersion="24" android:targetSdkVersion="26" />
 
     <!-- TEST: exists only for testing ephemeral app1 can't see this app -->
     <application
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
index 8b04f9b..b9089f0 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
@@ -30,6 +30,7 @@
 
 LOCAL_PACKAGE_NAME := CtsEphemeralTestsImplicitApp
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
index d2c3667..0246b88 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
@@ -32,6 +32,7 @@
 
 LOCAL_PACKAGE_NAME := CtsEphemeralTestsNormalApp
 LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 24
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
index 2265dec..3fd47d8 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
@@ -438,6 +438,8 @@
 
         final ContentResolver contentResolver =
                 InstrumentationRegistry.getContext().getContentResolver();
+	// TODO(b/120026065): Re-enable this when we fine a more reliable way to toggle the setting
+	/*
         final int originalSetting = Secure.getInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 1);
         Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, 0);
         try {
@@ -448,7 +450,7 @@
                 startViewIntent.addCategory(Intent.CATEGORY_BROWSABLE);
                 startViewIntent.setData(Uri.parse("https://cts.google.com/ephemeral"));
                 InstrumentationRegistry.getContext().startActivity(
-                        startViewIntent, null /*options*/);
+                        startViewIntent, null);
                 final TestResult testResult = getResult();
                 fail();
             } catch (TestResultNotFoundException expected) {
@@ -461,7 +463,7 @@
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                 | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
                 InstrumentationRegistry.getContext().startActivity(
-                        startEphemeralIntent, null /*options*/);
+                        startEphemeralIntent, null);
                 final TestResult testResult = getResult();
                 assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
                 assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
@@ -470,6 +472,7 @@
         } finally {
             Secure.putInt(contentResolver, Secure.INSTANT_APPS_ENABLED, originalSetting);
         }
+	*/
 
         // connect to the instant app provider
         {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
index fbacf95..dec0643 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
@@ -27,7 +27,7 @@
             <!-- TEST: when installed as an instant app, normal apps can't query for it -->
             <!-- TEST: when installed as a full app, normal apps can query for it -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.instantappusertest.QUERY" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             </activity>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/src/com/android/cts/userapptest/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/src/com/android/cts/userapptest/ClientTest.java
index ed038ee..35b9adc 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/src/com/android/cts/userapptest/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/src/com/android/cts/userapptest/ClientTest.java
@@ -34,7 +34,7 @@
 public class ClientTest {
     /** Action to query for test activities */
     private static final String ACTION_QUERY_ACTIVITY =
-            "com.android.cts.ephemeraltest.QUERY";
+            "com.android.cts.instantappusertest.QUERY";
 
     @Test
     public void testQueryInstant() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
index 4b0bec3..753977b 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
@@ -167,6 +167,18 @@
         return paths;
     }
 
+    public static List<File> getAllPackageSpecificObbGiftPaths(Context context,
+            String targetPackageName) {
+        final File[] files = context.getObbDirs();
+        final List<File> targetFiles = new ArrayList<>();
+        for (File file : files) {
+            final File targetFile = new File(
+                    file.getAbsolutePath().replace(context.getPackageName(), targetPackageName));
+            targetFiles.add(new File(targetFile, targetPackageName + ".gift"));
+        }
+        return targetFiles;
+    }
+
     /**
      * Return a set of several package-specific external storage paths pointing
      * at "gift" files designed to be exchanged with the target package.
@@ -362,6 +374,10 @@
         }
     }
 
+    public static void assertFileNotPresent(File path) {
+        assertFalse(path + " exists!", path.exists());
+    }
+
     public static void assertMediaNoAccess(ContentResolver resolver, boolean legacyApp)
             throws Exception {
         final ContentValues values = new ContentValues();
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
index 14a0180..b8d5da4 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
@@ -20,8 +20,10 @@
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNotPresent;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificGiftPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificObbGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
 
 import android.test.AndroidTestCase;
@@ -50,4 +52,32 @@
             assertFileNoAccess(write);
         }
     }
+
+    /**
+     * Verify we can read our gifts in obb dirs.
+     */
+    public void testObbGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+            assertFileReadWriteAccess(none);
+            assertEquals(100, readInt(none));
+        }
+    }
+
+    public void testRemoveObbGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+            none.delete();
+        }
+    }
+
+    /**
+     * Verify we can't access gifts in obb dirs.
+     */
+    public void testNoObbGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+            assertFileNotPresent(none);
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.mk b/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.mk
index e0bc1cf..f4c51c9 100644
--- a/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.mk
@@ -36,4 +36,6 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
+LOCAL_MIN_SDK_VERSION := 25
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
index a958c4c..6cb2f57 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.instrumentationdiffcertapp">
+       package="com.android.cts.instrumentationdiffcertapp"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple app to test that an instrumentation cannot target an app signed with a different
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/src/com/android/cts/instrumentationdiffcertapp/InstrumentationFailToRunTest.java b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/src/com/android/cts/instrumentationdiffcertapp/InstrumentationFailToRunTest.java
index 9c320d9..4cdaf55 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/src/com/android/cts/instrumentationdiffcertapp/InstrumentationFailToRunTest.java
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/src/com/android/cts/instrumentationdiffcertapp/InstrumentationFailToRunTest.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.instrumentationdiffcertapp;
 
+import static org.junit.Assert.assertFalse;
+
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,18 +29,27 @@
  */
 public class InstrumentationFailToRunTest extends InstrumentationTestCase {
 
-    public void testInstrumentationNotAllowed() {
-        Context myContext = getInstrumentation().getContext();
+    public void testInstrumentationNotAllowed_exception() {
+        final Context myContext = getInstrumentation().getContext();
         // assumes android.app.Instrumentation has been defined in this app's AndroidManifest.xml
         // as targeting an app with a different cert
-        ComponentName appDiffCertInstrumentation = new ComponentName(myContext,
-                Instrumentation.class);
+        final ComponentName appDiffCertInstrumentation =
+                new ComponentName(myContext, Instrumentation.class);
         try {
-            getInstrumentation().getContext().startInstrumentation(appDiffCertInstrumentation,
-                null, new Bundle());
-            fail("could launch instrumentation");
-        } catch (SecurityException e) {
-            // expected
+            assertFalse("instrumentation started",
+                    myContext.startInstrumentation(appDiffCertInstrumentation, null, new Bundle()));
+            fail("SecurityException not thrown");
+        } catch (SecurityException expected) {
         }
     }
+
+    public void testInstrumentationNotAllowed_fail() {
+        final Context myContext = getInstrumentation().getContext();
+        // assumes android.app.Instrumentation has been defined in this app's AndroidManifest.xml
+        // as targeting an app with a different cert
+        final ComponentName appDiffCertInstrumentation =
+                new ComponentName(myContext, Instrumentation.class);
+        assertFalse("instrumentation started",
+                myContext.startInstrumentation(appDiffCertInstrumentation, null, new Bundle()));
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
index 2f3a374..a903c37 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.isolatedsplitapp"
-    android:isolatedSplits="true">
+    android:isolatedSplits="true"
+    android:targetSandboxVersion="2">
 
     <application android:label="IsolatedSplitApp">
 
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
index 958b8d0..d9ca271 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.isolatedsplitapp.feature_a"
-        featureSplit="feature_a">
+        featureSplit="feature_a"
+        android:targetSandboxVersion="2">
 
     <application>
         <activity android:name=".FeatureAActivity">
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
index d89a1f2..8b4f16d 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.isolatedsplitapp.feature_b"
-        featureSplit="feature_b">
+        featureSplit="feature_b"
+        android:targetSandboxVersion="2">
 
     <uses-split android:name="feature_a" />
 
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
index 64b087c..012543b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
@@ -16,7 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.isolatedsplitapp.feature_c"
-        featureSplit="feature_c">
+        featureSplit="feature_c"
+        android:targetSandboxVersion="2">
 
     <application>
         <activity android:name=".FeatureCActivity">
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.mk b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.mk
new file mode 100644
index 0000000..08e46da
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_PACKAGE_NAME := CtsListeningPortsTest
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/AndroidManifest.xml
new file mode 100644
index 0000000..0d1f1af
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.appsecurity.cts.listeningports">
+    <application>
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <instrumentation
+        android:targetPackage="android.appsecurity.cts.listeningports"
+        android:name="android.support.test.runner.AndroidJUnitRunner" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
new file mode 100644
index 0000000..d0373e1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.appsecurity.cts.listeningports;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import junit.framework.AssertionFailedError;
+import android.support.test.InstrumentationRegistry;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+/**
+ * Verifies that Android devices are not listening on accessible
+ * open ports. Open ports are often targeted by attackers looking to break
+ * into computer systems remotely, and minimizing the number of open ports
+ * is considered a security best practice.
+ */
+public class ListeningPortsTest extends AndroidTestCase {
+    private static final String TAG = "ListeningPortsTest";
+
+    private static final String PROC_FILE_CONTENTS_PARAM = "procFileContents";
+    private static final String IS_TCP_PARAM = "isTcp";
+    private static final String LOOPBACK_PARAM = "loopback";
+
+    private static final int CONN_TIMEOUT_IN_MS = 5000;
+
+    /** Ports that are allowed to be listening. */
+    private static final List<String> EXCEPTION_PATTERNS = new ArrayList<String>(6);
+
+    static {
+        // IPv4 exceptions
+        // Patterns containing ":" are allowed address port combinations
+        // Pattterns contains " " are allowed address UID combinations
+        // Patterns containing both are allowed address, port, and UID combinations
+        EXCEPTION_PATTERNS.add("0.0.0.0:5555");     // emulator port
+        EXCEPTION_PATTERNS.add("0.0.0.0:9101");     // verified ports
+        EXCEPTION_PATTERNS.add("0.0.0.0:9551");     // verified ports
+        EXCEPTION_PATTERNS.add("0.0.0.0:9552");     // verified ports
+        EXCEPTION_PATTERNS.add("10.0.2.15:5555");   // net forwarding for emulator
+        EXCEPTION_PATTERNS.add("127.0.0.1:5037");   // adb daemon "smart sockets"
+        EXCEPTION_PATTERNS.add("0.0.0.0 1020");     // used by the cast receiver
+        EXCEPTION_PATTERNS.add("0.0.0.0 10000");    // used by the cast receiver
+        EXCEPTION_PATTERNS.add("127.0.0.1 10000");  // used by the cast receiver
+        EXCEPTION_PATTERNS.add(":: 1002");          // used by remote control
+        EXCEPTION_PATTERNS.add(":: 1020");          // used by remote control
+        EXCEPTION_PATTERNS.add("0.0.0.0:7275");     // used by supl
+        //no current patterns involve address, port and UID combinations
+        //Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
+
+        // IPv6 exceptions
+        // TODO: this is not standard notation for IPv6. Use [$addr]:$port instead as per RFC 3986.
+        EXCEPTION_PATTERNS.add(":::5555");          // emulator port for adb
+        EXCEPTION_PATTERNS.add(":::7275");          // used by supl
+    }
+
+    /**
+     * Remotely accessible ports (loopback==false) are often used by
+     * attackers to gain unauthorized access to computers systems without
+     * user knowledge or awareness.
+     *
+     * Locally accessible ports (loopback==true) are often targeted by
+     * malicious locally installed programs to gain unauthorized access to
+     * program data or cause system corruption.
+     *
+     * Since direct /proc/net access is no longer possible the contents of the file and the boolean
+     * values are received as parameters from the host side test.
+     */
+    public void testNoAccessibleListeningPorts() throws Exception {
+        final Bundle testArgs = InstrumentationRegistry.getArguments();
+        final String procFileContents = testArgs.getString(PROC_FILE_CONTENTS_PARAM);
+        final boolean isTcp = Boolean.valueOf(testArgs.getString(IS_TCP_PARAM));
+        final boolean loopback = Boolean.valueOf(testArgs.getString(LOOPBACK_PARAM));
+
+        String errors = "";
+        List<ParsedProcEntry> entries = ParsedProcEntry.parse(procFileContents);
+        for (ParsedProcEntry entry : entries) {
+            String addrPort = entry.localAddress.getHostAddress() + ':' + entry.port;
+            String addrUid = entry.localAddress.getHostAddress() + ' ' + entry.uid;
+            String addrPortUid = addrPort + ' ' + entry.uid;
+
+            if (isPortListening(entry.state, isTcp)
+                    && !(isException(addrPort) || isException(addrUid) || isException(addrPortUid))
+                    && (!entry.localAddress.isLoopbackAddress() ^ loopback)) {
+                if (isTcp && !isTcpConnectable(entry.localAddress, entry.port)) {
+                    continue;
+                }
+                // allow non-system processes to listen
+                int appId = UserHandle.getAppId(entry.uid);
+                if (appId >= Process.FIRST_APPLICATION_UID
+                        && appId <= Process.LAST_APPLICATION_UID) {
+                    continue;
+                }
+                errors += "\nFound port listening on addr="
+                        + entry.localAddress.getHostAddress() + ", port="
+                        + entry.port + ", UID=" + entry.uid
+                        + " " + uidToPackage(entry.uid);
+            }
+        }
+        if (!errors.equals("")) {
+            throw new ListeningPortsAssertionError(errors);
+        }
+    }
+
+    private String uidToPackage(int uid) {
+        PackageManager pm = this.getContext().getPackageManager();
+        String[] packages = pm.getPackagesForUid(uid);
+        if (packages == null) {
+            return "[unknown]";
+        }
+        return Arrays.asList(packages).toString();
+    }
+
+    private boolean isTcpConnectable(InetAddress address, int port) {
+        Socket socket = new Socket();
+
+        try {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Trying to connect " + address + ":" + port);
+            }
+            socket.connect(new InetSocketAddress(address, port), CONN_TIMEOUT_IN_MS);
+        } catch (IOException ioe) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Unable to connect:" + ioe);
+            }
+            return false;
+        } finally {
+            try {
+                socket.close();
+            } catch (IOException closeError) {
+                Log.e(TAG, "Unable to close socket: " + closeError);
+            }
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, address + ":" + port + " is connectable.");
+        }
+        return true;
+    }
+
+    private static boolean isException(String localAddress) {
+        return isPatternMatch(EXCEPTION_PATTERNS, localAddress);
+    }
+
+    private static boolean isPatternMatch(List<String> patterns, String input) {
+        for (String pattern : patterns) {
+            pattern = Pattern.quote(pattern);
+            if (Pattern.matches(pattern, input)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isPortListening(String state, boolean isTcp) {
+        // 0A = TCP_LISTEN from include/net/tcp_states.h
+        String listeningState = isTcp ? "0A" : "07";
+        return listeningState.equals(state);
+    }
+
+    private static class ListeningPortsAssertionError extends AssertionFailedError {
+        private ListeningPortsAssertionError(String msg) {
+            super(msg);
+        }
+    }
+
+    private static class ParsedProcEntry {
+        private final InetAddress localAddress;
+        private final int port;
+        private final String state;
+        private final int uid;
+
+        private ParsedProcEntry(InetAddress addr, int port, String state, int uid) {
+            this.localAddress = addr;
+            this.port = port;
+            this.state = state;
+            this.uid = uid;
+        }
+
+
+        private static List<ParsedProcEntry> parse(String procFileContents) throws IOException {
+
+            List<ParsedProcEntry> retval = new ArrayList<ParsedProcEntry>();
+            /*
+            * Sample output of "cat /proc/net/tcp" on emulator:
+            *
+            * sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  ...
+            * 0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0   ...
+            * 1: 00000000:15B3 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0   ...
+            * 2: 0F02000A:15B3 0202000A:CE8A 01 00000000:00000000 00:00000000 00000000     0   ...
+            *
+            */
+
+            Scanner scanner = null;
+            try {
+                scanner = new Scanner(procFileContents);
+                while (scanner.hasNextLine()) {
+                    String line = scanner.nextLine().trim();
+
+                    // Skip column headers
+                    if (line.startsWith("sl")) {
+                        continue;
+                    }
+
+                    String[] fields = line.split("\\s+");
+                    final int expectedNumColumns = 12;
+                    assertTrue(line + " should have at least " + expectedNumColumns
+                            + " columns of output " + Arrays.toString(fields),
+                            fields.length >= expectedNumColumns);
+
+                    String state = fields[3];
+                    int uid = Integer.parseInt(fields[7]);
+                    InetAddress localIp = addrToInet(fields[1].split(":")[0]);
+                    int localPort = Integer.parseInt(fields[1].split(":")[1], 16);
+
+                    retval.add(new ParsedProcEntry(localIp, localPort, state, uid));
+                }
+            } finally {
+                if (scanner != null) {
+                    scanner.close();
+                }
+            }
+            return retval;
+        }
+
+        /**
+         * Convert a string stored in little endian format to an IP address.
+         */
+        private static InetAddress addrToInet(String s) throws UnknownHostException {
+            int len = s.length();
+            if (len != 8 && len != 32) {
+                throw new IllegalArgumentException(len + "");
+            }
+            byte[] retval = new byte[len / 2];
+
+            for (int i = 0; i < len / 2; i += 4) {
+                retval[i] = (byte) ((Character.digit(s.charAt(2*i + 6), 16) << 4)
+                        + Character.digit(s.charAt(2*i + 7), 16));
+                retval[i + 1] = (byte) ((Character.digit(s.charAt(2*i + 4), 16) << 4)
+                        + Character.digit(s.charAt(2*i + 5), 16));
+                retval[i + 2] = (byte) ((Character.digit(s.charAt(2*i + 2), 16) << 4)
+                        + Character.digit(s.charAt(2*i + 3), 16));
+                retval[i + 3] = (byte) ((Character.digit(s.charAt(2*i), 16) << 4)
+                        + Character.digit(s.charAt(2*i + 1), 16));
+            }
+            return InetAddress.getByAddress(retval);
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
index 2a63cd7..190c5d0 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
@@ -15,8 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.majorversion"
-        android:versionCodeMajor="0x00000000" android:versionCode="0x0000ffff">
-
+        android:versionCodeMajor="0x00000000" android:versionCode="0x0000ffff"
+        android:targetSandboxVersion="2">
+  
     <application/>
 
     <instrumentation
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
index 934deec..a2a90fc 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
@@ -15,8 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.majorversion"
-        android:versionCodeMajor="0x00000000" android:versionCode="0xffffffff">
-
+        android:versionCodeMajor="0x00000000" android:versionCode="0xffffffff"
+        android:targetSandboxVersion="2">
+  
     <application/>
 
     <instrumentation
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
index c7b9dd0..33a1dc2 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
@@ -15,8 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.majorversion"
-        android:versionCodeMajor="0x000000ff" android:versionCode="0x00000000">
-
+        android:versionCodeMajor="0x000000ff" android:versionCode="0x00000000"
+        android:targetSandboxVersion="2">
+  
     <application/>
 
     <instrumentation
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
index 91d4e39..bb0a3e6 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
@@ -15,7 +15,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.majorversion"
-        android:versionCodeMajor="0x000000ff" android:versionCode="0xffffffff">
+        android:versionCodeMajor="0x000000ff" android:versionCode="0xffffffff"
+        android:targetSandboxVersion="2">
 
     <application/>
 
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.mk
new file mode 100644
index 0000000..efb4bce
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsMediaStorageApp
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..4635b52
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.mediastorageapp">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".StubActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.APP_GALLERY" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".GetResultActivity" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.mediastorageapp" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
+    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/GetResultActivity.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/GetResultActivity.java
new file mode 100644
index 0000000..a3ab19f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * 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.mediastorageapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    private static LinkedBlockingQueue<Result> sResult;
+
+    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 onActivityResult(int requestCode, int resultCode, Intent data) {
+        try {
+            sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void clearResult() {
+        sResult = new LinkedBlockingQueue<>();
+    }
+
+    public Result getResult() {
+        final Result result;
+        try {
+            result = sResult.poll(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalStateException("Activity didn't receive a Result in 30 seconds");
+        }
+        return result;
+    }
+
+    public Result getResult(long timeout, TimeUnit unit) {
+        try {
+            return sResult.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
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
new file mode 100644
index 0000000..fa1ca7b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.mediastorageapp;
+
+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.Activity;
+import android.app.Instrumentation;
+import android.app.RecoverableSecurityException;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiSelector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStorageTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+    }
+
+    @Test
+    public void testMediaNone() throws Exception {
+        final Uri red = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        final Uri blue = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        clearMediaOwner(blue);
+
+        // Since we have no permissions, we should only be able to see media
+        // that we've contributed
+        final HashSet<Long> seen = new HashSet<>();
+        try (Cursor c = mContentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                seen.add(c.getLong(0));
+            }
+        }
+
+        assertTrue(seen.contains(ContentUris.parseId(red)));
+        assertFalse(seen.contains(ContentUris.parseId(blue)));
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+            fail("Expected read access to be blocked");
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+            fail("Expected write access to be blocked");
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testMediaRead() throws Exception {
+        final Uri red = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        final Uri blue = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        clearMediaOwner(blue);
+
+        // Holding read permission we can see items we don't own
+        final HashSet<Long> seen = new HashSet<>();
+        try (Cursor c = mContentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                seen.add(c.getLong(0));
+            }
+        }
+
+        assertTrue(seen.contains(ContentUris.parseId(red)));
+        assertTrue(seen.contains(ContentUris.parseId(blue)));
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+            fail("Expected write access to be blocked");
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testMediaWrite() throws Exception {
+        final Uri red = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        final Uri blue = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+
+        clearMediaOwner(blue);
+
+        // Holding read permission we can see items we don't own
+        final HashSet<Long> seen = new HashSet<>();
+        try (Cursor c = mContentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                seen.add(c.getLong(0));
+            }
+        }
+
+        assertTrue(seen.contains(ContentUris.parseId(red)));
+        assertTrue(seen.contains(ContentUris.parseId(blue)));
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+        }
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+        }
+    }
+
+    @Test
+    public void testMediaEscalation() throws Exception {
+        final Uri red = createImage(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        clearMediaOwner(red);
+
+        // Confirm that we get can take action to get write access
+        RecoverableSecurityException exception = null;
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+            fail("Expected write access to be blocked");
+        } catch (RecoverableSecurityException expected) {
+            exception = expected;
+        }
+
+        // Try launching the action to grant ourselves access
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final Intent intent = new Intent(inst.getContext(), GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // Wake up the device and dismiss the keyguard before the test starts
+        final UiDevice device = UiDevice.getInstance(inst);
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        device.executeShellCommand("wm dismiss-keyguard");
+
+        final GetResultActivity activity = (GetResultActivity) inst.startActivitySync(intent);
+        device.waitForIdle();
+        activity.clearResult();
+        activity.startIntentSenderForResult(
+                exception.getUserAction().getActionIntent().getIntentSender(),
+                42, null, 0, 0, 0);
+
+        device.waitForIdle();
+        device.findObject(new UiSelector().text("Allow")).click();
+
+        // Verify that we now have access
+        final GetResultActivity.Result res = activity.getResult();
+        assertEquals(Activity.RESULT_OK, res.resultCode);
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+        }
+    }
+
+    private static Uri createImage(Uri collectionUri) throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String displayName = "cts" + System.nanoTime();
+        final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                collectionUri, displayName, "image/png");
+        final Uri pendingUri = MediaStore.createPending(context, params);
+        try (MediaStore.PendingSession session = MediaStore.openPending(context, pendingUri)) {
+            try (OutputStream out = session.openOutputStream()) {
+                final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
+                bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
+            }
+            return session.publish();
+        }
+    }
+
+    private static void clearMediaOwner(Uri uri) throws IOException {
+        try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+                InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                        "content update --uri " + uri + " --bind owner_package_name:n:"))) {
+            while (is.read() != -1) {
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
index 7140333..c7550e0 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
@@ -16,12 +16,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.cts.norestart"
+    android:targetSandboxVersion="2"
     tools:ignore="MissingVersion" >
 
-    <uses-sdk
-        android:minSdkVersion="8"
-        android:targetSdkVersion="23" />
-
     <application
         tools:ignore="AllowBackup,MissingApplicationIcon" >
         <activity
@@ -32,6 +29,14 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="https" />
+                <data android:host="cts.android.com" />
+                <data android:path="/norestart" />
+            </intent-filter>
+            <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml
index b2fa3e8..9c71363 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/AndroidManifest.xml
@@ -17,12 +17,9 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.cts.norestart"
     split="feature"
+    android:targetSandboxVersion="2"
     tools:ignore="MissingVersion" >
 
-    <uses-sdk
-        android:minSdkVersion="8"
-        android:targetSdkVersion="23" />
-
     <application
         android:allowBackup="false"
         tools:ignore="MissingApplicationIcon" >
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk
index 67fe9f4..97e63d0 100644
--- a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.mk
@@ -22,7 +22,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := current
 LOCAL_PACKAGE_NAME := CtsOrderedActivityApp
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
index 383f000..6d08f9e 100644
--- a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
@@ -16,7 +16,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.appsecurity.cts.orderedactivity"
         android:versionCode="10"
-        android:versionName="1.0">
+        android:versionName="1.0"
+        android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
         <!-- Activities used for queries -->
         <activity android:name=".OrderActivity2">
diff --git a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
index a8d659b..c696530 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
@@ -29,6 +29,7 @@
 
 LOCAL_PACKAGE_NAME := CtsPermissionPolicyTest25
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 25
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/Android.mk b/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/Android.mk
new file mode 100644
index 0000000..4bb835d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/Android.mk
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../UsePermissionApp22/src) \
+    ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res
+
+LOCAL_PACKAGE_NAME := RequestsOnlyCalendarApp22
+
+# For ACCESS_BACKGROUND_LOCATION
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+LOCAL_MIN_SDK_VERSION := 22
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/AndroidManifest.xml
new file mode 100644
index 0000000..d19206e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/RequestsOnlyCalendarApp22/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+    <!-- Calendar -->
+    <uses-permission android:name="android.permission.READ_CALENDAR"/>
+    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".BasePermissionActivity" />
+        <activity android:name=".AutoClosingActivity" android:exported="true" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.usepermission" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp
new file mode 100644
index 0000000..3ce27fad3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+android_test {
+    name: "ReviewPermissionHelper",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "ub-uiautomator",
+        "android-support-test",
+        "compatibility-device-util",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
new file mode 100644
index 0000000..67f597e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.reviewpermissionhelper">
+
+    <application>
+        <activity android:name=".ActivityStarter" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.reviewpermissionhelper" />
+
+</manifest>
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
new file mode 100644
index 0000000..90dbca9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt
@@ -0,0 +1,36 @@
+/*
+ * 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
new file mode 100644
index 0000000..7d76a01
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.Manifest.permission.READ_CALENDAR
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.support.test.InstrumentationRegistry
+import android.support.test.rule.ActivityTestRule
+import android.support.test.runner.AndroidJUnit4
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.Until
+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.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+private const val UI_TIMEOUT_UNEXPECTED = 500L
+private const val UI_TIMEOUT = 5000L
+private const val USE_PERMISSION_PKG = "com.android.cts.usepermission"
+
+@RunWith(AndroidJUnit4::class)
+class ReviewPermissionsTest {
+    @get:Rule
+    val activityStarter = ActivityTestRule(ActivityStarter::class.java)
+
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+    val uiDevice = UiDevice.getInstance(instrumentation)
+
+    fun startActivityInReviewedAp() {
+        val startAutoClosingActivity = Intent()
+        startAutoClosingActivity.component = ComponentName(USE_PERMISSION_PKG,
+                USE_PERMISSION_PKG + ".AutoClosingActivity")
+        activityStarter.activity.startActivityForResult(startAutoClosingActivity, 42)
+    }
+
+    fun clickContinue() {
+        uiDevice.wait(Until.findObject(
+                By.res("com.android.permissioncontroller:id/continue_button")), UI_TIMEOUT).click()
+    }
+
+    @Test
+    fun approveReviewPermissions() {
+        startActivityInReviewedAp()
+        clickContinue()
+        assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+    }
+
+    @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))
+    }
+
+    @Test
+    fun assertNoReviewPermissionsNeeded() {
+        startActivityInReviewedAp()
+        assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+    }
+
+    @Test
+    fun denyGrantDenyCalendarPermissions() {
+        startActivityInReviewedAp()
+
+        // Deny
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        // Confirm deny
+        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+
+        // Grant
+        uiDevice.waitForIdle()
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+
+        // Deny
+        uiDevice.waitForIdle()
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+
+        uiDevice.waitForIdle()
+        clickContinue()
+    }
+
+    @Test
+    fun denyGrantCalendarPermissions() {
+        startActivityInReviewedAp()
+
+        // Deny
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        // Confirm deny
+        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+
+        // Grant
+        uiDevice.waitForIdle()
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+
+        uiDevice.waitForIdle()
+        clickContinue()
+    }
+
+    @Test
+    fun denyCalendarPermissions() {
+        startActivityInReviewedAp()
+
+        // Deny
+        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        // Confirm deny
+        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+
+        uiDevice.waitForIdle()
+        clickContinue()
+    }
+
+    @Test
+    fun reviewPermissionWhenServiceIsBound() {
+        val permissionCheckerServiceIntent = Intent()
+        permissionCheckerServiceIntent.component = ComponentName(USE_PERMISSION_PKG,
+                "$USE_PERMISSION_PKG.PermissionCheckerService")
+
+        val results = LinkedBlockingQueue<Int>()
+        permissionCheckerServiceIntent.putExtra("$USE_PERMISSION_PKG.RESULT",
+                object : ResultReceiver(Handler(Looper.getMainLooper())) {
+                    override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
+                        results.offer(resultCode)
+                    }
+                })
+        permissionCheckerServiceIntent.putExtra("$USE_PERMISSION_PKG.PERMISSION", READ_CALENDAR)
+
+        activityStarter.activity.startService(permissionCheckerServiceIntent)
+
+        // Service is not started before permission are reviewed
+        assertNull(results.poll(UI_TIMEOUT_UNEXPECTED, TimeUnit.MILLISECONDS))
+
+        clickContinue()
+
+        // Service should be started after permission review
+        assertEquals(PERMISSION_GRANTED, results.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/AndroidManifest.xml
index 247617e..942cae0 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.simpleappinstall">
+       package="com.android.cts.simpleappinstall"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple app to test that apps cannot be installed over existing app with
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/AndroidManifest.xml
index da48194..1491d9c 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.simpleappinstall">
+       package="com.android.cts.simpleappinstall"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple second app to test that apps cannot be installed over existing app
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index 28aa01a..5739eb8 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -53,6 +53,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
@@ -82,6 +83,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
@@ -110,6 +112,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
@@ -131,6 +134,36 @@
 include $(BUILD_CTS_SUPPORT_PACKAGE)
 
 
+#################################################
+# Define a variant requiring a split for install
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := CtsNeedSplitApp
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
+LOCAL_PACKAGE_SPLITS := xxhdpi-v4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+
 ifeq (,$(ONE_SHOT_MAKEFILE))
 include $(LOCAL_PATH)/libs/Android.mk $(LOCAL_PATH)/feature/Android.mk
 endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index abb7f32..1b717c1 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,15 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.splitapp">
-    <!-- TODO(b/73365611) Remove targetSdkVersion once EncryptionApp tests
-         are fixed to no longer access SplitApp's data by path. -->
+    package="com.android.cts.splitapp"
+    android:targetSandboxVersion="2">
+
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
-    <application android:label="SplitApp">
+    <application android:label="SplitApp" android:multiArch="true">
         <activity android:name=".MyActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
index 736c891..85bf806 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
@@ -20,6 +20,7 @@
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_PACKAGE_NAME := CtsSplitAppFeature
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
 LOCAL_PACKAGE_SPLITS := v7
 
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
@@ -38,3 +39,36 @@
 LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+
+#################################################
+# Define a variant requiring a split for install
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := CtsNeedSplitFeature
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_APK_LIBRARIES := CtsSplitApp
+LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
+LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
+
+LOCAL_USE_AAPT2 := true
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
index 8ba3c2f..be3adfc 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
@@ -16,7 +16,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
-        featureName="feature">
+        split="feature">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
 
     <!-- New permission should be ignored -->
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
new file mode 100644
index 0000000..7ce1830
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="feature"
+        android:isSplitRequired="true">
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+
+    <!-- New permission should be ignored -->
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <!-- New application flag should be ignored -->
+    <application android:largeHeap="true">
+        <activity android:name=".FeatureActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+                android:enabled="@bool/feature_receiver_enabled">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED" />
+            </intent-filter>
+        </receiver>
+        <service android:name=".FeatureService">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service" />
+            </intent-filter>
+        </service>
+        <provider android:name=".FeatureProvider" android:authorities="com.android.cts.splitapp.provider" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
new file mode 100644
index 0000000..58a0d2b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.splitapp"
+    android:targetSandboxVersion="2"
+    android:isSplitRequired="true" >
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+    <application android:label="SplitApp">
+        <activity android:name=".MyActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+        </activity>
+        <receiver android:name=".MyReceiver"
+                android:enabled="@bool/my_receiver_enabled">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".BootReceiver" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.splitapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
index 8e053ba..029f3de 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
@@ -16,8 +16,11 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
+        android:targetSandboxVersion="2"
         android:revisionCode="12">
 
+    <uses-sdk android:targetSdkVersion="27" />
+
     <uses-permission android:name="android.permission.CAMERA" />
 
     <application android:label="SplitApp">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 4cfd7d0..8d48c16 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -35,10 +35,12 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.ConditionVariable;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.StatFs;
+import android.support.test.InstrumentationRegistry;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructStat;
@@ -236,8 +238,10 @@
         assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
 
         // And that we can access resources from feature
-        assertEquals("red", r.getString(r.getIdentifier("feature_string", "string", PKG)));
-        assertEquals(123, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
+        assertEquals("red", r.getString(r.getIdentifier(
+                "com.android.cts.splitapp.feature:feature_string", "string", PKG)));
+        assertEquals(123, r.getInteger(r.getIdentifier(
+                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -308,6 +312,23 @@
         }
     }
 
+    private Intent createLaunchIntent() {
+        final boolean isInstant = Boolean.parseBoolean(
+                InstrumentationRegistry.getArguments().getString("is_instant", "false"));
+        if (isInstant) {
+            final Intent i = new Intent(Intent.ACTION_VIEW);
+            i.addCategory(Intent.CATEGORY_BROWSABLE);
+            i.setData(Uri.parse("https://cts.android.com/norestart"));
+            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return i;
+        } else {
+            final Intent i = new Intent("com.android.cts.norestart.START");
+            i.addCategory(Intent.CATEGORY_DEFAULT);
+            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return i;
+        }
+    }
+
     public void testBaseInstalled() throws Exception {
         final ConditionVariable cv = new ConditionVariable();
         final BroadcastReceiver r = new BroadcastReceiver() {
@@ -319,10 +340,8 @@
             }
         };
         final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
-        getContext().registerReceiver(r, filter);
-        final Intent i = new Intent("com.android.cts.norestart.START");
-        i.addCategory(Intent.CATEGORY_DEFAULT);
-        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        final Intent i = createLaunchIntent();
         getContext().startActivity(i);
         assertTrue(cv.block(2000L));
         getContext().unregisterReceiver(r);
@@ -345,10 +364,8 @@
             }
         };
         final IntentFilter filter = new IntentFilter("com.android.cts.norestart.BROADCAST");
-        getContext().registerReceiver(r, filter);
-        final Intent i = new Intent("com.android.cts.norestart.START");
-        i.addCategory(Intent.CATEGORY_DEFAULT);
-        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().registerReceiver(r, filter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        final Intent i = createLaunchIntent();
         getContext().startActivity(i);
         assertTrue(cv.block(2000L));
         getContext().unregisterReceiver(r);
@@ -362,7 +379,8 @@
         assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
 
         // And that we can access resources from feature
-        assertEquals(321, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
+        assertEquals(321, r.getInteger(r.getIdentifier(
+                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index 23e4c9f..fc3af59 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -30,20 +30,31 @@
 import static com.android.cts.storageapp.Utils.makeUniqueFile;
 import static com.android.cts.storageapp.Utils.useSpace;
 
+import android.app.Activity;
 import android.app.usage.StorageStats;
 import android.app.usage.StorageStatsManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiSelector;
 import android.test.InstrumentationTestCase;
+import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.UUID;
 
 /**
@@ -75,6 +86,32 @@
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
     }
 
+    public void testClearSpace() throws Exception {
+        // First, disk better be full!
+        assertTrue(getContext().getDataDir().getUsableSpace() < 256_000_000);
+
+        final Activity activity = launchActivity("com.android.cts.storageapp_a",
+                UtilsActivity.class, null);
+
+        final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.fromParts("package", "com.android.cts.storageapp_b", null));
+        activity.startActivity(intent);
+
+        final UiDevice device = UiDevice.getInstance(getInstrumentation());
+        device.waitForIdle();
+
+        // Hunt around to clear storage of other app
+        device.findObject(new UiSelector().textContains("internal storage")).click();
+        device.waitForIdle();
+        device.findObject(new UiSelector().textContains("Clear")).click();
+        device.waitForIdle();
+        device.findObject(new UiSelector().text("OK")).click();
+        device.waitForIdle();
+
+        // Now, disk better be less-full!
+        assertTrue(getContext().getDataDir().getUsableSpace() > 256_000_000);
+    }
+
     /**
      * Measure ourselves manually.
      */
@@ -214,4 +251,69 @@
         try { sm.setCacheBehaviorTombstone(ext, true); fail(); } catch (IOException expected) { }
         try { sm.setCacheBehaviorTombstone(ext, false); fail(); } catch (IOException expected) { }
     }
+
+    /**
+     * Create "cts" probe files in every possible common storage location that
+     * we can think of.
+     */
+    public void testExternalStorageIsolatedWrite() throws Exception {
+        final Context context = getContext();
+        final List<File> paths = new ArrayList<File>();
+        Collections.addAll(paths, Environment.getExternalStorageDirectory());
+        Collections.addAll(paths,
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
+        Collections.addAll(paths, context.getExternalCacheDirs());
+        Collections.addAll(paths, context.getExternalFilesDirs(null));
+        Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES));
+        Collections.addAll(paths, context.getExternalMediaDirs());
+        Collections.addAll(paths, context.getObbDirs());
+
+        final String name = "cts_" + System.nanoTime();
+        for (File path : paths) {
+            final File otherPath = new File(path.getAbsolutePath()
+                    .replace("com.android.cts.storageapp_a", "com.android.cts.storageapp_b"));
+
+            path.mkdirs();
+            otherPath.mkdirs();
+
+            final File file = new File(path, name);
+            final File otherFile = new File(otherPath, name);
+
+            file.createNewFile();
+            otherFile.createNewFile();
+
+            assertTrue(file.exists());
+            assertTrue(otherFile.exists());
+        }
+    }
+
+    /**
+     * Verify that we can't see any of the "cts" probe files created above,
+     * since our storage should be fully isolated.
+     */
+    public void testExternalStorageIsolatedRead() throws Exception {
+        final LinkedList<File> traverse = new LinkedList<>();
+        traverse.push(Environment.getStorageDirectory());
+        traverse.push(Environment.getExternalStorageDirectory());
+
+        while (!traverse.isEmpty()) {
+            final File dir = traverse.poll();
+            for (File f : dir.listFiles()) {
+                if (f.getName().startsWith("cts_")) {
+                    fail("Found leaked file " + f.getAbsolutePath());
+                }
+                if (f.isDirectory()) {
+                    traverse.push(f);
+                }
+            }
+        }
+    }
+
+    public void testExternalStorageIsolatedLegacy() throws Exception {
+        assertTrue(new File("/sdcard/cts_top").exists());
+    }
+
+    public void testExternalStorageIsolatedNonLegacy() throws Exception {
+        assertFalse(new File("/sdcard/cts_top").exists());
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
index 08dc7ad..029041d 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
@@ -137,7 +137,9 @@
         for (File f : dir.listFiles()) {
             if (f.isDirectory()) {
                 if (excludeObb && f.getName().equalsIgnoreCase("obb")
-                        && f.getParentFile().getName().equalsIgnoreCase("Android")) {
+                        && f.getParentFile().getName().equalsIgnoreCase("Android")
+                        && !f.getParentFile().getParentFile().getParentFile().getName()
+                                .equalsIgnoreCase("sandbox")) {
                     Log.d(TAG, "Ignoring OBB directory " + f);
                 } else {
                     size += getSizeManual(f, excludeObb);
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/UtilsActivity.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/UtilsActivity.java
new file mode 100644
index 0000000..101188c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/UtilsActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.storageapp;
+
+import android.app.Activity;
+
+public class UtilsActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
index ebbc892..732b8d5 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := test_current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    ub-uiautomator
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppA/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/StorageAppA/AndroidManifest.xml
index 688bb9d..192c9ce 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppA/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/StorageAppA/AndroidManifest.xml
@@ -18,6 +18,7 @@
 
     <application android:appCategory="video">
         <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.cts.storageapp.UtilsActivity"  />
         <receiver android:name="com.android.cts.storageapp.UtilsReceiver" android:exported="true" />
         <provider
             android:name="com.android.cts.storageapp.UtilsProvider"
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
index 3a5462e..77d166c 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
@@ -18,7 +18,9 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := test_current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    ub-uiautomator
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
diff --git a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/AndroidManifest.xml
index 8b15f1f..0410e1a 100644
--- a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.targetinstrumentationapp">
+       package="com.android.cts.targetinstrumentationapp"
+       android:targetSandboxVersion="2">
 
     <!--
     A simple app used to test that instrumentation cannot target an app signed with a different
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
index 7a62f09..36d7a13 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
@@ -33,6 +33,8 @@
     ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionApp22
+
+# For ACCESS_BACKGROUND_LOCATION
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
 # tag this module as a cts test artifact
@@ -43,4 +45,6 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
+LOCAL_MIN_SDK_VERSION := 22
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/AndroidManifest.xml
index ebb0cbf..90a7cc4 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/AndroidManifest.xml
@@ -68,6 +68,8 @@
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name=".BasePermissionActivity" />
+        <activity android:name=".AutoClosingActivity" android:exported="true" />
+        <service android:name=".PermissionCheckerService" android:exported="true" />
     </application>
 
     <instrumentation
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
index 6675383..ada3d19 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
@@ -1,4 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="Permissions">Permissions</string>
+    <string name="Deny">Deny</string>
+    <string name="Allow">Allow</string>
+    <string name="AllowAll">Allow all the time</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/AutoClosingActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/AutoClosingActivity.java
new file mode 100644
index 0000000..b1b4e9f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/AutoClosingActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.usepermission;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class AutoClosingActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setResult(RESULT_OK);
+        finish();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/PermissionCheckerService.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/PermissionCheckerService.java
new file mode 100644
index 0000000..db0c98a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/PermissionCheckerService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.usepermission;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * A service that can check if a permission is currently granted
+ */
+public class PermissionCheckerService extends IntentService {
+    private final String REVIEW_PERMISSION_PKG = "com.android.cts.reviewpermissionhelper";
+
+    public PermissionCheckerService() {
+        super(PermissionCheckerService.class.getSimpleName());
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        // Load bundle with context of client package so ResultReceiver class can be resolved
+        Context context;
+        try {
+            context = createPackageContext(REVIEW_PERMISSION_PKG,
+                    CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Cannot find client package " + REVIEW_PERMISSION_PKG);
+        }
+        ClassLoader cl = context.getClassLoader();
+        Bundle bundle = intent.getExtras();
+        bundle.setClassLoader(cl);
+
+        ResultReceiver result = bundle.getParcelable(getPackageName() + ".RESULT");
+        String permission = bundle.getString(getPackageName() + ".PERMISSION");
+
+        result.send(checkSelfPermission(permission), null);
+    }
+}
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 35ac7ec..20be2cc 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
@@ -17,23 +17,19 @@
 package com.android.cts.usepermission;
 
 import static junit.framework.Assert.assertEquals;
-
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
 import static junit.framework.Assert.assertTrue;
 
 import android.Manifest;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Environment;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Process;
+import android.provider.CalendarContract;
+
 import org.junit.Test;
 
-import java.io.File;
 import java.util.Arrays;
 
 /**
@@ -42,109 +38,99 @@
 public class UsePermissionTest22 extends BasePermissionsTest {
     private static final int REQUEST_CODE_PERMISSIONS = 42;
 
+    private final Context mContext = getInstrumentation().getContext();
+
     @Test
     public void testCompatDefault() throws Exception {
-        final Context context = getInstrumentation().getContext();
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
+        // Legacy permission model appears granted
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                mContext.checkPermission(android.Manifest.permission.READ_CALENDAR,
+                        Process.myPid(), Process.myUid()));
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                mContext.checkPermission(android.Manifest.permission.WRITE_CALENDAR,
+                        Process.myPid(), Process.myUid()));
 
-        // Legacy permission model is granted by default
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
-                        Process.myPid(), Process.myUid()));
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                        Process.myPid(), Process.myUid()));
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-        assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
-        for (File path : getAllPackageSpecificPaths(context)) {
-            if (path != null) {
-                assertDirReadWriteAccess(path);
-            }
+        // Read/write access should be allowed
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(1, c.getCount());
         }
-        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
     }
 
     @Test
     public void testCompatRevoked_part1() throws Exception {
         // Revoke the permission
-        revokePermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, true);
+        revokePermissions(new String[] {Manifest.permission.WRITE_CALENDAR}, true);
     }
 
     @Test
     public void testCompatRevoked_part2() throws Exception {
-        final Context context = getInstrumentation().getContext();
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
-
-        // Legacy permission model appears granted, but storage looks and
-        // behaves like it's ejected
+        // Legacy permission model appears granted
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                mContext.checkPermission(android.Manifest.permission.READ_CALENDAR,
                         Process.myPid(), Process.myUid()));
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                context.checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                mContext.checkPermission(android.Manifest.permission.WRITE_CALENDAR,
                         Process.myPid(), Process.myUid()));
-        assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
 
-        assertDirNoAccess(Environment.getExternalStorageDirectory());
-        for (File dir : getAllPackageSpecificPaths(context)) {
-            if (dir != null) {
-                assertDirNoAccess(dir);
-            }
+        // Read/write access should be ignored
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(0, c.getCount());
         }
-        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver(), true);
-
-        // Just to be sure, poke explicit path
-        assertDirNoAccess(new File(Environment.getExternalStorageDirectory(),
-                "/Android/data/" + getInstrumentation().getContext().getPackageName()));
     }
 
     @Test
     public void testAllPermissionsGrantedByDefault() throws Exception {
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.SEND_SMS));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.RECEIVE_SMS));
         // The APK does not request because of other tests Manifest.permission.READ_CONTACTS
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.WRITE_CONTACTS));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.READ_CALENDAR));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.WRITE_CALENDAR));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.READ_SMS));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.RECEIVE_WAP_PUSH));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.RECEIVE_MMS));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission("android.permission.READ_CELL_BROADCASTS"));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.READ_PHONE_STATE));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.CALL_PHONE));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.READ_CALL_LOG));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.WRITE_CALL_LOG));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.ADD_VOICEMAIL));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.USE_SIP));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.PROCESS_OUTGOING_CALLS));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.CAMERA));
-        assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
                 .checkSelfPermission(Manifest.permission.BODY_SENSORS));
+
+        // Split permissions
+        assertEquals(PackageManager.PERMISSION_GRANTED, mContext
+                .checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION));
     }
 
     @Test
@@ -165,4 +151,34 @@
         // Revoke a permission
         revokePermissions(new String[] {Manifest.permission.WRITE_CALENDAR}, true);
     }
+
+    @Test
+    public void testAssertNoCalendarAccess() throws Exception {
+        // Without access we're handed back a "fake" Uri that doesn't contain
+        // any of the data we tried persisting
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(0, c.getCount());
+        }
+    }
+
+    @Test
+    public void testAssertCalendarAccess() {
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(1, c.getCount());
+        }
+    }
+
+    /**
+     * Attempt to insert a new unique calendar item; this might be ignored if
+     * this legacy app has its permission revoked.
+     */
+    private Uri insertCalendarItem() {
+        final ContentValues values = new ContentValues();
+        values.put(CalendarContract.Calendars.NAME, "cts" + System.nanoTime());
+        values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "cts");
+        values.put(CalendarContract.Calendars.CALENDAR_COLOR, 0xffff0000);
+        return mContext.getContentResolver().insert(CalendarContract.Calendars.CONTENT_URI, values);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
index 58ee1c7..cfbcbea 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
@@ -31,6 +31,8 @@
     ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionApp23
+
+# For ACCESS_BACKGROUND_LOCATION
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
 # tag this module as a cts test artifact
@@ -41,4 +43,6 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
+LOCAL_MIN_SDK_VERSION := 23
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
index 6675383..ada3d19 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
@@ -1,4 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="Permissions">Permissions</string>
+    <string name="Deny">Deny</string>
+    <string name="Allow">Allow</string>
+    <string name="AllowAll">Allow all the time</string>
 </resources>
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 c26caba..3e3f040 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
@@ -17,6 +17,7 @@
 package com.android.cts.usepermission;
 
 import static junit.framework.Assert.assertEquals;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
@@ -34,6 +35,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObject2;
@@ -47,13 +49,16 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ScrollView;
 import android.widget.Switch;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
-import junit.framework.Assert;
-import org.junit.Before;
-import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 public abstract class BasePermissionsTest {
@@ -62,7 +67,7 @@
     private static final long IDLE_TIMEOUT_MILLIS = 500;
     private static final long GLOBAL_TIMEOUT_MILLIS = 5000;
 
-    private static final long RETRY_TIMEOUT = 3 * GLOBAL_TIMEOUT_MILLIS;
+    private static final long RETRY_TIMEOUT = 10 * GLOBAL_TIMEOUT_MILLIS;
     private static final String LOG_TAG = "BasePermissionsTest";
 
     private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
@@ -253,7 +258,7 @@
 
         PackageManager packageManager = mContext.getPackageManager();
         mWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
-        initPermissionToLabelMap(packageManager.isPermissionReviewModeEnabled());
+        initPermissionToLabelMap(packageManager.arePermissionsIndividuallyControlled());
 
         UiObject2 button = getUiDevice().findObject(By.text("Close"));
         if (button != null) {
@@ -289,25 +294,47 @@
 
     protected void clickAllowButton() throws Exception {
         scrollToBottomIfWatch();
-        getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_allow_button")).click();
+        waitForIdle();
+        getUiDevice().wait(Until.findObject(By.res(
+                "com.android.permissioncontroller:id/permission_allow_button")),
+                GLOBAL_TIMEOUT_MILLIS).click();
+    }
+
+    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 clickAllowForegroundButton() throws Exception {
+        waitForIdle();
+        getUiDevice().wait(Until.findObject(By.res(
+                "com.android.permissioncontroller:id/permission_allow_foreground_only_button")),
+                GLOBAL_TIMEOUT_MILLIS).click();
     }
 
     protected void clickDenyButton() throws Exception {
         scrollToBottomIfWatch();
-        getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_deny_button")).click();
+        waitForIdle();
+        getUiDevice().wait(Until.findObject(By.res(
+                "com.android.permissioncontroller:id/permission_deny_button")),
+                GLOBAL_TIMEOUT_MILLIS).click();
     }
 
-    protected void clickDontAskAgainCheckbox() throws Exception {
-        getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/do_not_ask_checkbox")).click();
+    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();
     }
 
     protected void clickDontAskAgainButton() throws Exception {
         scrollToBottomIfWatch();
-        getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click();
+        waitForIdle();
+        getUiDevice().wait(Until.findObject(By.res(
+                "com.android.permissioncontroller:id/permission_deny_dont_ask_again_button")),
+                GLOBAL_TIMEOUT_MILLIS).click();
     }
 
     protected void grantPermission(String permission) throws Exception {
@@ -366,54 +393,64 @@
         Assert.assertNotNull("Permissions label should be present", permLabelView);
 
         AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
-        Assert.assertNotNull("Permissions item should be present", permItemView);
 
         click(permItemView);
 
         waitForIdle();
 
         for (String permission : permissions) {
-            // Find the permission toggle
+            // Find the permission screen
             String permissionLabel = getPermissionLabel(permission);
 
-            AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel), true);
-            Assert.assertNotNull("Permission label should be present", labelView);
+            UiObject2 permissionView = null;
+            long start = System.currentTimeMillis();
+            while (permissionView == null && start + RETRY_TIMEOUT > System.currentTimeMillis()) {
+                permissionView = getUiDevice().wait(Until.findObject(By.text(permissionLabel)),
+                        GLOBAL_TIMEOUT_MILLIS);
 
-            AccessibilityNodeInfo itemView = findCollectionItem(labelView);
-            Assert.assertNotNull("Permission item should be present", itemView);
+                if (permissionView == null) {
+                    getUiDevice().findObject(By.res("android:id/list_container"))
+                            .scroll(Direction.DOWN, 1);
+                }
+            }
 
-            final AccessibilityNodeInfo toggleView = findSwitch(itemView);
-            Assert.assertNotNull("Permission toggle should be present", toggleView);
+            permissionView.click();
+            waitForIdle();
 
-            final boolean wasGranted = toggleView.isChecked();
+            String denyLabel = mContext.getResources().getString(R.string.Deny);
+
+            final boolean wasGranted = !getUiDevice().wait(Until.findObject(By.text(denyLabel)),
+                    GLOBAL_TIMEOUT_MILLIS).isChecked();
             if (granted != wasGranted) {
                 // Toggle the permission
 
-                if (!itemView.getActionList().contains(AccessibilityAction.ACTION_CLICK)) {
-                    click(toggleView);
+                if (granted) {
+                    String allowLabel = mContext.getResources().getString(R.string.Allow);
+                    getUiDevice().findObject(By.text(allowLabel)).click();
                 } else {
-                    click(itemView);
+                    getUiDevice().findObject(By.text(denyLabel)).click();
                 }
-
                 waitForIdle();
 
                 if (wasGranted && legacyApp) {
                     scrollToBottomIfWatch();
                     String packageName = getInstrumentation().getContext().getPackageManager()
                             .getPermissionControllerPackageName();
-                    String resIdName = "com.android.packageinstaller"
+                    String resIdName = "com.android.permissioncontroller"
                             + ":string/grant_dialog_button_deny_anyway";
                     Resources resources = getInstrumentation().getContext()
                             .createPackageContext(packageName, 0).getResources();
                     final int confirmResId = resources.getIdentifier(resIdName, null, null);
                     String confirmTitle = resources.getString(confirmResId);
-                    UiObject denyAnyway = getUiDevice().findObject(new UiSelector()
-                            .textStartsWith(confirmTitle));
-                    denyAnyway.click();
+                    getUiDevice().wait(Until.findObject(By.textStartsWith(confirmTitle)),
+                            GLOBAL_TIMEOUT_MILLIS).click();
 
                     waitForIdle();
                 }
             }
+
+            getUiDevice().pressBack();
+            waitForIdle();
         }
 
         getUiDevice().pressBack();
@@ -560,27 +597,6 @@
         return null;
     }
 
-    private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception {
-        if (Switch.class.getName().equals(root.getClassName().toString())) {
-            return root;
-        }
-        final int childCount = root.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            AccessibilityNodeInfo child = root.getChild(i);
-            if (child == null) {
-                continue;
-            }
-            if (Switch.class.getName().equals(child.getClassName().toString())) {
-                return child;
-            }
-            AccessibilityNodeInfo result = findSwitch(child);
-            if (result != null) {
-                return result;
-            }
-        }
-        return null;
-    }
-
     private static AccessibilityNodeInfo getNodeTimed(
             Callable<AccessibilityNodeInfo> callable, boolean retry) throws Exception {
         final long startTimeMillis = SystemClock.uptimeMillis();
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 79c03fb..ed74947 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
@@ -20,15 +20,14 @@
 
 import static org.junit.Assert.fail;
 
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
-
 import android.Manifest;
+import android.content.ContentValues;
+import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Environment;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.CalendarContract;
+import android.support.test.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +38,8 @@
 public class UsePermissionTest23 extends BasePermissionsTest {
     private static final int REQUEST_CODE_PERMISSIONS = 42;
 
+    private final Context mContext = getInstrumentation().getContext();
+
     private boolean mLeanback;
     private boolean mWatch;
 
@@ -61,49 +62,47 @@
 
     @Test
     public void testDefault() throws Exception {
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
-
         // New permission model is denied by default
         assertAllPermissionsRevoked();
-
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-        assertDirNoAccess(Environment.getExternalStorageDirectory());
-        assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
-        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver(), false);
     }
 
     @Test
     public void testGranted() throws Exception {
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
-        grantPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
+        grantPermission(Manifest.permission.READ_CALENDAR);
+
+        // Read/write access should be allowed
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE));
+                .checkSelfPermission(Manifest.permission.READ_CALENDAR));
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-        assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
-        assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
-        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
+                .checkSelfPermission(Manifest.permission.WRITE_CALENDAR));
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(1, c.getCount());
+        }
     }
 
     @Test
     public void testInteractiveGrant() throws Exception {
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
-
         // Start out without permission
         assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE));
+                .checkSelfPermission(Manifest.permission.READ_CALENDAR));
         assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-        assertDirNoAccess(Environment.getExternalStorageDirectory());
-        assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
-        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver(), false);
+                .checkSelfPermission(Manifest.permission.WRITE_CALENDAR));
+        try {
+            insertCalendarItem();
+            fail();
+        } catch (SecurityException expected) {
+        }
+        try (Cursor c = mContext.getContentResolver().query(
+                CalendarContract.Calendars.CONTENT_URI, null, null, null)) {
+            fail();
+        } catch (SecurityException expected) {
+        }
 
         // Go through normal grant flow
         BasePermissionActivity.Result result = requestPermissions(new String[] {
-                Manifest.permission.READ_EXTERNAL_STORAGE,
-                Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                Manifest.permission.READ_CALENDAR,
+                Manifest.permission.WRITE_CALENDAR},
                 REQUEST_CODE_PERMISSIONS,
                 BasePermissionActivity.class,
                 () -> {
@@ -116,22 +115,20 @@
                 });
 
         assertEquals(REQUEST_CODE_PERMISSIONS, result.requestCode);
-        assertEquals(Manifest.permission.READ_EXTERNAL_STORAGE, result.permissions[0]);
-        assertEquals(Manifest.permission.WRITE_EXTERNAL_STORAGE, result.permissions[1]);
+        assertEquals(Manifest.permission.READ_CALENDAR, result.permissions[0]);
+        assertEquals(Manifest.permission.WRITE_CALENDAR, result.permissions[1]);
         assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[0]);
         assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[1]);
 
-        logCommand("/system/bin/cat", "/proc/self/mountinfo");
-
         // We should have permission now!
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE));
+                .checkSelfPermission(Manifest.permission.READ_CALENDAR));
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-        assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
-        assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
-        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
+                .checkSelfPermission(Manifest.permission.WRITE_CALENDAR));
+        final Uri uri = insertCalendarItem();
+        try (Cursor c = mContext.getContentResolver().query(uri, null, null, null)) {
+            assertEquals(1, c.getCount());
+        }
     }
 
     @Test
@@ -497,9 +494,17 @@
                 Manifest.permission.CALL_PHONE,
                 Manifest.permission.RECORD_AUDIO,
                 Manifest.permission.BODY_SENSORS,
-                Manifest.permission.ACCESS_COARSE_LOCATION,
                 Manifest.permission.CAMERA
         });
+
+        // Don't use UI for granting location permission as this shows another dialog
+        String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        getInstrumentation().getUiAutomation().grantRuntimePermission(packageName,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        getInstrumentation().getUiAutomation().grantRuntimePermission(packageName,
+                Manifest.permission.ACCESS_COARSE_LOCATION);
+        getInstrumentation().getUiAutomation().grantRuntimePermission(packageName,
+                Manifest.permission.ACCESS_BACKGROUND_LOCATION);
     }
 
     @Test
@@ -650,7 +655,10 @@
                 Manifest.permission.ACCESS_COARSE_LOCATION,
                 Manifest.permission.CAMERA,
                 Manifest.permission.BODY_SENSORS,
-                "android.permission.READ_CELL_BROADCASTS"
+                Manifest.permission.READ_CELL_BROADCASTS,
+
+                // Split permissions
+                Manifest.permission.ACCESS_BACKGROUND_LOCATION
         }, grantState);
     }
 
@@ -665,8 +673,19 @@
         if (mLeanback || mWatch) {
             clickDontAskAgainButton();
         } else {
-            clickDontAskAgainCheckbox();
-            clickDenyButton();
+            clickDenyAndDontAskAgainButton();
         }
     }
+
+    /**
+     * Attempt to insert a new unique calendar item; this might be ignored if
+     * this legacy app has its permission revoked.
+     */
+    private Uri insertCalendarItem() {
+        final ContentValues values = new ContentValues();
+        values.put(CalendarContract.Calendars.NAME, "cts" + System.nanoTime());
+        values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "cts");
+        values.put(CalendarContract.Calendars.CALENDAR_COLOR, 0xffff0000);
+        return mContext.getContentResolver().insert(CalendarContract.Calendars.CONTENT_URI, values);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
index 8528752..acacc1e 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
@@ -32,6 +32,8 @@
 LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionApp25
+
+# For ACCESS_BACKGROUND_LOCATION
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
 # tag this module as a cts test artifact
@@ -42,4 +44,6 @@
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
 
+LOCAL_MIN_SDK_VERSION := 25
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/Android.mk
index 52c8ba4..a7f7d2c 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/Android.mk
@@ -31,7 +31,8 @@
 LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionApp26
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 26
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/Android.mk
new file mode 100644
index 0000000..07b19ab
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/Android.mk
@@ -0,0 +1,48 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
+
+LOCAL_PACKAGE_NAME := CtsUsePermissionApp28
+
+# For ACCESS_BACKGROUND_LOCATION
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+LOCAL_MIN_SDK_VERSION := 28
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/AndroidManifest.xml
new file mode 100644
index 0000000..9346444
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.usepermission">
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
+    </application>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.usepermission" />
+
+</manifest>
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
new file mode 100644
index 0000000..1c32d39
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
@@ -0,0 +1,66 @@
+/*
+ * 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.usepermission;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import org.junit.Test;
+
+/**
+ * Runtime permission behavior tests for apps targeting API 28
+ */
+public class UsePermissionTest28 extends BasePermissionsTest {
+    private static final int REQUEST_CODE_PERMISSIONS = 42;
+
+    @Test
+    public void testLocationPermissionWasSplit() throws Exception {
+        Context context = getInstrumentation().getTargetContext();
+
+        assertEquals(PERMISSION_DENIED, context.checkSelfPermission(ACCESS_FINE_LOCATION));
+        assertEquals(PERMISSION_DENIED, context.checkSelfPermission(ACCESS_BACKGROUND_LOCATION));
+
+        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);
+                    }
+                });
+
+        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS, 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/UsePermissionAppLatest/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/Android.mk
index 62a15e4..7882936 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/Android.mk
@@ -26,13 +26,16 @@
     ub-uiautomator
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../UsePermissionApp26/src)  \
+    $(call all-java-files-under, ../UsePermissionAppP0/src)  \
     ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java \
     ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
 LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
-LOCAL_SDK_VERSION := test_current
 
 LOCAL_PACKAGE_NAME := CtsUsePermissionAppLatest
 
+# For ACCESS_BACKGROUND_LOCATION
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
index 57a58ab..9d397d7 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
 
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
     <application>
         <uses-library android:name="android.test.runner" />
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/Android.mk
new file mode 100644
index 0000000..3eae70c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/Android.mk
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java \
+    ../UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
+
+LOCAL_PACKAGE_NAME := CtsUsePermissionAppP0
+
+# For ACCESS_BACKGROUND_LOCATION
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/AndroidManifest.xml
new file mode 100644
index 0000000..9d89608
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.usepermission">
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
+    </application>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.usepermission" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/src/com/android/cts/usepermission/UsePermissionTestP0.java b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/src/com/android/cts/usepermission/UsePermissionTestP0.java
new file mode 100644
index 0000000..f219030
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppP0/src/com/android/cts/usepermission/UsePermissionTestP0.java
@@ -0,0 +1,180 @@
+/*
+ * 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.usepermission;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static junit.framework.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Runtime permission behavior tests for apps targeting {@link android.os.Build.VERSION_CODES#P0}.
+ */
+public class UsePermissionTestP0 extends BasePermissionsTest {
+    private static final int REQUEST_CODE_PERMISSIONS = 42;
+
+    public interface UiInteraction {
+        void run() throws Exception;
+    }
+
+    private static void assertGranted(String permission) {
+        assertEquals(PERMISSION_GRANTED, getInstrumentation().getContext()
+                .checkSelfPermission(permission));
+    }
+
+    private static void assertDenied(String permission) {
+        assertEquals(PERMISSION_DENIED, getInstrumentation().getContext()
+                .checkSelfPermission(permission));
+    }
+
+    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);
+                    }
+                });
+    }
+
+    private static void assertPermissionRequestResult(BasePermissionActivity.Result result,
+            String[] permissions, boolean... granted) {
+        BasePermissionsTest.assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
+                permissions, granted);
+    }
+
+    @Before
+    public void assertPermissionsNotGranted() {
+        assertDenied(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void locationPermissionIsNotSplit() throws Exception {
+        String[] permissions = {ACCESS_FINE_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                this::clickAllowButton);
+        assertPermissionRequestResult(result, permissions, true);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void requestOnlyBackgroundNotPossible() throws Exception {
+        String[] permissions = {ACCESS_BACKGROUND_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions);
+        assertPermissionRequestResult(result, permissions, false);
+
+        assertDenied(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void requestBoth() throws Exception {
+        String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                this::clickAllowAlwaysButton);
+        assertPermissionRequestResult(result, permissions, true, true);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertGranted(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void requestBothInSequence() throws Exception {
+        // Step 1: request foreground only
+        String[] permissions = {ACCESS_FINE_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                this::clickAllowButton);
+        assertPermissionRequestResult(result, permissions, true);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+
+        // Step 2: request background only
+        permissions = new String[]{ACCESS_BACKGROUND_LOCATION};
+
+        result = requestPermissions(permissions, this::clickAllowButton);
+        assertPermissionRequestResult(result, permissions, true);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertGranted(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void requestBothButGrantInSequence() throws Exception {
+        // Step 1: grant foreground only
+        String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                this::clickAllowForegroundButton);
+        assertPermissionRequestResult(result, permissions, true, false);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+
+        // Step 2: grant background
+        result = requestPermissions(permissions, this::clickAllowButton);
+        assertPermissionRequestResult(result, permissions, true, true);
+
+        assertGranted(ACCESS_FINE_LOCATION);
+        assertGranted(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void denyBackgroundWithPrejudice() throws Exception {
+        // Step 1: deny the first time
+        String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                this::clickDenyButton);
+        assertPermissionRequestResult(result, permissions, false, false);
+
+        assertDenied(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+
+        // Step 2: deny with prejudice
+        result = requestPermissions(permissions, this::clickDenyAndDontAskAgainButton);
+        assertPermissionRequestResult(result, permissions, false, false);
+
+        assertDenied(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+
+        // Step 3: All further requests should be denied automatically
+        result = requestPermissions(permissions);
+        assertPermissionRequestResult(result, permissions, false, false);
+
+        assertDenied(ACCESS_FINE_LOCATION);
+        assertDenied(ACCESS_BACKGROUND_LOCATION);
+    }
+}
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 355de8c..9a67379 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
@@ -40,6 +40,7 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
 import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
 import com.android.cts.permissiondeclareapp.UtilsProvider;
@@ -1620,10 +1621,19 @@
             assertReadingClipNotAllowed(clip);
             assertWritingClipNotAllowed(clip);
 
-            // But if someone puts it on the clipboard, we can read it
-            setPrimaryClip(clip);
-            final ClipData clipFromClipboard = getContext().getSystemService(ClipboardManager.class)
-                    .getPrimaryClip();
+            // 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);
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
index b1e84cb..f01f5b9 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 28
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
 	compatibility-device-util
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
index 37e39e9..34ad6e3 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
@@ -24,5 +24,6 @@
         android:targetPackage="com.android.cts.writeexternalstorageapp" />
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
index db3813f..83a38f1 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
@@ -21,6 +21,7 @@
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificGiftPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificObbGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
 
@@ -64,4 +65,20 @@
             assertEquals(102, readInt(write));
         }
     }
+
+    /**
+     * Leave gifts for other packages in their obb directories.
+     */
+    public void testObbGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+
+            none.getParentFile().mkdirs();
+            none.createNewFile();
+            assertFileReadWriteAccess(none);
+
+            writeInt(none, 100);
+            assertEquals(100, readInt(none));
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
index 4822a11..da5eba3 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
@@ -21,7 +21,7 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := current
 LOCAL_PACKAGE_NAME := CtsPkgInstallTinyApp
 LOCAL_DEX_PREOPT := false
 
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
index def2931..1ead3a2 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
@@ -16,7 +16,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.appsecurity.cts.tinyapp"
         android:versionCode="10"
-        android:versionName="1.0">
+        android:versionName="1.0"
+        android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
         <activity
                 android:name=".MainActivity"
diff --git a/hostsidetests/atrace/Android.mk b/hostsidetests/atrace/Android.mk
index 0c84134..a4e5aca 100644
--- a/hostsidetests/atrace/Android.mk
+++ b/hostsidetests/atrace/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_MODULE := CtsAtraceHostTestCases
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
+LOCAL_STATIC_JAVA_LIBRARIES := trebuchet-core
 
 LOCAL_CTS_TEST_PACKAGE := android.host.atrace
 
diff --git a/hostsidetests/atrace/AtraceTestApp/Android.mk b/hostsidetests/atrace/AtraceTestApp/Android.mk
index 4b550db..b803e19 100644
--- a/hostsidetests/atrace/AtraceTestApp/Android.mk
+++ b/hostsidetests/atrace/AtraceTestApp/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_JNI_SHARED_LIBRARIES := libctstrace_jni
+LOCAL_MULTILIB := both
 
 LOCAL_PACKAGE_NAME := CtsAtraceTestApp
 
@@ -33,4 +35,11 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+# Disable AAPT2 manifest checks to fix:
+# out/target/common/obj/APPS/CtsAtraceTestApp_intermediates/manifest/AndroidManifest.xml:32: error: unexpected element <profileable> found in <manifest>.
+# TODO: Remove when AAPT2 recognizes the manifest elements.
+LOCAL_AAPT_FLAGS += --warn-manifest-validation
+
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
index c460300..cb57d14 100644
--- a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
+++ b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
@@ -19,7 +19,7 @@
     A simple app with a tracing section to test that apps tracing signals are
     emitted by atrace.
     -->
-    <application android:debuggable="true"> <!-- Debuggable to enable tracing -->
+    <application>
         <activity android:name=".AtraceTestAppActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -27,5 +27,10 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <!-- Profileable to enable tracing -->
+        <profileable android:shell="true"/>
     </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.atracetestapp" />
 </manifest>
diff --git a/hostsidetests/atrace/AtraceTestApp/jni/Android.mk b/hostsidetests/atrace/AtraceTestApp/jni/Android.mk
new file mode 100644
index 0000000..845b245
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/jni/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctstrace_jni
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+	CtsTrace.cpp
+
+LOCAL_CFLAGS += -Wall -Werror
+
+LOCAL_SHARED_LIBRARIES := libandroid
+LOCAL_NDK_STL_VARIANT := c++_static
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/hostsidetests/atrace/AtraceTestApp/jni/CtsTrace.cpp b/hostsidetests/atrace/AtraceTestApp/jni/CtsTrace.cpp
new file mode 100644
index 0000000..ce91662
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/jni/CtsTrace.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <android/trace.h>
+
+static jboolean isEnabled(JNIEnv*, jclass) {
+    return ATrace_isEnabled();
+}
+
+static void beginEndSection(JNIEnv*, jclass) {
+    ATrace_beginSection("ndk::beginEndSection");
+    ATrace_endSection();
+}
+
+static void asyncBeginEndSection(JNIEnv*, jclass) {
+    ATrace_beginAsyncSection("ndk::asyncBeginEndSection", 4770);
+    ATrace_endAsyncSection("ndk::asyncBeginEndSection", 4770);
+}
+
+
+static void counter(JNIEnv*, jclass) {
+    ATrace_setCounter("ndk::counter", 10);
+    ATrace_setCounter("ndk::counter", 20);
+    ATrace_setCounter("ndk::counter", 30);
+    ATrace_setCounter("ndk::counter", 9223372000000005807L);
+}
+
+static JNINativeMethod gMethods[] = {
+    { "isEnabled", "()Z", (void*) isEnabled },
+    { "beginEndSection", "()V", (void*) beginEndSection },
+    { "asyncBeginEndSection", "()V", (void*) asyncBeginEndSection },
+    { "counter", "()V", (void*) counter },
+};
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+    jclass clazz = env->FindClass("com/android/cts/atracetestapp/AtraceNdkMethods");
+    env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
+    return JNI_VERSION_1_4;
+}
diff --git a/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceDeviceTests.java b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceDeviceTests.java
new file mode 100644
index 0000000..b41b641
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceDeviceTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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.atracetestapp;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.os.Trace;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.Os;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class AtraceDeviceTests {
+
+    @BeforeClass
+    public static void reportPidTid() {
+        Bundle status = new Bundle();
+        status.putLong("AtraceDeviceTests_pid", Os.getpid());
+        status.putLong("AtraceDeviceTests_tid", Os.gettid());
+        InstrumentationRegistry.getInstrumentation().addResults(status);
+    }
+
+    @Rule
+    public ActivityTestRule<AtraceTestAppActivity> mActivity =
+            new ActivityTestRule<>(AtraceTestAppActivity.class, true, false);
+
+    @Test
+    public void assertTracingOn() {
+        assertTrue(Trace.isEnabled());
+        assertTrue(AtraceNdkMethods.isEnabled());
+    }
+
+    @Test
+    public void assertTracingOff() {
+        assertFalse(Trace.isEnabled());
+        assertFalse(AtraceNdkMethods.isEnabled());
+    }
+
+    @Test
+    public void beginEndSection() {
+        assertTrue(Trace.isEnabled());
+        assertTrue(AtraceNdkMethods.isEnabled());
+        Trace.beginSection("AtraceDeviceTest::beginEndSection");
+        Trace.endSection();
+        AtraceNdkMethods.beginEndSection();
+    }
+
+    @Test
+    public void asyncBeginEndSection() {
+        assertTrue(Trace.isEnabled());
+        assertTrue(AtraceNdkMethods.isEnabled());
+        Trace.beginAsyncSection("AtraceDeviceTest::asyncBeginEndSection", 42);
+        Trace.endAsyncSection("AtraceDeviceTest::asyncBeginEndSection", 42);
+        AtraceNdkMethods.asyncBeginEndSection();
+    }
+
+    @Test
+    public void counter() {
+        assertTrue(Trace.isEnabled());
+        assertTrue(AtraceNdkMethods.isEnabled());
+        Trace.setCounter("AtraceDeviceTest::counter", 10);
+        Trace.setCounter("AtraceDeviceTest::counter", 20);
+        Trace.setCounter("AtraceDeviceTest::counter", 30);
+        Trace.setCounter("AtraceDeviceTest::counter", 9223372000000005807L);
+        AtraceNdkMethods.counter();
+    }
+
+    @Test
+    public void launchActivity() {
+        AtraceTestAppActivity activity = mActivity.launchActivity(null);
+        activity.waitForDraw();
+        activity.finish();
+    }
+
+    static {
+        System.loadLibrary("ctstrace_jni");
+    }
+}
diff --git a/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceNdkMethods.java b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceNdkMethods.java
new file mode 100644
index 0000000..429eba4
--- /dev/null
+++ b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceNdkMethods.java
@@ -0,0 +1,24 @@
+/*
+ * 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.atracetestapp;
+
+public class AtraceNdkMethods {
+    public static native boolean isEnabled();
+    public static native void beginEndSection();
+    public static native void asyncBeginEndSection();
+    public static native void counter();
+}
diff --git a/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java
index 0269d0d..85adeee 100644
--- a/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java
+++ b/hostsidetests/atrace/AtraceTestApp/src/com/android/cts/atracetestapp/AtraceTestAppActivity.java
@@ -15,15 +15,87 @@
  */
 package com.android.cts.atracetestapp;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
 import android.os.Bundle;
 import android.os.Trace;
+import android.view.View;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class AtraceTestAppActivity extends Activity {
+
+    private CountDownLatch mHasDrawnFence;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        Trace.beginSection("traceable-app-test-section");
+        Trace.beginAsyncSection("AtraceActivity::created", 1);
         super.onCreate(savedInstanceState);
-        Trace.endSection();
+        mHasDrawnFence = new CountDownLatch(1);
+        MyView view = new MyView(this);
+        setContentView(view);
+        view.getViewTreeObserver().registerFrameCommitCallback(mHasDrawnFence::countDown);
+    }
+
+    @Override
+    protected void onStart() {
+        Trace.beginAsyncSection("AtraceActivity::started", 1);
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        Trace.beginAsyncSection("AtraceActivity::resumed", 1);
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Trace.endAsyncSection("AtraceActivity::resumed", 1);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Trace.endAsyncSection("AtraceActivity::started", 1);
+    }
+
+    @Override
+    protected void onDestroy() {
+        mHasDrawnFence = null;
+        super.onDestroy();
+        Trace.endAsyncSection("AtraceActivity::created", 1);
+    }
+
+    public void waitForDraw() {
+        try {
+            assertTrue(mHasDrawnFence.await(10, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            fail("Timed out: " + e.getMessage());
+        }
+    }
+
+    private static class MyView extends View {
+        private static int sDrawCount = 0;
+
+        public MyView(Context context) {
+            super(context);
+            setWillNotDraw(false);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            Trace.beginSection("MyView::onDraw");
+            Trace.setCounter("MyView::drawCount", ++sDrawCount);
+            canvas.drawColor(Color.BLUE);
+            Trace.endSection();
+        }
     }
 }
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceConfig.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceConfig.java
new file mode 100644
index 0000000..6679e96
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceConfig.java
@@ -0,0 +1,32 @@
+package android.atrace.cts;
+
+public final class AtraceConfig {
+
+    // Collection of all userspace tags, and 'sched'
+    public static final String[] RequiredCategories = {
+            "sched",
+            "gfx",
+            "input",
+            "view",
+            "webview",
+            "wm",
+            "am",
+            "sm",
+            "audio",
+            "video",
+            "camera",
+            "hal",
+            "res",
+            "dalvik",
+            "rs",
+            "bionic",
+            "power"
+    };
+
+    // Categories to use when capturing a trace with otherwise no categories specified
+    public static final String[] DefaultCategories = {
+            "view"
+    };
+
+    private AtraceConfig() {}
+}
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceDeviceTestList.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceDeviceTestList.java
new file mode 100644
index 0000000..28b6f8c
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceDeviceTestList.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.atrace.cts;
+
+public enum AtraceDeviceTestList {
+    assertTracingOn,
+    assertTracingOff,
+    beginEndSection,
+    asyncBeginEndSection,
+    counter,
+    launchActivity,
+}
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
index 3c6087c..feed45f 100644
--- a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
@@ -16,18 +16,17 @@
 
 package android.atrace.cts;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
+import static android.atrace.cts.AtraceDeviceTestList.assertTracingOff;
+import static android.atrace.cts.AtraceDeviceTestList.assertTracingOn;
+import static android.atrace.cts.AtraceDeviceTestList.asyncBeginEndSection;
+import static android.atrace.cts.AtraceDeviceTestList.beginEndSection;
+import static android.atrace.cts.AtraceDeviceTestList.counter;
+import static android.atrace.cts.AtraceDeviceTestList.launchActivity;
+
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.io.BufferedReader;
-import java.io.File;
 import java.io.Reader;
-import java.io.StringReader;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -35,12 +34,20 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import kotlin.Unit;
+import trebuchet.model.Counter;
+import trebuchet.model.CounterValue;
+import trebuchet.model.Model;
+import trebuchet.model.ProcessModel;
+import trebuchet.model.ThreadModel;
+import trebuchet.model.base.Slice;
+import trebuchet.model.fragments.AsyncSlice;
+import trebuchet.queries.SliceQueries;
+
 /**
  * Test to check that atrace is usable, to enable usage of systrace.
  */
-public class AtraceHostTest extends DeviceTestCase implements IBuildReceiver {
-    private static final String TEST_APK = "CtsAtraceTestApp.apk";
-    private static final String TEST_PKG = "com.android.cts.atracetestapp";
+public class AtraceHostTest extends AtraceHostTestBase {
 
     private interface FtraceEntryCallback {
         void onTraceEntry(String threadName, int pid, int tid, String eventType, String args);
@@ -52,57 +59,22 @@
      * Regexs copied from (and should be kept in sync with) ftrace importer in catapult.
      */
     private static class FtraceParser {
-        // Matches the trace record in 3.2 and later with the print-tgid option:
-        //          <idle>-0    0 [001] d...  1.23: sched_switch
-        private static final Pattern sLineWithTgid = Pattern.compile(
-                "^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]"
-                + "\\s+[dX.][N.][Hhs.][0-9a-f.]"
-                + "\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)");
+        private static final Pattern sFtraceLine = Pattern.compile(
+                "^ *(.{1,16})-(\\d+) +(?:\\( *(\\d+)?-*\\) )?\\[(\\d+)] (?:[dX.]...)? *([\\d.]*): *"
+                + "([^:]*): *(.*) *$");
 
-        // Matches the default trace record in 3.2 and later (includes irq-info):
-        //          <idle>-0     [001] d...  1.23: sched_switch
-        private static final Pattern sLineWithIrqInfo = Pattern.compile(
-                "^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]"
-                + "\\s+[dX.][N.][Hhs.][0-9a-f.]"
-                + "\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$");
-
-        // Matches the default trace record pre-3.2:
-        //          <idle>-0     [001]  1.23: sched_switch
-        private static final Pattern sLineLegacy = Pattern.compile(
-                "^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]\\s*(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)");
         private static void parseLine(String line, FtraceEntryCallback callback) {
-            Matcher m = sLineWithTgid.matcher(line);
+            Matcher m = sFtraceLine.matcher(line);
             if (m.matches()) {
                 callback.onTraceEntry(
                         /*threadname*/ m.group(1),
-                        /*pid*/ m.group(3).startsWith("-") ? -1 : Integer.parseInt(m.group(3)),
+                        /*pid*/ (m.group(3) == null || m.group(3).startsWith("-")) ? -1 : Integer.parseInt(m.group(3)),
                         /*tid*/ Integer.parseInt(m.group(2)),
                         /*eventName*/ m.group(6),
                         /*details*/ m.group(7));
                 return;
             }
 
-            m = sLineWithIrqInfo.matcher(line);
-            if (m.matches()) {
-                callback.onTraceEntry(
-                        /*threadname*/ m.group(1),
-                        /*pid*/ -1,
-                        /*tid*/ Integer.parseInt(m.group(2)),
-                        /*eventName*/ m.group(5),
-                        /*details*/ m.group(6));
-                return;
-            }
-
-            m = sLineLegacy.matcher(line);
-            if (m.matches()) {
-                callback.onTraceEntry(
-                        /*threadname*/ m.group(1),
-                        /*pid*/ -1,
-                        /*tid*/ Integer.parseInt(m.group(2)),
-                        /*eventName*/ m.group(5),
-                        /*details*/ m.group(6));
-                return;
-            }
             CLog.i("line doesn't match: " + line);
         }
 
@@ -119,42 +91,11 @@
         }
     }
 
-    private IBuildInfo mCtsBuild;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    // Collection of all userspace tags, and 'sched'
-    private static final List<String> sRequiredCategoriesList = Arrays.asList(
-            "sched",
-            "gfx",
-            "input",
-            "view",
-            "webview",
-            "wm",
-            "am",
-            "sm",
-            "audio",
-            "video",
-            "camera",
-            "hal",
-            "res",
-            "dalvik",
-            "rs",
-            "bionic",
-            "power"
-    );
-
     /**
      * Tests that atrace exists and is runnable with no args
      */
-    public void testSimpleRun() throws Exception {
-        String output = getDevice().executeShellCommand("atrace");
+    public void testSimpleRun() {
+        String output = shell("atrace");
         String[] lines = output.split("\\r?\\n");
 
         // check for expected stdout
@@ -168,11 +109,12 @@
     /**
      * Tests the output of "atrace --list_categories" to ensure required categories exist.
      */
-    public void testCategories() throws Exception {
-        String output = getDevice().executeShellCommand("atrace --list_categories");
+    public void testCategories() {
+        String output = shell("atrace --list_categories");
         String[] categories = output.split("\\r?\\n");
 
-        Set<String> requiredCategories = new HashSet<String>(sRequiredCategoriesList);
+        Set<String> requiredCategories = new HashSet<String>(Arrays.asList(
+                AtraceConfig.RequiredCategories));
 
         for (String category : categories) {
             int dashIndex = category.indexOf("-");
@@ -191,110 +133,150 @@
         }
     }
 
+    public void testTracingIsEnabled() {
+        runSingleAppTest(assertTracingOff);
+        traceSingleTest(assertTracingOn, true);
+        runSingleAppTest(assertTracingOff);
+    }
+
+    // Verifies that although tracing is active, Trace.isEnabled() is false since the app
+    // category isn't enabled
+    public void testTracingIsDisabled() {
+        runSingleAppTest(assertTracingOff);
+        traceSingleTest(assertTracingOff, false);
+        runSingleAppTest(assertTracingOff);
+    }
+
+    private static ThreadModel findThread(Model model, int id) {
+        for (ProcessModel process : model.getProcesses().values()) {
+            for (ThreadModel thread : process.getThreads()) {
+                if (thread.getId() == id) {
+                    return thread;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static ProcessModel findProcess(Model model, int id) {
+        for (ProcessModel process : model.getProcesses().values()) {
+            if (process.getId() == id) {
+                return process;
+            }
+        }
+        return null;
+    }
+
+    private static Counter findCounter(ProcessModel processModel, String name) {
+        for (Counter counter : processModel.getCounters()) {
+            if (name.equals(counter.getName())) {
+                return counter;
+            }
+        }
+        return null;
+    }
+
+    public void testBeginEndSection() {
+        TraceResult result = traceSingleTest(beginEndSection);
+        assertTrue(result.getPid() > 0);
+        assertTrue(result.getTid() > 0);
+        assertNotNull(result.getModel());
+        ThreadModel thread = findThread(result.getModel(), result.getTid());
+        assertNotNull(thread);
+        assertEquals(2, thread.getSlices().size());
+        Slice sdkSlice = thread.getSlices().get(0);
+        assertEquals("AtraceDeviceTest::beginEndSection", sdkSlice.getName());
+        Slice ndkSlice = thread.getSlices().get(1);
+        assertEquals("ndk::beginEndSection", ndkSlice.getName());
+    }
+
+    public void testAsyncBeginEndSection() {
+        TraceResult result = traceSingleTest(asyncBeginEndSection);
+        assertTrue(result.getPid() > 0);
+        assertTrue(result.getTid() > 0);
+        assertNotNull(result.getModel());
+        ProcessModel process = findProcess(result.getModel(), result.getPid());
+        assertNotNull(process);
+        assertEquals(2, process.getAsyncSlices().size());
+        AsyncSlice sdkSlice = process.getAsyncSlices().get(0);
+        assertEquals("AtraceDeviceTest::asyncBeginEndSection", sdkSlice.getName());
+        assertEquals(42, sdkSlice.getCookie());
+        AsyncSlice ndkSlice = process.getAsyncSlices().get(1);
+        assertEquals("ndk::asyncBeginEndSection", ndkSlice.getName());
+        assertEquals(4770, ndkSlice.getCookie());
+    }
+
+    public void testCounter() {
+        TraceResult result = traceSingleTest(counter);
+        assertTrue(result.getPid() > 0);
+        assertTrue(result.getTid() > 0);
+        assertNotNull(result.getModel());
+        ProcessModel process = findProcess(result.getModel(), result.getPid());
+        assertNotNull(process);
+        assertTrue(process.getCounters().size() > 0);
+        Counter counter = findCounter(process, "AtraceDeviceTest::counter");
+        assertNotNull(counter);
+        List<CounterValue> values = counter.getEvents();
+        assertEquals(4, values.size());
+        assertEquals(10, values.get(0).getCount());
+        assertEquals(20, values.get(1).getCount());
+        assertEquals(30, values.get(2).getCount());
+        assertEquals(9223372000000005807L, values.get(3).getCount());
+    }
+
     /**
      * Tests that atrace captures app launch, including app level tracing
      */
-    public void testTracingContent() throws Exception {
-        String atraceOutput = null;
-        try {
-            // cleanup test apps that might be installed from previous partial test run
-            getDevice().uninstallPackage(TEST_PKG);
+    public void testTracingContent() {
+        turnScreenOn();
+        TraceResult result = traceSingleTest(launchActivity, AtraceConfig.DefaultCategories);
 
-            // install the test app
-            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-            File testAppFile = buildHelper.getTestFile(TEST_APK);
-            String installResult = getDevice().installPackage(testAppFile, false);
-            assertNull(
-                    String.format("failed to install atrace test app. Reason: %s", installResult),
-                    installResult);
+        final Set<String> requiredSections = new HashSet<>(Arrays.asList(
+                // From the 'view' category
+                "inflate",
+                "Choreographer#doFrame",
+                "traversal",
+                "measure",
+                "layout",
+                "draw",
+                "Record View#draw()",
 
-            // capture a launch of the app with async tracing
-            // content traced by 'view' tag tested below, 'sched' used to ensure tgid printed
-            String atraceArgs = "-a " + TEST_PKG + " -c -b 16000 view"; // TODO: zipping
-            getDevice().executeShellCommand("atrace --async_stop " + atraceArgs);
-            getDevice().executeShellCommand("atrace --async_start " + atraceArgs);
-            getDevice().executeShellCommand("am start " + TEST_PKG);
-            getDevice().executeShellCommand("sleep 5");
-            atraceOutput = getDevice().executeShellCommand("atrace --async_stop " + atraceArgs);
-        } finally {
-            assertNotNull("unable to capture atrace output", atraceOutput);
-            getDevice().uninstallPackage(TEST_PKG);
+                // From our app code
+                "MyView::onDraw"
+        ));
+
+        ThreadModel thread = findThread(result.getModel(), result.getPid());
+        SliceQueries.INSTANCE.iterSlices(thread, (Slice slice) -> {
+            requiredSections.remove(slice.getName());
+            return Unit.INSTANCE;
+        });
+
+        assertEquals("Didn't find all required sections",
+                0, requiredSections.size());
+
+        ProcessModel processModel = findProcess(result.getModel(), result.getPid());
+        Counter drawCounter = findCounter(processModel, "MyView::drawCount");
+        assertNotNull(drawCounter);
+        assertTrue(drawCounter.getEvents().size() > 0);
+        long previousCount = 0;
+        for (CounterValue value : drawCounter.getEvents()) {
+            assertTrue(previousCount < value.getCount());
+            previousCount = value.getCount();
         }
+        assertTrue(previousCount > 0);
 
+        final Set<String> requiredAsyncSections = new HashSet<>(Arrays.asList(
+                "AtraceActivity::created",
+                "AtraceActivity::started",
+                "AtraceActivity::resumed"
+        ));
 
-        // now parse the trace data (see external/chromium-trace/systrace.py)
-        final String MARKER = "TRACE:";
-        int dataStart = atraceOutput.indexOf(MARKER);
-        assertTrue(dataStart >= 0);
-        String traceData = atraceOutput.substring(dataStart + MARKER.length());
-
-        FtraceEntryCallback callback = new FtraceEntryCallback() {
-            private int userSpaceMatches = 0;
-            private int beginMatches = 0;
-            private int nextSectionIndex = -1;
-            private int appTid = -1;
-
-
-            private final String initialSection = "traceable-app-test-section";
-            // list of tags expected to be seen on app launch, in order, after the initial.
-            private final String[] requiredSectionList = {
-                    "inflate",
-                    "Choreographer#doFrame",
-                    "traversal",
-                    "measure",
-                    "layout",
-                    "draw",
-                    "Record View#draw()"
-            };
-
-            @Override
-            public void onTraceEntry(String truncatedThreadName, int pid, int tid,
-                    String eventName, String details) {
-                if (!"tracing_mark_write".equals(eventName)) {
-                    // not userspace trace, ignore
-                    return;
-                }
-
-                assertNotNull(truncatedThreadName);
-                assertTrue(tid > 0);
-                userSpaceMatches++;
-
-                if (details == null || !details.startsWith("B|")) {
-                    // not a begin event
-                    return;
-                }
-                beginMatches++;
-
-                if (details.endsWith("|" + initialSection)) {
-                    // initial section observed, start looking for others in order
-                    assertEquals(nextSectionIndex, -1);
-                    nextSectionIndex = 0;
-                    appTid = tid;
-                    return;
-                }
-
-                if (nextSectionIndex >= 0
-                        && tid == appTid
-                        && nextSectionIndex < requiredSectionList.length
-                        && details.endsWith("|" + requiredSectionList[nextSectionIndex])) {
-                    // found next required section in sequence
-                    nextSectionIndex++;
-                }
+        for (AsyncSlice asyncSlice : processModel.getAsyncSlices()) {
+            if (requiredAsyncSections.remove(asyncSlice.getName())) {
+                assertEquals(1, asyncSlice.getCookie());
             }
-
-            @Override
-            public void onFinished() {
-                assertTrue("Unable to parse any userspace sections from atrace output",
-                        userSpaceMatches != 0);
-                assertTrue("Unable to parse any section begin events from atrace output",
-                        beginMatches != 0);
-                assertTrue("Unable to parse initial userspace sections from test app",
-                        nextSectionIndex >= 0);
-                assertEquals("Didn't see required list of traced sections, in order",
-                        requiredSectionList.length, nextSectionIndex);
-            }
-        };
-
-        FtraceParser.parse(new StringReader(traceData), callback);
+        }
+        assertEquals("Didn't find all async sections",
+                0, requiredAsyncSections.size());
     }
 }
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTestBase.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTestBase.java
new file mode 100644
index 0000000..4a3c672
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTestBase.java
@@ -0,0 +1,248 @@
+/*
+ * 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.atrace.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+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.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import trebuchet.io.BufferProducer;
+import trebuchet.io.DataSlice;
+import trebuchet.model.Model;
+import trebuchet.task.ImportTask;
+import trebuchet.util.PrintlnImportFeedback;
+
+public class AtraceHostTestBase extends DeviceTestCase implements IBuildReceiver {
+    private static final String TEST_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+    private static final String TEST_APK = "CtsAtraceTestApp.apk";
+    // TODO: Make private
+    protected static final String TEST_PKG = "com.android.cts.atracetestapp";
+    private static final String TEST_CLASS = "com.android.cts.atracetestapp.AtraceDeviceTests";
+
+    private static final String START_TRACE_CMD = "atrace --async_start -a \\* -c -b 16000";
+    private static final String START_TRACE_NO_APP_CMD = "atrace --async_start -c -b 16000";
+    private static final String STOP_TRACE_CMD = "atrace --async_stop";
+
+    private IBuildInfo mCtsBuild;
+    private boolean mIsInstalled = false;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Install a device side test package.
+     *
+     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+     * @param grantPermissions whether to give runtime permissions.
+     */
+    private void installPackage(String appFileName, boolean grantPermissions)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        LogUtil.CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final String result = getDevice().installPackage(
+                buildHelper.getTestFile(appFileName), true, grantPermissions);
+        assertNull("Failed to install " + appFileName + ": " + result, result);
+    }
+
+    private final void requireApk() {
+        if (mIsInstalled) return;
+        try {
+            System.out.println("Installing APK");
+            installPackage(TEST_APK, true);
+            mIsInstalled = true;
+        } catch (FileNotFoundException | DeviceNotAvailableException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected final void turnScreenOn() {
+        shell("input keyevent KEYCODE_WAKEUP");
+        shell("wm dismiss-keyguard");
+    }
+
+    @Override
+    public void run(junit.framework.TestResult result) {
+        try {
+            super.run(result);
+        } finally {
+            try {
+                // We don't have the equivalent of @AfterClass, but this basically does that
+                System.out.println("Uninstalling APK");
+                getDevice().uninstallPackage(TEST_PKG);
+                mIsInstalled = false;
+            } catch (DeviceNotAvailableException e) {}
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        try {
+            shell("atrace --async_stop");
+        } finally {
+            super.setUp();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            shell("atrace --async_stop");
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    /**
+     * Run a device side test.
+     *
+     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+     * @param testMethodName Test method name.
+     * @throws DeviceNotAvailableException
+     */
+    private PidTidPair runDeviceTests(String pkgName,
+            String testClassName,  String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, getDevice().getIDevice());
+        testRunner.setMaxTimeout(60, TimeUnit.SECONDS);
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertTrue(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(
+                        com.android.ddmlib.testrunner.TestResult.TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+        return new PidTidPair(result);
+    }
+
+    private final PidTidPair runAppTest(String testname) {
+        requireApk();
+        try {
+            return runDeviceTests(TEST_PKG, TEST_CLASS, testname);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected final String shell(String command, String... args) {
+        if (args != null && args.length > 0) {
+            command += " " + String.join(" ", args);
+        }
+        try {
+            return getDevice().executeShellCommand(command);
+        } catch (DeviceNotAvailableException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static class StringAdapter implements BufferProducer {
+        private byte[] data;
+        private boolean hasRead = false;
+
+        StringAdapter(String str) {
+            this.data = str.getBytes(StandardCharsets.UTF_8);
+        }
+
+        @Override
+        public DataSlice next() {
+            if (!hasRead) {
+                hasRead = true;
+                return new DataSlice(data);
+            }
+            return null;
+        }
+
+        @Override
+        public void close() {
+            hasRead = true;
+        }
+    }
+
+    private Model parse(String traceOutput) {
+        ImportTask importTask = new ImportTask(new PrintlnImportFeedback());
+        return importTask.importTrace(new StringAdapter(traceOutput));
+    }
+
+    protected final PidTidPair runSingleAppTest(AtraceDeviceTestList test) {
+        return runAppTest(test.toString());
+    }
+
+    protected final TraceResult traceSingleTest(AtraceDeviceTestList test, boolean withAppTracing,
+            String... categories) {
+        requireApk();
+        shell(withAppTracing ? START_TRACE_CMD : START_TRACE_NO_APP_CMD, categories);
+        PidTidPair pidTid = runSingleAppTest(test);
+        String traceOutput = shell("atrace --async_stop", categories);
+        assertNotNull("unable to capture atrace output", traceOutput);
+        return new TraceResult(pidTid, parse(traceOutput));
+    }
+
+    protected final TraceResult traceSingleTest(AtraceDeviceTestList test, String... categories) {
+        return traceSingleTest(test, true, categories);
+    }
+}
diff --git a/hostsidetests/atrace/src/android/atrace/cts/PidTidPair.java b/hostsidetests/atrace/src/android/atrace/cts/PidTidPair.java
new file mode 100644
index 0000000..91c944c
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/PidTidPair.java
@@ -0,0 +1,32 @@
+/*
+ * 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.atrace.cts;
+
+import com.android.tradefed.result.TestRunResult;
+
+import java.util.Map;
+
+public class PidTidPair {
+    public final int pid;
+    public final int tid;
+
+    PidTidPair(TestRunResult testRunResult) {
+        Map<String, String> metrics = testRunResult.getRunMetrics();
+        pid = Integer.parseInt(metrics.get("AtraceDeviceTests_pid"));
+        tid = Integer.parseInt(metrics.get("AtraceDeviceTests_tid"));
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/atrace/src/android/atrace/cts/TraceResult.java b/hostsidetests/atrace/src/android/atrace/cts/TraceResult.java
new file mode 100644
index 0000000..20d624a
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/TraceResult.java
@@ -0,0 +1,33 @@
+/*
+ * 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.atrace.cts;
+
+import trebuchet.model.Model;
+
+public class TraceResult {
+    private final PidTidPair mPidTidPair;
+    private final Model mModel;
+
+    public TraceResult(PidTidPair pidTidPair, Model model) {
+        mModel = model;
+        mPidTidPair = pidTidPair;
+    }
+
+    public int getTid() { return mPidTidPair.tid; }
+    public int getPid() { return mPidTidPair.pid; }
+    public Model getModel() { return mModel; }
+}
diff --git a/hostsidetests/atrace/src/android/atrace/cts/UncheckedThrow.java b/hostsidetests/atrace/src/android/atrace/cts/UncheckedThrow.java
new file mode 100644
index 0000000..a1c21c8
--- /dev/null
+++ b/hostsidetests/atrace/src/android/atrace/cts/UncheckedThrow.java
@@ -0,0 +1,48 @@
+/*
+ * 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.atrace.cts;
+
+public class UncheckedThrow {
+    /**
+     * Throw any kind of exception without needing it to be checked
+     * @param e any instance of a Exception
+     */
+    public static void throwAnyException(Exception e) {
+        /**
+         *  Abuse type erasure by making the compiler think we are throwing RuntimeException,
+         *  which is unchecked, but then inserting any exception in there.
+         */
+        UncheckedThrow.<RuntimeException>throwAnyImpl(e);
+    }
+
+    /**
+     * Throw any kind of throwable without needing it to be checked
+     * @param e any instance of a Throwable
+     */
+    public static void throwAnyException(Throwable e) {
+        /**
+         *  Abuse type erasure by making the compiler think we are throwing RuntimeException,
+         *  which is unchecked, but then inserting any exception in there.
+         */
+        UncheckedThrow.<RuntimeException>throwAnyImpl(e);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static<T extends Throwable> void throwAnyImpl(Throwable e) throws T {
+        throw (T) e;
+    }
+}
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.mk b/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.mk
index aa9a22c..22cdb87 100644
--- a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.mk
+++ b/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.mk b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.mk
index c218a28..4ffff06 100644
--- a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.mk
+++ b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java b/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
index 643b594..efe52ae 100644
--- a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
+++ b/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
 import android.content.Context;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
@@ -41,6 +42,7 @@
  *
  */
 @RunWith(AndroidJUnit4.class)
+@AppModeFull
 public class AllowBackupTest {
     public static final String TAG = "AllowBackupCTSApp";
     private static final int FILE_SIZE_BYTES = 1024 * 1024;
diff --git a/hostsidetests/backup/Android.mk b/hostsidetests/backup/Android.mk
index fd5b3c6..bbe7d47 100644
--- a/hostsidetests/backup/Android.mk
+++ b/hostsidetests/backup/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_MODULE := CtsBackupHostTestCases
 
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util truth-prebuilt
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 868e20f..1b38d76 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -16,6 +16,9 @@
 <configuration description="Config for CTS Backup host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="backup" />
+    <!-- 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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsFullbackupApp.apk" />
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.mk b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.mk
index 6155ffb..a4c449f 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.mk
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.mk b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.mk
index 62b9860..a27c75b 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.mk
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.mk b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.mk
index 8257ea7..ad892e8 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.mk
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/FullBackupOnly/src/FullBackupOnlyTest.java b/hostsidetests/backup/FullBackupOnly/src/FullBackupOnlyTest.java
index 17a7e2c..df33281 100644
--- a/hostsidetests/backup/FullBackupOnly/src/FullBackupOnlyTest.java
+++ b/hostsidetests/backup/FullBackupOnly/src/FullBackupOnlyTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
 import android.content.Context;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
@@ -41,6 +42,7 @@
  * test.
  */
 @RunWith(AndroidJUnit4.class)
+@AppModeFull
 public class FullBackupOnlyTest {
     private static final String TAG = "FullBackupOnlyTest";
 
diff --git a/hostsidetests/backup/KeyValueApp/Android.mk b/hostsidetests/backup/KeyValueApp/Android.mk
index d03f85f..da13f11 100644
--- a/hostsidetests/backup/KeyValueApp/Android.mk
+++ b/hostsidetests/backup/KeyValueApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java
index 807f5ae..e77b815 100644
--- a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java
+++ b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupRestoreTest.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.platform.test.annotations.AppModeFull;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
@@ -55,6 +56,7 @@
  *
  */
 @RunWith(AndroidJUnit4.class)
+@AppModeFull
 public class KeyValueBackupRestoreTest {
     private static final String TAG = "KeyValueBackupRestore";
 
diff --git a/hostsidetests/backup/OtherSoundsSettingsApp/Android.mk b/hostsidetests/backup/OtherSoundsSettingsApp/Android.mk
new file mode 100644
index 0000000..a74bb1d
--- /dev/null
+++ b/hostsidetests/backup/OtherSoundsSettingsApp/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    platform-test-annotations \
+    compatibility-device-util
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsBackupOtherSoundsSettingsApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+
diff --git a/hostsidetests/backup/OtherSoundsSettingsApp/AndroidManifest.xml b/hostsidetests/backup/OtherSoundsSettingsApp/AndroidManifest.xml
new file mode 100644
index 0000000..d7f54cc8
--- /dev/null
+++ b/hostsidetests/backup/OtherSoundsSettingsApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.othersoundssettingsapp">
+
+    <application android:label="OtherSoundsSettingsApp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.othersoundssettingsapp" />
+</manifest>
diff --git a/hostsidetests/backup/OtherSoundsSettingsApp/src/android/cts/backup/othersoundssettingsapp/OtherSoundsSettingsTest.java b/hostsidetests/backup/OtherSoundsSettingsApp/src/android/cts/backup/othersoundssettingsapp/OtherSoundsSettingsTest.java
new file mode 100644
index 0000000..27d3f7f
--- /dev/null
+++ b/hostsidetests/backup/OtherSoundsSettingsApp/src/android/cts/backup/othersoundssettingsapp/OtherSoundsSettingsTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.cts.backup.othersoundssettingsapp;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.content.ContentResolver;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.BackupUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Device side routines to be invoked by the host side OtherSoundsSettingsHostSideTest. 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 OtherSoundsSettingsTest {
+    /** The name of the package for backup */
+    private static final String SETTINGS_PACKAGE_NAME = "com.android.providers.settings";
+
+    private ContentResolver mContentResolver;
+    private BackupUtils mBackupUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+        mBackupUtils =
+                new BackupUtils() {
+                    @Override
+                    protected InputStream executeShellCommand(String command) throws IOException {
+                        ParcelFileDescriptor pfd =
+                                getInstrumentation().getUiAutomation().executeShellCommand(command);
+                        return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                    }
+                };
+    }
+
+    /**
+     * Test backup and restore of Dial pad tones.
+     *
+     * Test logic:
+     * 1. Check Dial pad tones exists.
+     * 2. Backup Settings.
+     * 3. Toggle Dial pad tones.
+     * 4. Restore Settings.
+     * 5. Check restored Dial pad tones is the same with backup value.
+     */
+    @Test
+    public void testOtherSoundsSettings_dialPadTones() throws Exception {
+        int originalValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.DTMF_TONE_WHEN_DIALING, -1);
+        assertTrue("Dial pad tones does not exist.", originalValue != -1);
+
+        mBackupUtils.backupNowAndAssertSuccess(SETTINGS_PACKAGE_NAME);
+
+        boolean ret =
+                Settings.System.putInt(
+                        mContentResolver, Settings.System.DTMF_TONE_WHEN_DIALING,
+                        1 - originalValue);
+        assertTrue("Toggle Dial pad tones fail.", ret);
+
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, SETTINGS_PACKAGE_NAME);
+
+        int restoreValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.DTMF_TONE_WHEN_DIALING, -1);
+        assertTrue("Dial pad tones restore fail.", originalValue == restoreValue);
+    }
+
+    /**
+     * Test backup and restore of Touch sounds.
+     *
+     * Test logic:
+     * 1. Check Touch sounds exists.
+     * 2. Backup Settings.
+     * 3. Toggle Touch sounds.
+     * 4. Restore Settings.
+     * 5. Check restored Touch sounds is the same with backup value.
+     */
+    @Test
+    public void testOtherSoundsSettings_touchSounds() throws Exception {
+        int originalValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.SOUND_EFFECTS_ENABLED, -1);
+        assertTrue("Touch sounds does not exist.", originalValue != -1);
+
+        mBackupUtils.backupNowAndAssertSuccess(SETTINGS_PACKAGE_NAME);
+
+        boolean ret =
+                Settings.System.putInt(
+                        mContentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 1 - originalValue);
+        assertTrue("Toggle Touch sounds fail.", ret);
+
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, SETTINGS_PACKAGE_NAME);
+
+        int restoreValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.SOUND_EFFECTS_ENABLED, -1);
+        assertTrue("Touch sounds restore fail.", originalValue == restoreValue);
+    }
+
+    /**
+     * Test backup and restore of Touch vibration.
+     *
+     * Test logic:
+     * 1. Check Touch vibration exists.
+     * 2. Backup Settings.
+     * 3. Toggle Touch vibration.
+     * 4. Restore Settings.
+     * 5. Check restored Touch vibration is the same with backup value.
+     */
+    @Test
+    public void testOtherSoundsSettings_touchVibration() throws Exception {
+        int originalValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, -1);
+        assertTrue("Touch vibration does not exist.", originalValue != -1);
+
+        mBackupUtils.backupNowAndAssertSuccess(SETTINGS_PACKAGE_NAME);
+
+        boolean ret =
+                Settings.System.putInt(
+                        mContentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED,
+                        1 - originalValue);
+        assertTrue("Toggle Touch vibration fail.", ret);
+
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, SETTINGS_PACKAGE_NAME);
+
+        int restoreValue =
+                Settings.System.getInt(
+                        mContentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, -1);
+        assertTrue("Touch vibration restore fail.", originalValue == restoreValue);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/backup/ProfileFullBackupApp/Android.mk b/hostsidetests/backup/ProfileFullBackupApp/Android.mk
new file mode 100644
index 0000000..1451e97
--- /dev/null
+++ b/hostsidetests/backup/ProfileFullBackupApp/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    platform-test-annotations \
+    truth-prebuilt
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsProfileFullBackupApp
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/ProfileFullBackupApp/AndroidManifest.xml b/hostsidetests/backup/ProfileFullBackupApp/AndroidManifest.xml
new file mode 100644
index 0000000..36111b0
--- /dev/null
+++ b/hostsidetests/backup/ProfileFullBackupApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.profilefullbackupapp">
+
+    <application android:label="ProfileFullBackupApp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.profilefullbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/ProfileFullBackupApp/src/android/cts/backup/profilefullbackupapp/ProfileFullBackupRestoreTest.java b/hostsidetests/backup/ProfileFullBackupApp/src/android/cts/backup/profilefullbackupapp/ProfileFullBackupRestoreTest.java
new file mode 100644
index 0000000..198a180
--- /dev/null
+++ b/hostsidetests/backup/ProfileFullBackupApp/src/android/cts/backup/profilefullbackupapp/ProfileFullBackupRestoreTest.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.cts.backup.profilefullbackupapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/** Device side test invoked by ProfileFullBackupRestoreHostSideTest. */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class ProfileFullBackupRestoreTest {
+    private static final String FILE_ONE_CONTENT = "file1text";
+    private static final String FILE_TWO_CONTENT = "file2text";
+
+    private File mFile1;
+    private File mFile2;
+
+    @Before
+    public void setUp() throws Exception {
+        File dir = getTargetContext().getFilesDir();
+        dir.mkdirs();
+
+        mFile1 = new File(dir, "file1");
+        mFile2 = new File(dir, "file2");
+    }
+
+    @Test
+    public void assertFilesDontExist() throws Exception {
+        assertThat(mFile1.exists()).isFalse();
+        assertThat(mFile2.exists()).isFalse();
+    }
+
+    @Test
+    public void writeFilesAndAssertSuccess() throws Exception {
+        Files.write(mFile1.toPath(), FILE_ONE_CONTENT.getBytes());
+        assertFileContains(mFile1, FILE_ONE_CONTENT);
+
+        Files.write(mFile2.toPath(), FILE_TWO_CONTENT.getBytes());
+        assertFileContains(mFile2, FILE_TWO_CONTENT);
+    }
+
+    @Test
+    public void clearFilesAndAssertSuccess() throws Exception {
+        mFile1.delete();
+        mFile2.delete();
+        assertFilesDontExist();
+    }
+
+    @Test
+    public void assertFilesRestored() throws Exception {
+        assertThat(mFile1.exists()).isTrue();
+        assertFileContains(mFile1, FILE_ONE_CONTENT);
+
+        assertThat(mFile2.exists()).isTrue();
+        assertFileContains(mFile2, FILE_TWO_CONTENT);
+    }
+
+    private void assertFileContains(File file, String content) throws IOException {
+        assertThat(Files.readAllBytes(file.toPath())).isEqualTo(content.getBytes());
+    }
+}
diff --git a/hostsidetests/backup/ProfileKeyValueApp/Android.mk b/hostsidetests/backup/ProfileKeyValueApp/Android.mk
new file mode 100644
index 0000000..89213f2
--- /dev/null
+++ b/hostsidetests/backup/ProfileKeyValueApp/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    platform-test-annotations \
+    truth-prebuilt
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsProfileKeyValueApp
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/ProfileKeyValueApp/AndroidManifest.xml b/hostsidetests/backup/ProfileKeyValueApp/AndroidManifest.xml
new file mode 100644
index 0000000..fbc8602
--- /dev/null
+++ b/hostsidetests/backup/ProfileKeyValueApp/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.cts.backup.profilekeyvalueapp">
+
+    <application
+        android:backupAgent=".ProfileKeyValueBackupAgent"
+        android:killAfterRestore="false" >
+
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.profilekeyvalueapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupAgent.java b/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupAgent.java
new file mode 100644
index 0000000..d4cd3a7
--- /dev/null
+++ b/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupAgent.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.profilekeyvalueapp;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+
+/**
+ * Backup agent that uses {@link SharedPreferencesBackupHelper} to backup/restore shared
+ * preferences.
+ */
+public class ProfileKeyValueBackupAgent extends BackupAgentHelper {
+    private static final String PREFS_PREFIX = "USER-KV";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        SharedPreferencesBackupHelper backupHelper =
+                new SharedPreferencesBackupHelper(
+                        this, ProfileKeyValueBackupRestoreTest.TEST_PREFS_NAME);
+        addHelper(PREFS_PREFIX, backupHelper);
+    }
+}
diff --git a/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupRestoreTest.java b/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupRestoreTest.java
new file mode 100644
index 0000000..5db78aa
--- /dev/null
+++ b/hostsidetests/backup/ProfileKeyValueApp/src/android.cts.backup.profilekeyvalueapp/ProfileKeyValueBackupRestoreTest.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.cts.backup.profilekeyvalueapp;
+
+import static android.support.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 android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Device side test invoked by profile host side tests. */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class ProfileKeyValueBackupRestoreTest {
+    static final String TEST_PREFS_NAME = "user-test-prefs";
+    private static final String PREFS_USER_KEY = "userId";
+    private static final int PREFS_USER_DEFAULT = -1000;
+
+    private Context mContext;
+    private int mUserId;
+    private SharedPreferences mPreferences;
+
+    @Before
+    public void setUp() {
+        mContext = getTargetContext();
+        mUserId = mContext.getUserId();
+        mPreferences = mContext.getSharedPreferences(TEST_PREFS_NAME, Context.MODE_PRIVATE);
+    }
+
+    @Test
+    public void assertSharedPrefsIsEmpty() {
+        int userIdPref = mPreferences.getInt(PREFS_USER_KEY, PREFS_USER_DEFAULT);
+        assertThat(userIdPref).isEqualTo(PREFS_USER_DEFAULT);
+    }
+
+    @Test
+    public void writeSharedPrefsAndAssertSuccess() {
+        SharedPreferences.Editor editor = mPreferences.edit();
+        editor.putInt(PREFS_USER_KEY, mUserId);
+        assertThat(editor.commit()).isTrue();
+    }
+
+    @Test
+    public void assertSharedPrefsRestored() {
+        int userIdPref = mPreferences.getInt(PREFS_USER_KEY, PREFS_USER_DEFAULT);
+        assertThat(userIdPref).isEqualTo(mUserId);
+    }
+
+    @Test
+    public void callDataChanged() {
+        BackupManager backupManager = new BackupManager(mContext);
+        backupManager.dataChanged();
+    }
+}
diff --git a/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.mk b/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.mk
index 0a0efba..ad32558 100644
--- a/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.mk
+++ b/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.mk b/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.mk
index 94d9376..2958849 100644
--- a/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.mk
+++ b/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.mk b/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.mk
index 81652a7..431e53b 100644
--- a/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.mk
+++ b/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
 
diff --git a/hostsidetests/backup/RestoreAnyVersion/src/RestoreAnyVersionTest.java b/hostsidetests/backup/RestoreAnyVersion/src/RestoreAnyVersionTest.java
index 1dbdb05..69f376b 100644
--- a/hostsidetests/backup/RestoreAnyVersion/src/RestoreAnyVersionTest.java
+++ b/hostsidetests/backup/RestoreAnyVersion/src/RestoreAnyVersionTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -45,6 +46,7 @@
  * test.
  */
 @RunWith(AndroidJUnit4.class)
+@AppModeFull
 public class RestoreAnyVersionTest {
     private static final String TAG = "BackupTestRestoreAnyVer";
 
diff --git a/hostsidetests/backup/SharedPreferencesRestoreApp/Android.mk b/hostsidetests/backup/SharedPreferencesRestoreApp/Android.mk
index 4e4f0be..6d25d36 100644
--- a/hostsidetests/backup/SharedPreferencesRestoreApp/Android.mk
+++ b/hostsidetests/backup/SharedPreferencesRestoreApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/backup/SuccessNotificationApp/Android.mk b/hostsidetests/backup/SuccessNotificationApp/Android.mk
index 8531225..70e74be 100644
--- a/hostsidetests/backup/SuccessNotificationApp/Android.mk
+++ b/hostsidetests/backup/SuccessNotificationApp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
index 3fb8ef2..5bf05a2 100644
--- a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
@@ -20,12 +20,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@AppModeFull
 public class SuccessNotificationTest {
     protected static final String PREFS_FILE = "android.cts.backup.successnotificationapp.PREFS";
     private static final String KEY_VALUE_RESTORE_APP_PACKAGE =
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/Android.mk b/hostsidetests/backup/SyncAdapterSettingsApp/Android.mk
new file mode 100644
index 0000000..c9ea6bf
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/Android.mk
@@ -0,0 +1,42 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        android-support-test \
+        platform-test-annotations \
+        compatibility-device-util
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsBackupSyncAdapterSettingsApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
new file mode 100644
index 0000000..3cda4e6
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.cts.backup.syncadaptersettingsapp">
+
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+
+    <application android:label="BackupSyncAdapterSettings">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".SyncAdapterSettingsAuthenticator">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator"/>
+            </intent-filter>
+            <meta-data
+                android:name="android.accounts.AccountAuthenticator"
+                android:resource="@xml/authenticator"/>
+        </service>
+
+        <service android:name=".SyncAdapterSettingsService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                       android:resource="@xml/syncadapter"/>
+        </service>
+
+        <provider android:name=".SyncAdapterSettingsProvider"
+                  android:authorities="android.cts.backup.syncadaptersettingsapp.provider"/>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.cts.backup.syncadaptersettingsapp"/>
+</manifest>
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/authenticator.xml b/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/authenticator.xml
new file mode 100644
index 0000000..a0ec2f2
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/authenticator.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:accountType="android.cts.backup.syncadaptersettingsapp"
+                       android:label="BackupSyncAdapterSettings"/>
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/syncadapter.xml b/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/syncadapter.xml
new file mode 100644
index 0000000..bbe551c
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/res/xml/syncadapter.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+              android:contentAuthority="android.cts.backup.syncadaptersettingsapp.provider"
+              android:accountType="android.cts.backup.syncadaptersettingsapp"
+/>
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAdapter.java b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAdapter.java
new file mode 100644
index 0000000..9bb0dca
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAdapter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.cts.backup.syncadaptersettingsapp;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+/**
+ * Sync adapter for the sync adapter settings test.
+ */
+public class SyncAdapterSettingsAdapter extends AbstractThreadedSyncAdapter {
+
+    public SyncAdapterSettingsAdapter(Context context) {
+        super(context, false);
+    }
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+    }
+}
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAuthenticator.java b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAuthenticator.java
new file mode 100644
index 0000000..2eb4209
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsAuthenticator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cts.backup.syncadaptersettingsapp;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * Authenticator for the sync adapter settings test.
+ */
+public class SyncAdapterSettingsAuthenticator extends Service {
+
+    private Authenticator mAuthenticator;
+
+    @Override
+    public void onCreate() {
+        mAuthenticator = new Authenticator(this);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mAuthenticator.getIBinder();
+    }
+
+    public static class Authenticator extends AbstractAccountAuthenticator {
+
+        public Authenticator(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] requiredFeatures, Bundle options)
+                throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+                Bundle options) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getAuthTokenLabel(String authTokenType) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+                String[] features) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
+
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsProvider.java b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsProvider.java
new file mode 100644
index 0000000..1f6839a
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cts.backup.syncadaptersettingsapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Provider for the sync adapter settings test.
+ */
+public class SyncAdapterSettingsProvider extends ContentProvider {
+    public static final String AUTHORITY = "android.cts.backup.syncadaptersettingsapp.provider";
+
+    @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) {
+        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/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsService.java b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsService.java
new file mode 100644
index 0000000..12ccb8c
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsService.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cts.backup.syncadaptersettingsapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service for the sync adapter settings test.
+ */
+public class SyncAdapterSettingsService extends Service {
+
+    private static SyncAdapterSettingsAdapter sAdapter;
+    private static final Object LOCK = new Object();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        synchronized (LOCK) {
+            if (sAdapter == null) {
+                sAdapter = new SyncAdapterSettingsAdapter(getApplicationContext());
+            }
+        }
+        return sAdapter.getSyncAdapterBinder();
+    }
+}
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsTest.java b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsTest.java
new file mode 100644
index 0000000..cb4482a
--- /dev/null
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/src/android/cts/backup/syncadaptersettingsapp/SyncAdapterSettingsTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.cts.backup.syncadaptersettingsapp;
+
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.BackupUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Device side routines to be invoked by the host side SyncAdapterSettingsHostSideTest. 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 SyncAdapterSettingsTest {
+
+    /** The name of the package for backup */
+    private static final String ANDROID_PACKAGE = "android";
+
+    private static final String ACCOUNT_NAME = "SyncAdapterSettings";
+    private static final String ACCOUNT_TYPE = "android.cts.backup.syncadaptersettingsapp";
+
+    private Context mContext;
+    private Account mAccount;
+    private BackupUtils mBackupUtils =
+            new BackupUtils() {
+                @Override
+                protected InputStream executeShellCommand(String command) {
+                    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+                    ParcelFileDescriptor pfd =
+                            instrumentation.getUiAutomation().executeShellCommand(command);
+                    return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                }
+            };
+
+    @Before
+    public void setUp() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = instrumentation.getTargetContext();
+        mAccount = getAccount();
+    }
+
+    /**
+     * Test backup and restore of MasterSyncAutomatically=true.
+     *
+     * Test logic:
+     * 1. Enable MasterSyncAutomatically.
+     * 2. Backup android package, disable MasterSyncAutomatically and restore android package.
+     * 3. Check restored MasterSyncAutomatically=true is the same with backup value.
+     */
+    @Test
+    public void testMasterSyncAutomatically_whenOn_isRestored() throws Exception {
+        ContentResolver.setMasterSyncAutomatically(true);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        ContentResolver.setMasterSyncAutomatically(false);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertTrue(ContentResolver.getMasterSyncAutomatically());
+    }
+
+    /**
+     * Test backup and restore of MasterSyncAutomatically=false.
+     *
+     * Test logic:
+     * 1. Disable MasterSyncAutomatically.
+     * 2. Backup android package, enable MasterSyncAutomatically and restore android package.
+     * 3. Check restored MasterSyncAutomatically=false is the same with backup value.
+     */
+    @Test
+    public void testMasterSyncAutomatically_whenOff_isRestored() throws Exception {
+        ContentResolver.setMasterSyncAutomatically(false);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        ContentResolver.setMasterSyncAutomatically(true);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertFalse(ContentResolver.getMasterSyncAutomatically());
+    }
+
+    /**
+     * Test that if syncEnabled=true and isSyncable=1 in restore data, syncEnabled will be true
+     * and isSyncable will be left as the current value in the device.
+     *
+     * Test logic:
+     * 1. Add an account if account does not exist and set syncEnabled=true, isSyncable=1.
+     * 2. Backup android package, set syncEnabled=false and set isSyncable=0. Then restore
+     * android package.
+     * 3. Check restored syncEnabled=true and isSyncable=0. Then remove account.
+     *
+     * @see AccountSyncSettingsBackupHelper#restoreExistingAccountSyncSettingsFromJSON(JSONObject)
+     */
+    @Test
+    public void testIsSyncableChanged_ifTurnOnSyncEnabled() throws Exception {
+        addAccount(mContext);
+        initSettings(/* masterSyncAutomatically= */true, /* syncAutomatically= */
+                true, /* isSyncable= */1);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        setSyncAutomaticallyAndIsSyncable(false, 0);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertSettings(/* syncAutomatically= */true, /* isSyncable= */0);
+        removeAccount(mContext);
+    }
+
+    /**
+     * Test that if syncEnabled=false and isSyncable=0 in restore data, syncEnabled will be false
+     * and isSyncable will be set to 0.
+     *
+     * Test logic:
+     * 1. Add an account if account does not exist and set syncEnabled=false, isSyncable=0.
+     * 2. Backup android package, set syncEnabled=true and set isSyncable=1. Then restore android
+     * package.
+     * 3. Check restored syncEnabled=false and isSyncable=0. Then remove account.
+     *
+     * @see AccountSyncSettingsBackupHelper#restoreExistingAccountSyncSettingsFromJSON(JSONObject)
+     */
+    @Test
+    public void testIsSyncableIsZero_ifTurnOffSyncEnabled() throws Exception {
+        addAccount(mContext);
+        initSettings(/* masterSyncAutomatically= */true, /* syncAutomatically= */
+                false, /* isSyncable= */0);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        setSyncAutomaticallyAndIsSyncable(true, 1);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertSettings(/* syncAutomatically= */false, /* isSyncable= */0);
+        removeAccount(mContext);
+    }
+
+    /**
+     * Test that if syncEnabled=false and isSyncable=1 in restore data, syncEnabled will be false
+     * and isSyncable will be set to 1.
+     * According to
+     * {@link AccountSyncSettingsBackupHelper#restoreExistingAccountSyncSettingsFromJSON(JSONObject)}
+     * isSyncable will be set to 2, but function
+     * {@link ContentService#setIsSyncable(Account, String, int)} would call
+     * {@link ContentService#normalizeSyncable(int)} and set isSyncable to 1.
+     *
+     * Test logic:
+     * 1. Add an account if account does not exist and set syncEnabled=false, isSyncable=1.
+     * 2. Backup android package, set syncEnabled=true and set isSyncable=0. Then restore android
+     * package.
+     * 3. Check restored syncEnabled=false and isSyncable=1. Then remove account.
+     */
+    @Test
+    public void testIsSyncableIsOne_ifTurnOffSyncEnabled() throws Exception {
+        addAccount(mContext);
+        initSettings(/* masterSyncAutomatically= */true, /* syncAutomatically= */
+                false, /* isSyncable= */1);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        setSyncAutomaticallyAndIsSyncable(true, 0);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertSettings(/* syncAutomatically= */false, /* isSyncable= */1);
+        removeAccount(mContext);
+    }
+
+    private void initSettings(boolean masterSyncAutomatically, boolean syncAutomatically,
+            int isSyncable) throws Exception {
+        ContentResolver.setMasterSyncAutomatically(masterSyncAutomatically);
+        setSyncAutomaticallyAndIsSyncable(syncAutomatically, isSyncable);
+    }
+
+    private void setSyncAutomaticallyAndIsSyncable(boolean syncAutomatically, int isSyncable)
+            throws Exception {
+        ContentResolver.setSyncAutomatically(mAccount, SyncAdapterSettingsProvider.AUTHORITY,
+                syncAutomatically);
+
+        ContentResolver.setIsSyncable(mAccount, SyncAdapterSettingsProvider.AUTHORITY, isSyncable);
+    }
+
+    private void assertSettings(boolean syncAutomatically, int isSyncable) throws Exception {
+        assertEquals(syncAutomatically, ContentResolver.getSyncAutomatically(mAccount,
+                SyncAdapterSettingsProvider.AUTHORITY));
+        assertEquals(isSyncable,
+                ContentResolver.getIsSyncable(mAccount, SyncAdapterSettingsProvider.AUTHORITY));
+    }
+
+    /**
+     * Get the account.
+     */
+    private Account getAccount() {
+        return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    }
+
+    /**
+     * Add an account, if it doesn't exist yet.
+     */
+    private void addAccount(Context context) {
+        final String password = "password";
+
+        final AccountManager am = context.getSystemService(AccountManager.class);
+
+        if (!Arrays.asList(am.getAccountsByType(mAccount.type)).contains(mAccount)) {
+            am.addAccountExplicitly(mAccount, password, new Bundle());
+        }
+    }
+
+    /**
+     * Remove the account.
+     */
+    private void removeAccount(Context context) {
+        final AccountManager am = context.getSystemService(AccountManager.class);
+        final Account[] accounts = am.getAccountsByType(ACCOUNT_TYPE);
+
+        for (int i = 0, size = accounts.length; i < size; i++) {
+            Account account = accounts[i];
+            am.removeAccountExplicitly(account);
+        }
+    }
+}
diff --git a/hostsidetests/backup/fullbackupapp/Android.mk b/hostsidetests/backup/fullbackupapp/Android.mk
index 40a90d6..6edd939 100644
--- a/hostsidetests/backup/fullbackupapp/Android.mk
+++ b/hostsidetests/backup/fullbackupapp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
index ee49b87..61f868b 100644
--- a/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
+++ b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 
 import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
@@ -40,6 +41,7 @@
  * 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 FullbackupTest {
     public static final String TAG = "FullbackupCTSApp";
     private static final int FILE_SIZE_BYTES = 1024 * 1024;
diff --git a/hostsidetests/backup/includeexcludeapp/Android.mk b/hostsidetests/backup/includeexcludeapp/Android.mk
index 2043a14..0232d34 100644
--- a/hostsidetests/backup/includeexcludeapp/Android.mk
+++ b/hostsidetests/backup/includeexcludeapp/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/hostsidetests/backup/includeexcludeapp/res/xml/my_backup_rules.xml b/hostsidetests/backup/includeexcludeapp/res/xml/my_backup_rules.xml
index 0cdbea9..acb1d89 100644
--- a/hostsidetests/backup/includeexcludeapp/res/xml/my_backup_rules.xml
+++ b/hostsidetests/backup/includeexcludeapp/res/xml/my_backup_rules.xml
@@ -1,14 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <full-backup-content>
-    <include domain="file" path="."/>
+    <include domain="file" path="file_to_include"/>
+    <exclude domain="file" path="file_to_exclude"/>
+    <include domain="file" path="include_folder"/>
+    <exclude domain="file" path="include_folder/file_to_exclude"/>
     <exclude domain="file" path="exclude_folder"/>
-    <include domain="sharedpref" path="."/>
-    <exclude domain="sharedpref" path="exclude_shared_pref.xml"/>
+    <include domain="file" path="exclude_folder/file_to_include"/>
+
+
+    <include domain="sharedpref" path="include_shared_pref1.xml"/>
+    <include domain="sharedpref" path="include_shared_pref2.xml"/>
+    <exclude domain="sharedpref" path="exclude_shared_pref1.xml"/>
+    <exclude domain="sharedpref" path="exclude_shared_pref2.xml"/>
+
     <include domain="database" path="db_name/file_to_include"/>
     <exclude domain="database" path="db_name/file_to_exclude"/>
-    <include domain="external" path="."/>
+    <include domain="database" path="db_name/include_folder"/>
+    <exclude domain="database" path="db_name/include_folder/file_to_exclude"/>
+    <exclude domain="database" path="db_name/exclude_folder"/>
+    <include domain="database" path="db_name/exclude_folder/file_to_include"/>
+
+    <include domain="external" path="file_to_include"/>
+    <exclude domain="external" path="file_to_exclude"/>
+    <include domain="external" path="include_folder"/>
+    <exclude domain="external" path="include_folder/file_to_exclude"/>
     <exclude domain="external" path="exclude_folder"/>
+    <include domain="external" path="exclude_folder/file_to_include"/>
+
     <include domain="root" path="file_to_include"/>
     <exclude domain="root" path="file_to_exclude"/>
+    <include domain="root" path="include_folder"/>
+    <exclude domain="root" path="include_folder/file_to_exclude"/>
+    <exclude domain="root" path="exclude_folder"/>
+    <include domain="root" path="exclude_folder/file_to_include"/>
     <include domain="root" requireFlags="fakeClientSideEncryption" path="fake_encryption_file" />
 </full-backup-content>
\ No newline at end of file
diff --git a/hostsidetests/backup/includeexcludeapp/src/android/cts/backup/includeexcludeapp/IncludeExcludeTest.java b/hostsidetests/backup/includeexcludeapp/src/android/cts/backup/includeexcludeapp/IncludeExcludeTest.java
index 286d7a3..ee11415 100644
--- a/hostsidetests/backup/includeexcludeapp/src/android/cts/backup/includeexcludeapp/IncludeExcludeTest.java
+++ b/hostsidetests/backup/includeexcludeapp/src/android/cts/backup/includeexcludeapp/IncludeExcludeTest.java
@@ -25,6 +25,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.platform.test.annotations.AppModeFull;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -44,13 +45,18 @@
  * 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 IncludeExcludeTest {
     public static final String TAG = "IncludeExcludeCTSApp";
     private static final int FILE_SIZE_BYTES = 1024 * 1024;
     private static final String SHARED_PREF_KEY1 = "dummy_key1";
     private static final String SHARED_PREF_KEY2 = "dummy_key2";
+    private static final String SHARED_PREF_KEY3 = "dummy_key3";
+    private static final String SHARED_PREF_KEY4 = "dummy_key4";
     private static final int SHARED_PREF_VALUE1 = 1337;
     private static final int SHARED_PREF_VALUE2 = 1338;
+    private static final int SHARED_PREF_VALUE3 = 1339;
+    private static final int SHARED_PREF_VALUE4 = 1340;
 
     private Context mContext;
 
@@ -58,8 +64,10 @@
     private List<File> mExcludeFiles;
     private List<File> mRequireFakeClientSideEncryptionFiles;
 
-    private SharedPreferences mIncludeSharedPref;
-    private SharedPreferences mExcludeSharedPref;
+    private SharedPreferences mIncludeSharedPref1;
+    private SharedPreferences mIncludeSharedPref2;
+    private SharedPreferences mExcludeSharedPref1;
+    private SharedPreferences mExcludeSharedPref2;
 
     @Before
     public void setUp() {
@@ -77,32 +85,42 @@
 
         // Files in the normal files directory.
         File filesDir = mContext.getFilesDir();
-        File excludeFolder = new File(filesDir, "exclude_folder");
-        mIncludeFiles.add(new File(filesDir, "file_to_include"));
-        mExcludeFiles.add(new File(excludeFolder, "file_to_exclude"));
+        setupFileTree(filesDir);
 
         // Files in database directory.
         File databaseDir = mContext.getDatabasePath("db_name");
-        mIncludeFiles.add(new File(databaseDir, "file_to_include"));
-        mExcludeFiles.add(new File(databaseDir, "file_to_exclude"));
+        setupFileTree(databaseDir);
 
         // Files in external files directory.
         File externalDir = mContext.getExternalFilesDir(null);
-        File excludeExternalFolder = new File(externalDir, "exclude_folder");
-        mIncludeFiles.add(new File(externalDir, "file_to_include"));
-        mExcludeFiles.add(new File(excludeExternalFolder, "file_to_exclude"));
+        setupFileTree(externalDir);
 
         // Files in root directory
         File rootDir = mContext.getDataDir();
-        mIncludeFiles.add(new File(rootDir, "file_to_include"));
-        mExcludeFiles.add(new File(rootDir, "file_to_exclude"));
+        setupFileTree(rootDir);
         mRequireFakeClientSideEncryptionFiles.add(new File(rootDir, "fake_encryption_file"));
 
         // Set up SharedPreferences
-        mIncludeSharedPref =
-                mContext.getSharedPreferences("include_shared_pref", Context.MODE_PRIVATE);
-        mExcludeSharedPref =
-                mContext.getSharedPreferences("exclude_shared_pref", Context.MODE_PRIVATE);
+        mIncludeSharedPref1 =
+                mContext.getSharedPreferences("include_shared_pref1", Context.MODE_PRIVATE);
+        mIncludeSharedPref2 =
+                mContext.getSharedPreferences("include_shared_pref2", Context.MODE_PRIVATE);
+        mExcludeSharedPref1 =
+                mContext.getSharedPreferences("exclude_shared_pref1", Context.MODE_PRIVATE);
+        mExcludeSharedPref2 =
+                mContext.getSharedPreferences("exclude_shared_pref2", Context.MODE_PRIVATE);
+    }
+
+    private void setupFileTree(File directory) {
+        setupFilesInDirectory(directory);
+
+        setupFilesInDirectory(new File(directory, "include_folder"));
+        setupFilesInDirectory(new File(directory, "exclude_folder"));
+    }
+
+    private void setupFilesInDirectory(File directory) {
+        mIncludeFiles.add(new File(directory, "file_to_include"));
+        mExcludeFiles.add(new File(directory, "file_to_exclude"));
     }
 
     @Test
@@ -250,49 +268,75 @@
     }
 
     private void generateSharedPrefs() {
-        SharedPreferences.Editor editor = mIncludeSharedPref.edit();
+        SharedPreferences.Editor editor = mIncludeSharedPref1.edit();
         editor.putInt(SHARED_PREF_KEY1, SHARED_PREF_VALUE1);
         editor.commit();
+        editor = mIncludeSharedPref2.edit();
+        editor.putInt(SHARED_PREF_KEY2, SHARED_PREF_VALUE2);
+        editor.commit();
 
 
-        editor = mExcludeSharedPref.edit();
-        editor.putInt(SHARED_PREF_KEY2, SHARED_PREF_VALUE2);
+        editor = mExcludeSharedPref1.edit();
+        editor.putInt(SHARED_PREF_KEY3, SHARED_PREF_VALUE3);
+        editor.commit();
+        editor = mExcludeSharedPref2.edit();
+        editor.putInt(SHARED_PREF_KEY4, SHARED_PREF_VALUE4);
         editor.commit();
     }
 
     private void checkSharedPrefExist() {
-        int value = mIncludeSharedPref.getInt(SHARED_PREF_KEY1, 0);
+        int value = mIncludeSharedPref1.getInt(SHARED_PREF_KEY1, 0);
         assertEquals("Shared preference did not exist", SHARED_PREF_VALUE1, value);
-
-        value = mExcludeSharedPref.getInt(SHARED_PREF_KEY2, 0);
+        value = mIncludeSharedPref2.getInt(SHARED_PREF_KEY2, 0);
         assertEquals("Shared preference did not exist", SHARED_PREF_VALUE2, value);
+
+        value = mExcludeSharedPref1.getInt(SHARED_PREF_KEY3, 0);
+        assertEquals("Shared preference did not exist", SHARED_PREF_VALUE3, value);
+        value = mExcludeSharedPref2.getInt(SHARED_PREF_KEY4, 0);
+        assertEquals("Shared preference did not exist", SHARED_PREF_VALUE4, value);
     }
 
     private void deleteSharedPref() {
-        SharedPreferences.Editor editor = mIncludeSharedPref.edit();
+        SharedPreferences.Editor editor = mIncludeSharedPref1.edit();
+        editor.clear();
+        editor.commit();
+        editor = mIncludeSharedPref2.edit();
         editor.clear();
         editor.commit();
 
-        editor = mExcludeSharedPref.edit();
+        editor = mExcludeSharedPref1.edit();
+        editor.clear();
+        editor.commit();
+        editor = mExcludeSharedPref2.edit();
         editor.clear();
         editor.commit();
     }
 
     private void checkSharedPrefDontExist() {
-        int value = mIncludeSharedPref.getInt(SHARED_PREF_KEY1, 0);
+        int value = mIncludeSharedPref1.getInt(SHARED_PREF_KEY1, 0);
+        assertEquals("Shared preference did exist", 0, value);
+        value = mIncludeSharedPref2.getInt(SHARED_PREF_KEY2, 0);
         assertEquals("Shared preference did exist", 0, value);
 
-        value = mExcludeSharedPref.getInt(SHARED_PREF_KEY2, 0);
+        value = mExcludeSharedPref1.getInt(SHARED_PREF_KEY3, 0);
+        assertEquals("Shared preference did exist", 0, value);
+        value = mExcludeSharedPref2.getInt(SHARED_PREF_KEY4, 0);
         assertEquals("Shared preference did exist", 0, value);
     }
 
     private void checkIncludeSharedPrefExist() {
-        int value = mIncludeSharedPref.getInt(SHARED_PREF_KEY1, 0);
+        int value = mIncludeSharedPref1.getInt(SHARED_PREF_KEY1, 0);
         assertEquals("Shared preference did not exist", SHARED_PREF_VALUE1, value);
+
+        value = mIncludeSharedPref2.getInt(SHARED_PREF_KEY2, 0);
+        assertEquals("Shared preference did not exist", SHARED_PREF_VALUE2, value);
     }
 
     private void checkExcludeSharedPrefDoNotExist() {
-        int value = mExcludeSharedPref.getInt(SHARED_PREF_KEY2, 0);
+        int value = mExcludeSharedPref1.getInt(SHARED_PREF_KEY3, 0);
+        assertEquals("Shared preference did exist", 0, value);
+
+        value = mExcludeSharedPref2.getInt(SHARED_PREF_KEY4, 0);
         assertEquals("Shared preference did exist", 0, value);
     }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
index 73373d3..1a8bb22 100644
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertNull;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -48,6 +50,7 @@
  * android.cts.backup.backupnotallowedapp.AllowBackupTest.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class AllowBackupHostSideTest extends BaseBackupHostSideTest {
 
     private static final String ALLOWBACKUP_APP_NAME = "android.cts.backup.backupnotallowedapp";
@@ -87,10 +90,7 @@
         // Generate the files that are going to be backed up.
         checkAllowBackupDeviceTest("createFiles");
 
-        // Do a backup
-        String backupnowOutput = backupNow(ALLOWBACKUP_APP_NAME);
-
-        assertBackupIsNotAllowed(ALLOWBACKUP_APP_NAME, backupnowOutput);
+        getBackupUtils().backupNowAndAssertBackupNotAllowed(ALLOWBACKUP_APP_NAME);
 
         assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
 
@@ -112,9 +112,7 @@
         checkAllowBackupDeviceTest("createFiles");
 
         // Do a backup
-        String backupnowOutput = backupNow(ALLOWBACKUP_APP_NAME);
-
-        assertBackupIsSuccessful(ALLOWBACKUP_APP_NAME, backupnowOutput);
+        getBackupUtils().backupNowAndAssertSuccess(ALLOWBACKUP_APP_NAME);
 
         assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
 
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
index eae5383..11a1dbc 100644
--- a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
@@ -51,7 +51,7 @@
     private static final String FEATURE_BACKUP = "android.software.backup";
 
     private static final String LOCAL_TRANSPORT =
-            "android/com.android.internal.backup.LocalTransport";
+            "com.android.localtransport/.LocalTransport";
 
     private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
     private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
index 9c931da..6aa7a7b 100644
--- a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
@@ -19,7 +19,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -28,7 +32,10 @@
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
-import java.util.Scanner;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -36,6 +43,7 @@
  * Base class for CTS backup/restore hostside tests
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public abstract class BaseBackupHostSideTest extends BaseHostJUnit4Test {
     protected boolean mIsBackupSupported;
 
@@ -43,10 +51,17 @@
     private static final String FEATURE_BACKUP = "android.software.backup";
 
     protected static final String LOCAL_TRANSPORT =
-            "android/com.android.internal.backup.LocalTransport";
+            "com.android.localtransport/.LocalTransport";
+
+    private BackupUtils mBackupUtils = new BackupUtils() {
+        @Override
+        protected InputStream executeShellCommand(String command) throws IOException {
+            return executeDeviceShellCommand(getDevice(), command);
+        }
+    };
 
     @Before
-    public void setUp() throws DeviceNotAvailableException, Exception {
+    public void setUp() throws Exception {
         mIsBackupSupported = getDevice().hasFeature("feature:" + FEATURE_BACKUP);
         if (!mIsBackupSupported) {
             CLog.i("android.software.backup feature is not supported on this device");
@@ -55,7 +70,7 @@
 
         // Check that the backup wasn't disabled and the transport wasn't switched unexpectedly.
         assertTrue("Backup was unexpectedly disabled during the module test run",
-                isBackupEnabled());
+                getBackupUtils().isBackupEnabled());
         assertEquals("LocalTransport should be selected at this point", LOCAL_TRANSPORT,
                 getCurrentTransport());
     }
@@ -65,36 +80,8 @@
         // Not deleting to avoid breaking the tests calling super.tearDown()
     }
 
-    /**
-     * Execute shell command "bmgr backupnow <packageName>" and return output from this command.
-     */
-    protected String backupNow(String packageName) throws DeviceNotAvailableException {
-        return getDevice().executeShellCommand("bmgr backupnow " + packageName);
-    }
-
-    /**
-     * Execute shell command "bmgr backupnow <packageName>" and assert success.
-     */
-    protected void backupNowAndAssertSuccess(String packageName)
-            throws DeviceNotAvailableException {
-        String backupnowOutput = backupNow(packageName);
-
-        assertBackupIsSuccessful(packageName, backupnowOutput);
-    }
-
-    /**
-     * Execute shell command "bmgr restore <packageName>" and assert success.
-     */
-    protected void restoreAndAssertSuccess(String packageName) throws DeviceNotAvailableException {
-        String restoreOutput = restore(packageName);
-        assertRestoreIsSuccessful(restoreOutput);
-    }
-
-    /**
-     * Execute shell command "bmgr restore <packageName>" and return output from this command.
-     */
-    protected String restore(String packageName) throws DeviceNotAvailableException {
-        return getDevice().executeShellCommand("bmgr restore " + packageName);
+    protected BackupUtils getBackupUtils() {
+        return mBackupUtils;
     }
 
     /**
@@ -114,62 +101,6 @@
         assertTrue("Device test failed: " + testName, result);
     }
 
-    /**
-     * Parsing the output of "bmgr backupnow" command and checking that the package under test
-     * was backed up successfully.
-     *
-     * Expected format: "Package <packageName> with result: Success"
-     */
-    protected void assertBackupIsSuccessful(String packageName, String backupnowOutput) {
-        Scanner in = new Scanner(backupnowOutput);
-        boolean success = false;
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-
-            if (line.contains(packageName)) {
-                String result = line.split(":")[1].trim();
-                if ("Success".equals(result)) {
-                    success = true;
-                }
-            }
-        }
-        in.close();
-        assertTrue(success);
-    }
-
-    /**
-     * Parsing the output of "bmgr backupnow" command and checking that the package under test
-     * wasn't backed up because backup is not allowed
-     *
-     * Expected format: "Package <packageName> with result:  Backup is not allowed"
-     */
-    protected void assertBackupIsNotAllowed(String packageName, String backupnowOutput) {
-        Scanner in = new Scanner(backupnowOutput);
-        boolean found = false;
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-
-            if (line.contains(packageName)) {
-                String result = line.split(":")[1].trim();
-                if ("Backup is not allowed".equals(result)) {
-                    found = true;
-                }
-            }
-        }
-        in.close();
-        assertTrue("Didn't find \'Backup not allowed\' in the output", found);
-    }
-
-    /**
-     * Parsing the output of "bmgr restore" command and checking that the package under test
-     * was restored successfully.
-     *
-     * Expected format: "restoreFinished: 0"
-     */
-    protected void assertRestoreIsSuccessful(String restoreOutput) {
-        assertTrue("Restore not successful", restoreOutput.contains("restoreFinished: 0"));
-    }
-
     protected void startActivityInPackageAndWait(String packageName, String className)
             throws DeviceNotAvailableException {
         getDevice().executeShellCommand(String.format(
@@ -182,10 +113,11 @@
      * Clears backup data stored in Local Transport for a package.
      * NB: 'bmgr wipe' does not produce any useful output if the package or transport not found,
      * so we cannot really check the success of the operation
-      */
+     */
     protected void clearBackupDataInLocalTransport(String packageName)
             throws DeviceNotAvailableException {
-        getDevice().executeShellCommand(String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
+        getDevice().executeShellCommand(
+                String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
     }
 
     /**
@@ -195,19 +127,6 @@
         getDevice().executeShellCommand(String.format("pm clear %s", packageName));
     }
 
-    protected boolean isBackupEnabled() throws DeviceNotAvailableException {
-        boolean isEnabled;
-        String output = getDevice().executeShellCommand("bmgr enabled");
-        Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
-        Matcher matcher = pattern.matcher(output.trim());
-        if (matcher.find()) {
-            isEnabled = "enabled".equals(matcher.group(1));
-        } else {
-            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
-        }
-        return isEnabled;
-    }
-
     protected String getCurrentTransport() throws DeviceNotAvailableException {
         String output = getDevice().executeShellCommand("bmgr list transports");
         Pattern pattern = Pattern.compile("\\* (.*)");
@@ -231,4 +150,14 @@
     protected void disableFakeEncryptionOnTransport() throws Exception {
         setLocalTransportParameters("fake_encryption_flag=false");
     }
+
+    static InputStream executeDeviceShellCommand(
+            ITestDevice device, String command) throws IOException {
+        try {
+            String result = device.executeShellCommand(command);
+            return new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
+        } catch (DeviceNotAvailableException e) {
+            throw new IOException(e);
+        }
+    }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
new file mode 100644
index 0000000..93029c1
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/** Base class for CTS multi-user backup/restore host-side tests. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public abstract class BaseMultiUserBackupHostSideTest extends BaseBackupHostSideTest {
+    private static final String USER_SETUP_COMPLETE_SETTING = "user_setup_complete";
+    private static final long USER_STATE_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5);
+    private static final long TRANSPORT_INITIALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(2);
+
+    // Key-value test package.
+    static final String KEY_VALUE_APK = "CtsProfileKeyValueApp.apk";
+    static final String KEY_VALUE_TEST_PACKAGE = "android.cts.backup.profilekeyvalueapp";
+    static final String KEY_VALUE_DEVICE_TEST_NAME =
+            KEY_VALUE_TEST_PACKAGE + ".ProfileKeyValueBackupRestoreTest";
+
+    // Full backup test package.
+    static final String FULL_BACKUP_APK = "CtsProfileFullBackupApp.apk";
+    static final String FULL_BACKUP_TEST_PACKAGE = "android.cts.backup.profilefullbackupapp";
+    static final String FULL_BACKUP_DEVICE_TEST_NAME =
+            FULL_BACKUP_TEST_PACKAGE + ".ProfileFullBackupRestoreTest";
+
+    private final BackupUtils mBackupUtils = getBackupUtils();
+    private ITestDevice mDevice;
+
+    // Store initial device state as Optional as tearDown() will execute even if we have assumption
+    // failures in setUp().
+    private Optional<Integer> mInitialUser = Optional.empty();
+
+    /** Check device features and keep track of pre-test device state. */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        // Check that backup and multi-user features are both supported.
+        assumeTrue("Backup feature not supported", mIsBackupSupported);
+        assumeTrue("Multi-user feature not supported", mDevice.isMultiUserSupported());
+
+        // Keep track of initial user state to restore in tearDown.
+        int currentUserId = mDevice.getCurrentUser();
+        mInitialUser = Optional.of(currentUserId);
+
+        // Switch to primary user.
+        int primaryUserId = mDevice.getPrimaryUserId();
+        if (currentUserId != primaryUserId) {
+            mDevice.switchUser(primaryUserId);
+        }
+    }
+
+    /** Restore pre-test device state. */
+    @After
+    public void tearDown() throws Exception {
+        if (mInitialUser.isPresent()) {
+            mDevice.switchUser(mInitialUser.get());
+            mInitialUser = Optional.empty();
+        }
+    }
+
+    /**
+     * Attempts to create a profile tied to the parent user {@code parentId}. Returns the user id of
+     * the profile if successful, otherwise throws a {@link RuntimeException}.
+     */
+    int createProfileUser(int parentId, String profileName) throws IOException {
+        String output =
+                mBackupUtils.executeShellCommandAndReturnOutput(
+                        String.format("pm create-user --profileOf %d %s", parentId, profileName));
+        try {
+            // Expected output is "Success: created user id <id>"
+            String userId = output.substring(output.lastIndexOf(" ")).trim();
+            return Integer.parseInt(userId);
+        } catch (NumberFormatException e) {
+            CLog.d("Failed to parse user id when creating a profile user");
+            throw new RuntimeException();
+        }
+    }
+
+    /** Start the user and set necessary conditions for backup to be enabled in the user. */
+    void startUserAndInitializeForBackup(int userId)
+            throws IOException, DeviceNotAvailableException {
+        // Turn on multi-user feature for this user.
+        mBackupUtils.executeShellCommandSync(
+                String.format("bmgr --user %d activate %b", userId, true));
+
+        mDevice.startUser(userId);
+        // Wait for user to be fully started.
+        boolean isUserStarted = waitForUserState(userId, "RUNNING_UNLOCKED");
+        assertThat(isUserStarted).isTrue();
+
+        mDevice.setSetting(userId, "secure", USER_SETUP_COMPLETE_SETTING, "1");
+        mBackupUtils.enableBackupForUser(true, userId);
+        assertThat(mBackupUtils.isBackupEnabledForUser(userId)).isTrue();
+    }
+
+    /**
+     * 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 {
+        // 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.
+        boolean hasLocalTransport = false;
+        long timeout = System.currentTimeMillis() + TRANSPORT_INITIALIZATION_TIMEOUT_MS;
+        while (System.currentTimeMillis() <= timeout) {
+            if (mBackupUtils.userHasBackupTransport(localTransport, userId)) {
+                hasLocalTransport = true;
+                break;
+            }
+
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                // Do nothing.
+            }
+        }
+        assertThat(hasLocalTransport).isTrue();
+
+        // Switch to the local transport and assert success.
+        mBackupUtils.setBackupTransportForUser(localTransport, userId);
+        assertThat(mBackupUtils.isLocalTransportSelectedForUser(userId)).isTrue();
+
+        return localTransport;
+    }
+
+    /** Runs "bmgr --user <id> wipe <transport> <package>" to clear the backup data. */
+    void clearBackupDataInTransportForUser(String packageName, String transport, int userId)
+            throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(
+                String.format("bmgr --user %d wipe %s %s", userId, transport, packageName));
+    }
+
+    /** Clears data of {@code packageName} for user {@code userId}. */
+    void clearPackageDataAsUser(String packageName, int userId) throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(String.format("pm clear --user %d %s", userId, packageName));
+    }
+
+    /** Uninstalls {@code packageName} for user {@code userId}. */
+    void uninstallPackageAsUser(String packageName, int userId) throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(
+                String.format("pm uninstall --user %d %s", userId, packageName));
+    }
+
+    /** Run device side test as user {@code userId}. */
+    void checkDeviceTestAsUser(String packageName, String className, String testName, int userId)
+            throws DeviceNotAvailableException {
+        boolean result = runDeviceTests(mDevice, packageName, className, testName, userId, null);
+        assertThat(result).isTrue();
+    }
+
+    /**
+     * Waits for user {@code userId} to be in the state {@code expectedState}. Returns {@code true}
+     * if the user is in the state within the timeout.
+     */
+    boolean waitForUserState(int userId, String expectedState) throws IOException {
+        long timeout = System.currentTimeMillis() + USER_STATE_TIMEOUT_MS;
+        while (System.currentTimeMillis() <= timeout) {
+            String output =
+                    mBackupUtils.executeShellCommandAndReturnOutput(
+                            String.format("am get-started-user-state %d", userId));
+            if (output.contains(expectedState)) {
+                return true;
+            }
+
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                // Do nothing.
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
index 6589993..9ca68a0 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertNull;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -47,6 +49,7 @@
  * - Only file in no_backup folder for key/value.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class FullBackupOnlyHostSideTest extends BaseBackupHostSideTest {
 
     private static final String FULLBACKUPONLY_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
@@ -105,7 +108,7 @@
 
         checkFullBackupOnlyDeviceTest("createFiles");
 
-        backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
 
         assertNull(uninstallPackage(FULLBACKUPONLY_APP_PACKAGE));
 
@@ -131,7 +134,7 @@
 
         checkFullBackupOnlyDeviceTest("createFiles");
 
-        backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
 
         assertNull(uninstallPackage(FULLBACKUPONLY_APP_PACKAGE));
 
@@ -157,7 +160,7 @@
 
         checkFullBackupOnlyDeviceTest("createFiles");
 
-        backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(FULLBACKUPONLY_APP_PACKAGE);
 
         assertNull(uninstallPackage(FULLBACKUPONLY_APP_PACKAGE));
 
diff --git a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
index c6538d0..a7cd5a0 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
@@ -16,6 +16,11 @@
 
 package android.cts.backup;
 
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -33,6 +38,7 @@
  * android.cts.backup.includeexcludeapp.IncludeExcludeTest.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class FullbackupRulesHostSideTest extends BaseBackupHostSideTest {
 
     private static final String FULLBACKUP_TESTS_APP_NAME = "android.cts.backup.fullbackupapp";
@@ -61,18 +67,14 @@
                 "createFiles");
 
         // Do a backup
-        String backupnowOutput = backupNow(FULLBACKUP_TESTS_APP_NAME);
-
-        assertBackupIsSuccessful(FULLBACKUP_TESTS_APP_NAME, backupnowOutput);
+        getBackupUtils().backupNowAndAssertSuccess(FULLBACKUP_TESTS_APP_NAME);
 
         // Delete the files
         checkDeviceTest(FULLBACKUP_TESTS_APP_NAME, FULLBACKUP_DEVICE_TEST_CLASS_NAME,
                 "deleteFilesAfterBackup");
 
         // Do a restore
-        String restoreOutput = restore(FULLBACKUP_TESTS_APP_NAME);
-
-        assertRestoreIsSuccessful(restoreOutput);
+        getBackupUtils().restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, FULLBACKUP_TESTS_APP_NAME);
 
         // Check that the right files were restored
         checkDeviceTest(FULLBACKUP_TESTS_APP_NAME, FULLBACKUP_DEVICE_TEST_CLASS_NAME,
@@ -91,16 +93,15 @@
                 "createFiles");
 
         // Do a backup
-        String backupNowOutput = backupNow(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertBackupIsSuccessful(INCLUDE_EXCLUDE_TESTS_APP_NAME, backupNowOutput);
+        getBackupUtils().backupNowAndAssertSuccess(INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Delete the files
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
                 "deleteFilesAfterBackup");
 
         // Do a restore
-        String restoreOutput = restore(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertRestoreIsSuccessful(restoreOutput);
+        getBackupUtils()
+                .restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Check that the right files were restored
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
@@ -122,16 +123,15 @@
                 "createFiles");
 
         // Do a backup
-        String backupNowOutput = backupNow(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertBackupIsSuccessful(INCLUDE_EXCLUDE_TESTS_APP_NAME, backupNowOutput);
+        getBackupUtils().backupNowAndAssertSuccess(INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Delete the files
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
                 "deleteFilesAfterBackup");
 
         // Do a restore
-        String restoreOutput = restore(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertRestoreIsSuccessful(restoreOutput);
+        getBackupUtils()
+                .restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Check that the client-side encryption files were restored
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
@@ -153,16 +153,15 @@
                 "createFiles");
 
         // Do a backup
-        String backupNowOutput = backupNow(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertBackupIsSuccessful(INCLUDE_EXCLUDE_TESTS_APP_NAME, backupNowOutput);
+        getBackupUtils().backupNowAndAssertSuccess(INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Delete the files
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
                 "deleteFilesAfterBackup");
 
         // Do a restore
-        String restoreOutput = restore(INCLUDE_EXCLUDE_TESTS_APP_NAME);
-        assertRestoreIsSuccessful(restoreOutput);
+        getBackupUtils()
+                .restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, INCLUDE_EXCLUDE_TESTS_APP_NAME);
 
         // Check that the client-side encryption files were not restored
         checkDeviceTest(INCLUDE_EXCLUDE_TESTS_APP_NAME, INCLUDE_EXCLUDE_DEVICE_TEST_CLASS_NAME,
diff --git a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
index 5ce86d9..f3944b4 100644
--- a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
@@ -16,8 +16,12 @@
 
 package android.cts.backup;
 
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
 import static org.junit.Assert.assertNull;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -35,6 +39,7 @@
  * NB: The tests use "bmgr backupnow" for backup, which works on N+ devices.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class KeyValueBackupRestoreHostSideTest extends BaseBackupHostSideTest {
 
     /** The name of the package of the app under test */
@@ -103,7 +108,7 @@
 
         checkDeviceTest("saveSharedPreferencesAndNotifyBackupManager");
 
-        backupNowAndAssertSuccess(KEY_VALUE_RESTORE_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(KEY_VALUE_RESTORE_APP_PACKAGE);
 
         assertNull(uninstallPackage(KEY_VALUE_RESTORE_APP_PACKAGE));
 
@@ -145,11 +150,12 @@
 
         checkDeviceTest("launchSharedPrefActivity");
 
-        backupNowAndAssertSuccess(SHARED_PREFERENCES_RESTORE_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(SHARED_PREFERENCES_RESTORE_APP_PACKAGE);
 
         checkDeviceTest("updateSharedPrefActivity");
 
-        restoreAndAssertSuccess(SHARED_PREFERENCES_RESTORE_APP_PACKAGE);
+        getBackupUtils().restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN,
+                SHARED_PREFERENCES_RESTORE_APP_PACKAGE);
 
         checkDeviceTest("checkSharedPrefActivity");
     }
diff --git a/hostsidetests/backup/src/android/cts/backup/OtherSoundsSettingsHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/OtherSoundsSettingsHostSideTest.java
new file mode 100644
index 0000000..6d5b731
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/OtherSoundsSettingsHostSideTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for checking other sounds settings value backup and restore works correctly.
+ * The tests use "bmgr backupnow com.android.providers.settings" for backup
+ *
+ * Invokes device side tests provided by
+ * {@link android.cts.backup.othersoundssettingsapp.OtherSoundsSettingsTest}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class OtherSoundsSettingsHostSideTest extends BaseBackupHostSideTest {
+
+    /** The name of the APK that a other sounds settings test will be run for */
+    private static final String APP_APK = "CtsBackupOtherSoundsSettingsApp.apk";
+
+    /** The name of the package of the app under test */
+    private static final String APP_PACKAGE = "android.cts.backup.othersoundssettingsapp";
+
+    /** The name of the device side test class */
+    private static final String TEST_CLASS = APP_PACKAGE + ".OtherSoundsSettingsTest";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        installPackage(APP_APK);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        // Clear backup data and uninstall the package (in that order!)
+        clearBackupDataInLocalTransport(APP_PACKAGE);
+        assertNull(uninstallPackage(APP_PACKAGE));
+    }
+
+    /**
+     * Test backup and restore of Dial pad tones
+     */
+    @Test
+    public void testOtherSoundsSettings_dialPadTones() throws Exception {
+        if (!mIsBackupSupported) {
+            LogUtil.CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testOtherSoundsSettings_dialPadTones");
+    }
+
+    /**
+     * Test backup and restore of Touch sounds
+     */
+    @Test
+    public void testOtherSoundsSettings_touchSounds() throws Exception {
+        if (!mIsBackupSupported) {
+            LogUtil.CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testOtherSoundsSettings_touchSounds");
+    }
+
+    /**
+     * Test backup and restore of Touch vibration
+     */
+    @Test
+    public void testOtherSoundsSettings_touchVibration() throws Exception {
+        if (!mIsBackupSupported) {
+            LogUtil.CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testOtherSoundsSettings_touchVibration");
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTest(APP_PACKAGE, TEST_CLASS, methodName);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/ProfileFullBackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/ProfileFullBackupRestoreHostSideTest.java
new file mode 100644
index 0000000..0aabbce
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/ProfileFullBackupRestoreHostSideTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test the backup and restore flow for a full-backup app in a profile. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class ProfileFullBackupRestoreHostSideTest extends BaseMultiUserBackupHostSideTest {
+    private final BackupUtils mBackupUtils = getBackupUtils();
+    private ITestDevice mDevice;
+    private int mProfileUserId;
+    private String mTransport;
+
+    /** Create the profile, switch to the local transport and setup the test package. */
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        // Create profile user.
+        int parentUserId = mDevice.getCurrentUser();
+        mProfileUserId = createProfileUser(parentUserId, "Profile-Full");
+        startUserAndInitializeForBackup(mProfileUserId);
+
+        // Switch to local transport.
+        mTransport = switchUserToLocalTransportAndAssertSuccess(mProfileUserId);
+
+        // Setup test package.
+        installPackageAsUser(FULL_BACKUP_APK, true, mProfileUserId);
+        clearPackageDataAsUser(FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+    }
+
+    /** Uninstall the test package and remove the profile. */
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        clearBackupDataInTransportForUser(FULL_BACKUP_TEST_PACKAGE, mTransport, mProfileUserId);
+        uninstallPackageAsUser(FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+        mDevice.removeUser(mProfileUserId);
+        super.tearDown();
+    }
+
+    /**
+     * Tests full-backup app backup and restore in the profile user using system restore.
+     *
+     * <ol>
+     *   <li>App writes files to its directory.
+     *   <li>Force a backup.
+     *   <li>Clear the app's files.
+     *   <li>Force a restore.
+     *   <li>Check that the files and its contents are restored.
+     * </ol>
+     */
+    @Test
+    public void testFullBackupAndSystemRestore() throws Exception {
+        checkDeviceTest("assertFilesDontExist");
+        checkDeviceTest("writeFilesAndAssertSuccess");
+
+        mBackupUtils.backupNowAndAssertSuccessForUser(FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+
+        checkDeviceTest("clearFilesAndAssertSuccess");
+
+        mBackupUtils.restoreAndAssertSuccessForUser(
+                BackupUtils.LOCAL_TRANSPORT_TOKEN, FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+
+        checkDeviceTest("assertFilesRestored");
+    }
+
+    /**
+     * Tests full-backup app backup and restore in the profile user using restore at install.
+     *
+     * <ol>
+     *   <li>App writes files to its directory.
+     *   <li>Force a backup.
+     *   <li>Uninstall the app.
+     *   <li>Install the app to perform a restore-at-install operation.
+     *   <li>Check that the files and its contents are restored.
+     * </ol>
+     */
+    @Test
+    public void testFullBackupAndRestoreAtInstall() throws Exception {
+        checkDeviceTest("assertFilesDontExist");
+        checkDeviceTest("writeFilesAndAssertSuccess");
+
+        mBackupUtils.backupNowAndAssertSuccessForUser(FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+
+        checkDeviceTest("clearFilesAndAssertSuccess");
+
+        uninstallPackageAsUser(FULL_BACKUP_TEST_PACKAGE, mProfileUserId);
+
+        installPackageAsUser(FULL_BACKUP_APK, true, mProfileUserId);
+
+        checkDeviceTest("assertFilesRestored");
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTestAsUser(
+                FULL_BACKUP_TEST_PACKAGE, FULL_BACKUP_DEVICE_TEST_NAME, methodName, mProfileUserId);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/ProfileKeyValueBackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/ProfileKeyValueBackupRestoreHostSideTest.java
new file mode 100644
index 0000000..92e4372
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/ProfileKeyValueBackupRestoreHostSideTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test the backup and restore flow for a key-value app in a profile. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class ProfileKeyValueBackupRestoreHostSideTest extends BaseMultiUserBackupHostSideTest {
+    private final BackupUtils mBackupUtils = getBackupUtils();
+    private ITestDevice mDevice;
+    private int mProfileUserId;
+    private String mTransport;
+
+    /** Create the profile, switch to the local transport and setup the test package. */
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        // Create profile user.
+        int parentUserId = mDevice.getCurrentUser();
+        mProfileUserId = createProfileUser(parentUserId, "Profile-KV");
+        startUserAndInitializeForBackup(mProfileUserId);
+
+        // Switch to local transport.
+        mTransport = switchUserToLocalTransportAndAssertSuccess(mProfileUserId);
+
+        // Setup test package.
+        installPackageAsUser(KEY_VALUE_APK, true, mProfileUserId);
+        clearPackageDataAsUser(KEY_VALUE_TEST_PACKAGE, mProfileUserId);
+    }
+
+    /** Uninstall the test package and remove the profile. */
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        clearBackupDataInTransportForUser(KEY_VALUE_TEST_PACKAGE, mTransport, mProfileUserId);
+        uninstallPackageAsUser(KEY_VALUE_TEST_PACKAGE, mProfileUserId);
+        mDevice.removeUser(mProfileUserId);
+        super.tearDown();
+    }
+
+    /**
+     * Tests key-value app backup and restore in the profile user.
+     *
+     * <ol>
+     *   <li>App writes shared preferences.
+     *   <li>Force a backup.
+     *   <li>Uninstall the app.
+     *   <li>Install the app to perform a restore-at-install operation.
+     *   <li>Check that the shared preferences are restored.
+     * </ol>
+     */
+    @Test
+    public void testKeyValueBackupAndRestore() throws Exception {
+        checkDeviceTest("assertSharedPrefsIsEmpty");
+        checkDeviceTest("writeSharedPrefsAndAssertSuccess");
+
+        mBackupUtils.backupNowAndAssertSuccessForUser(KEY_VALUE_TEST_PACKAGE, mProfileUserId);
+
+        uninstallPackageAsUser(KEY_VALUE_TEST_PACKAGE, mProfileUserId);
+
+        installPackageAsUser(KEY_VALUE_APK, true, mProfileUserId);
+
+        checkDeviceTest("assertSharedPrefsRestored");
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTestAsUser(
+                KEY_VALUE_TEST_PACKAGE, KEY_VALUE_DEVICE_TEST_NAME, methodName, mProfileUserId);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
new file mode 100644
index 0000000..627ff78
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.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.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.compatibility.common.util.LogcatInspector;
+import com.android.tradefed.device.ITestDevice;
+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.io.IOException;
+import java.io.InputStream;
+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)
+@AppModeFull
+public class ProfileScheduledJobHostSideTest extends BaseMultiUserBackupHostSideTest {
+    private static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+
+    /** From {@link com.android.server.backup.KeyValueBackupJob}. */
+    private static final int KEY_VALUE_MIN_JOB_ID = 52417896;
+
+    private static final String KEY_VALUE_JOB_NAME =
+            "android/com.android.server.backup.KeyValueBackupJob";
+
+    /** From {@link com.android.server.backup.FullBackupJob}. */
+    private static final int FULL_BACKUP_MIN_JOB_ID = 52418896;
+
+    private static final String FULL_BACKUP_JOB_NAME =
+            "android/com.android.server.backup.FullBackupJob";
+
+    private static final String TAG = "ProfileScheduledJobHostSideTest";
+    private static final String LOGCAT_FILTER =
+            "BackupManagerService:* KeyValueBackupTask:* PFTBT:* " + TAG + ":* *:S";
+    private static final String KEY_VALUE_SUCCESS_LOG = "K/V backup pass finished";
+    private static final String FULL_BACKUP_SUCCESS_LOG = "Full data backup pass finished";
+    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();
+    private ITestDevice mDevice;
+    private int mProfileUserId;
+
+    private LogcatInspector mLogcatInspector =
+            new LogcatInspector() {
+                @Override
+                protected InputStream executeShellCommand(String command) throws IOException {
+                    return executeDeviceShellCommand(getDevice(), command);
+                }
+            };
+
+    /** Create a profile user and switch to the local transport. */
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        int parentUserId = mDevice.getCurrentUser();
+        mProfileUserId = createProfileUser(parentUserId, "Profile-Jobs");
+        startUserAndInitializeForBackup(mProfileUserId);
+
+        switchUserToLocalTransportAndAssertSuccess(mProfileUserId);
+    }
+
+    /** Remove the profile. */
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        mDevice.removeUser(mProfileUserId);
+        super.tearDown();
+    }
+
+    /** Assert that the key value backup job for the profile is scheduled. */
+    @Test
+    public void testKeyValueBackupJobScheduled() throws Exception {
+        int jobId = getJobIdForUser(KEY_VALUE_MIN_JOB_ID, mProfileUserId);
+        assertThat(isSystemJobScheduled(jobId, KEY_VALUE_JOB_NAME)).isTrue();
+    }
+
+    /**
+     * Tests the flow of the key value backup job for the profile.
+     *
+     * <ol>
+     *   <li>Install key value backup app to ensure eligibility.
+     *   <li>Force run the key value backup job.
+     *   <li>Assert success via logcat.
+     *   <li>Check that the job was rescheduled.
+     * </ol>
+     */
+    @Test
+    public void testKeyValueBackupJobRunsSuccessfully() throws Exception {
+        // Install a new key value backup app and simulate data changed.
+        installPackageAsUser(KEY_VALUE_APK, true, mProfileUserId);
+        checkDeviceTestAsUser(
+                KEY_VALUE_TEST_PACKAGE,
+                KEY_VALUE_DEVICE_TEST_NAME,
+                "callDataChanged",
+                mProfileUserId);
+
+        // Force run k/v job.
+        int jobId = getJobIdForUser(KEY_VALUE_MIN_JOB_ID, mProfileUserId);
+        mBackupUtils.executeShellCommandSync(JOB_SCHEDULER_RUN_COMMAND + " " + jobId);
+
+        // Check logs for success.
+        mLogcatInspector.assertLogcatContainsInOrder(
+                LOGCAT_FILTER,
+                TIMEOUT_FOR_KEY_VALUE_SECONDS,
+                mLogcatInspector.mark(TAG),
+                KEY_VALUE_SUCCESS_LOG);
+
+        // Check job rescheduled.
+        assertThat(isSystemJobScheduled(jobId, KEY_VALUE_JOB_NAME)).isTrue();
+    }
+
+    /** Stop the profile user and assert that the key value job is no longer scheduled. */
+    @Test
+    public void testKeyValueBackupJobCancelled() throws Exception {
+        int jobId = getJobIdForUser(KEY_VALUE_MIN_JOB_ID, mProfileUserId);
+        assertThat(isSystemJobScheduled(jobId, KEY_VALUE_JOB_NAME)).isTrue();
+
+        mDevice.stopUser(mProfileUserId, /* waitFlag */ true, /* forceFlag */ true);
+
+        assertThat(isSystemJobScheduled(jobId, KEY_VALUE_JOB_NAME)).isFalse();
+    }
+
+    /** Assert that the full backup job for the profile is scheduled. */
+    @Test
+    public void testFullBackupJobScheduled() throws Exception {
+        int jobId = getJobIdForUser(FULL_BACKUP_MIN_JOB_ID, mProfileUserId);
+        assertThat(isSystemJobScheduled(jobId, FULL_BACKUP_JOB_NAME)).isTrue();
+    }
+
+    /**
+     * Tests the flow of the full backup job for the profile.
+     *
+     * <ol>
+     *   <li>Install full backup app to ensure eligibility.
+     *   <li>Force run the full backup job.
+     *   <li>Assert success via logcat.
+     *   <li>Check that the job was rescheduled.
+     * </ol>
+     */
+    @Test
+    public void testFullBackupJobRunsSuccessfully() throws Exception {
+        // Install a new eligible full backup app and run a backup pass for @pm@ as we cannot
+        // perform a full backup pass before @pm@ is backed up.
+        installPackageAsUser(FULL_BACKUP_APK, true, mProfileUserId);
+        mBackupUtils.backupNowAndAssertSuccessForUser(PACKAGE_MANAGER_SENTINEL, mProfileUserId);
+
+        // Force run full backup job.
+        int jobId = getJobIdForUser(FULL_BACKUP_MIN_JOB_ID, mProfileUserId);
+        mBackupUtils.executeShellCommandSync(JOB_SCHEDULER_RUN_COMMAND + " " + jobId);
+
+        // Check logs for success.
+        mLogcatInspector.assertLogcatContainsInOrder(
+                LOGCAT_FILTER,
+                TIMEOUT_FOR_FULL_BACKUP_SECONDS,
+                mLogcatInspector.mark(TAG),
+                FULL_BACKUP_SUCCESS_LOG);
+
+        // Check job rescheduled.
+        assertThat(isSystemJobScheduled(jobId, FULL_BACKUP_JOB_NAME)).isTrue();
+    }
+
+    /** Stop the profile user and assert that the full backup job is no longer scheduled. */
+    @Test
+    public void testFullBackupJobCancelled() throws Exception {
+        int jobId = getJobIdForUser(FULL_BACKUP_MIN_JOB_ID, mProfileUserId);
+        assertThat(isSystemJobScheduled(jobId, FULL_BACKUP_JOB_NAME)).isTrue();
+
+        mDevice.stopUser(mProfileUserId, /* waitFlag */ true, /* forceFlag */ true);
+
+        assertThat(isSystemJobScheduled(jobId, FULL_BACKUP_JOB_NAME)).isFalse();
+    }
+
+    private int getJobIdForUser(int minJobId, int userId) {
+        return minJobId + userId;
+    }
+
+    /** 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();
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
index a62eab8..3dca567 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertNull;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.TargetSetupError;
@@ -34,6 +36,7 @@
  * android.cts.backup.restoreanyversionapp.RestoreAnyVersionTest.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class RestoreAnyVersionHostSideTest extends BaseBackupHostSideTest {
 
     /** The name of the package of the app under test */
@@ -85,7 +88,7 @@
         saveSharedPreferenceValue();
         checkRestoreAnyVersionDeviceTest("checkSharedPrefIsNew");
 
-        backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
 
         assertNull(uninstallPackage(RESTORE_ANY_VERSION_APP_PACKAGE));
 
@@ -111,7 +114,7 @@
         saveSharedPreferenceValue();
         checkRestoreAnyVersionDeviceTest("checkSharedPrefIsNew");
 
-        backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
 
         assertNull(uninstallPackage(RESTORE_ANY_VERSION_APP_PACKAGE));
 
@@ -137,7 +140,7 @@
         saveSharedPreferenceValue();
         checkRestoreAnyVersionDeviceTest("checkSharedPrefIsOld");
 
-        backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(RESTORE_ANY_VERSION_APP_PACKAGE);
 
         assertNull(uninstallPackage(RESTORE_ANY_VERSION_APP_PACKAGE));
 
diff --git a/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
index 414e3bc..c833b40 100644
--- a/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertNull;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -38,6 +40,7 @@
  * NB: The tests use "bmgr backupnow" for backup, which works on N+ devices.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class SuccessNotificationHostSideTest extends BaseBackupHostSideTest {
 
     /** The name of the package that a key/value backup will be run for */
@@ -136,7 +139,7 @@
 
         checkDeviceTest(KEY_VALUE_BACKUP_APP_PACKAGE, KEY_VALUE_BACKUP_DEVICE_TEST_NAME,
                 "saveSharedPreferencesAndNotifyBackupManager");
-        backupNowAndAssertSuccess(KEY_VALUE_BACKUP_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(KEY_VALUE_BACKUP_APP_PACKAGE);
 
         checkDeviceTest("verifyBackupSuccessNotificationReceivedForKeyValueApp");
     }
@@ -155,7 +158,7 @@
         }
 
         checkDeviceTest(FULL_BACKUP_APP_PACKAGE, FULL_BACKUP_DEVICE_TEST_CLASS_NAME, "createFiles");
-        backupNowAndAssertSuccess(FULL_BACKUP_APP_PACKAGE);
+        getBackupUtils().backupNowAndAssertSuccess(FULL_BACKUP_APP_PACKAGE);
 
         checkDeviceTest("verifyBackupSuccessNotificationReceivedForFullBackupApp");
     }
diff --git a/hostsidetests/backup/src/android/cts/backup/SyncAdapterSettingsHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/SyncAdapterSettingsHostSideTest.java
new file mode 100644
index 0000000..1d4030d
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/SyncAdapterSettingsHostSideTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for checking sync adapter settings value backup and restore works correctly.
+ * The app uses AccountSyncSettingsBackupHelper to do sync adapter settings backup of those values.
+ * The tests use "bmgr backupnow android" for backup
+ *
+ * Invokes device side tests provided by
+ * {@link android.cts.backup.syncadaptersettingsapp.SyncAdapterSettingsTest}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class SyncAdapterSettingsHostSideTest extends BaseBackupHostSideTest {
+
+    /** The name of the APK that a sync adapter settings will be run for */
+    private static final String APP_APK = "CtsBackupSyncAdapterSettingsApp.apk";
+
+    /** The name of the package of the app under test */
+    private static final String APP_PACKAGE = "android.cts.backup.syncadaptersettingsapp";
+
+    /** The name of the device side test class */
+    private static final String TEST_CLASS = APP_PACKAGE + ".SyncAdapterSettingsTest";
+
+    /** The name of the package for backup */
+    private static final String ANDROID_PACKAGE = "android";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        installPackage(APP_APK);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        // Clear backup data and uninstall the package (in that order!)
+        // BackupManagerService won't let us wipe the data if package is uninstalled
+        clearBackupDataInLocalTransport(ANDROID_PACKAGE);
+        assertNull(uninstallPackage(APP_PACKAGE));
+    }
+
+    /**
+     * Test backup and restore of MasterSyncAutomatically=true.
+     */
+    @Test
+    public void testMasterSyncAutomatically_whenOn_isRestored() throws Exception {
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testMasterSyncAutomatically_whenOn_isRestored");
+    }
+
+    /**
+     * Test backup and restore of MasterSyncAutomatically=false.
+     */
+    @Test
+    public void testMasterSyncAutomatically_whenOff_isRestored() throws Exception {
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testMasterSyncAutomatically_whenOff_isRestored");
+    }
+
+    /**
+     * Test that if syncEnabled=true and isSyncable=1 in restore data, syncEnabled will be true
+     * and isSyncable will be left as the current value in the device.
+     */
+    @Test
+    public void testIsSyncableChanged_ifTurnOnSyncEnabled() throws Exception {
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testIsSyncableChanged_ifTurnOnSyncEnabled");
+    }
+
+    /**
+     * Test that if syncEnabled=false and isSyncable=0 in restore data, syncEnabled will be false
+     * and isSyncable will be set to 0.
+     */
+    @Test
+    public void testIsSyncableIsZero_ifTurnOffSyncEnabled() throws Exception {
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testIsSyncableIsZero_ifTurnOffSyncEnabled");
+    }
+
+    /**
+     * Test that if syncEnabled=false and isSyncable=1 in restore data, syncEnabled will be false
+     * and isSyncable will be set to 1.
+     */
+    @Test
+    public void testIsSyncableIsOne_ifTurnOffSyncEnabled() throws Exception {
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        checkDeviceTest("testIsSyncableIsOne_ifTurnOffSyncEnabled");
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTest(APP_PACKAGE, TEST_CLASS, methodName);
+    }
+}
diff --git a/hostsidetests/compilation/Android.mk b/hostsidetests/compilation/Android.mk
index 5047435..5a27baf 100644
--- a/hostsidetests/compilation/Android.mk
+++ b/hostsidetests/compilation/Android.mk
@@ -27,6 +27,8 @@
 
 LOCAL_MODULE := CtsCompilationTestCases
 
+LOCAL_MIN_SDK_VERSION := 23
+
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util guava
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/content/AndroidTest.xml b/hostsidetests/content/AndroidTest.xml
index badbda7..300d94b 100644
--- a/hostsidetests/content/AndroidTest.xml
+++ b/hostsidetests/content/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for the CTS Content host tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- 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" />
     <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/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.mk b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.mk
index 3069bac..8ac2416 100644
--- a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.mk
+++ b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.mk
@@ -20,12 +20,13 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations android-support-test ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsSyncInvalidAccountAuthorityTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_SDK_VERSION := current
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/src/android/content/sync/cts/StubProvider.java b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/src/android/content/sync/cts/StubProvider.java
index f082cc8..c8343d4 100644
--- a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/src/android/content/sync/cts/StubProvider.java
+++ b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/src/android/content/sync/cts/StubProvider.java
@@ -16,11 +16,11 @@
 
 package android.content.sync.cts;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.net.Uri;
 
 public class StubProvider extends ContentProvider {
diff --git a/hostsidetests/deviceidle/AndroidTest.xml b/hostsidetests/deviceidle/AndroidTest.xml
index fecade9..66ac3ec 100644
--- a/hostsidetests/deviceidle/AndroidTest.xml
+++ b/hostsidetests/deviceidle/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for the CTS Content host tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- These are tests for the shell command to manage device idle whitelist. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsDeviceIdleHostTestCases.jar" />
         <option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/devicepolicy/Android.mk b/hostsidetests/devicepolicy/Android.mk
index f9a861c..856e24b 100644
--- a/hostsidetests/devicepolicy/Android.mk
+++ b/hostsidetests/devicepolicy/Android.mk
@@ -22,7 +22,15 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed compatibility-host-util
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_JAVA_LIBRARIES := \
+    tools-common-prebuilt \
+    cts-tradefed \
+    tradefed \
+    compatibility-host-util \
+    guava \
+    truth-prebuilt
 
 LOCAL_CTS_TEST_PACKAGE := android.adminhostside
 
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index 6fc38c8..e3929cc 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -16,6 +16,11 @@
 <configuration description="Config for the CTS Device Policy host tests">
     <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" />
 
     <!-- Push the list of public APIs to device -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
index 5746089..22f3436 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
@@ -36,5 +36,6 @@
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 25
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.mk b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.mk
index b3adc70..7706e73 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.mk
@@ -30,5 +30,6 @@
 LOCAL_STATIC_JAVA_LIBRARIES := androidx.legacy_legacy-support-v4 ctstestrunner ub-uiautomator android-support-test
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 25
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
index 78795cf..3aed12d 100644
--- a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.mk
@@ -25,6 +25,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/Android.mk b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
index 86440fc..425b960 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
+++ b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
@@ -24,9 +24,19 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs \
+    android.test.base.stubs \
 
-LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_MIN_SDK_VERSION := 22
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+    truth-prebuilt \
+    testng \
+	cts-security-test-support-library
+
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
index 6bf1451..bda007a 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -19,7 +19,11 @@
 
     <uses-sdk android:minSdkVersion="22"/>
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <receiver android:name=".CertInstallerReceiver">
             <intent-filter>
                 <action android:name="com.android.cts.certinstaller.install_cert" />
@@ -29,6 +33,26 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </receiver>
+        <activity
+            android:name="android.app.Activity"
+            android:exported="true">
+        </activity>
+        <receiver
+            android:name=".CertSelectionDelegateTest$CertSelectionReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <intent-filter>
+                <action android:name="android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"/>
+            </intent-filter>
+        </receiver>
     </application>
 
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="Delegated Cert Installer CTS test"
+        android:targetPackage="com.android.cts.certinstaller">
+        <meta-data
+            android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
 </manifest>
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
new file mode 100644
index 0000000..609ea1f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.certinstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.admin.DelegatedAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Process;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+
+import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests a delegate app with DELEGATION_CERT_SELECTION receives the
+ * {@link android.app.admin.DelegatedAdminReceiver#onChoosePrivateKeyAlias} callback when a
+ * requesting app (in this case, ourselves) invokes {@link KeyChain#choosePrivateKeyAlias},
+ * and is able to force a designated cert to be returned.
+ *
+ * This test is driven by hostside {@code DeviceAndProfileOwnerTest#testDelegationCertSelection},
+ * which grants this app DELEGATION_CERT_SELECTION permission and executes tests in this class.
+ */
+public class CertSelectionDelegateTest extends InstrumentationTestCase {
+
+    private static final long KEYCHAIN_TIMEOUT_MINS = 1;
+
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+    private Activity mActivity;
+
+    public static class CertSelectionReceiver extends DelegatedAdminReceiver {
+
+        @Override
+        public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
+                String alias) {
+            if (uid != Process.myUid() || uri == null) {
+                return null;
+            }
+            return uri.getQueryParameter("delegate-alias");
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mContext = getInstrumentation().getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+
+        final UiDevice device = UiDevice.getInstance(getInstrumentation());
+        mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
+                Activity.class, null);
+        device.waitForIdle();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mActivity.finish();
+        super.tearDown();
+    }
+
+    public void testCanSelectKeychainKeypairs() throws Exception {
+        assertThat(mDpm.getDelegatedScopes(null, mContext.getPackageName())).contains(
+                DevicePolicyManager.DELEGATION_CERT_SELECTION);
+
+        final String alias = "com.android.test.valid-rsa-key-1";
+        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
+        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
+        // Install keypair.
+        assertThat(mDpm.installKeyPair(null, privKey, cert, alias)).isTrue();
+        try {
+            assertThat(new KeyChainAliasFuture(alias).get()).isEqualTo(alias);
+
+            // Verify key is at least something like the one we put in.
+            assertThat(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm()).isEqualTo("RSA");
+        } finally {
+            // Delete regardless of whether the test succeeded.
+            assertThat(mDpm.removeKeyPair(null, alias)).isTrue();
+        }
+    }
+
+    private class KeyChainAliasFuture implements KeyChainAliasCallback {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private String mChosenAlias = null;
+
+        @Override
+        public void alias(final String chosenAlias) {
+            mChosenAlias = chosenAlias;
+            mLatch.countDown();
+        }
+
+        public KeyChainAliasFuture(String alias)
+                throws UnsupportedEncodingException {
+            /* Pass the alias as a GET to an imaginary server instead of explicitly asking for it,
+             * to make sure the DPC actually has to do some work to grant the cert.
+             */
+            final Uri uri = Uri.parse("https://example.org/?delegate-alias="
+                    + URLEncoder.encode(alias, "UTF-8"));
+            KeyChain.choosePrivateKeyAlias(mActivity, this,
+                    null /* keyTypes */, null /* issuers */, uri, null /* alias */);
+        }
+
+        public String get() throws InterruptedException {
+            assertWithMessage("Chooser timeout")
+                    .that(mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES))
+                    .isTrue();
+            return mChosenAlias;
+        }
+    }
+
+    private static PrivateKey getPrivateKey(final byte[] key, String type)
+            throws NoSuchAlgorithmException, InvalidKeySpecException {
+        return KeyFactory.getInstance(type).generatePrivate(
+                new PKCS8EncodedKeySpec(key));
+    }
+
+    private static Certificate getCertificate(byte[] cert) throws CertificateException {
+        return CertificateFactory.getInstance("X.509").generateCertificate(
+                new ByteArrayInputStream(cert));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DelegatedDeviceIdAttestationTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DelegatedDeviceIdAttestationTest.java
new file mode 100644
index 0000000..e16aa42
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DelegatedDeviceIdAttestationTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.certinstaller;
+
+import android.app.admin.DevicePolicyManager;
+import android.keystore.cts.KeyGenerationUtils;
+import android.test.InstrumentationTestCase;
+
+public class DelegatedDeviceIdAttestationTest extends InstrumentationTestCase {
+    private DevicePolicyManager mDpm;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDpm = getInstrumentation().getContext().getSystemService(DevicePolicyManager.class);
+    }
+
+    // Test that a key generation request succeeds when device identifiers are requested.
+    public void testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess() {
+        if (mDpm.isDeviceIdAttestationSupported()) {
+            KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingSuccess(mDpm, null);
+        }
+    }
+
+    // Test that a key generation request fails when device identifiers are requested.
+    public void testGenerateKeyPairWithDeviceIdAttestationExpectingFailure() {
+        KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingFailure(mDpm, null);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
new file mode 100644
index 0000000..59d386e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.certinstaller;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Build;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.util.Base64;
+import android.util.Base64InputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.List;
+
+/*
+ * Tests the delegated certificate installer functionality.
+ *
+ * This class is configured as DelegatedCertInstaller by the DelegatedCertInstallerHelper and is
+ * invoked directly from the host class,
+ * DeviceAndProfileOwnerTest#testDelegatedCertInstallerDirectly.
+ *
+ * TODO: this class is missing more functionality of the DelegatedCertInstaller tests.
+ * When this class is done then the DelegatedCertInstallerTest can be deleted.
+ */
+public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase {
+    // Content from cacert.pem
+    private static final String TEST_CA =
+            "-----BEGIN CERTIFICATE-----\n" +
+                    "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+                    "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+                    "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+                    "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+                    "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+                    "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+                    "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+                    "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+                    "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+                    "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+                    "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+                    "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+                    "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+                    "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+                    "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+                    "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+                    "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+                    "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+                    "wQ==\n" +
+                    "-----END CERTIFICATE-----";
+    // Content from userkey.pem without the private key header and footer.
+    private static final String TEST_KEY =
+            "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALCYprGsTU+5L3KM\n" +
+                    "fhkm0gXM2xjGUH+543YLiMPGVr3eVS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqU\n" +
+                    "mhegxG4s3IvGYVB0KZoRIjDKmnnvlx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T\n" +
+                    "9sV6zW2Wzri+f5mvzKjhnArbLktHAgMBAAECgYBlfVVPhtZnmuXJzzQpAEZzTugb\n" +
+                    "tN1OimZO0RIocTQoqj4KT+HkiJOLGFQPwbtFpMre+q4SRqNpM/oZnI1yRtKcCmIc\n" +
+                    "mZgkwJ2k6pdSxqO0ofxFFTdT9czJ3rCnqBHy1g6BqUQFXT4olcygkxUpKYUwzlz1\n" +
+                    "oAl487CoPxyr4sVEAQJBANwiUOHcdGd2RoRILDzw5WOXWBoWPOKzX/K9wt0yL+mO\n" +
+                    "wlFNFSymqo9eLheHcEq/VD9qK9rT700dCewJfWj6+bECQQDNXmWNYIxGii5NJilT\n" +
+                    "OBOHiMD/F0NE178j+/kmacbhDJwpkbLYXaP8rW4+Iswrm4ORJ59lvjNuXaZ28+sx\n" +
+                    "fFp3AkA6Z7Bl/IO135+eATgbgx6ZadIqObQ1wbm3Qbmtzl7/7KyJvZXcnuup1icM\n" +
+                    "fxa//jtwB89S4+Ad6ZJ0WaA4dj5BAkEAuG7V9KmIULE388EZy8rIfyepa22Q0/qN\n" +
+                    "hdt8XasRGHsio5Jdc0JlSz7ViqflhCQde/aBh/XQaoVgQeO8jKyI8QJBAJHekZDj\n" +
+                    "WA0w1RsBVVReN1dVXgjm1CykeAT8Qx8TUmBUfiDX6w6+eGQjKtS7f4KC2IdRTV6+\n" +
+                    "bDzDoHBChHNC9ms=\n";
+
+    // Content from usercert.pem without the header and footer.
+    private static final String TEST_CERT =
+            "MIIDEjCCAfqgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTET\n" +
+                    "MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ\n" +
+                    "dHkgTHRkMB4XDTE1MDUwMTE2NTQwNVoXDTI1MDQyODE2NTQwNVowWzELMAkGA1UE\n" +
+                    "BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp\n" +
+                    "ZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY2xpZW50IGNlcnQwgZ8wDQYJKoZIhvcN\n" +
+                    "AQEBBQADgY0AMIGJAoGBALCYprGsTU+5L3KMfhkm0gXM2xjGUH+543YLiMPGVr3e\n" +
+                    "VS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqUmhegxG4s3IvGYVB0KZoRIjDKmnnv\n" +
+                    "lx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T9sV6zW2Wzri+f5mvzKjhnArbLktH\n" +
+                    "AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu\n" +
+                    "ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ8GL+jKSarvTn9fVNA2AzjY7qq\n" +
+                    "gjAfBgNVHSMEGDAWgBRzBBA5sNWyT/fK8GrhN3tOqO5tgjANBgkqhkiG9w0BAQsF\n" +
+                    "AAOCAQEAgwQEd2bktIDZZi/UOwU1jJUgGq7NiuBDPHcqgzjxhGFLQ8SQAAP3v3PR\n" +
+                    "mLzcfxsxnzGynqN5iHQT4rYXxxaqrp1iIdj9xl9Wl5FxjZgXITxhlRscOd/UOBvG\n" +
+                    "oMrazVczjjdoRIFFnjtU3Jf0Mich68HD1Z0S3o7X6sDYh6FTVR5KbLcxbk6RcoG4\n" +
+                    "VCI5boR5LUXgb5Ed5UxczxvN12S71fyxHYVpuuI0z0HTIbAxKeRw43I6HWOmR1/0\n" +
+                    "G6byGCNL/1Fz7Y+264fGqABSNTKdZwIU2K4ANEH7F+9scnhoO6OBp+gjBe5O+7jb\n" +
+                    "wZmUCAoTka4hmoaOCj7cqt/IkmxozQ==\n";
+
+    private DevicePolicyManager mDpm;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDpm = getContext().getSystemService(DevicePolicyManager.class);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mDpm.uninstallCaCert(null, TEST_CA.getBytes());
+        super.tearDown();
+    }
+
+    public void testCaCertsOperations() throws GeneralSecurityException, IOException {
+        final byte[] cert = TEST_CA.getBytes();
+        final Certificate caCert = CertificateFactory.getInstance("X.509")
+                .generateCertificate(new ByteArrayInputStream(cert));
+
+        // Exercise installCaCert()
+        KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
+        keyStore.load(null, null);
+        assertWithMessage("CA certificate must not be installed in KeyStore at the"
+                + " beginning of the test").that(keyStore.getCertificateAlias(caCert)).isNull();
+        assertWithMessage("CA certificate must not be installed in the DPM at the"
+                + " beginning of the test").that(mDpm.hasCaCertInstalled(null, cert)).isFalse();
+
+
+        assertWithMessage("Expecting CA certificate installation to succeed").that(
+                mDpm.installCaCert(null, cert)).isTrue();
+        assertWithMessage("Expecting CA cert to be installed").that(
+                mDpm.hasCaCertInstalled(null, cert)).isTrue();
+
+        // Exercise getInstalledCaCerts()
+        assertWithMessage("Expecting CA cert to be in the list of installed CA certs").that(
+                containsCertificate(mDpm.getInstalledCaCerts(null), cert)).isTrue();
+
+        // Verify that the CA cert was marked as installed by the Device Owner / Profile Owner.
+        assertWithMessage("CA cert should have a KeyStore alias").that(
+                keyStore.getCertificateAlias(caCert)).isNotNull();
+
+        mDpm.uninstallCaCert(null, cert);
+        assertWithMessage("Expecting CA cert to no longer be installed").that(
+                mDpm.hasCaCertInstalled(null, cert)).isFalse();
+    }
+
+    public void testInstallKeyPair()
+            throws GeneralSecurityException, KeyChainException, InterruptedException {
+        final String alias = "delegated-cert-installer-test-key";
+
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
+                Base64.decode(TEST_KEY, Base64.DEFAULT));
+        PrivateKey privatekey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
+
+        Certificate certificate = CertificateFactory.getInstance("X.509")
+                .generateCertificate(
+                        new Base64InputStream(new ByteArrayInputStream(TEST_CERT.getBytes()),
+                                Base64.DEFAULT));
+        assertThat(mDpm.installKeyPair(null, privatekey, new Certificate[]{certificate}, alias,
+                true)).isTrue();
+
+        // Test that the installed private key can be obtained.
+        PrivateKey obtainedKey = KeyChain.getPrivateKey(getContext(), alias);
+        assertThat(obtainedKey).isNotNull();
+        assertThat(obtainedKey.getAlgorithm()).isEqualTo("RSA");
+
+        // Test cleaning up the key.
+        assertThat(mDpm.removeKeyPair(null, alias)).isTrue();
+        assertThrows(
+                KeyChainException.class, () -> KeyChain.getPrivateKey(getContext(), alias));
+    }
+
+    // Test that a key generation request succeeds when device identifiers are not requested.
+    public void testGenerateKeyPairWithoutDeviceIdAttestation() {
+        final String alias = "com.android.test.generated-rsa-1";
+        try {
+            KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setKeySize(2048)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                            KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                    .setIsStrongBoxBacked(false)
+                    .setAttestationChallenge(new byte[]{'a', 'b', 'c'})
+                    .build();
+
+            AttestedKeyPair generated = mDpm.generateKeyPair(
+                    null, "RSA", keySpec, 0);
+            assertThat(generated).isNotNull();
+        } finally {
+            assertThat(mDpm.removeKeyPair(null, alias)).isTrue();
+        }
+    }
+
+    public void testAccessToDeviceIdentifiers() {
+        String serialNumber = Build.getSerial();
+        assertThat(Build.getSerial()).doesNotMatch(Build.UNKNOWN);
+
+        TelephonyManager telephonyService = (TelephonyManager) getContext().getSystemService(
+                Context.TELEPHONY_SERVICE);
+        assertWithMessage("Telephony service must be available.")
+                .that(telephonyService).isNotNull();
+
+        assertWithMessage("Must be able to obtain a valid IMEI.")
+                .that(telephonyService.getImei()).isNotNull();
+    }
+
+    private static boolean containsCertificate(List<byte[]> certificates, byte[] toMatch)
+            throws CertificateException {
+        Certificate certificateToMatch = readCertificate(toMatch);
+        for (byte[] certBuffer : certificates) {
+            Certificate cert = readCertificate(certBuffer);
+            if (certificateToMatch.equals(cert)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Certificate readCertificate(byte[] certBuffer) throws CertificateException {
+        final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer));
+    }
+
+    private Context getContext() {
+        return getInstrumentation().getContext();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/AdminReceiver.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/AdminReceiver.java
index bf18ebb..ef07632 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/AdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/AdminReceiver.java
@@ -37,7 +37,7 @@
         super.onProfileProvisioningComplete(context, intent);
         Log.i(TAG, "onProfileProvisioningComplete");
         // Enabled profile
-        getManager(context).setProfileEnabled(getComponentName(context));
-        getManager(context).setProfileName(getComponentName(context), "Corp owned Managed Profile");
+        getManager(context).setProfileEnabled(getWho(context));
+        getManager(context).setProfileName(getWho(context), "Corp owned Managed Profile");
     }
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
index 64034b9..009a289 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
@@ -34,6 +34,7 @@
 	ub-uiautomator
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
index 81d98f1..2cea654 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
@@ -33,5 +33,6 @@
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
index 711c44b..87891d6 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
@@ -29,7 +29,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES = \
     androidx.legacy_legacy-support-v4 \
     ctstestrunner \
-    android-support-test
+    android-support-test \
+    ub-uiautomator \
+    truth-prebuilt
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
index cc139a4..545867e 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
@@ -17,12 +17,21 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.delegate">
 
-    <application>
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
         <activity
             android:name="com.android.cts.delegate.DelegatedScopesReceiverActivity"
             android:exported="true">
         </activity>
+        <receiver
+            android:name=".NetworkLoggingDelegateTest$NetworkLogsReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <intent-filter>
+                <action android:name="android.app.action.NETWORK_LOGS_AVAILABLE"/>
+            </intent-filter>
+        </receiver>
     </application>
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.cts.delegate"
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsIsCallerDelegateHelper.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsIsCallerDelegateHelper.java
new file mode 100644
index 0000000..ed88773
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsIsCallerDelegateHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.delegate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.test.InstrumentationTestCase;
+
+/**
+ *  A helper for testing the {@link
+ *  DevicePolicyManager#isCallerApplicationRestrictionsManagingPackage()} method.
+ *  <p>The method names start with "test" to be recognized by {@link InstrumentationTestCase}.
+ */
+public class AppRestrictionsIsCallerDelegateHelper extends InstrumentationTestCase {
+
+    private DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevicePolicyManager =
+            getInstrumentation().getContext().getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testAssertCallerIsApplicationRestrictionsManagingPackage() {
+        assertThat(mDevicePolicyManager.isCallerApplicationRestrictionsManagingPackage()).isTrue();
+    }
+
+    public void testAssertCallerIsNotApplicationRestrictionsManagingPackage() {
+        assertThat(mDevicePolicyManager.isCallerApplicationRestrictionsManagingPackage()).isFalse();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
index c403ffb..4da1400 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
@@ -15,28 +15,14 @@
  */
 package com.android.cts.delegate;
 
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
-
 import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Bundle;
-import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
 import android.test.InstrumentationTestCase;
 import android.test.MoreAsserts;
 
-import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Test general properties of delegate applications that should apply to any delegation scope
@@ -45,15 +31,7 @@
 public class GeneralDelegateTest extends InstrumentationTestCase {
 
     private DevicePolicyManager mDpm;
-
-    private static final String EXPECTED_DELEGATION_SCOPES[] = {
-        DELEGATION_APP_RESTRICTIONS,
-        DELEGATION_BLOCK_UNINSTALL,
-        DELEGATION_CERT_INSTALL,
-        DELEGATION_PERMISSION_GRANT,
-        DELEGATION_PACKAGE_ACCESS,
-        DELEGATION_ENABLE_SYSTEM_APP
-    };
+    private static final String PARAM_SCOPES = "scopes";
 
     @Override
     protected void setUp() throws Exception {
@@ -62,12 +40,14 @@
     }
 
     public void testGetsExpectedDelegationScopes() {
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        String[] expectedScopes = arguments.getString(PARAM_SCOPES).split(",");
         List<String> delegatedScopes = mDpm.getDelegatedScopes(null,
                 getInstrumentation().getContext().getPackageName());
 
         assertNotNull("Received null scopes", delegatedScopes);
         MoreAsserts.assertContentsInAnyOrder("Delegated scopes do not match expected scopes",
-                delegatedScopes, EXPECTED_DELEGATION_SCOPES);
+                delegatedScopes, expectedScopes);
     }
 
     public void testDifferentPackageNameThrowsException() {
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java
new file mode 100644
index 0000000..73bf1fc
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.delegate;
+
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.admin.DelegatedAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.NetworkEvent;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests that a delegate app with DELEGATION_NETWORK_LOGGING is able to control and access
+ * network logging.
+ */
+public class NetworkLoggingDelegateTest extends InstrumentationTestCase {
+
+    private static final String TAG = "NetworkLoggingDelegateTest";
+    private static final long TIMEOUT_MIN = 1;
+
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+    private Activity mActivity;
+    private UiDevice mDevice;
+
+
+    private static final String[] URL_LIST = {
+            "example.edu",
+            "ipv6.google.com",
+            "google.co.jp",
+            "google.fr",
+            "google.com.br",
+            "google.com.tr",
+            "google.co.uk",
+            "google.de"
+    };
+
+    public static class NetworkLogsReceiver extends DelegatedAdminReceiver {
+        static CountDownLatch mBatchCountDown;
+        static Throwable mExceptionFromReceiver;
+
+        @Override
+        public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+                int networkLogsCount) {
+            try {
+                DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+                final List<NetworkEvent> events = dpm.retrieveNetworkLogs(null, batchToken);
+                if (events == null || events.size() == 0) {
+                    fail("Failed to retrieve batch of network logs with batch token " + batchToken);
+                }
+            } catch (Throwable e) {
+                mExceptionFromReceiver = e;
+            } finally {
+            mBatchCountDown.countDown();
+            }
+        }
+
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mContext = getInstrumentation().getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+        NetworkLogsReceiver.mBatchCountDown = new CountDownLatch(1);
+        NetworkLogsReceiver.mExceptionFromReceiver = null;
+    }
+
+    public void testCanAccessApis() throws Throwable {
+        assertThat(mDpm.getDelegatedScopes(null, mContext.getPackageName())).contains(
+                DevicePolicyManager.DELEGATION_NETWORK_LOGGING);
+        testNetworkLogging();
+    }
+
+    public void testCannotAccessApis()throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.isNetworkLoggingEnabled(null));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.setNetworkLoggingEnabled(null, true));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.retrieveNetworkLogs(null, 0));
+    }
+
+    public void testNetworkLogging() throws Throwable {
+        mDpm.setNetworkLoggingEnabled(null, true);
+        assertTrue(mDpm.isNetworkLoggingEnabled(null));
+
+        try {
+            for (final String url : URL_LIST) {
+                connectToWebsite(url);
+            }
+            mDevice.executeShellCommand("dpm force-network-logs");
+
+            assertTrue("Delegated app did not receive network logs within time limit",
+                    NetworkLogsReceiver.mBatchCountDown.await(TIMEOUT_MIN, TimeUnit.MINUTES));
+            if (NetworkLogsReceiver.mExceptionFromReceiver != null) {
+                // Rethrow any exceptions that might have happened in the receiver.
+                throw NetworkLogsReceiver.mExceptionFromReceiver;
+            }
+        } finally {
+            mDpm.setNetworkLoggingEnabled(null, false);
+            assertFalse(mDpm.isNetworkLoggingEnabled(null));
+        }
+    }
+
+    private void connectToWebsite(String server) throws Exception {
+        final URL url = new URL("http://" + server);
+        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+        try (AutoCloseable ac = () -> urlConnection.disconnect()){
+            urlConnection.setConnectTimeout(2000);
+            urlConnection.setReadTimeout(2000);
+            urlConnection.getResponseCode();
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to connect to " + server, e);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
index 5cf7f05..2d3a367 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
@@ -33,6 +33,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 23
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
index a7c7470..c19d0c0 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
@@ -33,6 +33,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 23
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/Android.mk
new file mode 100644
index 0000000..82fdba0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/Android.mk
@@ -0,0 +1,40 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsDeviceAdminApp29
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
new file mode 100644
index 0000000..57f4b88
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.deviceadmin29" >
+
+    <!-- STOPSHIP(b/114173216): Uncomment this once Q's API level is finalized -->
+    <!--<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="29"/>-->
+
+    <application
+        android:testOnly="true">
+
+        <uses-library android:name="android.test.runner" />
+        <receiver
+                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+                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>
+
+        <receiver
+                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+                >
+            <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="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.deviceadmin29"
+            android:label="Device Admin CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
index 22eab0b..4c10faa 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
@@ -18,6 +18,7 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.test.AndroidTestCase;
 
@@ -28,6 +29,7 @@
 
     protected String mPackageName;
     protected ComponentName mAdminComponent;
+    protected boolean mHasSecureLockScreen;
 
     public DevicePolicyManager dpm;
 
@@ -38,6 +40,8 @@
         dpm = mContext.getSystemService(DevicePolicyManager.class);
         mPackageName = mContext.getPackageName();
         mAdminComponent = new ComponentName(mContext, AdminReceiver.class);
+        mHasSecureLockScreen = mContext.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
     }
 
     /**
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 714ce10..1e9759c 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
@@ -49,7 +49,7 @@
             if (!shouldResetPasswordThrow()) {
                 fail("Shouldn't throw");
             }
-            MoreAsserts.assertContainsRegex("Admin cannot change current password", e.getMessage());
+            MoreAsserts.assertContainsRegex("Cannot change current password", e.getMessage());
         }
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminTest.java
index 7a03902..2394a9a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminTest.java
@@ -18,6 +18,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 
 /**
  * Device admin device side tests.
@@ -35,10 +36,14 @@
 
         final PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), /* flags =*/ 0);
 
-        assertEquals(getTargetApiLevel(), pi.applicationInfo.targetSdkVersion);
+        assertTrue(getTargetApiLevel() == pi.applicationInfo.targetSdkVersion ||
+                Build.VERSION_CODES.CUR_DEVELOPMENT == pi.applicationInfo.targetSdkVersion);
     }
 
     public void testGetMaximumFailedPasswordsForWipe() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
         dpm.setMaximumFailedPasswordsForWipe(mAdminComponent, 3);
         assertEquals(3, dpm.getMaximumFailedPasswordsForWipe(mAdminComponent));
 
@@ -47,6 +52,9 @@
     }
 
     public void testPasswordHistoryLength() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
         // Password history length restriction is only imposed if password quality is at least
         // numeric.
         dpm.setPasswordQuality(mAdminComponent,
@@ -64,6 +72,9 @@
     }
 
     public void testPasswordExpirationTimeout() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
         long originalValue = dpm.getPasswordExpirationTimeout(mAdminComponent);
         try {
             for (long testLength : new long[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminWithEnterprisePoliciesBlockedTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminWithEnterprisePoliciesBlockedTest.java
new file mode 100644
index 0000000..402ca9f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminWithEnterprisePoliciesBlockedTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.deviceadmin;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+/**
+ * Device admin device side tests with enterprise policies disallowed.
+ */
+public class DeviceAdminWithEnterprisePoliciesBlockedTest extends DeviceAdminTest {
+    public void testCameraDisabled() {
+        boolean originalValue = dpm.getCameraDisabled(mAdminComponent);
+        assertSecurityException(() -> dpm.setCameraDisabled(mAdminComponent, true));
+        assertEquals(originalValue, dpm.getCameraDisabled(mAdminComponent));
+    }
+
+    public void testKeyguardDisabledFeatures() {
+        int originalValue = dpm.getKeyguardDisabledFeatures(mAdminComponent);
+        assertSecurityException(() -> dpm.setKeyguardDisabledFeatures(mAdminComponent,
+            DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL));
+        assertEquals(originalValue, dpm.getKeyguardDisabledFeatures(mAdminComponent));
+    }
+
+    public void testIsActivePasswordSufficient() {
+        assertSecurityException(() -> dpm.isActivePasswordSufficient());
+    }
+
+    @Override
+    public void testPasswordHistoryLength() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        int originalValue = dpm.getPasswordHistoryLength(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordHistoryLength(mAdminComponent, 3));
+        assertEquals(originalValue, dpm.getPasswordHistoryLength(mAdminComponent));
+    }
+
+    public void testPasswordMinimumLength() {
+        int originalValue = dpm.getPasswordMinimumLength(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumLength(mAdminComponent, 4));
+        assertEquals(originalValue, dpm.getPasswordMinimumLength(mAdminComponent));
+    }
+
+    public void testPasswordMinimumLetters() {
+        int originalValue = dpm.getPasswordMinimumLetters(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumLetters(mAdminComponent, 3));
+        assertEquals(originalValue, dpm.getPasswordMinimumLetters(mAdminComponent));
+    }
+
+    public void testPasswordMinimumLowerCase() {
+        int originalValue = dpm.getPasswordMinimumLowerCase(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumLowerCase(mAdminComponent, 3));
+        assertEquals(originalValue, dpm.getPasswordMinimumLowerCase(mAdminComponent));
+    }
+
+    public void testPasswordMinimumNonLetter() {
+        int originalValue = dpm.getPasswordMinimumNonLetter(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumNonLetter(mAdminComponent, 3));
+        assertEquals(originalValue, dpm.getPasswordMinimumNonLetter(mAdminComponent));
+    }
+
+    public void testPasswordMinimumNumeric() {
+        int originalValue = dpm.getPasswordMinimumNumeric(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumNumeric(mAdminComponent, 2));
+        assertEquals(originalValue, dpm.getPasswordMinimumNumeric(mAdminComponent));
+    }
+
+    public void testPasswordMinimumSymbols() {
+        int originalValue = dpm.getPasswordMinimumSymbols(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumSymbols(mAdminComponent, 2));
+        assertEquals(originalValue, dpm.getPasswordMinimumSymbols(mAdminComponent));
+    }
+
+    public void testPasswordMinimumUpperCase() {
+        int originalValue = dpm.getPasswordMinimumUpperCase(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordMinimumUpperCase(mAdminComponent, 3));
+        assertEquals(originalValue, dpm.getPasswordMinimumUpperCase(mAdminComponent));
+    }
+
+    public void testPasswordQuality() {
+        int originalValue = dpm.getPasswordQuality(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordQuality(mAdminComponent,
+            DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC));
+        assertEquals(originalValue, dpm.getPasswordQuality(mAdminComponent));
+    }
+
+    @Override
+    public void testPasswordExpirationTimeout() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        long originalValue = dpm.getPasswordExpirationTimeout(mAdminComponent);
+        assertSecurityException(() -> dpm.setPasswordExpirationTimeout(mAdminComponent, 1234L));
+        assertEquals(originalValue, dpm.getPasswordExpirationTimeout(mAdminComponent));
+    }
+
+    private void assertSecurityException(Runnable r) {
+        boolean securityExceptionThrown = false;
+        try {
+            r.run();
+        } catch (SecurityException e) {
+            securityExceptionThrown = true;
+        }
+
+        assertTrue("Expected SecurityException was not thrown", securityExceptionThrown);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk
index 31e193d..3c2b52b 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk
@@ -16,5 +16,7 @@
 
 include $(CLEAR_VARS)
 
+LOCAL_STATIC_JAVA_LIBRARIES := truth-prebuilt
+
 # Build the test APKs using their own makefiles
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
index 1599d60..7956f04 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -35,7 +35,8 @@
     compatibility-device-util \
     ctstestrunner \
     ub-uiautomator \
-    cts-security-test-support-library
+    cts-security-test-support-library \
+    truth-prebuilt
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.legacy_legacy-support-v4
@@ -44,6 +45,8 @@
 
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/../assets
 
+LOCAL_MIN_SDK_VERSION := 23
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index 481f821..dc7bb51 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -35,7 +35,8 @@
     compatibility-device-util \
     ctstestrunner \
     ub-uiautomator \
-    cts-security-test-support-library
+    cts-security-test-support-library \
+    truth-prebuilt
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.legacy_legacy-support-v4
@@ -44,6 +45,8 @@
 
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/../assets
 
+LOCAL_MIN_SDK_VERSION := 23
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 8c6d3fd..5f7ccfb 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -35,7 +35,8 @@
     compatibility-device-util \
     ctstestrunner \
     ub-uiautomator \
-    cts-security-test-support-library
+    cts-security-test-support-library \
+    truth-prebuilt
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.legacy_legacy-support-v4
@@ -44,6 +45,8 @@
 
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/../assets
 
+LOCAL_MIN_SDK_VERSION := 23
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
index c43856e..5ecdca7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
@@ -14,9 +14,14 @@
 -->
 <device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
     <uses-policies>
+         <watch-login />
          <reset-password />
          <limit-password />
+         <expire-password />
          <disable-keyguard-features />
          <disable-camera />
+         <encrypted-storage />
+         <force-lock />
+         <wipe-data />
     </uses-policies>
 </device-admin>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AccessibilityServicesTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AccessibilityServicesTest.java
new file mode 100644
index 0000000..5647f78
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AccessibilityServicesTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class AccessibilityServicesTest extends BaseDeviceAdminTest {
+    private final ComponentName badAdmin = new ComponentName("com.foo.bar", ".BazQuxReceiver");
+
+    public void testPermittedAccessibilityServices() {
+        // All accessibility services are allowed.
+        mDevicePolicyManager.setPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT, null);
+        assertThat(mDevicePolicyManager.
+                getPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT))
+                .isNull();
+
+        // Only system accessibility services are allowed.
+        mDevicePolicyManager.setPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT,
+                new ArrayList<>());
+        assertThat(mDevicePolicyManager.
+                getPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT))
+                .isEmpty();
+
+        // Some random services.
+        final List<String> packages = Arrays.asList("com.google.pkg.one", "com.google.pkg.two");
+        mDevicePolicyManager.setPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT, packages);
+        assertThat(mDevicePolicyManager.
+                getPermittedAccessibilityServices(ADMIN_RECEIVER_COMPONENT))
+                .containsExactlyElementsIn(packages);
+    }
+
+    public void testPermittedAccessibilityServicesThrowsIfWrongAdmin() {
+        try {
+            mDevicePolicyManager.setPermittedAccessibilityServices(badAdmin, null);
+            fail("setPermittedAccessibilityServices didn't throw when passed invalid admin");
+        } catch (SecurityException expected) {
+        }
+        try {
+            mDevicePolicyManager.getPermittedAccessibilityServices(badAdmin);
+            fail("getPermittedAccessibilityServices didn't throw when passed invalid admin");
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsIsCallerDelegateHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsIsCallerDelegateHelper.java
new file mode 100644
index 0000000..ca28138
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsIsCallerDelegateHelper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.test.InstrumentationTestCase;
+
+/**
+ * Helper to set app restriction managing package for
+ * {@link DevicePolicyManager#isCallerApplicationRestrictionsManagingPackage()}.
+ * <p>The method name starts with "test" to be recognized by {@link InstrumentationTestCase}.
+ */
+public class ApplicationRestrictionsIsCallerDelegateHelper extends BaseDeviceAdminTest {
+
+    private static final String APP_RESTRICTIONS_TARGET_PKG = "com.android.cts.delegate";
+
+    public void testSetApplicationRestrictionsManagingPackageToDelegate()
+            throws NameNotFoundException {
+        mDevicePolicyManager.setApplicationRestrictionsManagingPackage(
+            ADMIN_RECEIVER_COMPONENT, APP_RESTRICTIONS_TARGET_PKG);
+        assertThat(APP_RESTRICTIONS_TARGET_PKG)
+            .isEqualTo(mDevicePolicyManager.getApplicationRestrictionsManagingPackage(
+                ADMIN_RECEIVER_COMPONENT));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
index 5728cf6..891bd72 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
@@ -16,11 +16,11 @@
 
 package com.android.cts.deviceandprofileowner;
 
+import static android.os.UserManager.DISALLOW_AUTOFILL;
 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 
 import android.content.Intent;
-import static android.os.UserManager.DISALLOW_AUTOFILL;
 
 public class AutofillRestrictionsTest extends BaseDeviceAdminTest {
 
@@ -29,9 +29,11 @@
     private static final String AUTOFILL_PACKAGE_NAME = "com.android.cts.devicepolicy.autofillapp";
     private static final String AUTOFILL_ACTIVITY_NAME = AUTOFILL_PACKAGE_NAME + ".SimpleActivity";
 
-    // Currently, autofill_service is a cloned service, so it's only set in the default user.
-    // That might change, so we're using a guard to decide how to set it
-    private final boolean USES_CLONED_SETTINGS = true;
+    // Before, autofill_service was a cloned service, so it was only set in the default user,
+    // and we were using a guard to decide how to set it.
+    // Autofill_service has been changed now to be a per-user service so we are currently
+    // setting this to false.
+    private final boolean USES_CLONED_SETTINGS = false;
 
     int mUserId;
 
@@ -81,8 +83,8 @@
     }
 
     private void enableService() throws Exception {
-        runShellCommand("settings put secure --user %d %s %s default", mUserId, USER_SETUP_COMPLETE,
-                1);
+        runShellCommand("settings put secure --user %d %s %d default",
+                mUserId, USER_SETUP_COMPLETE, 1);
 
         if (USES_CLONED_SETTINGS) {
             runShellCommand("settings put secure %s %s default", AUTOFILL_SERVICE, SERVICE_NAME);
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 a88a463..fe6e0b9 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
@@ -24,12 +24,14 @@
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.compatibility.common.util.SystemUtil;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Base class for profile and device based tests.
@@ -50,6 +52,14 @@
             }
             return uri.getQueryParameter("alias");
         }
+
+        @Override
+        public void onPasswordExpiring(Context context, Intent intent, UserHandle user) {
+            super.onPasswordExpiring(context, intent, user);
+            if (mOnPasswordExpiryTimeoutCalled != null) {
+                mOnPasswordExpiryTimeoutCalled.countDown();
+            }
+        }
     }
 
     public static final String PACKAGE_NAME = BasicAdminReceiver.class.getPackage().getName();
@@ -59,6 +69,7 @@
     protected DevicePolicyManager mDevicePolicyManager;
     protected UserManager mUserManager;
     protected Context mContext;
+    static CountDownLatch mOnPasswordExpiryTimeoutCalled;
 
     private final String mTag = getClass().getSimpleName();
 
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
new file mode 100644
index 0000000..756058c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
@@ -0,0 +1,60 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
+import android.app.admin.DevicePolicyManager;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A helper class for setting the DelegatedCertInstaller to the desired package.
+ * Does not actually contain any tests.
+ */
+public class DelegatedCertInstallerHelper extends BaseDeviceAdminTest {
+    private static final String CERT_INSTALLER_PACKAGE = "com.android.cts.certinstaller";
+
+    private static final List<String> CERT_INSTALL_SCOPES = Arrays.asList(DELEGATION_CERT_INSTALL);
+
+    private DevicePolicyManager mDpm;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testManualSetCertInstallerDelegate() {
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE,
+                CERT_INSTALL_SCOPES);
+        assertTrue(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL).contains(CERT_INSTALLER_PACKAGE));
+    }
+
+    public void testManualClearCertInstallerDelegate() {
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE, Arrays.asList());
+        assertFalse(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL).contains(CERT_INSTALLER_PACKAGE));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index 99b310e..bc33910 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -16,7 +16,10 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import android.app.KeyguardManager;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -25,7 +28,6 @@
 import android.content.IntentFilter;
 import android.os.Build;
 import android.os.Process;
-import android.os.UserHandle;
 import android.security.KeyChainException;
 import android.test.MoreAsserts;
 
@@ -35,8 +37,8 @@
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -69,6 +71,8 @@
     private static final ComponentName CERT_INSTALLER_COMPONENT = new ComponentName(
             CERT_INSTALLER_PACKAGE, "com.android.cts.certinstaller.CertInstallerReceiver");
 
+    private static final List<String> CERT_INSTALL_SCOPES = Arrays.asList(DELEGATION_CERT_INSTALL);
+
     /*
      * The CA and keypair below are generated with:
      *
@@ -244,7 +248,6 @@
                 mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
 
         // Exercise installKeyPair()
-        checkKeyguardPrecondition();
         installKeyPair(TEST_KEY, TEST_CERT, alias);
         assertResult("installKeyPair", true);
     }
@@ -275,17 +278,39 @@
         }
     }
 
-    /**
-     * installKeyPair() requires the system to have a lockscreen password, which should have been
-     * set by the host side test.
-     */
-    private void checkKeyguardPrecondition() throws InterruptedException {
-        KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        if (!km.isKeyguardSecure()) {
-            Thread.sleep(5000);
-          }
-          assertTrue("A lockscreen password is required before keypair can be installed",
-                          km.isKeyguardSecure());
+    public void testSettingDelegatedCertInstallerAPICompatibility_oldSetNewGet() {
+        // Set a delegated cert installer using the deprecated API and verify that the same
+        // package is considered as the delegated cert installer using the new API.
+        mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
+        assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isEqualTo(
+                CERT_INSTALLER_PACKAGE);
+        assertThat(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL)).containsExactly(CERT_INSTALLER_PACKAGE);
+
+        // Remove a delegate using the old API, make sure no delegates are found using
+        // the new API.
+        mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
+        assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isNull();
+        assertThat(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL)).isEmpty();
+    }
+
+    public void testSettingDelegatedCertInstallerAPICompatibility_newSetOldGet() {
+        // Set a delegate using the new API, verify that the deprecated API returns the same
+        // delegate.
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE,
+                CERT_INSTALL_SCOPES);
+        assertThat(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL)).containsExactly(CERT_INSTALLER_PACKAGE);
+        assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isEqualTo(
+                CERT_INSTALLER_PACKAGE);
+
+        // Remove the delegate using the new API, verify that the deprecated API returns null
+        // as the current delegated cert installer.
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE, Arrays.asList());
+        assertThat(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
+                DELEGATION_CERT_INSTALL)).isEmpty();
+        assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isNull();
     }
 
     private void installCaCert(byte[] cert) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
index c9cf648..58cf561 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
@@ -16,11 +16,15 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
 import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
+import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION;
+import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -29,17 +33,22 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.test.MoreAsserts;
+import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+
 /**
  * Test that an app granted delegation scopes via {@link DevicePolicyManager#setDelegatedScopes} is
  * notified of its new scopes by a broadcast.
  */
 public class DelegationTest extends BaseDeviceAdminTest {
+    private static final String TAG = "DelegationTest";
 
     private static final String DELEGATE_PKG = "com.android.cts.delegate";
     private static final String DELEGATE_ACTIVITY_NAME =
@@ -87,6 +96,10 @@
     @Override
     public void tearDown() throws Exception {
         mContext.unregisterReceiver(mReceiver);
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT,
+                TEST_PKG, Collections.emptyList());
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT,
+                DELEGATE_PKG, Collections.emptyList());
         super.tearDown();
     }
 
@@ -174,6 +187,55 @@
                 .contains(TEST_PKG));
     }
 
+    public void testDeviceOwnerOnlyDelegationsOnlyPossibleToBeSetByDeviceOwner() throws Exception {
+        final String doDelegations[] = {
+                DELEGATION_NETWORK_LOGGING,
+                DELEGATION_PACKAGE_INSTALLATION};
+        final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
+                mContext.getPackageName());
+        for (String scope : doDelegations) {
+            try {
+                mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_PKG,
+                        Collections.singletonList(scope));
+                if (!isDeviceOwner()) {
+                    fail("PO shouldn't be able to delegate "+ scope);
+                }
+            } catch (SecurityException e) {
+                if (isDeviceOwner) {
+                    fail("DO fails to delegate " + scope + " exception: " + e);
+                    Log.e(TAG, "DO fails to delegate " + scope, e);
+                }
+            }
+        }
+    }
+
+    public void testExclusiveDelegations() throws Exception {
+        final List<String> exclusiveDelegations = new ArrayList<>(Arrays.asList(
+                DELEGATION_CERT_SELECTION));
+        if (mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName())) {
+            exclusiveDelegations.add(DELEGATION_NETWORK_LOGGING);
+        }
+        for (String scope : exclusiveDelegations) {
+            testExclusiveDelegation(scope);
+        }
+    }
+
+    private void testExclusiveDelegation(String scope) throws Exception {
+
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT,
+                DELEGATE_PKG, Collections.singletonList(scope));
+        // Set exclusive scope on TEST_PKG should lead to the scope being removed from the
+        // previous delegate DELEGATE_PKG
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT,
+                TEST_PKG, Collections.singletonList(scope));
+
+
+        assertThat(mDevicePolicyManager.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, TEST_PKG))
+                .containsExactly(scope);
+        assertThat(mDevicePolicyManager.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_PKG))
+                .isEmpty();
+    }
+
     private List<String> getDelegatePackages(String scope) {
         return mDevicePolicyManager.getDelegatePackages(ADMIN_RECEIVER_COMPONENT, scope);
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
new file mode 100755
index 0000000..bce0a08
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DeviceIdAttestationTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.keystore.cts.KeyGenerationUtils;
+import android.security.AttestedKeyPair;
+
+public class DeviceIdAttestationTest extends BaseDeviceAdminTest {
+    // Test that key generation when requesting the serial number to be included in the
+    // attestation record fails if the profile owner has not been explicitly granted access
+    // to it.
+    public void testFailsWithoutProfileOwnerIdsGrant() {
+        KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingFailure(mDevicePolicyManager,
+                getWho());
+    }
+
+    // Test that the same key generation request succeeds once the profile owner was granted
+    // access to device identifiers.
+    public void testSucceedsWithProfileOwnerIdsGrant() {
+        if (mDevicePolicyManager.isDeviceIdAttestationSupported()) {
+            KeyGenerationUtils.generateKeyWithDeviceIdAttestationExpectingSuccess(
+                    mDevicePolicyManager, getWho());
+        }
+    }
+
+    protected ComponentName getWho() {
+        return ADMIN_RECEIVER_COMPONENT;
+    }
+}
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
new file mode 100644
index 0000000..189c619
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_PROMPT;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.Settings.Secure.DEFAULT_INPUT_METHOD;
+import static android.provider.Settings.Secure.SKIP_FIRST_USE_HINTS;
+import static java.util.stream.Collectors.toList;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import java.util.List;
+
+/**
+ * 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
+ * are logged. Note that the metrics verification is done on the host-side.
+ */
+public class DevicePolicyLoggingTest extends BaseDeviceAdminTest {
+
+    public static final String PACKAGE_NAME = "com.android.cts.permissionapp";
+    public static final String PARAM_APP_TO_ENABLE = "app_to_enable";
+
+    public void testPasswordMethodsLogged() {
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 13);
+        mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, 14);
+        mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, 15);
+        mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, 16);
+        mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, 17);
+        mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, 18);
+        mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, 19);
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                PASSWORD_QUALITY_UNSPECIFIED);
+    }
+
+    public void testLockNowLogged() {
+        mDevicePolicyManager.lockNow(0);
+    }
+
+    public void testSetKeyguardDisabledFeaturesLogged() {
+        mDevicePolicyManager.setKeyguardDisabledFeatures(
+                ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_FEATURES_NONE);
+        mDevicePolicyManager.setKeyguardDisabledFeatures(
+                ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_FINGERPRINT);
+        mDevicePolicyManager.setKeyguardDisabledFeatures(
+                ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_TRUST_AGENTS);
+        mDevicePolicyManager.setKeyguardDisabledFeatures(ADMIN_RECEIVER_COMPONENT,
+                KEYGUARD_DISABLE_FEATURES_NONE);
+    }
+
+    public void testSetUserRestrictionLogged() {
+        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_CONFIG_LOCATION);
+        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_CONFIG_LOCATION);
+
+        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_ADJUST_VOLUME);
+        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_ADJUST_VOLUME);
+
+        mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_AUTOFILL);
+        mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_AUTOFILL);
+    }
+
+    public void testSetSecureSettingLogged()
+            throws InterruptedException {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        final int skipFirstUseHintsInitial =
+                Settings.Secure.getInt(contentResolver, SKIP_FIRST_USE_HINTS, 0);
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                SKIP_FIRST_USE_HINTS, "1");
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                SKIP_FIRST_USE_HINTS, "0");
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                SKIP_FIRST_USE_HINTS, String.valueOf(skipFirstUseHintsInitial));
+
+        final String defaultInputMethodInitial =
+                Settings.Secure.getString(contentResolver, DEFAULT_INPUT_METHOD);
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                DEFAULT_INPUT_METHOD, "com.example.input");
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                DEFAULT_INPUT_METHOD, null);
+        mDevicePolicyManager.setSecureSetting(ADMIN_RECEIVER_COMPONENT,
+                DEFAULT_INPUT_METHOD, defaultInputMethodInitial);
+    }
+
+    public void testSetPermissionPolicyLogged() {
+        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_POLICY_AUTO_DENY);
+        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_POLICY_AUTO_GRANT);
+        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_POLICY_PROMPT);
+    }
+
+    public void testSetPermissionGrantStateLogged() throws InterruptedException {
+        mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PACKAGE_NAME, READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+        mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME,
+                READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME,
+                READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+    }
+
+    public void testSetAutoTimeRequired() {
+        final boolean initialValue = mDevicePolicyManager.getAutoTimeRequired();
+        mDevicePolicyManager.setAutoTimeRequired(ADMIN_RECEIVER_COMPONENT, true);
+        mDevicePolicyManager.setAutoTimeRequired(ADMIN_RECEIVER_COMPONENT, false);
+        mDevicePolicyManager.setAutoTimeRequired(ADMIN_RECEIVER_COMPONENT, initialValue);
+    }
+
+    public void testEnableSystemAppLogged() {
+        final String systemPackageToEnable =
+                InstrumentationRegistry.getArguments().getString(PARAM_APP_TO_ENABLE);
+        mDevicePolicyManager.enableSystemApp(ADMIN_RECEIVER_COMPONENT, systemPackageToEnable);
+    }
+
+    public void testEnableSystemAppWithIntentLogged() {
+        final String systemPackageToEnable =
+                InstrumentationRegistry.getArguments().getString(PARAM_APP_TO_ENABLE);
+        final Intent intent =
+                mContext.getPackageManager().getLaunchIntentForPackage(systemPackageToEnable);
+        mDevicePolicyManager.enableSystemApp(ADMIN_RECEIVER_COMPONENT, intent);
+    }
+
+    public void testSetUninstallBlockedLogged() {
+        mDevicePolicyManager.setUninstallBlocked(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME, true);
+        mDevicePolicyManager.setUninstallBlocked(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME, false);
+    }
+
+    public void testDisallowAdjustVolumeMutedLogged() {
+        final boolean initialValue =
+                mDevicePolicyManager.isMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT);
+        mDevicePolicyManager.setMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT, false);
+        mDevicePolicyManager.setMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT, true);
+        mDevicePolicyManager.setMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT, initialValue);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetCurrentFailedPasswordAttemptsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetCurrentFailedPasswordAttemptsTest.java
new file mode 100644
index 0000000..e0b6c21
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetCurrentFailedPasswordAttemptsTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.deviceandprofileowner;
+
+public class GetCurrentFailedPasswordAttemptsTest extends BaseDeviceAdminTest {
+
+    public void testNoFailedPasswordAttempts() {
+        assertEquals(0, mDevicePolicyManager.getCurrentFailedPasswordAttempts());
+    }
+
+    public void testOneFailedPasswordAttempt() {
+        assertEquals(1, mDevicePolicyManager.getCurrentFailedPasswordAttempts());
+    }
+
+    public void testTwoFailedPasswordAttempts() {
+        assertEquals(2, mDevicePolicyManager.getCurrentFailedPasswordAttempts());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetPasswordExpirationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetPasswordExpirationTest.java
new file mode 100644
index 0000000..3ce561a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/GetPasswordExpirationTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.lang.Math.abs;
+
+public class GetPasswordExpirationTest extends BaseDeviceAdminTest {
+    final long TIMEOUT_RESET_TEST = 86400000L /* 1 day */;
+
+    public void testGetPasswordExpiration() throws Exception {
+        final long[] testTimeouts = new long[] {
+                86400000L /* 1 day */, 864000000L /* 10 days */, 8640000000L /* 100 days */};
+        for (long testTimeout : testTimeouts) {
+            mDevicePolicyManager.setPasswordExpirationTimeout(
+                    ADMIN_RECEIVER_COMPONENT, testTimeout);
+            checkPasswordExpiration("Password expiration time incorrect", testTimeout, 5000);
+        }
+        // Set password expiration timeout to 0 clears the expiration date.
+        mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, 0L);
+        assertThat(mDevicePolicyManager.getPasswordExpiration(ADMIN_RECEIVER_COMPONENT))
+                .isEqualTo(0L);
+    }
+
+    public void testGetPasswordExpirationUpdatedAfterPasswordReset_beforeReset() throws Exception {
+        mDevicePolicyManager.setPasswordExpirationTimeout(
+                ADMIN_RECEIVER_COMPONENT, TIMEOUT_RESET_TEST);
+        checkPasswordExpiration("Password expiration time incorrect", TIMEOUT_RESET_TEST, 5000);
+    }
+
+    public void testGetPasswordExpirationUpdatedAfterPasswordReset_afterReset() throws Exception {
+        checkPasswordExpiration("Password expiration time not refreshed correctly"
+                + " after reseting password", TIMEOUT_RESET_TEST, 10000);
+    }
+
+    private void checkPasswordExpiration(String error, long timeout, long tolerance) {
+        assertTrue(error, abs(System.currentTimeMillis() + timeout
+                - mDevicePolicyManager.getPasswordExpiration(
+                ADMIN_RECEIVER_COMPONENT)) <= tolerance);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java
new file mode 100644
index 0000000..ed5047c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class InputMethodsTest extends BaseDeviceAdminTest {
+    private final ComponentName badAdmin = new ComponentName("com.foo.bar", ".BazQuxReceiver");
+
+    public void testPermittedInputMethods() {
+        // All input methods are allowed.
+        mDevicePolicyManager.setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT, null);
+        assertThat(
+                mDevicePolicyManager.getPermittedInputMethods(ADMIN_RECEIVER_COMPONENT)).isNull();
+
+        // Only system input methods are allowed.
+        mDevicePolicyManager.setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT, new ArrayList<>());
+        assertThat(
+                mDevicePolicyManager.getPermittedInputMethods(ADMIN_RECEIVER_COMPONENT)).isEmpty();
+
+        // Some random methods.
+        final List<String> packages = Arrays.asList("com.google.pkg.one", "com.google.pkg.two");
+        mDevicePolicyManager.setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT, packages);
+        assertThat(
+                mDevicePolicyManager.getPermittedInputMethods(ADMIN_RECEIVER_COMPONENT))
+                .containsExactlyElementsIn(packages);
+    }
+
+    public void testPermittedInputMethodsThrowsIfWrongAdmin() {
+        try {
+            mDevicePolicyManager.setPermittedInputMethods(badAdmin, null);
+            fail("setPermittedInputMethods didn't throw when passed invalid admin");
+        } catch (SecurityException expected) {
+        }
+        try {
+            mDevicePolicyManager.getPermittedInputMethods(badAdmin);
+            fail("getPermittedInputMethods didn't throw when passed invalid admin");
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index 39dbf0a..85b2903 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -20,6 +20,8 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.keystore.cts.CertificateUtils.createCertificate;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,11 +37,10 @@
 import android.security.KeyChainException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
+import android.security.keystore.StrongBoxUnavailableException;
 import android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
-
 import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -61,12 +62,10 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
 import javax.security.auth.x500.X500Principal;
 
 public class KeyManagementTest extends BaseDeviceAdminTest {
@@ -112,22 +111,22 @@
 
     public void testCanInstallAndRemoveValidRsaKeypair() throws Exception {
         final String alias = "com.android.test.valid-rsa-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
+        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, cert, alias));
+        assertThat(mDevicePolicyManager.installKeyPair(getWho(), privKey, cert, alias)).isTrue();
         try {
             // Request and retrieve using the alias.
             assertGranted(alias, false);
-            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            assertThat(new KeyChainAliasFuture(alias).get()).isEqualTo(alias);
             assertGranted(alias, true);
 
             // Verify key is at least something like the one we put in.
-            assertEquals(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm(), "RSA");
+            assertThat(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm()).isEqualTo("RSA");
         } finally {
             // Delete regardless of whether the test succeeded.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
         // Verify alias is actually deleted.
         assertGranted(alias, false);
@@ -136,25 +135,29 @@
     public void testCanInstallWithAutomaticAccess() throws Exception {
         final String grant = "com.android.test.autogrant-key-1";
         final String withhold = "com.android.test.nongrant-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
+        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypairs.
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
-                grant, true));
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
-                withhold, false));
+        assertThat(
+                mDevicePolicyManager.installKeyPair(
+                        getWho(), privKey, new Certificate[] {cert}, grant, true))
+                .isTrue();
+        assertThat(
+                mDevicePolicyManager.installKeyPair(
+                        getWho(), privKey, new Certificate[] {cert}, withhold, false))
+                .isTrue();
         try {
             // Verify only the requested key was actually granted.
             assertGranted(grant, true);
             assertGranted(withhold, false);
 
             // Verify the granted key is actually obtainable in PrivateKey form.
-            assertEquals(KeyChain.getPrivateKey(mActivity, grant).getAlgorithm(), "RSA");
+            assertThat(KeyChain.getPrivateKey(mActivity, grant).getAlgorithm()).isEqualTo("RSA");
         } finally {
             // Delete both keypairs.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), grant));
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), withhold));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), grant)).isTrue();
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), withhold)).isTrue();
         }
         // Verify they're actually gone.
         assertGranted(grant, false);
@@ -165,7 +168,7 @@
         final Collection<Certificate> certs = loadCertificatesFromAsset(assetName);
         final ArrayList<Certificate> certChain = new ArrayList(certs);
         // Some sanity check on the cert chain
-        assertTrue(certs.size() > 1);
+        assertThat(certs.size()).isGreaterThan(1);
         for (int i = 1; i < certChain.size(); i++) {
             certChain.get(i - 1).verify(certChain.get(i).getPublicKey());
         }
@@ -180,20 +183,20 @@
         final String alias = "com.android.test.clientkeychain";
 
         // Install keypairs.
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
+        assertThat(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true))
+                .isTrue();
         try {
             // Verify only the requested key was actually granted.
             assertGranted(alias, true);
 
             // Verify the granted key is actually obtainable in PrivateKey form.
-            assertEquals(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm(), "RSA");
+            assertThat(KeyChain.getPrivateKey(mActivity, alias).getAlgorithm()).isEqualTo("RSA");
 
             // Verify the certificate chain is correct
-            X509Certificate[] returnedCerts = KeyChain.getCertificateChain(mActivity, alias);
-            assertTrue(Arrays.equals(certChain, returnedCerts));
+            assertThat(KeyChain.getCertificateChain(mActivity, alias)).isEqualTo(certChain);
         } finally {
             // Delete both keypairs.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
         // Verify they're actually gone.
         assertGranted(alias, false);
@@ -201,28 +204,32 @@
 
     public void testGrantsDoNotPersistBetweenInstallations() throws Exception {
         final String alias = "com.android.test.persistent-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
+        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
-                alias, true));
+        assertThat(
+                mDevicePolicyManager.installKeyPair(
+                        getWho(), privKey, new Certificate[] {cert}, alias, true))
+                .isTrue();
         try {
             assertGranted(alias, true);
         } finally {
             // Delete and verify.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
         assertGranted(alias, false);
 
         // Install again.
-        assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
-                alias, false));
+        assertThat(
+                mDevicePolicyManager.installKeyPair(
+                        getWho(), privKey, new Certificate[] {cert}, alias, false))
+                .isTrue();
         try {
             assertGranted(alias, false);
         } finally {
             // Delete.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
@@ -255,20 +262,22 @@
 
     public void testNotUserSelectableAliasCanBeChosenViaPolicy() throws Exception {
         final String alias = "com.android.test.not-selectable-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
+        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
-        assertTrue(mDevicePolicyManager.installKeyPair(
-            getWho(), privKey, new Certificate[] {cert}, alias, 0));
+        assertThat(
+                mDevicePolicyManager.installKeyPair(
+                        getWho(), privKey, new Certificate[] {cert}, alias, 0))
+                .isTrue();
         try {
             // Request and retrieve using the alias.
             assertGranted(alias, false);
-            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            assertThat(new KeyChainAliasFuture(alias).get()).isEqualTo(alias);
             assertGranted(alias, true);
         } finally {
             // Delete regardless of whether the test succeeded.
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
@@ -286,7 +295,7 @@
         Signature verify = Signature.getInstance(algoIdentifier);
         verify.initVerify(publicKey);
         verify.update(data);
-        assertTrue(verify.verify(signature));
+        assertThat(verify.verify(signature)).isTrue();
     }
 
     void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
@@ -310,26 +319,24 @@
         final String alias = "com.android.test.generated-rsa-1";
         try {
             AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                    getWho(), "RSA", buildRsaKeySpec(alias, false /* useStrongBox */),
-                    0 /* idAttestationFlags */);
-            assertNotNull(generated);
+                    getWho(), "RSA", buildRsaKeySpec(alias, false /* useStrongBox */), 0);
+            assertThat(generated).isNotNull();
             verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
         } finally {
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
     public void testCanGenerateRSAKeyPairUsingStrongBox() throws Exception {
         final String alias = "com.android.test.generated-rsa-sb-1";
-        AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                getWho(), "RSA", buildRsaKeySpec(alias, true /* useStrongBox */),
-                0 /* idAttestationFlags */);
-        if (generated == null) {
-            assertFalse(hasStrongBox());
-            return;
+        try {
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "RSA", buildRsaKeySpec(alias, true /* useStrongBox */), 0);
+            verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
+        } catch (StrongBoxUnavailableException expected) {
+            assertThat(hasStrongBox()).isFalse();
         }
-        verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
-        assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
     }
 
     private KeyGenParameterSpec buildEcKeySpec(String alias, boolean useStrongBox) {
@@ -345,25 +352,24 @@
         final String alias = "com.android.test.generated-ec-1";
         try {
             AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                    getWho(), "EC", buildEcKeySpec(alias, false /* useStrongBox */),
-                    0 /* idAttestationFlags */);
-            assertNotNull(generated);
+                    getWho(), "EC", buildEcKeySpec(alias, false /* useStrongBox */), 0);
+            assertThat(generated).isNotNull();
             verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
         } finally {
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
     public void testCanGenerateECKeyPairUsingStrongBox() throws Exception {
         final String alias = "com.android.test.generated-ec-sb-1";
-        AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                getWho(), "EC", buildEcKeySpec(alias, true /* useStrongBox */), 0);
-        if (generated == null) {
-            assertFalse(hasStrongBox());
-            return;
+        try {
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", buildEcKeySpec(alias, true /* useStrongBox */), 0);
+            verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
+        } catch (StrongBoxUnavailableException expected) {
+            assertThat(hasStrongBox()).isFalse();
         }
-        verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
-        assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
     }
 
     private void validateDeviceIdAttestationData(Certificate leaf,
@@ -371,42 +377,39 @@
             throws CertificateParsingException {
         Attestation attestationRecord = new Attestation((X509Certificate) leaf);
         AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
-        assertNotNull(teeAttestation);
-
+        assertThat(teeAttestation).isNotNull();
         validateBrandAttestationRecord(teeAttestation);
-        assertEquals(Build.DEVICE, teeAttestation.getDevice());
-        assertEquals(Build.PRODUCT, teeAttestation.getProduct());
-        assertEquals(Build.MANUFACTURER, teeAttestation.getManufacturer());
-        assertEquals(Build.MODEL, teeAttestation.getModel());
-        assertEquals(expectedSerial, teeAttestation.getSerialNumber());
-        assertEquals(expectedImei, teeAttestation.getImei());
-        assertEquals(expectedMeid, teeAttestation.getMeid());
+        assertThat(teeAttestation.getDevice()).isEqualTo(Build.DEVICE);
+        assertThat(teeAttestation.getProduct()).isEqualTo(Build.PRODUCT);
+        assertThat(teeAttestation.getManufacturer()).isEqualTo(Build.MANUFACTURER);
+        assertThat(teeAttestation.getModel()).isEqualTo(Build.MODEL);
+        assertThat(teeAttestation.getSerialNumber()).isEqualTo(expectedSerial);
+        assertThat(teeAttestation.getImei()).isEqualTo(expectedImei);
+        assertThat(teeAttestation.getMeid()).isEqualTo(expectedMeid);
     }
 
     private void validateBrandAttestationRecord(AuthorizationList teeAttestation) {
         if (!Build.MODEL.equals("Pixel 2")) {
-            assertEquals(Build.BRAND, teeAttestation.getBrand());
+            assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
         } else {
-            assertTrue(teeAttestation.getBrand().equals(Build.BRAND)
-                    || teeAttestation.getBrand().equals("htc"));
+            assertThat(teeAttestation.getBrand()).isAnyOf(Build.BRAND, "htc");
         }
     }
 
-    private void validateAttestationRecord(List<Certificate> attestation,
-            byte[] providedChallenge) throws CertificateParsingException {
-        assertNotNull(attestation);
-        assertTrue(attestation.size() >= 2);
+    private void validateAttestationRecord(List<Certificate> attestation, byte[] providedChallenge)
+            throws CertificateParsingException {
+        assertThat(attestation).isNotNull();
+        assertThat(attestation.size()).isGreaterThan(1);
         X509Certificate leaf = (X509Certificate) attestation.get(0);
         Attestation attestationRecord = new Attestation(leaf);
-        assertTrue(Arrays.equals(providedChallenge,
-                    attestationRecord.getAttestationChallenge()));
+        assertThat(attestationRecord.getAttestationChallenge()).isEqualTo(providedChallenge);
     }
 
     private void validateSignatureChain(List<Certificate> chain, PublicKey leafKey)
             throws GeneralSecurityException {
         X509Certificate leaf = (X509Certificate) chain.get(0);
         PublicKey keyFromCert = leaf.getPublicKey();
-        assertTrue(Arrays.equals(keyFromCert.getEncoded(), leafKey.getEncoded()));
+        assertThat(keyFromCert.getEncoded()).isEqualTo(leafKey.getEncoded());
         // Check that the certificate chain is valid.
         for (int i = 1; i < chain.size(); i++) {
             X509Certificate intermediate = (X509Certificate) chain.get(i);
@@ -455,37 +458,35 @@
             KeyGenParameterSpec spec = specBuilder.build();
             AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
                     getWho(), keyAlgorithm, spec, deviceIdAttestationFlags);
-            // Bail out early if StrongBox was requested and generation failed.
-            // Note the underlying assumption that StrongBox supports key attestation _and_
-            // Device ID attestation (i.e. no StrongBox implementations that do not implement
-            // Device ID attestation).
-            // If generation has failed and StrongBox was requested, it is then a failure
-            // regardless of the kind of attestation requested.
-            if (useStrongBox && generated == null) {
-                assertFalse("StrongBox-backed key attestation must not fail if the device " +
-                        "declares support for StrongBox", hasStrongBox());
-                return null;
-            }
-
             // If Device ID attestation was requested, check it succeeded if and only if device ID
             // attestation is supported.
             if (isDeviceIdAttestationRequested(deviceIdAttestationFlags)) {
                 if (generated == null) {
-                    assertFalse(String.format("Failed getting Device ID attestation for key " +
-                                "algorithm %s, with flags %s, despite device declaring support.",
-                                keyAlgorithm, deviceIdAttestationFlags),
-                            isDeviceIdAttestationSupported());
+                    assertWithMessage(
+                            String.format(
+                                    "Failed getting Device ID attestation for key "
+                                    + "algorithm %s, with flags %s, despite device declaring support.",
+                                    keyAlgorithm, deviceIdAttestationFlags))
+                            .that(isDeviceIdAttestationSupported())
+                            .isFalse();
                     return null;
                 } else {
-                    assertTrue(String.format("Device ID attestation for key " +
-                                "algorithm %s, with flags %d should not have succeeded.",
-                                keyAlgorithm, deviceIdAttestationFlags),
-                            isDeviceIdAttestationSupported());
+                    assertWithMessage(
+                            String.format(
+                                    "Device ID attestation for key "
+                                    + "algorithm %s, with flags %d should not have succeeded.",
+                                    keyAlgorithm, deviceIdAttestationFlags))
+                            .that(isDeviceIdAttestationSupported())
+                            .isTrue();
                 }
             } else {
-                assertNotNull(
-                        String.format("Key generation (of type %s) must succeed when Device ID " +
-                            "attestation was not requested.", keyAlgorithm), generated);
+                assertWithMessage(
+                        String.format(
+                                "Key generation (of type %s) must succeed when Device ID "
+                                + "attestation was not requested.",
+                                keyAlgorithm))
+                        .that(generated)
+                        .isNotNull();
             }
             final KeyPair keyPair = generated.getKeyPair();
             verifySignatureOverData(signatureAlgorithm, keyPair);
@@ -494,13 +495,17 @@
             validateSignatureChain(attestation, keyPair.getPublic());
             return attestation.get(0);
         } catch (UnsupportedOperationException ex) {
-            assertTrue(String.format("Unexpected failure while generating key %s with ID flags %d: %s",
-                        keyAlgorithm, deviceIdAttestationFlags, ex),
-                    isDeviceIdAttestationRequested(deviceIdAttestationFlags) &&
-                    !isDeviceIdAttestationSupported());
+            assertWithMessage(
+                    String.format(
+                            "Unexpected failure while generating key %s with ID flags %d: %s",
+                            keyAlgorithm, deviceIdAttestationFlags, ex))
+                    .that(
+                            isDeviceIdAttestationRequested(deviceIdAttestationFlags)
+                            && !isDeviceIdAttestationSupported())
+                    .isTrue();
             return null;
         } finally {
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
@@ -509,19 +514,32 @@
      * algorithms.
      */
     public void testCanGenerateKeyPairWithKeyAttestation() throws Exception {
-        for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
-            assertNotNull(generateKeyAndCheckAttestation(
-                    supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
-                    supportedKey.signaturePaddingSchemes, false /* useStrongBox */, 0));
+        for (SupportedKeyAlgorithm supportedKey : SUPPORTED_KEY_ALGORITHMS) {
+            assertThat(
+                    generateKeyAndCheckAttestation(
+                            supportedKey.keyAlgorithm,
+                            supportedKey.signatureAlgorithm,
+                            supportedKey.signaturePaddingSchemes,
+                            false /* useStrongBox */,
+                            0))
+                    .isNotNull();
         }
     }
 
     public void testCanGenerateKeyPairWithKeyAttestationUsingStrongBox() throws Exception {
-        for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
-            generateKeyAndCheckAttestation(
-                    supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
-                    supportedKey.signaturePaddingSchemes, true /* useStrongBox */,
-                    0 /* idAttestationFlags */);
+        try {
+            for (SupportedKeyAlgorithm supportedKey : SUPPORTED_KEY_ALGORITHMS) {
+                assertThat(
+                        generateKeyAndCheckAttestation(
+                                supportedKey.keyAlgorithm,
+                                supportedKey.signatureAlgorithm,
+                                supportedKey.signaturePaddingSchemes,
+                                true /* useStrongBox */,
+                                0))
+                        .isNotNull();
+            }
+        } catch (StrongBoxUnavailableException expected) {
+            assertThat(hasStrongBox()).isFalse();
         }
     }
 
@@ -536,9 +554,11 @@
         if (isDeviceOwner()) {
             modesToTest.add(ID_TYPE_SERIAL);
             // Get IMEI and MEID of the device.
-            TelephonyManager telephonyService = (TelephonyManager) mActivity.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            assertNotNull("Need to be able to read device identifiers", telephonyService);
+            TelephonyManager telephonyService =
+                    (TelephonyManager) mActivity.getSystemService(Context.TELEPHONY_SERVICE);
+            assertWithMessage("Need to be able to read device identifiers")
+                    .that(telephonyService)
+                    .isNotNull();
             imei = telephonyService.getImei(0);
             meid = telephonyService.getMeid(0);
             // If the device has a valid IMEI it must support attestation for it.
@@ -571,15 +591,12 @@
                     if (attestation == null && !isDeviceIdAttestationSupported()) {
                         continue;
                     }
-                    // The attestation must only be null if StrongBox attestation was requested,
-                    // but StrongBox is not available on the device.
-                    if (attestation == null && useStrongBox) {
-                        assertFalse(hasStrongBox());
-                        continue;
-                    }
-                    assertNotNull(String.format(
-                            "Attestation should be valid for key %s with attestation modes %s",
-                            supportedKey.keyAlgorithm, devIdOpt), attestation);
+                    assertWithMessage(
+                            String.format(
+                                    "Attestation should be valid for key %s with attestation modes %s",
+                                    supportedKey.keyAlgorithm, devIdOpt))
+                            .that(attestation)
+                            .isNotNull();
                     // Set the expected values for serial, IMEI and MEID depending on whether
                     // attestation for them was requested.
                     String expectedSerial = null;
@@ -600,7 +617,10 @@
             } catch (UnsupportedOperationException expected) {
                 // Make sure the test only fails if the device is not meant to support Device
                 // ID attestation.
-                assertFalse(isDeviceIdAttestationSupported());
+                assertThat(isDeviceIdAttestationSupported()).isFalse();
+            } catch (StrongBoxUnavailableException expected) {
+                // This exception must only be thrown if StrongBox attestation was requested.
+                assertThat(useStrongBox && !hasStrongBox()).isTrue();
             }
         }
     }
@@ -620,8 +640,9 @@
         int[] forbiddenModes = new int[] {ID_TYPE_SERIAL, ID_TYPE_IMEI, ID_TYPE_MEID};
         for (int i = 0; i < forbiddenModes.length; i++) {
             try {
-                for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
-                    generateKeyAndCheckAttestation(supportedKey.keyAlgorithm,
+                for (SupportedKeyAlgorithm supportedKey : SUPPORTED_KEY_ALGORITHMS) {
+                    generateKeyAndCheckAttestation(
+                            supportedKey.keyAlgorithm,
                             supportedKey.signatureAlgorithm,
                             supportedKey.signaturePaddingSchemes,
                             false /* useStrongBox */,
@@ -630,7 +651,8 @@
                             + "possible from profile owner");
                 }
             } catch (SecurityException e) {
-                assertTrue(e.getMessage().contains("does not own the device"));
+                assertThat(e.getMessage()).contains(
+                        "Profile Owner is not allowed to access Device IDs.");
             }
         }
     }
@@ -644,9 +666,9 @@
                     .setDigests(KeyProperties.DIGEST_SHA256)
                     .build();
 
-            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                    getWho(), "EC", spec, 0);
-            assertNotNull(generated);
+            AttestedKeyPair generated =
+                    mDevicePolicyManager.generateKeyPair(getWho(), "EC", spec, 0);
+            assertThat(generated).isNotNull();
             // Create a self-signed cert to go with it.
             X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
             X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
@@ -656,13 +678,13 @@
             certs.add(cert);
             mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, certs, true);
             // Make sure that the alias can now be obtained.
-            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            assertThat(new KeyChainAliasFuture(alias).get()).isEqualTo(alias);
             // And can be retrieved from KeyChain
             X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(mActivity, alias);
-            assertEquals(fetchedCerts.length, certs.size());
-            assertTrue(Arrays.equals(fetchedCerts[0].getEncoded(), certs.get(0).getEncoded()));
+            assertThat(fetchedCerts.length).isEqualTo(certs.size());
+            assertThat(fetchedCerts[0].getEncoded()).isEqualTo(certs.get(0).getEncoded());
         } finally {
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
@@ -675,21 +697,21 @@
                     .setDigests(KeyProperties.DIGEST_SHA256)
                     .build();
 
-            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
-                    getWho(), "EC", spec, 0);
-            assertNotNull(generated);
+            AttestedKeyPair generated =
+                    mDevicePolicyManager.generateKeyPair(getWho(), "EC", spec, 0);
+            assertThat(generated).isNotNull();
             List<Certificate> chain = loadCertificateChain("user-cert-chain.crt");
             mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, chain, true);
             // Make sure that the alias can now be obtained.
-            assertEquals(alias, new KeyChainAliasFuture(alias).get());
+            assertThat(new KeyChainAliasFuture(alias).get()).isEqualTo(alias);
             // And can be retrieved from KeyChain
             X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(mActivity, alias);
-            assertEquals(fetchedCerts.length, chain.size());
+            assertThat(fetchedCerts.length).isEqualTo(chain.size());
             for (int i = 0; i < chain.size(); i++) {
-                assertTrue(Arrays.equals(fetchedCerts[i].getEncoded(), chain.get(i).getEncoded()));
+                assertThat(fetchedCerts[i].getEncoded()).isEqualTo(chain.get(i).getEncoded());
             }
         } finally {
-            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
         }
     }
 
@@ -702,7 +724,7 @@
                 e.printStackTrace();
             }
         }
-        assertEquals("Grant for alias: \"" + alias + "\"", expected, granted);
+        assertWithMessage("Grant for alias: \"" + alias + "\"").that(granted).isEqualTo(expected);
     }
 
     private static PrivateKey getPrivateKey(final byte[] key, String type)
@@ -767,7 +789,9 @@
         }
 
         public String get() throws InterruptedException {
-            assertTrue("Chooser timeout", mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES));
+            assertWithMessage("Chooser timeout")
+                    .that(mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES))
+                    .isTrue();
             return mChosenAlias;
         }
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordExpirationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordExpirationTest.java
new file mode 100644
index 0000000..73841fa
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordExpirationTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.deviceandprofileowner;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class PasswordExpirationTest extends BaseDeviceAdminTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mOnPasswordExpiryTimeoutCalled = new CountDownLatch(1);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, 0);
+        mOnPasswordExpiryTimeoutCalled = null;
+    }
+
+    public void testPasswordExpires() throws Exception {
+        final long testTimeoutMs = 5000;
+        // Set a significantly longer wait time to check that the countDownLatch is called to prevent
+        // tests flakiness due to setups delays.
+        final long testWaitTimeMs = 300000;
+        mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, testTimeoutMs);
+        assertTrue("The password expiry timeout was not triggered.",
+                mOnPasswordExpiryTimeoutCalled.await(testWaitTimeMs, TimeUnit.MILLISECONDS));
+    }
+
+    public void testNoNegativeTimeout() {
+        final long testTimeoutMs = -1;
+        try {
+            mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, testTimeoutMs);
+            fail("Setting a negative value for a timeout is expected to throw an exception.");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    // TODO: investigate this test's failure. b/110976462
+    /* public void testPasswordNotYetExpiredIsInEffect() throws Exception {
+        final long testTimeoutMs = 5000;
+        mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, testTimeoutMs);
+        assertFalse("The password expiry timeout was triggered too early.",
+                mOnPasswordExpiryTimeoutCalled.await(testTimeoutMs, TimeUnit.MILLISECONDS));
+    }
+    */
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/StorageEncryptionTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/StorageEncryptionTest.java
new file mode 100644
index 0000000..947d0e6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/StorageEncryptionTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
+import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
+import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.support.test.InstrumentationRegistry;
+
+/**
+ * Test {@link DevicePolicyManager#setStorageEncryption(ComponentName, boolean)} and
+ * {@link DevicePolicyManager#getStorageEncryption(ComponentName)}.
+ * <p>Note that most physical devices are required to have storage encryption, but since emulators
+ * do not support encryption yet, we allow the {@link
+ * DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} result to pass to reduce noise in our
+ * testing dashboards. If a physical device does not have storage encryption support, it will
+ * be caught in CTSVerifier.
+ */
+public class StorageEncryptionTest extends BaseDeviceAdminTest {
+
+    private static final ComponentName ADMIN_RECEIVER_COMPONENT =
+        BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+    private static final String IS_PRIMARY_USER_PARAM = "isPrimaryUser";
+
+    /**
+     * When the test is run from the managed profile user, {@link
+     * DevicePolicyManager#setStorageEncryption(ComponentName, boolean)} is expected to return
+     * {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED}, as it is not the primary user.
+     */
+    private boolean mIsPrimaryUser;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIsPrimaryUser = Boolean.parseBoolean(
+                InstrumentationRegistry.getArguments().getString(IS_PRIMARY_USER_PARAM));
+    }
+
+    public void testSetStorageEncryption_enabled() {
+        if (mDevicePolicyManager.getStorageEncryptionStatus() == ENCRYPTION_STATUS_UNSUPPORTED) {
+            return;
+        }
+        assertThat(mDevicePolicyManager.setStorageEncryption(ADMIN_RECEIVER_COMPONENT, true))
+            .isEqualTo(mIsPrimaryUser ? ENCRYPTION_STATUS_ACTIVE : ENCRYPTION_STATUS_UNSUPPORTED);
+        assertThat(mDevicePolicyManager.getStorageEncryption(ADMIN_RECEIVER_COMPONENT))
+                .isEqualTo(mIsPrimaryUser);
+    }
+
+    public void testSetStorageEncryption_disabled() {
+        if (mDevicePolicyManager.getStorageEncryptionStatus() == ENCRYPTION_STATUS_UNSUPPORTED) {
+            return;
+        }
+        assertThat(mDevicePolicyManager.setStorageEncryption(ADMIN_RECEIVER_COMPONENT, false))
+            .isEqualTo(mIsPrimaryUser ? ENCRYPTION_STATUS_INACTIVE
+                    : ENCRYPTION_STATUS_UNSUPPORTED);
+        assertThat(mDevicePolicyManager.getStorageEncryption(ADMIN_RECEIVER_COMPONENT)).isFalse();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index 0361fd8..5569653 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -37,6 +37,7 @@
             UserManager.DISALLOW_UNINSTALL_APPS,
             UserManager.DISALLOW_SHARE_LOCATION,
             UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
             UserManager.DISALLOW_CONFIG_BLUETOOTH,
             UserManager.DISALLOW_USB_FILE_TRANSFER,
             UserManager.DISALLOW_CONFIG_CREDENTIALS,
@@ -85,6 +86,7 @@
             UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
             UserManager.DISALLOW_SMS,
             UserManager.DISALLOW_FUN,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.DISALLOW_CREATE_WINDOWS,
             UserManager.DISALLOW_BLUETOOTH,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
index 9743840..0534f2a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
@@ -25,6 +25,7 @@
             UserManager.DISALLOW_UNINSTALL_APPS,
             // UserManager.DISALLOW_SHARE_LOCATION, // Has unrecoverable side effects.
             // UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, // Has unrecoverable side effects.
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
             UserManager.DISALLOW_CONFIG_BLUETOOTH,
             UserManager.DISALLOW_USB_FILE_TRANSFER,
             UserManager.DISALLOW_CONFIG_CREDENTIALS,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
index 2a9db15..5c9700e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
@@ -28,6 +28,7 @@
             UserManager.DISALLOW_UNINSTALL_APPS,
             // UserManager.DISALLOW_SHARE_LOCATION, // Has unrecoverable side effects.
             // UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, // Has unrecoverable side effects.
+            // UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, // Same as above.
             UserManager.DISALLOW_CONFIG_BLUETOOTH,
             UserManager.DISALLOW_CONFIG_CREDENTIALS,
             UserManager.DISALLOW_REMOVE_USER,
@@ -64,6 +65,10 @@
             UserManager.DISALLOW_BLUETOOTH
     };
 
+    public static final String[] ALLOWED_BUT_LEAKY = new String[] {
+        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+    };
+
     @Override
     protected String[] getAllowedRestrictions() {
         return ALLOWED;
@@ -78,7 +83,7 @@
     protected String[] getDefaultEnabledRestrictions() { return new String[0]; }
 
     /**
-     * This is called after DO setting all DO restrictions.  Global restrictions should be
+     * This is called after DO setting all DO restrictions. Global restrictions should be
      * visible on other users.
      */
     public void testHasGlobalRestrictions() {
@@ -111,13 +116,14 @@
     }
 
     /**
-     * Only the default restrictions should be set.
+     * Only the default restrictions should be set. Excludes restrictions that leak from other
+     * profiles.
      */
-    public void testDefaultRestrictionsOnly() {
-        final HashSet<String> expected = new HashSet<>(
-                // No restrictions.
-        );
-
+    public void testDefaultAndLeakyRestrictions() {
+        // The secondary profile test expects no allowed restrictions to be leaked, with the
+        // exception being the restrictions that are leaky. "Leaky" restrictions are global,
+        // device-wide restrictions that "leak" from the secondary profile to other profiles.
+        final HashSet<String> expected = new HashSet<>(Arrays.asList(ALLOWED_BUT_LEAKY));
         assertRestrictions(expected);
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index a8e3b68..ea73f19 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -41,12 +41,20 @@
     compatibility-device-util \
     android-support-test \
     cts-security-test-support-library \
-    testng
+    testng \
+    truth-prebuilt
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.legacy_legacy-support-v4
 
+LOCAL_MIN_SDK_VERSION := 21
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 
+# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds
+ifeq (true,$(EMMA_INSTRUMENT))
+LOCAL_DX_FLAGS := --multi-dex
+endif # EMMA_INSTRUMENT
+
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index b4dff59..8b9b3a0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
@@ -29,6 +30,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <application
         android:testOnly="true"
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 9175d9b..9562a1f 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -18,6 +18,7 @@
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
+import android.content.pm.PackageManager;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
@@ -35,6 +36,8 @@
     protected DevicePolicyManager mDevicePolicyManager;
     protected Instrumentation mInstrumentation;
     protected UiDevice mDevice;
+    protected boolean mHasSecureLockScreen;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -42,6 +45,8 @@
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(mInstrumentation);
         mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_SECURE_LOCK_SCREEN);
         assertDeviceOwner();
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index bbe6855..6632c09 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -16,7 +16,6 @@
 
 package com.android.cts.deviceowner;
 
-import android.app.ActivityManager;
 import android.app.Service;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
@@ -29,7 +28,6 @@
 import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -54,31 +52,16 @@
 public class CreateAndManageUserTest extends BaseDeviceOwnerTest {
     private static final String TAG = "CreateAndManageUserTest";
 
-    private static final String BROADCAST_EXTRA = "broadcastExtra";
-    private static final String ACTION_EXTRA = "actionExtra";
-    private static final String SERIAL_EXTRA = "serialExtra";
-    private static final String PROFILE_OWNER_EXTRA = "profileOwnerExtra";
-    private static final String SETUP_COMPLETE_EXTRA = "setupCompleteExtra";
     private static final int BROADCAST_TIMEOUT = 15_000;
-    private static final int USER_SWITCH_DELAY = 10_000;
 
     private static final String AFFILIATION_ID = "affiliation.id";
     private static final String EXTRA_AFFILIATION_ID = "affiliationIdExtra";
     private static final String EXTRA_METHOD_NAME = "methodName";
     private static final long ON_ENABLED_TIMEOUT_SECONDS = 120;
 
-
-    private PackageManager mPackageManager;
-    private ActivityManager mActivityManager;
-    private volatile boolean mReceived;
-    private volatile boolean mTestProfileOwnerWasUsed;
-    private volatile boolean mSetupComplete;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mPackageManager = mContext.getPackageManager();
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
     }
 
     @Override
@@ -88,122 +71,7 @@
         super.tearDown();
     }
 
-    // This class is used by createAndManageUserTest as profile owner for the new user. When
-    // enabled, it sends a broadcast to signal success.
-    public static class TestProfileOwner extends DeviceAdminReceiver {
-        @Override
-        public void onEnabled(Context context, Intent intent) {
-            if (intent.getBooleanExtra(BROADCAST_EXTRA, false)) {
-                Intent i = new Intent(intent.getStringExtra(ACTION_EXTRA));
-                UserManager userManager = (UserManager)
-                        context.getSystemService(Context.USER_SERVICE);
-                long serial = intent.getLongExtra(SERIAL_EXTRA, 0);
-                UserHandle handle = userManager.getUserForSerialNumber(serial);
-                i.putExtra(PROFILE_OWNER_EXTRA, true);
-                // find value of user_setup_complete on new user, and send the result back
-                try {
-                    boolean setupComplete = (Settings.Secure.getInt(context.getContentResolver(),
-                            "user_setup_complete") == 1);
-                    i.putExtra(SETUP_COMPLETE_EXTRA, setupComplete);
-                } catch (Settings.SettingNotFoundException e) {
-                    fail("Did not find settings user_setup_complete");
-                }
-
-                context.sendBroadcastAsUser(i, handle);
-            }
-        }
-
-        public static ComponentName getComponentName() {
-            return new ComponentName(CreateAndManageUserTest.class.getPackage().getName(),
-                    TestProfileOwner.class.getName());
-        }
-    }
-
-    private void waitForBroadcastLocked() {
-        // Wait for broadcast. Time is measured in a while loop because of spurious wakeups.
-        final long initTime = System.currentTimeMillis();
-        while (!mReceived) {
-            try {
-                wait(BROADCAST_TIMEOUT - (System.currentTimeMillis() - initTime));
-            } catch (InterruptedException e) {
-                fail("InterruptedException: " + e.getMessage());
-            }
-            if (!mReceived && System.currentTimeMillis() - initTime > BROADCAST_TIMEOUT) {
-                fail("Timeout while waiting for broadcast after createAndManageUser.");
-            }
-        }
-    }
-
-    // This test will create a user that will get passed a bundle that we specify. The bundle will
-    // contain an action and a serial (for user handle) to broadcast to notify the test that the
-    // configuration was triggered.
-    private void createAndManageUserTest(final int flags) {
-        // This test sets a profile owner on the user, which requires the managed_users feature.
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            return;
-        }
-
-        final boolean expectedSetupComplete = (flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0;
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
-        UserHandle firstUser = Process.myUserHandle();
-        final String testUserName = "TestUser_" + System.currentTimeMillis();
-        String action = "com.android.cts.TEST_USER_ACTION";
-        PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(BROADCAST_EXTRA, true);
-        bundle.putLong(SERIAL_EXTRA, userManager.getSerialNumberForUser(firstUser));
-        bundle.putString(ACTION_EXTRA, action);
-
-        mReceived = false;
-        mTestProfileOwnerWasUsed = false;
-        mSetupComplete = !expectedSetupComplete;
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mReceived = true;
-                if (intent.getBooleanExtra(PROFILE_OWNER_EXTRA, false)) {
-                    mTestProfileOwnerWasUsed = true;
-                }
-                mSetupComplete = intent.getBooleanExtra(SETUP_COMPLETE_EXTRA,
-                        !expectedSetupComplete);
-                synchronized (CreateAndManageUserTest.this) {
-                    CreateAndManageUserTest.this.notify();
-                }
-            }
-        };
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(action);
-        mContext.registerReceiver(receiver, filter);
-
-        synchronized (this) {
-            UserHandle userHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
-                    TestProfileOwner.getComponentName(), bundle, flags);
-            assertNotNull(userHandle);
-
-            mDevicePolicyManager.switchUser(getWho(), userHandle);
-            try {
-                wait(USER_SWITCH_DELAY);
-            } catch (InterruptedException e) {
-                fail("InterruptedException: " + e.getMessage());
-            }
-            mDevicePolicyManager.switchUser(getWho(), firstUser);
-
-            waitForBroadcastLocked();
-
-            assertTrue(mReceived);
-            assertTrue(mTestProfileOwnerWasUsed);
-            assertEquals(expectedSetupComplete, mSetupComplete);
-
-            assertTrue(mDevicePolicyManager.removeUser(getWho(), userHandle));
-
-            userHandle = null;
-        }
-
-        mContext.unregisterReceiver(receiver);
-    }
-
-    public void testCreateAndManageUser() throws Exception {
+    public void testCreateAndManageUser() {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
@@ -215,7 +83,7 @@
         Log.d(TAG, "User create: " + userHandle);
     }
 
-    public void testCreateAndManageUser_LowStorage() throws Exception {
+    public void testCreateAndManageUser_LowStorage() {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         try {
@@ -231,7 +99,7 @@
         }
     }
 
-    public void testCreateAndManageUser_MaxUsers() throws Exception {
+    public void testCreateAndManageUser_MaxUsers() {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         try {
@@ -247,7 +115,20 @@
         }
     }
 
-    public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+    @SuppressWarnings("unused")
+    private static void assertSkipSetupWizard(Context context,
+            DevicePolicyManager devicePolicyManager, ComponentName componentName) throws Exception {
+        assertEquals("user setup not completed", 1,
+                Settings.Secure.getInt(context.getContentResolver(),
+                        Settings.Secure.USER_SETUP_COMPLETE));
+    }
+
+    public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
+        runCrossUserVerification(DevicePolicyManager.SKIP_SETUP_WIZARD, "assertSkipSetupWizard");
+        PrimaryUserService.assertCrossUserCallArrived();
+    }
+
+    public void testCreateAndManageUser_GetSecondaryUsers() {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
@@ -343,7 +224,7 @@
         }
     }
 
-    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
+    public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
@@ -496,8 +377,7 @@
         PrimaryUserService.assertCrossUserCallArrived();
     }
 
-    private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName)
-            throws Exception {
+    private UserHandle runCrossUserVerification(int createAndManageUserFlags, String methodName) {
         String testUserName = "TestUser_" + System.currentTimeMillis();
 
         // Set affiliation id to allow communication.
@@ -521,18 +401,6 @@
         return userHandle;
     }
 
-    public void testCreateAndManageUser_SkipSetupWizard() {
-        createAndManageUserTest(DevicePolicyManager.SKIP_SETUP_WIZARD);
-    }
-
-    public void testCreateAndManageUser_DontSkipSetupWizard() {
-        if (!mActivityManager.isRunningInTestHarness()) {
-            // In test harness, the setup wizard will be disabled by default, so this test is always
-            // failing.
-            createAndManageUserTest(0);
-        }
-    }
-
     // createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
     public void testCreateAndManageUser_AddRestrictionSet() {
         mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
@@ -581,7 +449,7 @@
     }
 
     static class LocalBroadcastReceiver extends BroadcastReceiver {
-        private LinkedBlockingQueue<UserHandle> mQueue = new LinkedBlockingQueue<UserHandle>(1);
+        private LinkedBlockingQueue<UserHandle> mQueue = new LinkedBlockingQueue<>(1);
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -592,7 +460,7 @@
 
         }
 
-        public UserHandle waitForBroadcastReceived() throws InterruptedException {
+        UserHandle waitForBroadcastReceived() throws InterruptedException {
             return mQueue.poll(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
         }
     }
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
new file mode 100644
index 0000000..ea60582
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.deviceowner;
+
+import android.content.Context;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+/**
+ * Verifies device identifier access for the device owner.
+ */
+public class DeviceIdentifiersTest extends BaseDeviceOwnerTest {
+
+    private static final String DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE =
+            "An unexpected value was received by the device owner with the READ_PHONE_STATE "
+                    + "permission when invoking %s";
+    private static final String NO_SECURITY_EXCEPTION_ERROR_MESSAGE =
+            "A device owner that does not have the READ_PHONE_STATE permission must receive a "
+                    + "SecurityException when invoking %s";
+
+    public void testDeviceOwnerCanGetDeviceIdentifiersWithPermission() throws Exception {
+        // The device owner with the READ_PHONE_STATE permission should have access to all device
+        // identifiers. However since the TelephonyManager methods can return null this method
+        // verifies that the device owner with the READ_PHONE_STATE permission receives the same
+        // value that the shell identity receives with the READ_PRIVILEGED_PHONE_STATE permission.
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        try {
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getDeviceId"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getDeviceId()), telephonyManager.getDeviceId());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getImei"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getImei()), telephonyManager.getImei());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getMeid"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getMeid()), telephonyManager.getMeid());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSubscriberId"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getSubscriberId()), telephonyManager.getSubscriberId());
+            assertEquals(
+                    String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSimSerialNumber"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getSimSerialNumber()),
+                    telephonyManager.getSimSerialNumber());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
+                    ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
+                    Build.getSerial());
+        } catch (SecurityException e) {
+            fail("The device owner with the READ_PHONE_STATE permission must be able to access "
+                    + "the device IDs: " + e);
+        }
+    }
+
+    public void testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+        // The device owner without the READ_PHONE_STATE permission should still receive a
+        // SecurityException when querying for device identifiers.
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        try {
+            telephonyManager.getDeviceId();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getImei();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getMeid();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getSubscriberId();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getSimSerialNumber();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            Build.getSerial();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+        } 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
new file mode 100644
index 0000000..9aad7b5
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.deviceowner;
+
+/**
+ * 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
+ * are logged. Note that the metrics verification is done on the host-side.
+ */
+public class DevicePolicyLoggingTest extends BaseDeviceOwnerTest {
+    public void testSetKeyguardDisabledLogged() {
+        mDevicePolicyManager.setKeyguardDisabled(getWho(), true);
+        mDevicePolicyManager.setKeyguardDisabled(getWho(), false);
+    }
+
+    public void testSetStatusBarDisabledLogged() {
+        mDevicePolicyManager.setStatusBarDisabled(getWho(), true);
+        mDevicePolicyManager.setStatusBarDisabled(getWho(), false);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/InstallUpdateTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/InstallUpdateTest.java
new file mode 100644
index 0000000..c6c62ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/InstallUpdateTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.SystemClock;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.app.admin.DevicePolicyManager#installSystemUpdate}
+ */
+public class InstallUpdateTest extends BaseDeviceOwnerTest {
+    private static final int BATTERY_STATE_CHANGE_TIMEOUT_MS = 5000;
+    private static final int BATTERY_STATE_CHANGE_SLEEP_PER_CHECK_MS = 50;
+    private static final int TEST_BATTERY_THRESHOLD = 10;
+    private static final IntentFilter BATTERY_CHANGED_FILTER =
+            new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+
+    public static final String TEST_SYSTEM_UPDATES_DIR =
+            "/data/local/tmp/cts/deviceowner/";
+    public static final int TIMEOUT = 5;
+
+    public void testInstallUpdate_failFileNotFound() throws InterruptedException {
+        assertUpdateError(
+                "random",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND);
+    }
+
+    public void testInstallUpdate_failWrongVersion() throws InterruptedException {
+        assertUpdateError(
+                "wrongVersion.zip",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
+    }
+
+    public void testInstallUpdate_failNoZipOtaFile() throws InterruptedException {
+        assertUpdateError("notZip.zi",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+    }
+
+    public void testInstallUpdate_failWrongPayloadFile() throws InterruptedException {
+        assertUpdateError("wrongPayload.zip",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+    }
+
+    public void testInstallUpdate_failEmptyOtaFile() throws InterruptedException {
+        assertUpdateError("empty.zip",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+    }
+
+    public void testInstallUpdate_failWrongHash() throws InterruptedException {
+        assertUpdateError("wrongHash.zip",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+    }
+
+    public void testInstallUpdate_failWrongSize() throws InterruptedException {
+        assertUpdateError("wrongSize.zip",
+                DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+    }
+
+    public void testInstallUpdate_notCharging_belowThreshold_failsBatteryCheck() throws Exception {
+        try {
+            setNonChargingBatteryThreshold(TEST_BATTERY_THRESHOLD);
+            setNonChargingBatteryLevelAndWait(TEST_BATTERY_THRESHOLD - 1);
+            assertUpdateError("wrongSize.zip",
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW);
+        } finally {
+            resetBatteryState();
+            resetDevicePolicyConstants();
+        }
+    }
+
+    public void testInstallUpdate_notCharging_aboveThreshold_passesBatteryCheck() throws Exception {
+        try {
+            setNonChargingBatteryThreshold(TEST_BATTERY_THRESHOLD);
+            setNonChargingBatteryLevelAndWait(TEST_BATTERY_THRESHOLD);
+            // Positive CTS tests aren't possible, so we verify that we get the file-related error
+            // rather than the battery one.
+            assertUpdateError("wrongSize.zip",
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            resetBatteryState();
+            resetDevicePolicyConstants();
+        }
+    }
+
+    public void testInstallUpdate_charging_belowThreshold_failsBatteryCheck() throws Exception {
+        try {
+            setChargingBatteryThreshold(TEST_BATTERY_THRESHOLD);
+            setChargingBatteryLevelAndWait(TEST_BATTERY_THRESHOLD - 1);
+            assertUpdateError("wrongSize.zip",
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_BATTERY_LOW);
+        } finally {
+            resetBatteryState();
+            resetDevicePolicyConstants();
+        }
+    }
+
+    public void testInstallUpdate_charging_aboveThreshold_passesBatteryCheck() throws Exception {
+        try {
+            setChargingBatteryThreshold(TEST_BATTERY_THRESHOLD);
+            setChargingBatteryLevelAndWait(TEST_BATTERY_THRESHOLD);
+            // Positive CTS tests aren't possible, so we verify that we get the file-related error
+            // rather than the battery one.
+            assertUpdateError("wrongSize.zip",
+                    DevicePolicyManager.InstallUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            resetBatteryState();
+            resetDevicePolicyConstants();
+        }
+    }
+
+    private void assertUpdateError(String fileName, int expectedErrorCode)
+            throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        Uri uri = Uri.fromFile(new File(TEST_SYSTEM_UPDATES_DIR, fileName));
+        mDevicePolicyManager.installSystemUpdate(getWho(), uri,
+                Runnable::run, new DevicePolicyManager.InstallUpdateCallback() {
+                    @Override
+                    public void onInstallUpdateError(int errorCode, String errorMessage) {
+                        try {
+                            assertEquals(expectedErrorCode, errorCode);
+                        } finally {
+                            latch.countDown();
+                        }
+                    }
+                });
+        assertTrue(latch.await(TIMEOUT, TimeUnit.MINUTES));
+    }
+
+    private void setNonChargingBatteryThreshold(int threshold) {
+        SystemUtil.runShellCommand(
+                "settings put global device_policy_constants battery_threshold_not_charging="
+                        + threshold);
+    }
+
+    private void setNonChargingBatteryLevelAndWait(int level) throws Exception {
+        setBatteryStateAndWait(/* plugged= */ false, level);
+    }
+
+    private void setChargingBatteryThreshold(int threshold) {
+        SystemUtil.runShellCommand(
+                "settings put global device_policy_constants battery_threshold_charging="
+                        + threshold);
+    }
+
+    private void setChargingBatteryLevelAndWait(int level) throws Exception {
+        setBatteryStateAndWait(/* plugged= */ true, level);
+    }
+
+    /** Should be paired with {@link #resetBatteryState()} in a {@code finally} block. */
+    private void setBatteryStateAndWait(boolean plugged, int level) throws Exception {
+        SystemUtil.runShellCommand(plugged ? "cmd battery set ac 1" : "cmd battery unplug");
+        SystemUtil.runShellCommand("cmd battery set -f level " + level);
+        long startTime = SystemClock.elapsedRealtime();
+        while (!isBatteryState(plugged, level)
+                && SystemClock.elapsedRealtime() <= startTime + BATTERY_STATE_CHANGE_TIMEOUT_MS) {
+            Thread.sleep(BATTERY_STATE_CHANGE_SLEEP_PER_CHECK_MS);
+        }
+    }
+
+    private boolean isBatteryState(boolean plugged, int level) {
+        final Intent batteryStatus =
+                mContext.registerReceiver(/* receiver= */ null, BATTERY_CHANGED_FILTER);
+        return isPluggedIn(batteryStatus) == plugged
+                && calculateBatteryPercentage(batteryStatus) == level;
+    }
+
+    private boolean isPluggedIn(Intent batteryStatus) {
+        return batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, /* defaultValue= */ -1) > 0;
+    }
+
+    private float calculateBatteryPercentage(Intent batteryStatus) {
+        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, /* defaultValue= */ -1);
+        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, /* defaultValue= */ -1);
+        return 100 * level / (float) scale;
+    }
+
+    private void resetBatteryState() {
+        SystemUtil.runShellCommand("dumpsys battery reset");
+    }
+
+    private void resetDevicePolicyConstants() {
+        SystemUtil.runShellCommand("settings delete global device_policy_constants");
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index e727d56..a4c8f21 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.admin.ConnectEvent;
 import android.app.admin.DnsEvent;
 import android.app.admin.NetworkEvent;
@@ -22,8 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import android.util.Log;
 
 import java.io.BufferedReader;
@@ -38,10 +40,13 @@
 import java.net.Socket;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
 public class NetworkLoggingTest extends BaseDeviceOwnerTest {
 
     private static final String TAG = "NetworkLoggingTest";
@@ -193,6 +198,80 @@
         verifyNetworkLogs(mNetworkEvents, eventsExpected);
     }
 
+    private void verifyDnsEvent(DnsEvent dnsEvent) {
+        // Verify that we didn't log a hostname lookup when network logging was disabled.
+        if (dnsEvent.getHostname().contains(NOT_LOGGED_URLS_LIST[0])
+                || dnsEvent.getHostname().contains(NOT_LOGGED_URLS_LIST[1])) {
+            fail("A hostname that was looked-up when network logging was disabled"
+                    + " was logged.");
+        }
+
+        // Verify that as many IP addresses were logged as were reported (max 10).
+        final List<InetAddress> ips = dnsEvent.getInetAddresses();
+        assertThat(ips.size()).isAtMost(MAX_IP_ADDRESSES_LOGGED);
+        final int expectedAddressCount = Math.min(MAX_IP_ADDRESSES_LOGGED,
+                dnsEvent.getTotalResolvedAddressCount());
+        assertThat(expectedAddressCount).isEqualTo(ips.size());
+
+        // Verify the IP addresses are valid IPv4 or IPv6 addresses.
+        for (final InetAddress ipAddress : ips) {
+            assertTrue(isIpv4OrIpv6Address(ipAddress));
+        }
+
+        //Verify writeToParcel.
+        Parcel parcel = Parcel.obtain();
+        try {
+            dnsEvent.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            final DnsEvent dnsEventOut = DnsEvent.CREATOR.createFromParcel(parcel);
+            assertThat(dnsEventOut).isNotNull();
+            verifyDnsEventsEqual(dnsEvent, dnsEventOut);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    private void verifyDnsEventsEqual(DnsEvent event1, DnsEvent event2) {
+        assertThat(event1.getHostname()).isEqualTo(event2.getHostname());
+        assertThat(new HashSet<InetAddress>(event1.getInetAddresses())).isEqualTo(
+                        new HashSet<InetAddress>(event2.getInetAddresses()));
+        assertThat(event1.getTotalResolvedAddressCount()).isEqualTo(
+                event2.getTotalResolvedAddressCount());
+        assertThat(event1.getPackageName()).isEqualTo(event2.getPackageName());
+        assertThat(event1.getTimestamp()).isEqualTo(event2.getTimestamp());
+        assertThat(event1.getId()).isEqualTo(event2.getId());
+    }
+
+    private void verifyConnectEvent(ConnectEvent connectEvent) {
+        // Verify the IP address is a valid IPv4 or IPv6 address.
+        final InetAddress ip = connectEvent.getInetAddress();
+        assertThat(isIpv4OrIpv6Address(ip)).isTrue();
+
+        // Verify that the port is a valid port.
+        assertThat(connectEvent.getPort()).isAtLeast(0);
+        assertThat(connectEvent.getPort()).isAtMost(65535);
+
+        // Verify writeToParcel.
+        Parcel parcel = Parcel.obtain();
+        try {
+            connectEvent.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            final ConnectEvent connectEventOut = ConnectEvent.CREATOR.createFromParcel(parcel);
+            assertThat(connectEventOut).isNotNull();
+            verifyConnectEventsEqual(connectEvent, connectEventOut);
+        } finally {
+             parcel.recycle();
+        }
+    }
+
+    private void verifyConnectEventsEqual(ConnectEvent event1, ConnectEvent event2) {
+        assertThat(event1.getInetAddress()).isEqualTo(event2.getInetAddress());
+        assertThat(event1.getPort()).isEqualTo(event2.getPort());
+        assertThat(event1.getPackageName()).isEqualTo(event2.getPackageName());
+        assertThat(event1.getTimestamp()).isEqualTo(event2.getTimestamp());
+        assertThat(event1.getId()).isEqualTo(event2.getId());
+    }
+
     private void verifyNetworkLogs(List<NetworkEvent> networkEvents, int eventsExpected) {
         // allow a batch to be slightly smaller or larger.
         assertTrue(Math.abs(eventsExpected - networkEvents.size()) <= 50);
@@ -216,35 +295,18 @@
                 ctsPackageNameCounter++;
                 if (currentEvent instanceof DnsEvent) {
                     final DnsEvent dnsEvent = (DnsEvent) currentEvent;
-                    // verify that we didn't log a hostname lookup when network logging was disabled
-                    if (dnsEvent.getHostname().contains(NOT_LOGGED_URLS_LIST[0])
-                            || dnsEvent.getHostname().contains(NOT_LOGGED_URLS_LIST[1])) {
-                        fail("A hostname that was looked-up when network logging was disabled"
-                                + " was logged.");
-                    }
-                    // count the frequencies of LOGGED_URLS_LIST's hostnames that were looked up
+                    // Mark which addresses from LOGGED_URLS_LIST were visited.
                     for (int j = 0; j < LOGGED_URLS_LIST.length; j++) {
                         if (dnsEvent.getHostname().contains(LOGGED_URLS_LIST[j])) {
                             visited[j] = true;
                             break;
                         }
                     }
-                    // verify that as many IP addresses were logged as were reported (max 10)
-                    final List<InetAddress> ips = dnsEvent.getInetAddresses();
-                    assertTrue(ips.size() <= MAX_IP_ADDRESSES_LOGGED);
-                    final int expectedAddressCount = Math.min(MAX_IP_ADDRESSES_LOGGED,
-                            dnsEvent.getTotalResolvedAddressCount());
-                    assertEquals(expectedAddressCount, ips.size());
-                    // verify the IP addresses are valid IPv4 or IPv6 addresses
-                    for (final InetAddress ipAddress : ips) {
-                        assertTrue(isIpv4OrIpv6Address(ipAddress));
-                    }
+
+                    verifyDnsEvent(dnsEvent);
                 } else if (currentEvent instanceof ConnectEvent) {
                     final ConnectEvent connectEvent = (ConnectEvent) currentEvent;
-                    // verify the IP address is a valid IPv4 or IPv6 address
-                    assertTrue(isIpv4OrIpv6Address(connectEvent.getInetAddress()));
-                    // verify that the port is a valid port
-                    assertTrue(connectEvent.getPort() >= 0 && connectEvent.getPort() <= 65535);
+                    verifyConnectEvent(connectEvent);
                 } else {
                     fail("An unknown NetworkEvent type logged: "
                             + currentEvent.getClass().getName());
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
new file mode 100644
index 0000000..8e51bc3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.deviceowner;
+
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.UserManager;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+public class PrivateDnsPolicyTest extends BaseDeviceOwnerTest {
+    private static final String DUMMY_PRIVATE_DNS_HOST = "resolver.example.com";
+    private static final String VALID_PRIVATE_DNS_HOST = "dns.google";
+
+    private UserManager mUserManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mUserManager = mContext.getSystemService(UserManager.class);
+        assertNotNull(mUserManager);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        setUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS, false);
+        mDevicePolicyManager.setGlobalPrivateDns(getWho(),
+                PRIVATE_DNS_MODE_OPPORTUNISTIC, null);
+    }
+
+    public void testDisallowPrivateDnsConfigurationRestriction() {
+        setUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS, true);
+        assertThat(mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_CONFIG_PRIVATE_DNS)).isTrue();
+    }
+
+    public void testClearDisallowPrivateDnsConfigurationRestriction() {
+        setUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS, false);
+        assertThat(mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_CONFIG_PRIVATE_DNS)).isFalse();
+    }
+
+    private void setUserRestriction(String restriction, boolean add) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (add) {
+            dpm.addUserRestriction(getWho(), restriction);
+        } else {
+            dpm.clearUserRestriction(getWho(), restriction);
+        }
+    }
+
+    /**
+     * Call DevicePolicyManager.setGlobalPrivateDns with the given mode, host, expecting
+     * the result code expectedResult.
+     */
+    private void callSetGlobalPrivateDnsExpectingResult(int mode, String privateDnsHost,
+            int expectedResult) {
+        int resultCode = mDevicePolicyManager.setGlobalPrivateDns(getWho(), mode, privateDnsHost);
+
+        assertEquals(
+                String.format(
+                        "Call to setGlobalPrivateDns with mode %d and host %s "
+                                + "should have produced result %d, but was %d",
+                        mode, privateDnsHost, expectedResult, resultCode),
+                expectedResult, resultCode);
+    }
+
+    public void testCannotSetOffMode() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mDevicePolicyManager.setGlobalPrivateDns(
+                        getWho(), PRIVATE_DNS_MODE_OFF, null));
+
+        assertThat(
+                mDevicePolicyManager.getGlobalPrivateDnsMode(getWho())).isNotEqualTo(
+                PRIVATE_DNS_MODE_OFF);
+    }
+
+    public void testSetOpportunisticMode() {
+        callSetGlobalPrivateDnsExpectingResult(PRIVATE_DNS_MODE_OPPORTUNISTIC, null,
+                DevicePolicyManager.PRIVATE_DNS_SET_SUCCESS);
+
+        assertThat(
+                mDevicePolicyManager.getGlobalPrivateDnsMode(getWho())).isEqualTo(
+                PRIVATE_DNS_MODE_OPPORTUNISTIC);
+        assertThat(mDevicePolicyManager.getGlobalPrivateDnsHost(getWho())).isNull();
+    }
+
+    public void testSetSpecificHostMode() {
+        callSetGlobalPrivateDnsExpectingResult(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+                VALID_PRIVATE_DNS_HOST,
+                DevicePolicyManager.PRIVATE_DNS_SET_SUCCESS);
+
+        assertThat(
+                mDevicePolicyManager.getGlobalPrivateDnsMode(getWho())).isEqualTo(
+                PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+        assertThat(
+                mDevicePolicyManager.getGlobalPrivateDnsHost(getWho())).isEqualTo(
+                VALID_PRIVATE_DNS_HOST);
+    }
+
+    public void testSetModeWithIncorrectHost() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mDevicePolicyManager.setGlobalPrivateDns(
+                        getWho(), PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, null));
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mDevicePolicyManager.setGlobalPrivateDns(
+                        getWho(), PRIVATE_DNS_MODE_OPPORTUNISTIC, DUMMY_PRIVATE_DNS_HOST));
+
+        // This host does not resolve, so would output an error.
+        callSetGlobalPrivateDnsExpectingResult(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+                DUMMY_PRIVATE_DNS_HOST,
+                DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
index 5f537a3..4d4e306 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
@@ -76,6 +76,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -252,9 +253,11 @@
     }
 
     private void verifyAdminEventsPresent(List<SecurityEvent> events) {
-        verifyPasswordComplexityEventsPresent(events);
-        verifyUserRestrictionEventsPresent(events);
+        if (mHasSecureLockScreen) {
+            verifyPasswordComplexityEventsPresent(events);
+        }
         verifyLockingPolicyEventsPresent(events);
+        verifyUserRestrictionEventsPresent(events);
     }
 
     /**
@@ -279,9 +282,11 @@
     }
 
     private void generateAdminEvents() {
-        generatePasswordComplexityEvents();
-        generateUserRestrictionEvents();
+        if (mHasSecureLockScreen) {
+            generatePasswordComplexityEvents();
+        }
         generateLockingPolicyEvents();
+        generateUserRestrictionEvents();
     }
 
     /**
@@ -399,9 +404,14 @@
     private void verifyOsStartupEventPresent(List<SecurityEvent> events) {
         final SecurityEvent event = findEvent("os startup", events, TAG_OS_STARTUP);
         // Verified boot state, empty if running on emulator
-        assertTrue(ImmutableSet.of("", "green", "yellow", "orange").contains(getString(event, 0)));
+        assertOneOf(ImmutableSet.of("", "green", "yellow", "orange"), getString(event, 0));
         // dm-verity mode, empty if it is disabled
-        assertTrue(ImmutableSet.of("enforcing", "eio", "").contains(getString(event, 1)));
+        assertOneOf(ImmutableSet.of("", "enforcing", "eio", "disabled"), getString(event, 1));
+    }
+
+    private void assertOneOf(Set<String> allowed, String s) {
+        assertTrue(String.format("\"%s\" is not one of [%s]", s, String.join(", ", allowed)),
+                allowed.contains(s));
     }
 
     private void verifyCryptoSelfTestEventPresent(List<SecurityEvent> events) {
@@ -600,9 +610,12 @@
     }
 
     private void generateLockingPolicyEvents() {
-        mDevicePolicyManager.setPasswordExpirationTimeout(getWho(), TEST_PWD_EXPIRATION_TIMEOUT);
-        mDevicePolicyManager.setPasswordHistoryLength(getWho(), TEST_PWD_HISTORY_LENGTH);
-        mDevicePolicyManager.setMaximumFailedPasswordsForWipe(getWho(), TEST_PWD_MAX_ATTEMPTS);
+        if (mHasSecureLockScreen) {
+            mDevicePolicyManager.setPasswordExpirationTimeout(getWho(),
+                    TEST_PWD_EXPIRATION_TIMEOUT);
+            mDevicePolicyManager.setPasswordHistoryLength(getWho(), TEST_PWD_HISTORY_LENGTH);
+            mDevicePolicyManager.setMaximumFailedPasswordsForWipe(getWho(), TEST_PWD_MAX_ATTEMPTS);
+        }
         mDevicePolicyManager.setKeyguardDisabledFeatures(getWho(), KEYGUARD_DISABLE_FINGERPRINT);
         mDevicePolicyManager.setMaximumTimeToLock(getWho(), TEST_MAX_TIME_TO_LOCK);
         mDevicePolicyManager.lockNow();
@@ -611,26 +624,28 @@
     private void verifyLockingPolicyEventsPresent(List<SecurityEvent> events) {
         final int userId = Process.myUserHandle().getIdentifier();
 
-        findEvent("set password expiration", events,
-                e -> e.getTag() == TAG_PASSWORD_EXPIRATION_SET &&
-                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
-                        getInt(e, ADMIN_USER_INDEX) == userId &&
-                        getInt(e, TARGET_USER_INDEX) == userId &&
-                        getLong(e, PWD_EXPIRATION_INDEX) == TEST_PWD_EXPIRATION_TIMEOUT);
+        if (mHasSecureLockScreen) {
+            findEvent("set password expiration", events,
+                    e -> e.getTag() == TAG_PASSWORD_EXPIRATION_SET &&
+                            getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                            getInt(e, ADMIN_USER_INDEX) == userId &&
+                            getInt(e, TARGET_USER_INDEX) == userId &&
+                            getLong(e, PWD_EXPIRATION_INDEX) == TEST_PWD_EXPIRATION_TIMEOUT);
 
-        findEvent("set password history length", events,
-                e -> e.getTag() == TAG_PASSWORD_HISTORY_LENGTH_SET &&
-                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
-                        getInt(e, ADMIN_USER_INDEX) == userId &&
-                        getInt(e, TARGET_USER_INDEX) == userId &&
-                        getInt(e, PWD_HIST_LEN_INDEX) == TEST_PWD_HISTORY_LENGTH);
+            findEvent("set password history length", events,
+                    e -> e.getTag() == TAG_PASSWORD_HISTORY_LENGTH_SET &&
+                            getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                            getInt(e, ADMIN_USER_INDEX) == userId &&
+                            getInt(e, TARGET_USER_INDEX) == userId &&
+                            getInt(e, PWD_HIST_LEN_INDEX) == TEST_PWD_HISTORY_LENGTH);
 
-        findEvent("set password attempts", events,
-                e -> e.getTag() == TAG_MAX_PASSWORD_ATTEMPTS_SET &&
-                        getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
-                        getInt(e, ADMIN_USER_INDEX) == userId &&
-                        getInt(e, TARGET_USER_INDEX) == userId &&
-                        getInt(e, MAX_PWD_ATTEMPTS_INDEX) == TEST_PWD_MAX_ATTEMPTS);
+            findEvent("set password attempts", events,
+                    e -> e.getTag() == TAG_MAX_PASSWORD_ATTEMPTS_SET &&
+                            getString(e, ADMIN_PKG_INDEX).equals(getWho().getPackageName()) &&
+                            getInt(e, ADMIN_USER_INDEX) == userId &&
+                            getInt(e, TARGET_USER_INDEX) == userId &&
+                            getInt(e, MAX_PWD_ATTEMPTS_INDEX) == TEST_PWD_MAX_ATTEMPTS);
+        }
 
         findEvent("set keyguard disabled features", events,
                 e -> e.getTag() == TAG_KEYGUARD_DISABLED_FEATURES_SET &&
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SystemUpdatePolicyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SystemUpdatePolicyTest.java
index e5598bb..e9d4126 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SystemUpdatePolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SystemUpdatePolicyTest.java
@@ -17,6 +17,8 @@
 
 import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FreezePeriod;
 import android.app.admin.SystemUpdatePolicy;
@@ -26,18 +28,20 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.icu.util.Calendar;
+import android.os.Parcel;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.Pair;
-
 import android.provider.Settings;
 import android.provider.Settings.Global;
 
+import com.google.common.collect.ImmutableList;
 import java.time.LocalDate;
 import java.time.MonthDay;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -255,6 +259,109 @@
         }
     }
 
+    public void testWriteSystemUpdatePolicyToParcel() {
+        final Parcel parcel1 = Parcel.obtain();
+        try {
+            final SystemUpdatePolicy policy1 = SystemUpdatePolicy.createAutomaticInstallPolicy();
+            policy1.writeToParcel(parcel1, 0);
+            parcel1.setDataPosition(0);
+            final SystemUpdatePolicy copy1 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel1);
+            assertThat(copy1).isNotNull();
+            assertSystemUpdatePoliciesEqual(policy1, copy1);
+        } finally {
+            parcel1.recycle();
+        }
+
+        final Parcel parcel2 = Parcel.obtain();
+        try {
+            final SystemUpdatePolicy policy2 = SystemUpdatePolicy
+                .createWindowedInstallPolicy(0, 720);
+            policy2.writeToParcel(parcel2, 0);
+            parcel2.setDataPosition(0);
+            final SystemUpdatePolicy copy2 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel2);
+            assertThat(copy2).isNotNull();
+            assertSystemUpdatePoliciesEqual(policy2, copy2);
+        } finally {
+            parcel2.recycle();
+        }
+
+        final Parcel parcel3 = Parcel.obtain();
+        try {
+            final SystemUpdatePolicy policy3 = SystemUpdatePolicy.createPostponeInstallPolicy();
+            policy3.writeToParcel(parcel3, 0);
+            parcel3.setDataPosition(0);
+            final SystemUpdatePolicy copy3 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel3);
+            assertThat(copy3).isNotNull();
+            assertSystemUpdatePoliciesEqual(policy3, copy3);
+        } finally {
+            parcel3.recycle();
+        }
+    }
+
+    public void testWriteValidationFailedExceptionToParcel() {
+        final List<FreezePeriod> freezePeriods =
+            ImmutableList.of(new FreezePeriod(MonthDay.of(1, 10), MonthDay.of(1, 9)));
+        try {
+            SystemUpdatePolicy.createAutomaticInstallPolicy().setFreezePeriods(freezePeriods);
+            fail("ValidationFailedException not thrown for invalid freeze period.");
+        } catch (ValidationFailedException e) {
+            final Parcel parcel = Parcel.obtain();
+            e.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+
+            final ValidationFailedException copy =
+                ValidationFailedException.CREATOR.createFromParcel(parcel);
+
+            assertThat(copy).isNotNull();
+            assertThat(e.getErrorCode()).isEqualTo(copy.getErrorCode());
+            assertThat(e.getMessage()).isEqualTo(copy.getMessage());
+        }
+    }
+
+    private void assertSystemUpdatePoliciesEqual(SystemUpdatePolicy policy,
+            SystemUpdatePolicy copy) {
+        assertThat(policy.getInstallWindowStart()).isEqualTo(copy.getInstallWindowStart());
+        assertThat(policy.getInstallWindowEnd()).isEqualTo(copy.getInstallWindowEnd());
+        assertFreezePeriodListsEqual(policy.getFreezePeriods(), copy.getFreezePeriods());
+        assertThat(policy.getPolicyType()).isEqualTo(copy.getPolicyType());
+    }
+
+    private void assertFreezePeriodListsEqual(List<FreezePeriod> original,
+            List<FreezePeriod> copy) {
+        assertThat(original).isNotNull();
+        assertThat(copy).isNotNull();
+        assertThat(original.size()).isEqualTo(copy.size());
+        for (FreezePeriod period1 : original) {
+            assertThat(period1).isNotNull();
+            assertFreezePeriodListContains(copy, period1);
+        }
+        for (FreezePeriod period1 : copy) {
+            assertThat(period1).isNotNull();
+            assertFreezePeriodListContains(original, period1);
+        }
+    }
+
+    private void assertFreezePeriodListContains(List<FreezePeriod> list, FreezePeriod period) {
+        for (FreezePeriod other : list) {
+            assertThat(other).isNotNull();
+            if (areFreezePeriodsEqual(period, other)) {
+                return;
+            }
+        }
+        final List<String> printablePeriods = new ArrayList<>();
+        for (FreezePeriod printablePeriod : list) {
+            printablePeriods.add(printablePeriod.toString());
+        }
+        fail(String.format("FreezePeriod list [%s] does not contain the specified period %s.",
+            String.join(", ", printablePeriods), period));
+    }
+
+    private boolean areFreezePeriodsEqual(FreezePeriod period1, FreezePeriod period2) {
+        return period1 != null && period2 != null
+            && Objects.equals(period1.getStart(), period2.getStart())
+            && Objects.equals(period1.getEnd(), period2.getEnd());
+    }
+
     private void testPolicy(SystemUpdatePolicy policy) {
         mDevicePolicyManager.setSystemUpdatePolicy(getWho(), policy);
         waitForPolicyChangedBroadcast();
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
index 60395c2..bb2393c 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
@@ -31,6 +31,7 @@
     ctstestrunner
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 19
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
index 6562b29..bd3ac6e 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
@@ -17,8 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.intent.sender">
 
-    <uses-sdk android:minSdkVersion="19" />
-
     <permission
         android:name="com.android.cts.intent.sender.permission.SAMPLE"
         android:label="Sample Permission" />
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java
index 46fc0c4..1b56727 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java
@@ -75,19 +75,6 @@
                 + INITIAL_TEXT, clipboardText == null || clipboardText.equals(INITIAL_TEXT));
     }
 
-    public void testIsNotified() throws Exception {
-        try {
-            mNotified = new Semaphore(0);
-            mActivity.addPrimaryClipChangedListener(this);
-
-            askCrossProfileReceiverToCopy(NEW_TEXT);
-
-            assertTrue(mNotified.tryAcquire(5, TimeUnit.SECONDS));
-        } finally {
-            mActivity.removePrimaryClipChangedListener(this);
-        }
-    }
-
     private void askCrossProfileReceiverToCopy(String text) throws Exception {
         Intent intent = new Intent(ACTION_COPY_TO_CLIPBOARD);
         intent.putExtra("extra_text", text);
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
index e37617e..f744237 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -35,6 +35,7 @@
 	testng
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
index e8a1291..73dfbc1 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.launchertests;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -22,10 +24,12 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,6 +52,10 @@
 public class LauncherAppsTests extends AndroidTestCase {
 
     public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp";
+    private static final String NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE =
+            "com.android.cts.nolaunchableactivityapp";
+
+    private static final String SYNTHETIC_APP_DETAILS_ACTIVITY = "android.app.AppDetailsActivity";
 
     public static final String USER_EXTRA = "user_extra";
     public static final String PACKAGE_EXTRA = "package_extra";
@@ -201,6 +209,70 @@
         assertFalse(mLauncherApps.isPackageEnabled("android", mUser));
     }
 
+    public void testNoLaunchableActivityAppHasAppDetailsActivityInjected() throws Exception {
+        // NoLaunchableActivityApp is installed for duration of this test - make sure
+        // it's present on the activity list, has the synthetic activity generated, and it's
+        // enabled and exported
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
+        boolean noLaunchableActivityAppFound = false;
+        for (LauncherActivityInfo activity : activities) {
+            if (!activity.getUser().equals(mUser)) {
+                continue;
+            }
+            ComponentName compName = activity.getComponentName();
+            if (compName.getPackageName().equals(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE)) {
+                noLaunchableActivityAppFound = true;
+                // make sure it points to the synthetic app details activity
+                assertEquals(activity.getName(), SYNTHETIC_APP_DETAILS_ACTIVITY);
+                // make sure it's both exported and enabled
+                try {
+                    PackageManager pm = mInstrumentation.getContext().getPackageManager();
+                    ActivityInfo ai = pm.getActivityInfo(compName, /*flags=*/ 0);
+                    assertTrue("Component " + compName + " is not enabled", ai.enabled);
+                    assertTrue("Component " + compName + " is not exported", ai.exported);
+                } catch (NameNotFoundException e) {
+                    fail("Package " + compName.getPackageName() + " not found.");
+                }
+            }
+        }
+        assertTrue(noLaunchableActivityAppFound);
+    }
+
+    public void testNoInjectedActivityFound() throws Exception {
+        // NoLaunchableActivityApp is installed for duration of this test - make sure
+        // it's NOT present on the activity list
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
+        for (LauncherActivityInfo activity : activities) {
+            if (!activity.getUser().equals(mUser)) {
+                continue;
+            }
+            ComponentName compName = activity.getComponentName();
+            if (compName.getPackageName().equals(NO_LAUNCHABLE_ACTIVITY_APP_PACKAGE)) {
+                fail("Injected activity found in package: " + compName.getPackageName());
+            }
+        }
+    }
+
+    public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
+        for (LauncherActivityInfo activity : activities) {
+            if (!activity.getUser().equals(mUser)) {
+                continue;
+            }
+            ApplicationInfo appInfo = activity.getApplicationInfo();
+            boolean isSystemApp = ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
+                    || ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+            if (isSystemApp) {
+                // make sure we haven't generated a synthetic app details activity for it
+                assertNotEquals("Found a system app that had a synthetic activity generated,"
+                        + " package name: " + activity.getComponentName().getPackageName()
+                        + "; activity name: " + activity.getName(),
+                        activity.getName(),
+                        SYNTHETIC_APP_DETAILS_ACTIVITY);
+            }
+        }
+    }
+
     private void expectSecurityException(ExceptionRunnable action, String failMessage)
             throws Exception {
         try {
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
index ead09a4..935be3b 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_JAVA_LIBRARIES := junit
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index e876054..5f71ade 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -33,12 +33,15 @@
 	compatibility-device-util \
 	ub-uiautomator \
 	android-support-test \
-	guava
+	guava \
+	truth-prebuilt \
+	testng
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     androidx.legacy_legacy-support-v4
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 27
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 58e232b..05c6eb7 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -17,8 +17,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.managedprofile">
 
-    <uses-sdk android:minSdkVersion="20"/>
+    <uses-sdk android:minSdkVersion="27"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
@@ -31,6 +32,8 @@
     <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <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" />
 
     <application
         android:testOnly="true">
@@ -161,6 +164,15 @@
 
         <activity android:name=".TimeoutActivity" android:exported="true"/>
 
+        <activity
+            android:name=".DummyCrossProfileViewEventActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.provider.calendar.action.VIEW_MANAGED_PROFILE_CALENDAR_EVENT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
         <service
             android:name=".CrossProfileNotificationListenerService"
             android:label="CrossProfileNotificationListenerService"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/res/layout/view_event.xml b/hostsidetests/devicepolicy/app/ManagedProfile/res/layout/view_event.xml
new file mode 100644
index 0000000..75f335f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/res/layout/view_event.xml
@@ -0,0 +1,27 @@
+<?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">
+
+    <TextView
+        android:id="@+id/view_event_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+    />
+
+</LinearLayout>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/device_admin.xml
index 30538af..b42c966 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/device_admin.xml
@@ -19,5 +19,7 @@
         <limit-password />
         <disable-keyguard-features/>
         <force-lock />
+        <expire-password />
+        <watch-login />
     </uses-policies>
 </device-admin>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index f10822c..ae530f3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,6 +19,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.test.InstrumentationTestCase;
 
 /**
@@ -38,6 +39,7 @@
     protected DevicePolicyManager mDevicePolicyManager;
     protected DevicePolicyManager mParentDevicePolicyManager;
     protected Context mContext;
+    protected boolean mHasSecureLockScreen;
 
     @Override
     protected void setUp() throws Exception {
@@ -54,6 +56,9 @@
         assertTrue(mDevicePolicyManager.isProfileOwnerApp(
                 ADMIN_RECEIVER_COMPONENT.getPackageName()));
         assertTrue(mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
+
+        mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_SECURE_LOCK_SCREEN);
     }
 
     protected DevicePolicyManager getDevicePolicyManager(boolean isParent) {
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
index 847ce7a..6a57e2e 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
@@ -29,9 +29,6 @@
 
     protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
 
-    private static final String PRIMARY_ADMIN_RECEIVER_TEST_CLASS =
-            MANAGED_PROFILE_PKG + ".PrimaryUserDeviceAdmin";
-
     private static final String MANAGED_PROFILE_ADMIN_RECEIVER_TEST_CLASS =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
 
@@ -39,8 +36,6 @@
 
     private CameraManager mCameraManager;
 
-    private ComponentName mPrimaryAdminComponent;
-
     private ComponentName mManagedProfileAdminComponent;
 
     private HandlerThread mBackgroundThread;
@@ -56,8 +51,6 @@
         mDevicePolicyManager = (DevicePolicyManager) getContext()
                 .getSystemService(Context.DEVICE_POLICY_SERVICE);
         mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
-        mPrimaryAdminComponent = new ComponentName(MANAGED_PROFILE_PKG,
-                PRIMARY_ADMIN_RECEIVER_TEST_CLASS);
         mManagedProfileAdminComponent = new ComponentName(MANAGED_PROFILE_PKG,
                 MANAGED_PROFILE_ADMIN_RECEIVER_TEST_CLASS);
         startBackgroundThread();
@@ -83,31 +76,6 @@
         checkCanOpenCamera(true);
     }
 
-    public void testDisableCameraInPrimaryProfile() throws Exception {
-        mDevicePolicyManager.setCameraDisabled(mPrimaryAdminComponent, true);
-        assertTrue(mDevicePolicyManager.getCameraDisabled(mPrimaryAdminComponent));
-        assertTrue(mDevicePolicyManager.getCameraDisabled(null));
-        checkCanOpenCamera(false);
-    }
-
-    public void testEnableCameraInPrimaryProfile() throws Exception {
-        mDevicePolicyManager.setCameraDisabled(mPrimaryAdminComponent, false);
-        assertFalse(mDevicePolicyManager.getCameraDisabled(mPrimaryAdminComponent));
-        assertFalse(mDevicePolicyManager.getCameraDisabled(null));
-        checkCanOpenCamera(true);
-    }
-
-    public void testIsCameraEnabledInPrimaryProfile() throws Exception {
-        assertFalse(mDevicePolicyManager.getCameraDisabled(mPrimaryAdminComponent));
-        assertFalse(mDevicePolicyManager.getCameraDisabled(null));
-        checkCanOpenCamera(true);
-    }
-
-    public void testIsCameraEnabledInManagedProfile() throws Exception {
-        assertFalse(mDevicePolicyManager.getCameraDisabled(mManagedProfileAdminComponent));
-        assertFalse(mDevicePolicyManager.getCameraDisabled(null));
-        checkCanOpenCamera(true);
-    }
 
     /**
      * Beginning with Android 7.0, the camera restriction policy isn't kept in the
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
index 1ca6402..1419e4c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
@@ -51,7 +51,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ContactsTest extends AndroidTestCase {
 
     private static final String TAG = "ContactsTest";
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
new file mode 100644
index 0000000..522f048
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
@@ -0,0 +1,536 @@
+/*
+ * 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.managedprofile;
+
+import static com.android.cts.managedprofile.BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.CalendarContract;
+import android.provider.Settings.Secure;
+import android.support.test.InstrumentationRegistry;
+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 android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class contains tests for cross profile calendar related features. Most of the tests in
+ * this class will need different setups, so the tests will be run separately.
+ */
+public class CrossProfileCalendarTest extends AndroidTestCase {
+
+    private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+
+    private static final String TEST_ACCOUNT_NAME = AccountAuthenticator.ACCOUNT_NAME;
+    private static final String TEST_ACCOUNT_TYPE = AccountAuthenticator.ACCOUNT_TYPE;
+
+    private static String WORK_CALENDAR_TITLE = "Calendar1";
+    private static int WORK_CALENDAR_COLOR = 0xFFFF0000;
+    // Make sure sync_event=1 for the test calendar so that instances table got updated.
+    private static int WORK_SYNC_EVENT = 1;
+    private static String WORK_TIMEZONE = "America/Los_Angeles";
+
+    private static String WORK_EVENT_TITLE = "event_title1";
+    private static String WORK_EVENT_TITLE_2= "event_title2";
+    private static final String WORK_EVENT_DTSTART_STRING = "2018-05-01T00:00:00";
+    private static final String WORK_EVENT_DTEND_STRING = "2018-05-01T20:00:00";
+    private static final String WORK_EVENT_DTSTART_STRING_2 = "2013-05-01T00:00:00";
+    private static final String WORK_EVENT_DTEND_STRING_2 = "2013-05-01T20:00:00";
+    private static long WORK_EVENT_DTSTART = parseTimeStringToMillis(
+            WORK_EVENT_DTSTART_STRING, WORK_TIMEZONE);
+    private static long WORK_EVENT_DTEND = parseTimeStringToMillis(
+            WORK_EVENT_DTEND_STRING, WORK_TIMEZONE);
+    private final long WORK_EVENT_DTSTART_2 = parseTimeStringToMillis(
+            WORK_EVENT_DTSTART_STRING_2, WORK_TIMEZONE);
+    private final long WORK_EVENT_DTEND_2 = parseTimeStringToMillis(
+            WORK_EVENT_DTEND_STRING_2, WORK_TIMEZONE);
+    private static long WORK_EVENT_DTSTART_JULIAN_DAY = parseTimeStringToJulianDay(
+            WORK_EVENT_DTSTART_STRING, WORK_TIMEZONE);
+    private static long WORK_EVENT_DTEND_JULIAN_DAY = parseTimeStringToJulianDay(
+            WORK_EVENT_DTEND_STRING, WORK_TIMEZONE);
+    private final long WORK_EVENT_DTSTART_2_JULIAN_DAY = parseTimeStringToJulianDay(
+            WORK_EVENT_DTSTART_STRING_2, WORK_TIMEZONE);
+    private final long WORK_EVENT_DTEND_2_JULIAN_DAY = parseTimeStringToJulianDay(
+            WORK_EVENT_DTEND_STRING_2, WORK_TIMEZONE);
+
+    private static int WORK_EVENT_COLOR = 0xff123456;
+    private static String WORK_EVENT_LOCATION = "Work event location.";
+    private static String WORK_EVENT_DESCRIPTION = "This is a work event.";
+
+    private static final String CROSS_PROFILE_CALENDAR_ENABLED =
+            "cross_profile_calendar_enabled";
+
+    private static final String SELECTION_ACCOUNT_TYPE = "(" +
+            CalendarContract.Calendars.ACCOUNT_TYPE + " = ? )";
+
+    private static final long TEST_VIEW_EVENT_ID = 1;
+    private static final long TEST_VIEW_EVENT_START = 100;
+    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;
+
+    private static long parseTimeStringToMillis(String timeStr, String timeZone) {
+        Time time = new Time(timeZone);
+        time.parse3339(timeStr);
+        return time.toMillis(/* ignoreDst= */false );
+    }
+
+    private static int parseTimeStringToJulianDay(String timeStr, String timeZone) {
+        Time time = new Time(timeZone);
+        time.parse3339(timeStr);
+        return Time.getJulianDay(time.toMillis(/* ignoreDst= */false), time.gmtoff);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = mContext.getContentResolver();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testCrossProfileCalendarPackage() {
+        requireRunningOnManagedProfile();
+
+        Set<String> whitelist = mDevicePolicyManager.getCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT);
+        assertThat(whitelist).isEmpty();
+
+        mDevicePolicyManager.setCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT, new ArraySet<String>(Arrays.asList(MANAGED_PROFILE_PKG)));
+        whitelist = mDevicePolicyManager.getCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT);
+        assertThat(whitelist.size()).isEqualTo(1);
+        assertThat(whitelist.contains(MANAGED_PROFILE_PKG)).isTrue();
+
+        mDevicePolicyManager.setCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT, Collections.emptySet());
+        whitelist = mDevicePolicyManager.getCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT);
+        assertThat(whitelist).isEmpty();
+    }
+
+    // This test should be run when the test package is not whitelised or cross-profile calendar
+    // is disabled in settings.
+    public void testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled() {
+        requireRunningOnPrimaryProfile();
+
+        final Cursor cursor = mResolver.query(
+                CalendarContract.Calendars.ENTERPRISE_CONTENT_URI,
+                null, null, null, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(0);
+    }
+
+    // This test should be run when the test package is not whitelised or cross-profile calendar
+    // is disabled in settings.
+    public void testPrimaryProfile_cannotAccessWorkEventsWhenDisabled() {
+        requireRunningOnPrimaryProfile();
+
+        final Cursor cursor = mResolver.query(
+                CalendarContract.Events.ENTERPRISE_CONTENT_URI,
+                null, SELECTION_ACCOUNT_TYPE, new String[]{TEST_ACCOUNT_TYPE}, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(0);
+    }
+
+    // This test should be run when the test package is not whitelised or cross-profile calendar
+    // is disabled in settings.
+    public void testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled() {
+        requireRunningOnPrimaryProfile();
+
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(CalendarContract.Instances.ENTERPRISE_CONTENT_URI,
+                        WORK_EVENT_DTSTART - DateUtils.YEAR_IN_MILLIS,
+                        WORK_EVENT_DTEND + DateUtils.YEAR_IN_MILLIS, null),
+                null, null, null, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(0);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final String[] projection = new String[] {
+                CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
+                CalendarContract.Calendars.CALENDAR_COLOR,
+                CalendarContract.Calendars.CALENDAR_TIME_ZONE
+        };
+        final Cursor cursor = mResolver.query(
+                CalendarContract.Calendars.ENTERPRISE_CONTENT_URI,
+                projection, SELECTION_ACCOUNT_TYPE, new String[]{TEST_ACCOUNT_TYPE}, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getString(0)).isEqualTo(WORK_CALENDAR_TITLE);
+        assertThat(cursor.getInt(1)).isEqualTo(WORK_CALENDAR_COLOR);
+        assertThat(cursor.getString(2)).isEqualTo(WORK_TIMEZONE);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_getCorrectWorkEventsWhenEnabled() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final String selection = "(" + CalendarContract.Calendars.ACCOUNT_TYPE  + "=? AND "
+                + CalendarContract.Events.TITLE  + " =? )";
+        final String[] selectionArgs = new String[] {
+                TEST_ACCOUNT_TYPE,
+                WORK_EVENT_TITLE
+        };
+        final String[] projection = new String[] {
+                CalendarContract.Events.TITLE,
+                CalendarContract.Events.EVENT_LOCATION,
+                CalendarContract.Events.DTSTART,
+                CalendarContract.Calendars.CALENDAR_DISPLAY_NAME
+        };
+        final Cursor cursor = mResolver.query(
+                CalendarContract.Events.ENTERPRISE_CONTENT_URI,
+                projection, selection, selectionArgs, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getString(0)).isEqualTo(WORK_EVENT_TITLE);
+        assertThat(cursor.getString(1)).isEqualTo(WORK_EVENT_LOCATION);
+        assertThat(cursor.getLong(2)).isEqualTo(WORK_EVENT_DTSTART);
+        assertThat(cursor.getString(3)).isEqualTo(WORK_CALENDAR_TITLE);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_getCorrectWorkInstancesWhenEnabled() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final String[] projection = new String[]{
+                CalendarContract.Instances.TITLE,
+                CalendarContract.Instances.DTSTART,
+                CalendarContract.Instances.CALENDAR_DISPLAY_NAME,
+        };
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(CalendarContract.Instances.ENTERPRISE_CONTENT_URI,
+                        WORK_EVENT_DTSTART - DateUtils.YEAR_IN_MILLIS,
+                        WORK_EVENT_DTEND + DateUtils.YEAR_IN_MILLIS, null),
+                projection, null, null, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getString(0)).isEqualTo(WORK_EVENT_TITLE);
+        assertThat(cursor.getLong(1)).isEqualTo(WORK_EVENT_DTSTART);
+        assertThat(cursor.getString(2)).isEqualTo(WORK_CALENDAR_TITLE);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final String[] projection = new String[]{
+                CalendarContract.Instances.TITLE,
+                CalendarContract.Instances.DTSTART,
+                CalendarContract.Instances.CALENDAR_DISPLAY_NAME,
+        };
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(CalendarContract.Instances.ENTERPRISE_CONTENT_BY_DAY_URI,
+                        WORK_EVENT_DTSTART_JULIAN_DAY - 1,
+                        WORK_EVENT_DTEND_JULIAN_DAY + 1, null),
+                projection, null, null, null);
+
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getString(0)).isEqualTo(WORK_EVENT_TITLE);
+        assertThat(cursor.getLong(1)).isEqualTo(WORK_EVENT_DTSTART);
+        assertThat(cursor.getString(2)).isEqualTo(WORK_CALENDAR_TITLE);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_canAccessWorkInstancesSearch1() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(CalendarContract.Instances.ENTERPRISE_CONTENT_SEARCH_URI,
+                        WORK_EVENT_DTSTART - DateUtils.YEAR_IN_MILLIS,
+                        WORK_EVENT_DTEND + DateUtils.YEAR_IN_MILLIS, WORK_EVENT_TITLE),
+                null, null, null, null);
+        // There is only one event that meets the search criteria.
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(1);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_canAccessWorkInstancesSearch2() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(CalendarContract.Instances.ENTERPRISE_CONTENT_SEARCH_URI,
+                        WORK_EVENT_DTSTART_2 - DateUtils.YEAR_IN_MILLIS,
+                        WORK_EVENT_DTEND + DateUtils.YEAR_IN_MILLIS, WORK_EVENT_DESCRIPTION),
+                null, null, null, null);
+        // There are two events that meet the search criteria.
+        assertThat(cursor).isNotNull();
+        assertThat(cursor.getCount()).isEqualTo(2);
+    }
+
+    // This test should be run when the test package is whitelisted and cross-profile calendar
+    // is enabled in settings.
+    public void testPrimaryProfile_canAccessWorkInstancesSearchByDay() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final Cursor cursor = mResolver.query(
+                buildQueryInstancesUri(
+                        CalendarContract.Instances.ENTERPRISE_CONTENT_SEARCH_BY_DAY_URI,
+                        WORK_EVENT_DTSTART_2_JULIAN_DAY - 1,
+                        WORK_EVENT_DTEND_2_JULIAN_DAY + 1,
+                        WORK_EVENT_DESCRIPTION),
+                null, null, null, null);
+        // There are two events that meet the search criteria.
+        assertThat(cursor).isNotNull();
+        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() {
+        requireRunningOnPrimaryProfile();
+
+        // Test the return cursor is correct when the all checks are met.
+        final String[] projection = new String[] {
+                CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
+                CalendarContract.Calendars.CALENDAR_COLOR,
+                CalendarContract.Calendars.OWNER_ACCOUNT
+        };
+        assertThrows(IllegalArgumentException.class, () -> mResolver.query(
+                CalendarContract.Calendars.ENTERPRISE_CONTENT_URI,
+                projection, SELECTION_ACCOUNT_TYPE, new String[]{TEST_ACCOUNT_TYPE}, null));
+    }
+
+    // This test should be run when the test package is not whitelisted.
+    public void testViewEventCrossProfile_intentFailedWhenNotWhitelisted() throws Exception {
+        requireRunningOnPrimaryProfile();
+
+        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)).isFalse();
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testWhitelistManagedProfilePackage() {
+        requireRunningOnManagedProfile();
+        mDevicePolicyManager.setCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT, new ArraySet<String>(Arrays.asList(MANAGED_PROFILE_PKG)));
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testWhitelistAllPackages() {
+        requireRunningOnManagedProfile();
+        mDevicePolicyManager.setCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT, null);
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testCleanupWhitelist() {
+        requireRunningOnManagedProfile();
+        mDevicePolicyManager.setCrossProfileCalendarPackages(
+                ADMIN_RECEIVER_COMPONENT, Collections.emptySet());
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testAddTestCalendarDataForWorkProfile() throws Exception {
+        requireRunningOnManagedProfile();
+        addTestAccount();
+        final long calendarId = insertWorkCalendar(WORK_CALENDAR_TITLE);
+        insertWorkEvent(WORK_EVENT_TITLE, calendarId, WORK_EVENT_DTSTART, WORK_EVENT_DTEND);
+        insertWorkEvent(WORK_EVENT_TITLE_2, calendarId, WORK_EVENT_DTSTART_2, WORK_EVENT_DTEND_2);
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testCleanupTestCalendarDataForWorkProfile() {
+        requireRunningOnManagedProfile();
+        int numDeleted = mResolver.delete(CalendarContract.Events.CONTENT_URI,
+                "(" + CalendarContract.Calendars.ACCOUNT_TYPE + " = ? )",
+                new String[]{TEST_ACCOUNT_TYPE});
+        assertThat(numDeleted).isEqualTo(2);
+        numDeleted = mResolver.delete(CalendarContract.Calendars.CONTENT_URI,
+                "(" + CalendarContract.Calendars.ACCOUNT_TYPE + " = ? )",
+                new String[]{TEST_ACCOUNT_TYPE});
+        assertThat(numDeleted).isEqualTo(1);
+        removeTestAccount();
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testEnableCrossProfileCalendarSettings() {
+        requireRunningOnManagedProfile();
+        Secure.putInt(mResolver, CROSS_PROFILE_CALENDAR_ENABLED,
+                /* value= */1);
+    }
+
+    // Utils method, not a actual test. Ran from ManagedProfileTest.java to set up for actual tests.
+    public void testDisableCrossProfileCalendarSettings() {
+        requireRunningOnManagedProfile();
+        Secure.putInt(mResolver, CROSS_PROFILE_CALENDAR_ENABLED,
+                /* value= */0);
+    }
+
+    // Builds an uri for querying Instances table.
+    private Uri buildQueryInstancesUri(Uri uri, long start, long end, String query) {
+        Uri.Builder builder = uri.buildUpon();
+        ContentUris.appendId(builder, start);
+        ContentUris.appendId(builder, end);
+        if (!TextUtils.isEmpty(query)) {
+            builder = builder.appendPath(query);
+        }
+        return builder.build();
+    }
+
+    // This method should align with
+    // DummyCrossProfileViewEventActivity#getViewEventCrossProfileString.
+    private String getViewEventCrossProfileString(long eventId, long start, long end,
+            boolean allDay, int flags) {
+        return String.format("id:%d, start:%d, end:%d, allday:%b, flag:%d", eventId,
+                start, end, allDay, flags);
+    }
+
+    // This method is to guard that particular tests are supposed to run on managed profile.
+    private void requireRunningOnManagedProfile() {
+        assertThat(isManagedProfile()).isTrue();
+    }
+
+    // This method is to guard that particular tests are supposed to run on primary profile.
+    private void requireRunningOnPrimaryProfile() {
+        assertThat(isManagedProfile()).isFalse();
+    }
+
+    private long insertWorkCalendar(String displayName) {
+        final ContentValues cv = new ContentValues();
+        cv.put(CalendarContract.Calendars.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        cv.put(CalendarContract.Calendars.OWNER_ACCOUNT, TEST_ACCOUNT_NAME);
+        cv.put(CalendarContract.Calendars.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        cv.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, displayName);
+        cv.put(CalendarContract.Calendars.CALENDAR_COLOR, WORK_CALENDAR_COLOR);
+        cv.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, WORK_TIMEZONE);
+        cv.put(CalendarContract.Calendars.SYNC_EVENTS, WORK_SYNC_EVENT);
+        final Uri uri = mResolver.insert(
+                addSyncQueryParams(CalendarContract.Calendars.CONTENT_URI, TEST_ACCOUNT_NAME,
+                        TEST_ACCOUNT_TYPE), cv);
+        return Long.parseLong(uri.getLastPathSegment());
+    }
+
+    private void insertWorkEvent(String eventTitle, long calendarId, long dtStart, long dtEnd) {
+        final ContentValues cv = new ContentValues();
+        cv.put(CalendarContract.Events.TITLE, eventTitle);
+        cv.put(CalendarContract.Events.CALENDAR_ID, calendarId);
+        cv.put(CalendarContract.Events.DESCRIPTION, WORK_EVENT_DESCRIPTION);
+        cv.put(CalendarContract.Events.EVENT_LOCATION, WORK_EVENT_LOCATION);
+        cv.put(CalendarContract.Events.EVENT_COLOR, WORK_EVENT_COLOR);
+        cv.put(CalendarContract.Events.DTSTART, dtStart);
+        cv.put(CalendarContract.Events.DTEND, dtEnd);
+        cv.put(CalendarContract.Events.EVENT_TIMEZONE, WORK_TIMEZONE);
+        mResolver.insert(CalendarContract.Events.CONTENT_URI, cv);
+    }
+
+    /**
+     * Constructs a URI from a base URI (e.g. "content://com.android.calendar/calendars"),
+     * an account name, and an account type.
+     */
+    private Uri addSyncQueryParams(Uri uri, String account, String accountType) {
+        return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account)
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType).build();
+    }
+
+    private void addTestAccount() {
+        Account account = new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE);
+        AccountManager.get(mContext).addAccountExplicitly(account, null, null);
+    }
+
+    private void removeTestAccount() {
+        Account account = new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE);
+        AccountManager.get(mContext).removeAccountExplicitly(account);
+    }
+
+    private boolean isManagedProfile() {
+        String adminPackage = ADMIN_RECEIVER_COMPONENT.getPackageName();
+        return mDevicePolicyManager.isProfileOwnerApp(adminPackage);
+    }
+}
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
new file mode 100644
index 0000000..7c26fad
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.managedprofile;
+
+import android.content.Context;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+/**
+ * Verifies device identifier access for the profile owner.
+ */
+public class DeviceIdentifiersTest extends BaseManagedProfileTest {
+
+    private static final String DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE =
+            "An unexpected value was received by the profile owner with the READ_PHONE_STATE "
+                    + "permission when invoking %s";
+    private static final String NO_SECURITY_EXCEPTION_ERROR_MESSAGE =
+            "A profile owner that does not have the READ_PHONE_STATE permission must receive a "
+                    + "SecurityException when invoking %s";
+
+    public void testProfileOwnerCanGetDeviceIdentifiersWithPermission() throws Exception {
+        // The profile owner with the READ_PHONE_STATE permission should have access to all device
+        // identifiers. However since the TelephonyManager methods can return null this method
+        // verifies that the profile owner with the READ_PHONE_STATE permission receives the same
+        // value that the shell identity receives with the READ_PRIVILEGED_PHONE_STATE permission.
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        try {
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getDeviceId"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getDeviceId()), telephonyManager.getDeviceId());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getImei"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getImei()), telephonyManager.getImei());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getMeid"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getMeid()), telephonyManager.getMeid());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSubscriberId"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getSubscriberId()), telephonyManager.getSubscriberId());
+            assertEquals(
+                    String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "getSimSerialNumber"),
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(telephonyManager,
+                            (tm) -> tm.getSimSerialNumber()),
+                    telephonyManager.getSimSerialNumber());
+            assertEquals(String.format(DEVICE_ID_WITH_PERMISSION_ERROR_MESSAGE, "Build#getSerial"),
+                    ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial),
+                    Build.getSerial());
+        } catch (SecurityException e) {
+            fail("The profile owner with the READ_PHONE_STATE permission must be able to access "
+                    + "the device IDs: " + e);
+        }
+    }
+
+    public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+        // The profile owner without the READ_PHONE_STATE permission should still receive a
+        // SecurityException when querying for device identifiers.
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        try {
+            telephonyManager.getDeviceId();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getImei();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getMeid();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getSubscriberId();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            telephonyManager.getSimSerialNumber();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            Build.getSerial();
+            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyLoggingTest.java
new file mode 100644
index 0000000..58cfc02
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyLoggingTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.managedprofile;
+
+/**
+ * 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
+ * are logged. Note that the metrics verification is done on the host-side.
+ */
+public class DevicePolicyLoggingTest extends BaseManagedProfileTest {
+    public void testSetProfileNameLogged() {
+        mDevicePolicyManager.setProfileName(ADMIN_RECEIVER_COMPONENT, "Test name");
+    }
+}
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
new file mode 100644
index 0000000..c7f905a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -0,0 +1,169 @@
+package com.android.cts.managedprofile;
+
+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;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.os.PersistableBundle;
+
+/**
+ * Tests that the {@link DevicePolicyManager} APIs that should work for {@link
+ * DevicePolicyManager#getParentProfileInstance(ComponentName)} are supported.
+ *
+ * <p>Minimum restriction APIs are already tested by {@link PasswordMinimumRestrictionsTest}.
+ */
+public class DevicePolicyManagerParentSupportTest extends BaseManagedProfileTest {
+    private static final ComponentName FAKE_COMPONENT = new ComponentName(
+            FakeComponent.class.getPackage().getName(), FakeComponent.class.getName());
+
+    public void testSetAndGetPasswordQuality_onParent() {
+        mParentDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+        final int actualPasswordQuality =
+                mParentDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualPasswordQuality).isEqualTo(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+    }
+
+    public void testSetAndGetPasswordHistoryLength_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final int passwordHistoryLength = 5;
+
+        mParentDevicePolicyManager.setPasswordHistoryLength(
+                ADMIN_RECEIVER_COMPONENT, passwordHistoryLength);
+        final int actualPasswordHistoryLength =
+                mParentDevicePolicyManager.getPasswordHistoryLength(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualPasswordHistoryLength).isEqualTo(passwordHistoryLength);
+    }
+
+    public void testSetAndGetPasswordExpirationTimeout_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final int passwordExpirationTimeout = 432000000;
+
+        mParentDevicePolicyManager.setPasswordExpirationTimeout(
+                ADMIN_RECEIVER_COMPONENT, passwordExpirationTimeout);
+        final long actualPasswordExpirationTimeout =
+                mParentDevicePolicyManager.getPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualPasswordExpirationTimeout).isEqualTo(passwordExpirationTimeout);
+    }
+
+    public void testGetPasswordExpiration_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final long passwordExpirationTimeout = 432000000;
+        final long currentTime = System.currentTimeMillis();
+
+        mParentDevicePolicyManager.setPasswordExpirationTimeout(
+                ADMIN_RECEIVER_COMPONENT, passwordExpirationTimeout);
+        final long actualPasswordExpiration =
+                mParentDevicePolicyManager.getPasswordExpiration(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualPasswordExpiration).isAtLeast(passwordExpirationTimeout + currentTime);
+    }
+
+    public void testGetMaximumPasswordLength_onParent() {
+        final int actualMaximumPasswordLength =
+                mParentDevicePolicyManager.getPasswordMaximumLength(
+                        PASSWORD_QUALITY_NUMERIC_COMPLEX);
+        assertThat(actualMaximumPasswordLength).isGreaterThan(0);
+    }
+
+    public void testIsActivePasswordSufficient_onParent_isSupported() {
+        setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+        assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+    }
+
+    private void setPasswordQuality(int quality) {
+        mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, quality);
+    }
+
+    public void testGetCurrentFailedPasswordAttempts_onParent_isSupported() {
+        assertThat(mParentDevicePolicyManager.getCurrentFailedPasswordAttempts()).isEqualTo(0);
+    }
+
+    public void testSetAndGetMaximumFailedPasswordsForWipe_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final int maximumFailedPasswordsForWipe = 15;
+
+        mParentDevicePolicyManager.setMaximumFailedPasswordsForWipe(
+                ADMIN_RECEIVER_COMPONENT, maximumFailedPasswordsForWipe);
+        final int actualMaximumFailedPasswordsForWipe =
+                mParentDevicePolicyManager.getMaximumFailedPasswordsForWipe(
+                        ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualMaximumFailedPasswordsForWipe).isEqualTo(maximumFailedPasswordsForWipe);
+    }
+
+    public void testSetAndGetMaximumTimeToLock_onParent() {
+        final int maximumTimeToLock = 6000;
+
+        mParentDevicePolicyManager.setMaximumTimeToLock(
+                ADMIN_RECEIVER_COMPONENT, maximumTimeToLock);
+        final long actualMaximumTimeToLock =
+                mParentDevicePolicyManager.getMaximumTimeToLock(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualMaximumTimeToLock).isEqualTo(maximumTimeToLock);
+    }
+
+    public void testLockNow_onParent_isSupported() {
+        mParentDevicePolicyManager.lockNow();
+        // Will fail if a SecurityException is thrown.
+    }
+
+    public void testSetAndGetKeyguardDisabledFeatures_onParent() {
+        mParentDevicePolicyManager.setKeyguardDisabledFeatures(
+                ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_TRUST_AGENTS);
+        long actualKeyguardDisabledFeatures =
+                mParentDevicePolicyManager.getKeyguardDisabledFeatures(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualKeyguardDisabledFeatures).isEqualTo(KEYGUARD_DISABLE_TRUST_AGENTS);
+    }
+
+    public void testSetAndGetTrustAgentConfiguration_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final PersistableBundle configuration = new PersistableBundle();
+        final String key = "key";
+        final String value = "value";
+        configuration.putString(key, value);
+
+        mParentDevicePolicyManager.setTrustAgentConfiguration(
+                ADMIN_RECEIVER_COMPONENT, FAKE_COMPONENT, configuration);
+        final PersistableBundle actualConfiguration =
+                mParentDevicePolicyManager.getTrustAgentConfiguration(
+                        ADMIN_RECEIVER_COMPONENT, FAKE_COMPONENT).get(0);
+
+        assertThat(actualConfiguration.get(key)).isEqualTo(value);
+    }
+
+    public void testSetAndGetRequiredStrongAuthTimeout_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        final int requiredStrongAuthTimeout = 4600000;
+
+        mParentDevicePolicyManager.setRequiredStrongAuthTimeout(
+                ADMIN_RECEIVER_COMPONENT, requiredStrongAuthTimeout);
+        final long actualRequiredStrongAuthTimeout =
+                mParentDevicePolicyManager.getRequiredStrongAuthTimeout(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualRequiredStrongAuthTimeout).isEqualTo(requiredStrongAuthTimeout);
+    }
+
+    public abstract class FakeComponent extends BroadcastReceiver {}
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DummyCrossProfileViewEventActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DummyCrossProfileViewEventActivity.java
new file mode 100644
index 0000000..ae48084
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DummyCrossProfileViewEventActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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.managedprofile;
+
+import static com.android.cts.managedprofile.BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.CalendarContract;
+import android.widget.TextView;
+
+public class DummyCrossProfileViewEventActivity extends Activity {
+
+    private DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.view_event);
+        mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        // Only set the text when managed profile is getting the
+        // ACTION_VIEW_MANAGED_PROFILE_CALENDAR_EVENT intent.
+        final Intent intent = getIntent();
+        if (intent.getAction() == CalendarContract.ACTION_VIEW_MANAGED_PROFILE_CALENDAR_EVENT
+                && isManagedProfile()) {
+            TextView textView = findViewById(R.id.view_event_text);
+            final Bundle bundle = getIntent().getExtras();
+            final String textVeiwString = getViewEventCrossProfileString(
+                    (long)bundle.get(CalendarContract.EXTRA_EVENT_ID),
+                    (long)bundle.get(CalendarContract.EXTRA_EVENT_BEGIN_TIME),
+                    (long)bundle.get(CalendarContract.EXTRA_EVENT_END_TIME),
+                    (boolean)bundle.get(CalendarContract.EXTRA_EVENT_ALL_DAY),
+                    getIntent().getFlags()
+            );
+            textView.setText(textVeiwString);
+        }
+    }
+
+    private String getViewEventCrossProfileString(long eventId, long start, long end,
+            boolean allDay, int flags) {
+        return String.format("id:%d, start:%d, end:%d, allday:%b, flag:%d", eventId,
+                start, end, allDay, flags);
+    }
+
+    private boolean isManagedProfile() {
+        String adminPackage = ADMIN_RECEIVER_COMPONENT.getPackageName();
+        return mDevicePolicyManager.isProfileOwnerApp(adminPackage);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
index 6986c39..c89ef13 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
@@ -15,13 +15,8 @@
  */
 package com.android.cts.managedprofile;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.UiAutomation;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -32,15 +27,19 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.uiautomator.UiDevice;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import android.util.Log;
 
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.google.common.collect.ImmutableList;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -107,16 +106,16 @@
         toggleNotificationListener(true);
 
         sendProfileNotification();
-        assertTrue(mReceiver.waitForNotificationPostedReceived());
+        assertThat(mReceiver.waitForNotificationPostedReceived()).isTrue();
         cancelProfileNotification();
-        assertTrue(mReceiver.waitForNotificationRemovedReceived());
+        assertThat(mReceiver.waitForNotificationRemovedReceived()).isTrue();
 
         mReceiver.reset();
 
         sendPersonalNotification();
-        assertTrue(mReceiver.waitForNotificationPostedReceived());
+        assertThat(mReceiver.waitForNotificationPostedReceived()).isTrue();
         cancelPersonalNotification();
-        assertTrue(mReceiver.waitForNotificationRemovedReceived());
+        assertThat(mReceiver.waitForNotificationRemovedReceived()).isTrue();
     }
 
     @Test
@@ -125,17 +124,29 @@
 
         sendProfileNotification();
         // Don't see notification or cancellation from work profile.
-        assertFalse(mReceiver.waitForNotificationPostedReceived());
+        assertThat(mReceiver.waitForNotificationPostedReceived()).isFalse();
         cancelProfileNotification();
-        assertFalse(mReceiver.waitForNotificationRemovedReceived());
+        assertThat(mReceiver.waitForNotificationRemovedReceived()).isFalse();
 
         mReceiver.reset();
 
         // Do see the one from the personal side.
         sendPersonalNotification();
-        assertTrue(mReceiver.waitForNotificationPostedReceived());
+        assertThat(mReceiver.waitForNotificationPostedReceived()).isTrue();
         cancelPersonalNotification();
-        assertTrue(mReceiver.waitForNotificationRemovedReceived());
+        assertThat(mReceiver.waitForNotificationRemovedReceived()).isTrue();
+    }
+
+    @Test
+    public void testSetAndGetPermittedCrossProfileNotificationListeners() {
+        List<String> packageList = ImmutableList.of("package1", "package2");
+
+        mDpm.setPermittedCrossProfileNotificationListeners(
+                BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT, packageList);
+        List<String> actualPackageList = mDpm.getPermittedCrossProfileNotificationListeners(
+                BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualPackageList).isEqualTo(packageList);
     }
 
     private void cancelProfileNotification() throws IOException {
@@ -170,7 +181,7 @@
                 + testListener);
         Log.i(TAG, "Toggled notification listener state" + testListener + " to state " + enable);
         if (enable) {
-            assertTrue(mReceiver.waitForListenerConnected());
+            assertThat(mReceiver.waitForListenerConnected()).isTrue();
         }
     }
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
index 4f94674..8b7d16b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
@@ -25,6 +25,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/** Tests minimum password restriction APIs, including on parent profile instances. */
 public class PasswordMinimumRestrictionsTest extends BaseManagedProfileTest {
 
     private static final int TEST_PASSWORD_LENGTH = 5;
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
index a386baa..7af838a 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
@@ -29,7 +29,8 @@
  */
 public class ProfileTimeoutTestHelper extends InstrumentationTestCase {
     // This should be sufficiently smaller than ManagedProfileTest.PROFILE_TIMEOUT_DELAY_SEC.
-    private static final int TIMEOUT_MS = 5_000;
+    // This should also be sufficiently larger than time required to run "input tap" on emulator.
+    private static final int TIMEOUT_MS = 30_000;
     private static final ComponentName ADMIN_COMPONENT = new ComponentName(
             BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
index 3b2c7f8..617953d 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
@@ -24,6 +24,8 @@
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -61,6 +63,7 @@
         mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
         mWifiEnabled = mWifiManager.isWifiEnabled();
         if (!mWifiEnabled) {
+            SystemUtil.runShellCommand("svc wifi enable");
             mWifiManager.setWifiEnabled(true);
             awaitWifiEnabledState(true);
         }
@@ -69,7 +72,7 @@
     @Override
     public void tearDown() throws Exception {
         if (!mWifiEnabled) {
-            mWifiManager.setWifiEnabled(false);
+            SystemUtil.runShellCommand("svc wifi disable");
             awaitWifiEnabledState(false);
         }
         super.tearDown();
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataNotificationTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataNotificationTest.java
new file mode 100644
index 0000000..f6ea266
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataNotificationTest.java
@@ -0,0 +1,55 @@
+package com.android.cts.managedprofile;
+
+import android.support.test.InstrumentationRegistry;
+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;
+
+/**
+ * Test wipeDataWithReason() has indeed shown the notification.
+ * The function wipeDataWithReason() is called and executed in another test.
+ */
+public class WipeDataNotificationTest extends AndroidTestCase {
+
+    private static final String WIPE_DATA_TITLE = "Work profile deleted";
+    // This reason string should be aligned with the one in WipeDataTest as this is a followup test.
+    private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
+    private static final long UI_TIMEOUT_MILLI = 5000;
+
+    public void testWipeDataWithReasonVerification() {
+        // The function wipeDataWithReason() is called and executed in another test.
+        // The data should be wiped and notification should be sent before.
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        uiDevice.openNotification();
+        Boolean wipeDataTitleExist = uiDevice.wait(Until.hasObject(By.text(WIPE_DATA_TITLE)),
+                UI_TIMEOUT_MILLI);
+        Boolean wipeDataReasonExist = uiDevice.wait(Until.hasObject(By.text(TEST_WIPE_DATA_REASON)),
+                UI_TIMEOUT_MILLI);
+
+        // Verify the notification is there.
+        assertEquals("Wipe notification title not found", Boolean.TRUE, wipeDataTitleExist);
+        assertEquals("Wipe notification content not found",
+                Boolean.TRUE, wipeDataReasonExist);
+
+        // Clear the notification by pressing "clear all" if found.
+        final UiObject2 clearAll = uiDevice.wait(
+                Until.findObject(By.res("com.android.systemui:id/dismiss_text")), UI_TIMEOUT_MILLI);
+        if (clearAll != null) {
+            clearAll.click();
+        }
+    }
+
+    public void testWipeDataWithoutReasonVerification() {
+        // The function wipeDataWithReason() is called with reason being null and executed in
+        // another test. The data should be wiped and notification should not be sent.
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        uiDevice.openNotification();
+        Boolean wipeDataTitleExist = uiDevice.wait(Until.hasObject(By.text(WIPE_DATA_TITLE)),
+                UI_TIMEOUT_MILLI);
+
+        // Verify the notification is not there.
+        assertEquals("Wipe notification title found", Boolean.FALSE, wipeDataTitleExist);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataReceiver.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataReceiver.java
index 0abea7d..9f65303 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataReceiver.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataReceiver.java
@@ -32,6 +32,8 @@
     private static final String ACTION_WIPE_DATA = "com.android.cts.managedprofile.WIPE_DATA";
     private static final String ACTION_WIPE_DATA_WITH_REASON =
             "com.android.cts.managedprofile.WIPE_DATA_WITH_REASON";
+    private static final String ACTION_WIPE_DATA_WITHOUT_REASON =
+            "com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON";
     private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
 
     @Override
@@ -41,6 +43,8 @@
             dpm.wipeData(0);
         } else if (ACTION_WIPE_DATA_WITH_REASON.equals(intent.getAction())) {
             dpm.wipeData(0, TEST_WIPE_DATA_REASON);
+        } else if (ACTION_WIPE_DATA_WITHOUT_REASON.equals(intent.getAction())) {
+            dpm.wipeData(DevicePolicyManager.WIPE_SILENTLY);
         }
     }
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java
deleted file mode 100644
index 3b35cb4..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.cts.managedprofile;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.Until;
-import android.test.AndroidTestCase;
-
-/**
- * Test wipeDataWithReason() has indeed shown the notification.
- * The function wipeDataWithReason() is called and executed in another test.
- */
-public class WipeDataWithReasonVerificationTest extends AndroidTestCase {
-
-    private static final String WIPE_DATA_TITLE = "Work profile deleted";
-    // This reason string should be aligned with the one in WipeDataTest as this is a followup test.
-    private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
-    private static final long UI_TIMEOUT_MILLI = 5000;
-
-    public void testWipeDataWithReasonVerification() throws UiObjectNotFoundException,
-            InterruptedException {
-        // The function wipeDataWithReason() is called and executed in another test.
-        // The data should be wiped and notification should be sent before.
-        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        uiDevice.openNotification();
-        Boolean wipeDataTitleExist = uiDevice.wait(Until.hasObject(By.text(WIPE_DATA_TITLE)),
-                UI_TIMEOUT_MILLI);
-        Boolean wipeDataReasonExist = uiDevice.wait(Until.hasObject(By.text(TEST_WIPE_DATA_REASON)),
-                UI_TIMEOUT_MILLI);
-
-        // Verify the notification is there.
-        assertEquals("Wipe notification title not found", Boolean.TRUE, wipeDataTitleExist);
-        assertEquals("Wipe notification content not found",
-                Boolean.TRUE, wipeDataReasonExist);
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.mk b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.mk
new file mode 100644
index 0000000..c3a2e9f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsNoLaunchableActivityApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/AndroidManifest.xml
new file mode 100755
index 0000000..7b8b808
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.nolaunchableactivityapp">
+
+    <application>
+        <service android:name=".EmptyService" android:enabled="true"></service>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/src/com/android/cts/nolaunchableactivityapp/EmptyService.java b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/src/com/android/cts/nolaunchableactivityapp/EmptyService.java
new file mode 100644
index 0000000..6cd0da6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/src/com/android/cts/nolaunchableactivityapp/EmptyService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.nolaunchableactivityapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class EmptyService extends Service {
+    public EmptyService() {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // do nothing, just here for the app to have some code
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
index a1514ff..f60242c 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
@@ -32,6 +32,7 @@
     ub-uiautomator
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 21
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
index 3550a3f..4f0ce40 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
@@ -117,19 +117,7 @@
 
     protected void assertInstallPackage() throws Exception {
         assertFalse(isPackageInstalled(TEST_APP_PKG));
-        synchronized (mPackageInstallerTimeoutLock) {
-            mCallbackReceived = false;
-            mCallbackStatus = PACKAGE_INSTALLER_STATUS_UNDEFINED;
-        }
-        installPackage(TEST_APP_LOCATION);
-        synchronized (mPackageInstallerTimeoutLock) {
-            try {
-                mPackageInstallerTimeoutLock.wait(PACKAGE_INSTALLER_TIMEOUT_MS);
-            } catch (InterruptedException e) {
-            }
-            assertTrue(mCallbackReceived);
-            assertEquals(PackageInstaller.STATUS_SUCCESS, mCallbackStatus);
-        }
+        assertTrue(executeAndWaitForPckageInstallCallback(() -> installPackage(TEST_APP_LOCATION)));
         assertTrue(isPackageInstalled(TEST_APP_PKG));
     }
 
@@ -178,4 +166,29 @@
             return false;
         }
     }
-}
+
+    protected boolean tryUninstallPackage() throws Exception {
+        assertTrue(isPackageInstalled(TEST_APP_PKG));
+        return executeAndWaitForPckageInstallCallback(
+                () -> mPackageInstaller.uninstall(TEST_APP_PKG, getCommitCallback(0)));
+    }
+
+    private interface ExceptionRunnable {
+        void run() throws Exception;
+    }
+    private boolean executeAndWaitForPckageInstallCallback(ExceptionRunnable task) throws Exception {
+        synchronized (mPackageInstallerTimeoutLock) {
+            mCallbackReceived = false;
+            mCallbackStatus = PACKAGE_INSTALLER_STATUS_UNDEFINED;
+        }
+        task.run();
+        synchronized (mPackageInstallerTimeoutLock) {
+            try {
+                mPackageInstallerTimeoutLock.wait(PACKAGE_INSTALLER_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+            }
+            assertTrue(mCallbackReceived);
+            return mCallbackStatus == PackageInstaller.STATUS_SUCCESS;
+        }
+    }
+ }
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/ManualPackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/ManualPackageInstallTest.java
index 98a47f2..88c3df9 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/ManualPackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/ManualPackageInstallTest.java
@@ -23,6 +23,8 @@
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
 
+import java.util.regex.Pattern;
+
 /**
  * This class tests manual package install and uninstall by a device owner.
  */
@@ -38,9 +40,8 @@
             .clazz(android.widget.ImageView.class.getName())
             .res("com.android.settings:id/admin_support_icon")
             .pkg("com.android.settings");
-    private static final BySelector INSTALL_BUTTON_SELECTOR = By
-            .clazz(android.widget.Button.class.getName())
-            .res("com.android.packageinstaller:id/ok_button");
+    private static final BySelector INSTALL_BUTTON_SELECTOR = By.text(Pattern.compile("Install",
+            Pattern.CASE_INSENSITIVE));
 
     public void testManualInstallSucceeded() throws Exception {
         assertInstallPackage();
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/SilentPackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/SilentPackageInstallTest.java
new file mode 100644
index 0000000..db2dce1
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/SilentPackageInstallTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.packageinstaller;
+
+/**
+ * This class tests silent package install and uninstall if we are DO or delegate.
+ */
+public class SilentPackageInstallTest extends BasePackageInstallTest {
+
+    public void testSilentInstallUninstall() throws Exception {
+        // install the app
+        assertInstallPackage();
+
+        // uninstall the app again
+        assertTrue(tryUninstallPackage());
+        assertFalse(isPackageInstalled(TEST_APP_PKG));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/PasswordComplexity/Android.mk b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.mk
new file mode 100644
index 0000000..cd8d07f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsPasswordComplexity
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/PasswordComplexity/AndroidManifest.xml b/hostsidetests/devicepolicy/app/PasswordComplexity/AndroidManifest.xml
new file mode 100644
index 0000000..baec5ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PasswordComplexity/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.passwordcomplexity" >
+
+    <uses-sdk android:minSdkVersion="29" />
+
+    <uses-permission android:name="android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY"/>
+
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.passwordcomplexity"
+            android:label="Password complexity CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/PasswordComplexity/src/com.android.cts.passwordcomplexity/GetPasswordComplexityTest.java b/hostsidetests/devicepolicy/app/PasswordComplexity/src/com.android.cts.passwordcomplexity/GetPasswordComplexityTest.java
new file mode 100644
index 0000000..046653b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/PasswordComplexity/src/com.android.cts.passwordcomplexity/GetPasswordComplexityTest.java
@@ -0,0 +1,177 @@
+package com.android.cts.passwordcomplexity;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.uiautomator.UiDevice;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests for {@link DevicePolicyManager#getPasswordComplexity()}. */
+@SmallTest
+public class GetPasswordComplexityTest {
+
+    private DevicePolicyManager mDpm;
+    private UiDevice mDevice;
+    private String mScreenLock;
+
+    @Before
+    public void setUp() throws Exception {
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        if (!mDevice.executeShellCommand("cmd lock_settings verify")
+                .startsWith("Lock credential verified successfully")) {
+            fail("Please remove the device screen lock before running this test");
+        }
+
+        mDpm = (DevicePolicyManager) InstrumentationRegistry
+                .getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        clearScreenLock();
+    }
+
+    @Test
+    public void getPasswordComplexity_none() {
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphanumeric6_high() throws Exception {
+        setPassword("abc!23");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphanumeric5_medium() throws Exception {
+        setPassword("bc!23");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphanumeric4_medium() throws Exception {
+        setPassword("c!23");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphabetic6_high() throws Exception {
+        setPassword("abc!qw");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphabetic5_medium() throws Exception {
+        setPassword("bc!qw");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_alphabetic4_medium() throws Exception {
+        setPassword("c!qw");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_numericComplex8_high() throws Exception {
+        setPin("12389647");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+    }
+
+    @Test
+    public void getPasswordComplexity_numericComplex7_medium() throws Exception {
+        setPin("1238964");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_numericComplex4_medium() throws Exception {
+        setPin("1238");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+    }
+
+    @Test
+    public void getPasswordComplexity_numeric16_low() throws Exception {
+        setPin("1234567898765432");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+    }
+
+    @Test
+    public void getPasswordComplexity_numeric4_low() throws Exception {
+        setPin("1234");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+    }
+
+    @Test
+    public void getPasswordComplexity_pattern9_low() throws Exception {
+        setPattern("123456789");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+    }
+
+    @Test
+    public void getPasswordComplexity_pattern4_low() throws Exception {
+        setPattern("1234");
+        assertPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+    }
+
+    private void setPattern(String pattern) throws Exception {
+        String out = mDevice.executeShellCommand("cmd lock_settings set-pattern " + pattern);
+        if (!out.startsWith("Pattern set to")) {
+            fail("Failed to set pattern: " + out);
+        }
+        mScreenLock = pattern;
+    }
+
+    private void setPin(String pin) throws Exception {
+        String out = mDevice.executeShellCommand("cmd lock_settings set-pin " + pin);
+        if (!out.startsWith("Pin set to")) {
+            fail("Failed to set pin: " + out);
+        }
+        mScreenLock = pin;
+    }
+
+    private void setPassword(String password) throws Exception {
+        String out =
+                mDevice.executeShellCommand("cmd lock_settings set-password " + password);
+        if (!out.startsWith("Password set to")) {
+            fail("Failed to set password: " + out);
+        }
+        mScreenLock = password;
+    }
+
+    private void clearScreenLock() throws Exception {
+        String out =
+                mDevice.executeShellCommand("cmd lock_settings clear --old " + mScreenLock);
+        if (!out.startsWith("Lock credential cleared")) {
+            fail("Failed to clear user credential: " + out);
+        }
+        mScreenLock = null;
+    }
+
+    private void assertPasswordComplexity(int expectedComplexity) {
+        // password metrics is updated asynchronously so let's be lenient here and retry a few times
+        final int maxRetries = 15;
+        int retry = 0;
+        while (retry < maxRetries && mDpm.getPasswordComplexity() != expectedComplexity) {
+            retry++;
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+        assertEquals(expectedComplexity, mDpm.getPasswordComplexity());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
index 8a024f8..98e1e87 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -19,6 +19,8 @@
 LOCAL_PACKAGE_NAME := CtsProfileOwnerApp
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
+LOCAL_MIN_SDK_VERSION := 24
+
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
index 533e925..4207596 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
@@ -26,7 +26,7 @@
 
     static final int OBSERVER_LIMIT = 1000;
 
-    public void testMinTimeLimit() throws Exception {
+    public void testAppUsageObserver_MinTimeLimit() throws Exception {
         final String[] packages = {"not.real.package.name"};
         final int obsId = 0;
         UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
@@ -46,6 +46,28 @@
         }
     }
 
+    public void testUsageSessionObserver_MinTimeLimit() throws Exception {
+        final String[] packages = {"not.real.package.name"};
+        final int obsId = 0;
+        UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        PendingIntent pendingIntent = PendingIntent.getActivity(
+                InstrumentationRegistry.getContext(),
+                1, intent, 0);
+
+        usm.registerUsageSessionObserver(obsId, packages, 60, TimeUnit.SECONDS, 10,
+                TimeUnit.SECONDS, pendingIntent, null);
+        usm.unregisterUsageSessionObserver(obsId);
+        try {
+            usm.registerUsageSessionObserver(obsId, packages, 59, TimeUnit.SECONDS, 10,
+                    TimeUnit.SECONDS, pendingIntent, null);
+            fail("Should have thrown an IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            // Do nothing. Exception is expected.
+        }
+    }
+
     public void testObserverLimit() throws Exception {
         final String[] packages = {"not.real.package.name"};
         UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
@@ -55,6 +77,7 @@
                 InstrumentationRegistry.getContext(),
                 1, intent, 0);
 
+        // Register too many AppUsageObservers
         for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
             usm.registerAppUsageObserver(obsId, packages, 60, TimeUnit.MINUTES, pendingIntent);
         }
@@ -65,8 +88,26 @@
         } catch (IllegalStateException expected) {
             // Do nothing. Exception is expected.
         }
+
+        // Register too many UsageSessionObservers.
+        for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
+            usm.registerUsageSessionObserver(obsId, packages, 60, TimeUnit.MINUTES, 10,
+                    TimeUnit.SECONDS, pendingIntent, null);
+        }
+        try {
+            usm.registerUsageSessionObserver(OBSERVER_LIMIT, packages, 60, TimeUnit.MINUTES, 10,
+                    TimeUnit.SECONDS, pendingIntent, null);
+            fail("Should have thrown an IllegalStateException");
+        } catch (IllegalStateException expected) {
+            // Do nothing. Exception is expected.
+        }
+
         for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
             usm.unregisterAppUsageObserver(obsId);
         }
+
+        for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
+            usm.unregisterUsageSessionObserver(obsId);
+        }
     }
 }
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/BackupServicePoliciesTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/BackupServicePoliciesTest.java
new file mode 100644
index 0000000..573f041
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/BackupServicePoliciesTest.java
@@ -0,0 +1,15 @@
+package com.android.cts.profileowner;
+
+public class BackupServicePoliciesTest extends BaseProfileOwnerTest {
+  /**
+   * Test: Test enabling and disabling backup service. This test should be executed after installing
+   * a profile owner so that we check that backup service is not enabled by default.
+   */
+  public void testEnablingAndDisablingBackupService() {
+    assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+    mDevicePolicyManager.setBackupServiceEnabled(getWho(), true);
+    assertTrue(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+    mDevicePolicyManager.setBackupServiceEnabled(getWho(), false);
+    assertFalse(mDevicePolicyManager.isBackupServiceEnabled(getWho()));
+  }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
index 7064258..3941a50 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
@@ -35,6 +35,7 @@
     testng
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 24
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
index b721292..06ebcbc 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
@@ -43,6 +43,15 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
+
+        <service
+            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminService"
+            android:exported="true"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
index 7a598d9..7822e1a 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
@@ -19,11 +19,15 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import android.app.Service;
 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.content.SharedPreferences;
+import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -44,23 +48,41 @@
         }
     }
 
+    public static class BasicAdminService extends Service {
+        @Override
+        public void onCreate() {
+            super.onCreate();
+            putBooleanPref(getApplicationContext(), KEY_TRANSFER_ADMIN_SERVICE_BOUND, true);
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return null;
+        }
+    }
+
     public static class BasicAdminReceiverNoMetadata extends DeviceAdminReceiver {
         public BasicAdminReceiverNoMetadata() {}
     }
 
     private final static String SHARED_PREFERENCE_NAME = "shared-preference-name";
     private final static String KEY_TRANSFER_COMPLETED_CALLED = "key-transfer-completed-called";
+    private final static String KEY_TRANSFER_ADMIN_SERVICE_BOUND =
+        "key-transfer-admin-service-bound";
     private final static String ARE_PARAMETERS_SAVED = "ARE_PARAMETERS_SAVED";
 
     protected Context mContext;
     protected ComponentName mIncomingComponentName;
     protected DevicePolicyManager mDevicePolicyManager;
+    protected boolean mHasSecureLockScreen;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
         mIncomingComponentName = new ComponentName(mContext, BasicAdminReceiver.class.getName());
+        mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_SECURE_LOCK_SCREEN);
     }
 
     @Test
@@ -97,6 +119,11 @@
         assertTrue(bundle.isEmpty());
     }
 
+    @Test
+    public void testAdminServiceIsBound() {
+        assertTrue(getBooleanPref(mContext, KEY_TRANSFER_ADMIN_SERVICE_BOUND));
+    }
+
     private static SharedPreferences getPrefs(Context context) {
         return context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
     }
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 ef7e8ac..1d91237 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
@@ -40,8 +40,10 @@
 
         DevicePolicyManager targetParentProfileInstance =
                 mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
-        assertEquals(
-                passwordExpirationTimeout,
-                targetParentProfileInstance.getPasswordExpirationTimeout(mIncomingComponentName));
+        if (mHasSecureLockScreen) {
+            assertEquals(
+                    passwordExpirationTimeout,
+                    targetParentProfileInstance.getPasswordExpirationTimeout(mIncomingComponentName));
+        }
     }
 }
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
index 14f6ab5..afe245c 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
@@ -35,6 +35,7 @@
     testng
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 24
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
index cd3f9b7..ebd9c6c 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
@@ -17,8 +17,10 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.deviceowner.wificonfigcreator">
+    <uses-sdk android:targetSdkVersion="28"/>
 
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
     <application>
diff --git a/hostsidetests/devicepolicy/res/empty.zip b/hostsidetests/devicepolicy/res/empty.zip
new file mode 100644
index 0000000..2bc043c
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/empty.zip
Binary files differ
diff --git a/hostsidetests/devicepolicy/res/notZip.zi b/hostsidetests/devicepolicy/res/notZip.zi
new file mode 100644
index 0000000..2bc043c
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/notZip.zi
Binary files differ
diff --git a/hostsidetests/devicepolicy/res/wrongHash.zip b/hostsidetests/devicepolicy/res/wrongHash.zip
new file mode 100644
index 0000000..4f55042
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/wrongHash.zip
Binary files differ
diff --git a/hostsidetests/devicepolicy/res/wrongPayload.zip b/hostsidetests/devicepolicy/res/wrongPayload.zip
new file mode 100644
index 0000000..d47d305
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/wrongPayload.zip
Binary files differ
diff --git a/hostsidetests/devicepolicy/res/wrongSize.zip b/hostsidetests/devicepolicy/res/wrongSize.zip
new file mode 100644
index 0000000..20698b8
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/wrongSize.zip
Binary files differ
diff --git a/hostsidetests/devicepolicy/res/wrongVersion.zip b/hostsidetests/devicepolicy/res/wrongVersion.zip
new file mode 100644
index 0000000..3dfcfa8
--- /dev/null
+++ b/hostsidetests/devicepolicy/res/wrongVersion.zip
Binary files differ
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
new file mode 100644
index 0000000..218d6b7
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
@@ -0,0 +1,76 @@
+/*
+ * 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.devicepolicy;
+
+import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.ADMIN_RECEIVER_TEST_CLASS;
+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 com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import java.io.FileNotFoundException;
+
+import android.stats.devicepolicy.EventId;
+
+public class AdbProvisioningTests extends BaseDevicePolicyTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        super.tearDown();
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+    }
+
+    public void testAdbDeviceOwnerLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            setDeviceOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mPrimaryUserId,
+                    /* expectFailure */ false);
+        }, new DevicePolicyEventWrapper.Builder(EventId.PROVISIONING_ENTRY_POINT_ADB_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .setStrings("device-owner")
+                    .build());
+    }
+
+    public void testAdbProfileOwnerLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            setProfileOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mPrimaryUserId,
+                    /* expectFailure */ false);
+        }, new DevicePolicyEventWrapper.Builder(EventId.PROVISIONING_ENTRY_POINT_ADB_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings("profile-owner")
+                .build());
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index 4425dcd..8141b11 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -91,7 +91,7 @@
     }
 
     public void testResetPassword_nycRestrictions() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
 
@@ -111,7 +111,7 @@
      * Run the tests in DeviceOwnerPasswordTest.java (as device owner).
      */
     public void testRunDeviceOwnerPasswordTest() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index ef51156..53e45fc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -88,12 +88,26 @@
      */
     private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
 
+    /**
+     * The amount of milliseconds to wait for the remove user calls in {@link #tearDown}.
+     * This is a temporary measure until b/114057686 is fixed.
+     */
+    private static final long USER_REMOVE_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;
 
+    /**
+     * The {@link android.os.BatteryManager} flags value representing all charging types; {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_AC}, {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_USB}, and {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_WIRELESS}.
+     */
+    private static final int STAY_ON_WHILE_PLUGGED_IN_FLAGS = 7;
+
     protected static interface Settings {
         public static final String GLOBAL_NAMESPACE = "global";
         public static interface Global {
@@ -102,7 +116,7 @@
     }
 
     protected IBuildInfo mCtsBuild;
-
+    protected CompatibilityBuildHelper mBuildHelper;
     private String mPackageVerifier;
     private HashSet<String> mAvailableFeatures;
 
@@ -119,9 +133,14 @@
     /** Whether file-based encryption (FBE) is supported. */
     protected boolean mSupportsFbe;
 
+    /** Whether the device has a lock screen.*/
+    protected boolean mHasSecureLockScreen;
+
     /** Users we shouldn't delete in the tests */
     private ArrayList<Integer> mFixedUsers;
 
+    private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
+
     @Override
     public void setBuild(IBuildInfo buildInfo) {
         mCtsBuild = buildInfo;
@@ -138,6 +157,9 @@
         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
         mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
         mFixedPackages = getDevice().getInstalledPackageNames();
+        mBuildHelper = new CompatibilityBuildHelper(mCtsBuild);
+
+        mHasSecureLockScreen = hasDeviceFeature("android.software.secure_lock_screen");
 
         // disable the package verifier to avoid the dialog when installing an app
         mPackageVerifier = getDevice().executeShellCommand(
@@ -155,6 +177,7 @@
         removeTestUsers();
         // Unlock keyguard before test
         wakeupAndDismissKeyguard();
+        stayAwake();
         // Go to home.
         executeShellCommand("input keyevent KEYCODE_HOME");
     }
@@ -324,9 +347,12 @@
             String stopUserCommand = "am stop-user -w -f " + userId;
             CLog.d("stopping and removing user " + userId);
             getDevice().executeShellCommand(stopUserCommand);
+            // TODO: Remove both sleeps and USER_REMOVE_WAIT constant when b/114057686 is fixed.
+            Thread.sleep(USER_REMOVE_WAIT);
             // Ephemeral users may have already been removed after being stopped.
             if (listUsers().contains(userId)) {
                 assertTrue("Couldn't remove user", getDevice().removeUser(userId));
+                Thread.sleep(USER_REMOVE_WAIT);
             }
         }
     }
@@ -437,7 +463,7 @@
 
     /** Reboots the device and block until the boot complete flag is set. */
     protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
-        getDevice().executeShellCommand("reboot");
+        getDevice().rebootUntilOnline();
         assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
     }
 
@@ -719,7 +745,7 @@
                 String[] tokens = line.split("\\{|\\}");
                 String componentName = tokens[1];
                 // Skip to user id line.
-                i += 3;
+                i += 4;
                 line = lines[i].trim();
                 // Line is User ID: <N>
                 tokens = line.split(":");
@@ -866,20 +892,47 @@
     }
 
     /**
-     * Verifies the lock credential for the given user, which unlocks the user.
+     * Verifies the lock credential for the given user.
      *
      * @param credential The credential to verify.
      * @param userId The id of the user.
      */
     protected void verifyUserCredential(String credential, int userId)
             throws DeviceNotAvailableException {
+        String commandOutput = verifyUserCredentialCommandOutput(credential, userId);
+        if (!commandOutput.startsWith(VERIFY_CREDENTIAL_CONFIRMATION)) {
+            fail("Failed to verify user credential: " + commandOutput);
+        }
+     }
+
+    /**
+     * Verifies the lock credential for the given user, which unlocks the user, and returns
+     * whether it was successful or not.
+     *
+     * @param credential The credential to verify.
+     * @param userId The id of the user.
+     */
+    protected boolean verifyUserCredentialIsCorrect(String credential, int userId)
+            throws DeviceNotAvailableException {
+        String commandOutput = verifyUserCredentialCommandOutput(credential, userId);
+        return commandOutput.startsWith(VERIFY_CREDENTIAL_CONFIRMATION);
+    }
+
+    /**
+     * Verifies the lock credential for the given user, which unlocks the user. Returns the
+     * commandline output, which includes whether the verification was successful.
+     *
+     * @param credential The credential to verify.
+     * @param userId The id of the user.
+     * @return The command line output.
+     */
+    protected String verifyUserCredentialCommandOutput(String credential, int userId)
+            throws DeviceNotAvailableException {
         final String credentialArgument = (credential == null || credential.isEmpty())
                 ? "" : ("--old " + credential);
         String commandOutput = getDevice().executeShellCommand(String.format(
                 "cmd lock_settings verify --user %d %s", userId, credentialArgument));
-        if (!commandOutput.startsWith("Lock credential verified")) {
-            fail("Failed to verify user credential: " + commandOutput);
-        }
+        return commandOutput;
     }
 
     protected void wakeupAndDismissKeyguard() throws Exception {
@@ -887,6 +940,15 @@
         executeShellCommand("wm dismiss-keyguard");
     }
 
+    protected void pressPowerButton() throws Exception {
+        executeShellCommand("input keyevent KEYCODE_POWER");
+    }
+
+    private void stayAwake() throws Exception {
+        executeShellCommand(
+                "settings put global stay_on_while_plugged_in " + STAY_ON_WHILE_PLUGGED_IN_FLAGS);
+    }
+
     protected void startActivityAsUser(int userId, String packageName, String activityName)
         throws Exception {
         wakeupAndDismissKeyguard();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
new file mode 100644
index 0000000..0d37457
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
@@ -0,0 +1,54 @@
+/*
+ * 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.devicepolicy;
+
+/**
+ * BaseDeviceAdminHostSideTest for device admin targeting API level 29.
+ */
+public class DeviceAdminHostSideTestApi29 extends DeviceAdminHostSideTestApi24 {
+    @Override
+    protected int getTargetApiVersion() {
+        return 29;
+    }
+
+    /**
+     * Test that we get expected SecurityExceptions for policies that were disallowed in version 29.
+     */
+    @Override
+    public void testRunDeviceAdminTest() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runTests(getDeviceAdminApkPackage(), "DeviceAdminWithEnterprisePoliciesBlockedTest");
+    }
+
+    /**
+     * This test is no longer relevant once DA is disallowed from using password policies.
+     */
+    @Override
+    public void testResetPassword_nycRestrictions() throws Exception {
+        return;
+    }
+
+    /**
+     * This test is no longer relevant since resetPassword() was deprecated in version 26.
+     * Device Owner functionality is now tested in DeviceAndProfileOwnerTest.
+     */
+    @Override
+    public void testRunDeviceOwnerPasswordTest() throws Exception {
+        return;
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
index a18c772..580ffde 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -1,8 +1,14 @@
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import android.stats.devicepolicy.EventId;
+
 public abstract class DeviceAndProfileOwnerHostSideTransferTest extends BaseDevicePolicyTest {
+
     protected static final String TRANSFER_OWNER_OUTGOING_PKG =
             "com.android.cts.transferowneroutgoing";
     protected static final String TRANSFER_OWNER_OUTGOING_APK = "CtsTransferOwnerOutgoingApp.apk";
@@ -14,6 +20,10 @@
             "com.android.cts.transferownerincoming";
     protected static final String TRANSFER_OWNER_INCOMING_APK = "CtsTransferOwnerIncomingApp.apk";
     protected static final String INVALID_TARGET_APK = "CtsIntentReceiverApp.apk";
+    protected static final String TRANSFER_PROFILE_OWNER_OUTGOING_TEST =
+        "com.android.cts.transferowner.TransferProfileOwnerOutgoingTest";
+    protected static final String TRANSFER_PROFILE_OWNER_INCOMING_TEST =
+        "com.android.cts.transferowner.TransferProfileOwnerIncomingTest";
 
     protected int mUserId;
     protected String mOutgoingTestClassName;
@@ -23,9 +33,16 @@
         if (!mHasFeature) {
             return;
         }
-        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
-                mOutgoingTestClassName,
-                "testTransferOwnership", mUserId);
+
+        final boolean hasManagedProfile = (mUserId != mPrimaryUserId);
+        final String expectedManagementType = hasManagedProfile ? "profile-owner" : "device-owner";
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG, mOutgoingTestClassName,
+                    "testTransferOwnership", mUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.TRANSFER_OWNERSHIP_VALUE)
+                .setAdminPackageName(TRANSFER_OWNER_OUTGOING_PKG)
+                .setStrings(TRANSFER_OWNER_INCOMING_PKG, expectedManagementType)
+                .build());
     }
 
     public void testTransferSameAdmin() throws Exception {
@@ -191,9 +208,40 @@
                 mPrimaryUserId);
     }
 
+    public void testTargetDeviceAdminServiceBound() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+            mOutgoingTestClassName,
+            "testTransferOwnership", mUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+            mIncomingTestClassName,
+            "testAdminServiceIsBound", mUserId);
+    }
+
+    protected void setSameAffiliationId(int profileUserId, String testClassName)
+        throws Exception {
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+            testClassName,
+            "testSetAffiliationId1", mPrimaryUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+            testClassName,
+            "testSetAffiliationId1", profileUserId);
+    }
+
+    protected void assertAffiliationIdsAreIntact(int profileUserId,
+        String testClassName) throws Exception {
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+            testClassName,
+            "testIsAffiliationId1", mPrimaryUserId);
+        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+            testClassName,
+            "testIsAffiliationId1", profileUserId);
+    }
+
     /* TODO: Add tests for:
-    * 1. startServiceForOwner
-    * 2. passwordOwner
+    * 1. passwordOwner
     *
     * */
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index f19bc3d..a4e047e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -16,12 +16,16 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+
 import android.platform.test.annotations.RequiresDevice;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
@@ -30,6 +34,9 @@
 import java.util.List;
 import java.util.Map;
 
+import android.stats.devicepolicy.EventId;
+import java.util.stream.Collectors;
+
 /**
  * Set of tests for use cases that apply to profile and device owner.
  * This class is the base class of MixedProfileOwnerTest, MixedDeviceOwnerTest and
@@ -40,11 +47,14 @@
  */
 public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest {
 
-    protected static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
-    protected static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp.apk";
-    protected static final String ADMIN_RECEIVER_TEST_CLASS
+    public static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+    public static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp.apk";
+    public static final String ADMIN_RECEIVER_TEST_CLASS
             = ".BaseDeviceAdminTest$BasicAdminReceiver";
 
+    protected static final String STORAGE_ENCRYPTION_TEST_CLASS = ".StorageEncryptionTest";
+    protected static final String IS_PRIMARY_USER_PARAM = "isPrimaryUser";
+
     private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
     private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
 
@@ -64,7 +74,7 @@
     private static final String CERT_INSTALLER_PKG = "com.android.cts.certinstaller";
     private static final String CERT_INSTALLER_APK = "CtsCertInstallerApp.apk";
 
-    private static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
+    protected static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
     private static final String DELEGATE_APP_APK = "CtsDelegateApp.apk";
     private static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
     private static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
@@ -72,13 +82,14 @@
     private static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
     private static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
     private static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+    private static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
 
-    private static final String TEST_APP_APK = "CtsSimpleApp.apk";
+    protected static final String TEST_APP_APK = "CtsSimpleApp.apk";
     private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
-    private static final String TEST_APP_LOCATION = "/data/local/tmp/";
+    protected static final String TEST_APP_LOCATION = "/data/local/tmp/";
 
-    private static final String PACKAGE_INSTALLER_PKG = "com.android.cts.packageinstaller";
-    private static final String PACKAGE_INSTALLER_APK = "CtsPackageInstallerApp.apk";
+    protected static final String PACKAGE_INSTALLER_PKG = "com.android.cts.packageinstaller";
+    protected static final String PACKAGE_INSTALLER_APK = "CtsPackageInstallerApp.apk";
 
     private static final String ACCOUNT_MANAGEMENT_PKG
             = "com.android.cts.devicepolicy.accountmanagement";
@@ -114,6 +125,10 @@
             = "com.android.cts.devicepolicy.meteredtestapp";
     private static final String METERED_DATA_APP_APK = "CtsMeteredDataTestApp.apk";
 
+    private static final String TEST_EMPTY_CTS_APP_APK = "CtsEmptyTestApp.apk";
+    private static final String TEST_EMPTY_CTS_APP_PKG =
+            "android.packageinstaller.emptytestapp.cts";
+
     private static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
             = "enabled_notification_policy_access_packages";
 
@@ -129,6 +144,26 @@
     private static final String RESTRICT_BACKGROUND_OFF_CMD =
         "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 WIPE_RESET_PROTECTION_DATA = 0x0002;
+    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;
+    private static final String DISALLOW_CONFIG_LOCATION = "no_config_location";
+    private static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
+    private static final String DISALLOW_AUTOFILL = "no_autofill";
+    private static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
+    private static final String DEFAULT_INPUT_METHOD = "default_input_method";
+    private static final int PERMISSION_POLICY_PROMPT = 0;
+    private static final int PERMISSION_POLICY_AUTO_GRANT = 1;
+    private static final int PERMISSION_POLICY_AUTO_DENY = 2;
+    private static final int PERMISSION_GRANT_STATE_DEFAULT = 0;
+    private static final int PERMISSION_GRANT_STATE_GRANTED = 1;
+    private static final int PERMISSION_GRANT_STATE_DENIED = 2;
+    private static final String PARAM_APP_TO_ENABLE = "app_to_enable";
+    public static final String RESOLVE_ACTIVITY_CMD = "cmd package resolve-activity --brief %s | tail -n 1";
+
     // ID of the user all tests are run as. For device owner this will be the primary user, for
     // profile owner it is the user id of the created profile.
     protected int mUserId;
@@ -168,6 +203,35 @@
         executeDeviceTestClass(".CaCertManagementTest");
     }
 
+    public void testInstallCaCertLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".CaCertManagementTest", "testCanInstallAndUninstallACaCert");
+        }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_CA_CERT_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.UNINSTALL_CA_CERTS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
+    }
+
+    public void testApplicationRestrictionIsRestricted() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(DELEGATE_APP_APK, mUserId);
+        runDeviceTestsAsUser(DELEGATE_APP_PKG, ".AppRestrictionsIsCallerDelegateHelper",
+            "testAssertCallerIsNotApplicationRestrictionsManagingPackage", mUserId);
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".ApplicationRestrictionsIsCallerDelegateHelper",
+            "testSetApplicationRestrictionsManagingPackageToDelegate", mUserId);
+        runDeviceTestsAsUser(DELEGATE_APP_PKG, ".AppRestrictionsIsCallerDelegateHelper",
+            "testAssertCallerIsApplicationRestrictionsManagingPackage", mUserId);
+    }
+
     public void testApplicationRestrictions() throws Exception {
         if (!mHasFeature) {
             return;
@@ -205,46 +269,94 @@
 
             // 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());
         } finally {
             changeApplicationRestrictionsManagingPackage(null);
         }
     }
 
+    /**
+     * Returns a list of delegation tests that are applicable to both device owner and profile
+     * owners. DO or PO specific tests should be added in the overridden method in subclass.
+     */
+    protected List<String> getDelegationTests() {
+        return new ArrayList<>(Arrays.asList(
+                ".AppRestrictionsDelegateTest",
+                ".CertInstallDelegateTest",
+                ".BlockUninstallDelegateTest",
+                ".PermissionGrantDelegateTest",
+                ".PackageAccessDelegateTest",
+                ".EnableSystemAppDelegateTest"));
+    }
+
+    /**
+     * Returns a list of delegation scopes that are applicable to both device owner and profile
+     * owners. DO or PO specific scopes should be added in the overridden method in subclass.
+     */
+    protected List<String> getDelegationScopes() {
+        return new ArrayList<>(Arrays.asList(
+                DELEGATION_APP_RESTRICTIONS,
+                DELEGATION_CERT_INSTALL,
+                DELEGATION_BLOCK_UNINSTALL,
+                DELEGATION_PERMISSION_GRANT,
+                DELEGATION_PACKAGE_ACCESS,
+                DELEGATION_ENABLE_SYSTEM_APP,
+                // CERT_SELECTION scope is in the list so it still participates GeneralDelegateTest.
+                // But its main functionality test is driven by testDelegationCertSelection() and
+                // hence missing from getDelegationTests() on purpose.
+                DELEGATION_CERT_SELECTION
+                ));
+    }
+
+    /**
+     * General instructions to add a new delegation test:
+     * 1. Test primary delegation functionalitiy
+     *    Implment the delegate's positive/negate functionaility tests in a new test class
+     *    in CtsDelegateApp.apk. Main entry point are {@code testCanAccessApis} and
+     *    {@code testCannotAccessApis}. Once implemented, add the delegation scope and the test
+     *    class name to {@link #getDelegationScopes}, {@link #getDelegationTests} to make the test
+     *    run on DO/PO/PO on primary user.  If the test should only run on a subset of these
+     *    combinations, add them to the subclass's {@link #getDelegationScopes} and
+     *    {@link #getDelegationTests} intead.
+     *    <p>Alternatively, create a separate hostside method to drive the test, similar to
+     *    {@link MixedDeviceOwnerTest#testDelegationPackageInstallation()} and
+     *    {@link #testDelegationCertSelection}. This is preferred if the delegated functionalities
+     *    already exist in another app.
+     * 2. Test access control of DO-only delegation
+     *    Add the delegation scope to
+     *    {@code DelegationTest#testDeviceOwnerOnlyDelegationsOnlyPossibleToBeSetByDeviceOwner} to
+     *    test that only DO can delegate this scope.
+     * 3. Test behaviour of exclusive delegation
+     *    Add the delegation scope to {@code DelegationTest#testExclusiveDelegations} to test that
+     *    the scope can only be delegatd to one app at a time.
+     */
     public void testDelegation() throws Exception {
         if (!mHasFeature) {
             return;
         }
 
-        final String delegationTests[] = {
-            ".AppRestrictionsDelegateTest",
-            ".CertInstallDelegateTest",
-            ".BlockUninstallDelegateTest",
-            ".PermissionGrantDelegateTest",
-            ".PackageAccessDelegateTest",
-            ".EnableSystemAppDelegateTest"
-        };
-
-        // Set a device lockscreen password (precondition for installing private key pairs).
-        changeUserCredential("1234", null, mPrimaryUserId);
-
         // Install relevant apps.
         installAppAsUser(DELEGATE_APP_APK, mUserId);
         installAppAsUser(TEST_APP_APK, mUserId);
         installAppAsUser(APP_RESTRICTIONS_TARGET_APP_APK, mUserId);
 
         try {
+            final List<String> delegationTests = getDelegationTests();
             // APIs are not accessible by default.
             executeDelegationTests(delegationTests, false /* negative result */);
 
             // Granting the appropriate delegation scopes makes APIs accessible.
-            setDelegatedScopes(DELEGATE_APP_PKG, Arrays.asList(
-                    DELEGATION_APP_RESTRICTIONS,
-                    DELEGATION_CERT_INSTALL,
-                    DELEGATION_BLOCK_UNINSTALL,
-                    DELEGATION_PERMISSION_GRANT,
-                    DELEGATION_PACKAGE_ACCESS,
-                    DELEGATION_ENABLE_SYSTEM_APP));
-            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".GeneralDelegateTest", mUserId);
+            final List<String> scopes = getDelegationScopes();
+            setDelegatedScopes(DELEGATE_APP_PKG, scopes);
+            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".GeneralDelegateTest", null, mUserId,
+                    ImmutableMap.of("scopes", String.join(",", scopes)));
             executeDelegationTests(delegationTests, true /* positive result */);
 
             // APIs are not accessible after revoking delegations.
@@ -255,13 +367,22 @@
             executeDeviceTestClass(".DelegationTest");
 
         } finally {
-            // Clear lockscreen password previously set for installing private key pairs.
-            changeUserCredential(null, "1234", mPrimaryUserId);
             // Remove any remaining delegations.
             setDelegatedScopes(DELEGATE_APP_PKG, null);
         }
     }
 
+    public void testDelegationCertSelection() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(CERT_INSTALLER_APK, mUserId);
+        setDelegatedScopes(CERT_INSTALLER_PKG, Arrays.asList(
+                DELEGATION_CERT_INSTALL, DELEGATION_CERT_SELECTION));
+        runDeviceTestsAsUser(CERT_INSTALLER_PKG, ".CertSelectionDelegateTest", mUserId);
+    }
+
     public void testPermissionGrant() throws Exception {
         if (!mHasFeature) {
             return;
@@ -318,7 +439,7 @@
         // the same. However we're only testing the FBE case here as we need to set a device
         // password during the test. This would cause FDE devices (e.g. angler) to prompt for the
         // password during reboot, which we can't handle easily.
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
 
@@ -400,6 +521,23 @@
         }
     }
 
+    @RequiresDevice
+    public void testAlwaysOnVpnPackageLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Will be uninstalled in tearDown().
+        installAppAsUser(VPN_APP_APK, mUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AlwaysOnVpnUnsupportedTest", "testSetSupportedVpnAlwaysOn");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_ALWAYS_ON_VPN_PACKAGE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(VPN_APP_PKG)
+                    .setBoolean(true)
+                    .setInt(0)
+                    .build());
+    }
+
     public void testPermissionPolicy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -470,19 +608,36 @@
             return;
         }
         executeDeviceTestClass(".PersistentIntentResolvingTest");
+        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());
     }
 
     public void testScreenCaptureDisabled() throws Exception {
         if (!mHasFeature) {
             return;
         }
-        // We need to ensure that the policy is deactivated for the device owner case, so making
-        // sure the second test is run even if the first one fails
-        try {
-            setScreenCaptureDisabled(mUserId, true);
-        } finally {
-            setScreenCaptureDisabled(mUserId, false);
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            // We need to ensure that the policy is deactivated for the device owner case, so making
+            // sure the second test is run even if the first one fails
+            try {
+                setScreenCaptureDisabled(mUserId, true);
+            } finally {
+                setScreenCaptureDisabled(mUserId, false);
+            }
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SCREEN_CAPTURE_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(true)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_SCREEN_CAPTURE_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
     }
 
     public void testScreenCaptureDisabled_assist() throws Exception {
@@ -505,6 +660,17 @@
             return;
         }
         executeDeviceTestClass(".SupportMessageTest");
+        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)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .build());
     }
 
     public void testApplicationHidden() throws Exception {
@@ -513,6 +679,20 @@
         }
         installAppPermissionAppAsUser();
         executeDeviceTestClass(".ApplicationHiddenTest");
+        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")
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .setStrings(PERMISSIONS_APP_PKG, "not_hidden")
+                    .build());
     }
 
     public void testAccountManagement_deviceAndProfileOwnerAlwaysAllowed() throws Exception {
@@ -597,20 +777,49 @@
 
         boolean isManagedProfile = (mPrimaryUserId != mUserId);
 
-        try {
-            // Set a non-empty device lockscreen password, which is a precondition for installing
-            // private key pairs.
-            changeUserCredential("1234", null, mUserId);
 
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
+        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());
+    }
+
+    public interface DelegatedCertInstallerTestAction {
+        void run() throws Exception;
+    }
+
+    protected void setUpDelegatedCertInstallerAndRunTests(DelegatedCertInstallerTestAction test)
+            throws Exception {
+        installAppAsUser(CERT_INSTALLER_APK, mUserId);
+
+        try {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualSetCertInstallerDelegate", mUserId);
+
+            test.run();
         } finally {
-            if (!isManagedProfile) {
-                // Skip managed profile as dpm doesn't allow clear password
-                changeUserCredential(null, "1234", mUserId);
-            }
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualClearCertInstallerDelegate", mUserId);
         }
     }
 
+    // This test currently duplicates the testDelegatedCertInstaller, with one difference:
+    // The Delegated cert installer app is called directly rather than via intents from
+    // the DelegatedCertinstallerTest.
+    public void testDelegatedCertInstallerDirectly() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        setUpDelegatedCertInstallerAndRunTests(() ->
+            runDeviceTestsAsUser("com.android.cts.certinstaller",
+                    ".DirectDelegatedCertInstallerTest", mUserId));
+    }
+
     // Sets restrictions and launches non-admin app, that tries to set wallpaper.
     // Non-admin apps must not violate any user restriction.
     public void testSetWallpaper_disallowed() throws Exception {
@@ -644,8 +853,12 @@
         if (!mHasFeature) {
             return;
         }
-        executeDeviceTestMethod(".CustomizationRestrictionsTest",
-                "testDisallowSetUserIcon_allowed");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".CustomizationRestrictionsTest",
+                    "testDisallowSetUserIcon_allowed");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_USER_ICON_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     public void testDisallowAutofill_allowed() throws Exception {
@@ -681,12 +894,14 @@
         }
         // UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES
         final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
+        // 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";
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        final File apk = buildHelper.getTestFile(TEST_APP_APK);
+        final File apk = mBuildHelper.getTestFile(TEST_APP_APK);
         String packageVerifierEnableSetting = null;
         String packageVerifierUserConsentSetting = null;
         try {
@@ -704,6 +919,15 @@
             // Clear restrictions and test if we can install the apk.
             changeUserRestrictionOrFail(DISALLOW_INSTALL_UNKNOWN_SOURCES, false, mUserId);
 
+            // Add global restriction and test if we can install the apk.
+            getDevice().uninstallPackage(TEST_APP_PKG);
+            changeUserRestrictionOrFail(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, true, mUserId);
+            runDeviceTestsAsUser(PACKAGE_INSTALLER_PKG, ".ManualPackageInstallTest",
+                    "testManualInstallBlocked", mUserId);
+
+            // 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);
@@ -746,14 +970,38 @@
         }
     }
 
+    public void testDisallowAdjustVolumeMutedLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest",
+                    "testDisallowAdjustVolumeMutedLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_MASTER_VOLUME_MUTED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(true)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_MASTER_VOLUME_MUTED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
+    }
+
     public void testSuspendPackage() throws Exception {
         if (!mHasFeature) {
             return;
         }
         installAppAsUser(INTENT_SENDER_APK, mUserId);
         installAppAsUser(INTENT_RECEIVER_APK, mUserId);
-        // Suspend a testing package.
-        executeDeviceTestMethod(".SuspendPackageTest", "testSetPackagesSuspended");
+        assertMetricsLogged(getDevice(), () -> {
+            // Suspend a testing package.
+            executeDeviceTestMethod(".SuspendPackageTest",
+                    "testSetPackagesSuspended");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PACKAGES_SUSPENDED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(INTENT_RECEIVER_PKG)
+                    .setBoolean(false)
+                    .build());
         // Verify that the package is suspended.
         executeSuspendPackageTestMethod("testPackageSuspended");
         // Undo the suspend.
@@ -765,7 +1013,7 @@
     }
 
     public void testTrustAgentInfo() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         executeDeviceTestClass(".TrustAgentInfoTest");
@@ -812,7 +1060,7 @@
     }
 
     public void testRequiredStrongAuthTimeout() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         executeDeviceTestClass(".RequiredStrongAuthTimeoutTest");
@@ -825,10 +1073,26 @@
         executeDeviceTestClass(".PolicyTransparencyTest");
     }
 
-    public void testResetPasswordWithToken() throws Exception {
+    public void testSetCameraDisabledLogged() throws Exception {
         if (!mHasFeature) {
             return;
         }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".PolicyTransparencyTest", "testCameraDisabled");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_CAMERA_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(true)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_CAMERA_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
+    }
+
+    public void testResetPasswordWithToken() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
         // If ResetPasswordWithTokenTest for managed profile is executed before device owner and
         // primary user profile owner tests, password reset token would have been disabled for
         // the primary user, so executing ResetPasswordWithTokenTest on user 0 would fail. We allow
@@ -846,6 +1110,64 @@
         executeDeviceTestClass(".PasswordSufficientInitiallyTest");
     }
 
+    public void testGetCurrentFailedPasswordAttempts() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        final String testPassword = "1234";
+        final String wrongPassword = "12345";
+
+        changeUserCredential(testPassword, null /*oldCredential*/, mUserId);
+        try {
+            // Test that before trying an incorrect password there are 0 failed attempts.
+            executeDeviceTestMethod(".GetCurrentFailedPasswordAttemptsTest",
+                    "testNoFailedPasswordAttempts");
+            // Try an incorrect password.
+            assertFalse(verifyUserCredentialIsCorrect(wrongPassword, mUserId));
+            // Test that now there is one failed attempt.
+            executeDeviceTestMethod(".GetCurrentFailedPasswordAttemptsTest",
+                    "testOneFailedPasswordAttempt");
+            // Try an incorrect password.
+            assertFalse(verifyUserCredentialIsCorrect(wrongPassword, mUserId));
+            // Test that now there are two failed attempts.
+            executeDeviceTestMethod(".GetCurrentFailedPasswordAttemptsTest",
+                    "testTwoFailedPasswordAttempts");
+            // TODO: re-enable the test below when b/110945754 is fixed.
+            // Try the correct password and check the failed attempts number has been reset to 0.
+            // assertTrue(verifyUserCredentialIsCorrect(testPassword, mUserId));
+            // executeDeviceTestMethod(".GetCurrentFailedPasswordAttemptsTest",
+            //         "testNoFailedPasswordAttempts");
+        } finally {
+            changeUserCredential(null /*newCredential*/, testPassword, mUserId);
+        }
+    }
+
+    public void testPasswordExpiration() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        executeDeviceTestClass(".PasswordExpirationTest");
+    }
+
+    public void testGetPasswordExpiration() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        executeDeviceTestMethod(".GetPasswordExpirationTest",
+                "testGetPasswordExpiration");
+        try {
+            executeDeviceTestMethod(".GetPasswordExpirationTest",
+                    "testGetPasswordExpirationUpdatedAfterPasswordReset_beforeReset");
+            // Wait for 20 seconds so we can make sure that the expiration date is refreshed later.
+            Thread.sleep(20000);
+            changeUserCredential("1234", null, mUserId);
+            executeDeviceTestMethod(".GetPasswordExpirationTest",
+                    "testGetPasswordExpirationUpdatedAfterPasswordReset_afterReset");
+        } finally {
+            changeUserCredential(null, "1234", mUserId);
+        }
+    }
+
     public void testSetSystemSetting() throws Exception {
         if (!mHasFeature) {
             return;
@@ -905,16 +1227,406 @@
             return;
         }
 
-        try {
-            // Set a non-empty device lockscreen password, which is a precondition for installing
-            // CA certificates.
-            changeUserCredential("1234", null, mUserId);
-            // Verify the credential immediately to unlock the work profile challenge
-            verifyUserCredential("1234", mUserId);
-            executeDeviceTestClass(".KeyManagementTest");
-        } finally {
-            changeUserCredential(null, "1234", mUserId);
+        executeDeviceTestClass(".KeyManagementTest");
+    }
+
+    public void testInstallKeyPairLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
         }
+
+        assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".KeyManagementTest", "testCanInstallCertChain");
+                }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_KEY_PAIR_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .build(),
+                new DevicePolicyEventWrapper.Builder(EventId.REMOVE_KEY_PAIR_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .build());
+    }
+
+    public void testGenerateKeyPairLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(
+                        ".KeyManagementTest", "testCanGenerateKeyPairWithKeyAttestation");
+                }, new DevicePolicyEventWrapper.Builder(EventId.GENERATE_KEY_PAIR_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(0)
+                .setStrings("RSA")
+                .build(),
+                new DevicePolicyEventWrapper.Builder(EventId.GENERATE_KEY_PAIR_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(0)
+                .setStrings("EC")
+                .build());
+
+    }
+
+    public void testSetKeyPairCertificateLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".KeyManagementTest", "testCanSetKeyPairCert");
+                }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEY_PAIR_CERTIFICATE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .build());
+    }
+
+    public void testPermittedAccessibilityServices() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestClass(".AccessibilityServicesTest");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AccessibilityServicesTest",
+                    "testPermittedAccessibilityServices");
+        }, new DevicePolicyEventWrapper
+                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings((String[]) null)
+                    .build(),
+            new DevicePolicyEventWrapper
+                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings((String[]) null)
+                    .build(),
+            new DevicePolicyEventWrapper
+                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
+                    .build());
+    }
+
+    public void testPermittedInputMethods() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestClass(".InputMethodsTest");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".InputMethodsTest",
+                    "testPermittedInputMethods");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings((String[]) null)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings((String[]) null)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
+                    .build());
+    }
+
+    public void testSetStorageEncryption() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        Map<String, String> params =
+                ImmutableMap.of(IS_PRIMARY_USER_PARAM, String.valueOf(mUserId == mPrimaryUserId));
+        runDeviceTestsAsUser(
+                DEVICE_ADMIN_PKG, STORAGE_ENCRYPTION_TEST_CLASS, null, mUserId, params);
+    }
+
+    public void testPasswordMethodsLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_QUALITY_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(PASSWORD_QUALITY_SOMETHING)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_LENGTH_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(13)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_NUMERIC_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(14)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_NON_LETTER_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(15)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_LETTERS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(16)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_LOWER_CASE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(17)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_UPPER_CASE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(18)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_SYMBOLS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(19)
+                    .build());
+    }
+
+    public void testLockNowLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testLockNowLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.LOCK_NOW_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setInt(0)
+                .build());
+    }
+
+    public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetKeyguardDisabledFeaturesLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_FEATURES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(KEYGUARD_DISABLE_FEATURES_NONE)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_FEATURES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(KEYGUARD_DISABLE_FINGERPRINT)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_FEATURES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(KEYGUARD_DISABLE_TRUST_AGENTS)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_FEATURES_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(KEYGUARD_DISABLE_FEATURES_NONE)
+                    .setBoolean(false)
+                    .build());
+    }
+
+    public void testSetUserRestrictionLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetUserRestrictionLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.ADD_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_CONFIG_LOCATION)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.REMOVE_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_CONFIG_LOCATION)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.ADD_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_ADJUST_VOLUME)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.REMOVE_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_ADJUST_VOLUME)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.ADD_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_AUTOFILL)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.REMOVE_USER_RESTRICTION_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DISALLOW_AUTOFILL)
+                    .build()
+        );
+    }
+
+    public void testSetSecureSettingLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetSecureSettingLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SECURE_SETTING_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(SKIP_FIRST_USE_HINTS, "1")
+                .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_SECURE_SETTING_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(SKIP_FIRST_USE_HINTS, "0")
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_SECURE_SETTING_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DEFAULT_INPUT_METHOD, "com.example.input")
+                    .build()
+                    ,
+            new DevicePolicyEventWrapper.Builder(EventId.SET_SECURE_SETTING_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(DEFAULT_INPUT_METHOD)
+                    .build());
+    }
+
+    public void testSetPermissionPolicyLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetPermissionPolicyLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_POLICY_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setInt(PERMISSION_POLICY_AUTO_DENY)
+                .setBoolean(false)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_POLICY_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setInt(PERMISSION_POLICY_AUTO_GRANT)
+                .setBoolean(false)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_POLICY_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setInt(PERMISSION_POLICY_PROMPT)
+                .setBoolean(false)
+                .build());
+    }
+
+    public void testSetPermissionGrantStateLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppPermissionAppAsUser();
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetPermissionGrantStateLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_GRANT_STATE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(PERMISSION_GRANT_STATE_GRANTED)
+                    .setBoolean(false)
+                    .setStrings("android.permission.READ_CONTACTS")
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_GRANT_STATE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(PERMISSION_GRANT_STATE_DENIED)
+                    .setBoolean(false)
+                    .setStrings("android.permission.READ_CONTACTS")
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMISSION_GRANT_STATE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(PERMISSION_GRANT_STATE_DEFAULT)
+                    .setBoolean(false)
+                    .setStrings("android.permission.READ_CONTACTS")
+                    .build());
+    }
+
+    public void testSetAutoTimeRequired() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetAutoTimeRequired");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_REQUIRED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(true)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_REQUIRED_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setBoolean(false)
+                    .build());
+    }
+
+    public void testEnableSystemAppLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
+        // We enable an enabled package to not worry about restoring the state.
+        final String systemPackageToEnable = enabledSystemPackageNames.get(0);
+        final Map<String, String> params =
+                ImmutableMap.of(PARAM_APP_TO_ENABLE, systemPackageToEnable);
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DevicePolicyLoggingTest",
+                    "testEnableSystemAppLogged", mUserId, params);
+        }, new DevicePolicyEventWrapper.Builder(EventId.ENABLE_SYSTEM_APP_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(systemPackageToEnable)
+                .build());
+    }
+
+    public void testEnableSystemAppWithIntentLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        final String systemPackageToEnable = getLaunchableSystemPackage();
+        if (systemPackageToEnable == null) {
+            return;
+        }
+        final Map<String, String> params =
+                ImmutableMap.of(PARAM_APP_TO_ENABLE, systemPackageToEnable);
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DevicePolicyLoggingTest",
+                    "testEnableSystemAppWithIntentLogged", mUserId, params);
+        }, new DevicePolicyEventWrapper.Builder(EventId.ENABLE_SYSTEM_APP_WITH_INTENT_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings("android.intent.action.MAIN")
+                .build());
+    }
+
+    public void testSetUninstallBlockedLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest",
+                    "testSetUninstallBlockedLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_UNINSTALL_BLOCKED_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(PERMISSIONS_APP_PKG)
+                .build());
+    }
+
+    private String getLaunchableSystemPackage() throws DeviceNotAvailableException {
+        final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
+        for (String enabledSystemPackage : enabledSystemPackageNames) {
+            final String result = getDevice().executeShellCommand(
+                    String.format(RESOLVE_ACTIVITY_CMD, enabledSystemPackage));
+            if (!result.contains("No activity found")) {
+                return enabledSystemPackage;
+            }
+        }
+        return null;
+    }
+
+    private List<String> getEnabledSystemPackageNames() throws DeviceNotAvailableException {
+        final String commandResult =
+                getDevice().executeShellCommand("pm list packages -e -s --user " + mUserId);
+        final int prefixLength = "package:".length();
+        return new ArrayList<>(Arrays.asList(commandResult.split("\n")))
+                .stream()
+                .map(line -> line.substring(prefixLength))
+                .collect(Collectors.toList());
     }
 
     /**
@@ -961,7 +1673,7 @@
                 ".AppRestrictionsDelegateTest", testName, mUserId);
     }
 
-    private void executeDelegationTests(String[] delegationTests, boolean positive)
+    private void executeDelegationTests(List<String> delegationTests, boolean positive)
             throws Exception {
         for (String delegationTestClass : delegationTests) {
             runDeviceTestsAsUser(DELEGATE_APP_PKG, delegationTestClass,
@@ -986,7 +1698,7 @@
         changePolicyOrFail("set-app-restrictions-manager", packageNameExtra, mUserId);
     }
 
-    private void setDelegatedScopes(String packageName, List<String> scopes)
+    protected void setDelegatedScopes(String packageName, List<String> scopes)
             throws DeviceNotAvailableException {
         final String packageNameExtra = "--es extra-package-name " + packageName;
         String scopesExtra = "";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index e4bf61c..fe3998d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -64,7 +64,7 @@
 
     /** Test for resetPassword for all devices. */
     public void testResetPassword() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPassword");
@@ -72,7 +72,7 @@
 
     /** Additional test for resetPassword for FBE-enabled devices. */
     public void testResetPasswordFbe() throws Exception {
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index ab81ef6..0b7cc6b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -16,8 +16,14 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper.Builder;
 import java.util.List;
 
+import android.stats.devicepolicy.EventId;
+
 /**
  * Tests for having both device owner and profile owner. Device owner is setup for you in
  * {@link #setUp()} and it is always the {@link #COMP_DPC_PKG}. You are required to call
@@ -41,6 +47,11 @@
             "com.android.cts.comp.ManagementTest";
 
     private static final String COMP_DPC_PKG = "com.android.cts.comp";
+    private static final DevicePolicyEventWrapper WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT =
+            new Builder(EventId.WIPE_DATA_WITH_REASON_VALUE)
+                    .setAdminPackageName(COMP_DPC_PKG)
+                    .setInt(0)
+                    .build();
     private static final String COMP_DPC_APK = "CtsCorpOwnedManagedProfile.apk";
     private static final String COMP_DPC_ADMIN =
             COMP_DPC_PKG + "/com.android.cts.comp.AdminReceiver";
@@ -314,6 +325,17 @@
         assertUserGetsRemoved(profileUserId);
     }
 
+    public void testWipeData_managedProfileLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
+        addDisallowRemoveManagedProfileRestriction();
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(COMP_DPC_PKG, MANAGEMENT_TEST, "testWipeData", profileUserId);
+        }, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
+    }
+
     public void testWipeData_secondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -330,6 +352,17 @@
         assertUserGetsRemoved(secondaryUserId);
     }
 
+    public void testWipeData_secondaryUserLogged() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+            return;
+        }
+        int secondaryUserId = setupManagedSecondaryUser();
+        addDisallowRemoveUserRestriction();
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(COMP_DPC_PKG, MANAGEMENT_TEST, "testWipeData", secondaryUserId);
+        }, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
+    }
+
     public void testNetworkAndSecurityLoggingAvailableIfAffiliated() 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 28dd552..7428066 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,15 +16,25 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import com.google.common.io.ByteStreams;
+
 import java.io.File;
-import java.util.Arrays;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import android.stats.devicepolicy.EventId;
+
 /**
  * Set of tests for Device Owner use cases.
  */
@@ -60,18 +70,26 @@
     private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
 
     private static final String ARG_NETWORK_LOGGING_BATCH_COUNT = "batchCount";
+    private static final String TEST_UPDATE_LOCATION = "/data/local/tmp/cts/deviceowner";
 
-    private final List<String> NO_SETUP_WIZARD_PROVISIONING_MODE =
-            Arrays.asList("DISABLED", "EMULATOR");
+    /**
+     * Copied from {@link
+     * DevicePolicyManager.InstallUpdateCallback#UPDATE_ERROR_UPDATE_FILE_INVALID }
+     */
+    private static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3;
+
+    private static final int TYPE_NONE = 0;
+
+    /**
+     * Copied from {@link android.app.admin.SystemUpdatePolicy}
+     */
+    private static final int TYPE_INSTALL_AUTOMATIC = 1;
+    private static final int TYPE_INSTALL_WINDOWED = 2;
+    private static final int TYPE_POSTPONE = 3;
 
     /** CreateAndManageUser is available and an additional user can be created. */
     private boolean mHasCreateAndManageUserFeature;
 
-    /**
-     * Whether Setup Wizard is disabled.
-     */
-    private boolean mSetupWizardDisabled;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -83,11 +101,11 @@
                 getDevice().uninstallPackage(DEVICE_OWNER_PKG);
                 fail("Failed to set device owner");
             }
+
+            getDevice().executeShellCommand(" mkdir " + TEST_UPDATE_LOCATION);
         }
         mHasCreateAndManageUserFeature = mHasFeature && canCreateAdditionalUsers(1)
                 && hasDeviceFeature("android.software.managed_users");
-        mSetupWizardDisabled = NO_SETUP_WIZARD_PROVISIONING_MODE.contains(
-                getDevice().getProperty("ro.setupwizard.mode"));
     }
 
     @Override
@@ -98,6 +116,7 @@
             getDevice().uninstallPackage(DEVICE_OWNER_PKG);
             switchUser(USER_SYSTEM);
             removeTestUsers();
+            getDevice().executeShellCommand(" rm -r " + TEST_UPDATE_LOCATION);
         }
 
         super.tearDown();
@@ -109,6 +128,11 @@
 
     public void testLockScreenInfo() throws Exception {
         executeDeviceOwnerTest("LockScreenInfoTest");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
     }
 
     public void testWifi() throws Exception {
@@ -116,6 +140,11 @@
             return;
         }
         executeDeviceOwnerTest("WifiTest");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
+        }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
     }
 
     public void testRemoteBugreportWithTwoUsers() throws Exception {
@@ -341,19 +370,10 @@
     }
 
     public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
-        if (mSetupWizardDisabled || !mHasCreateAndManageUserFeature) {
-            return;
-        }
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_SkipSetupWizard");
-    }
-
-    public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
-        if (mSetupWizardDisabled || !mHasCreateAndManageUserFeature) {
-            return;
-        }
-        executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_DontSkipSetupWizard");
+        if (mHasCreateAndManageUserFeature) {
+            executeDeviceTestMethod(".CreateAndManageUserTest",
+                    "testCreateAndManageUser_SkipSetupWizard");
+       }
     }
 
     public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
@@ -446,6 +466,24 @@
         }
     }
 
+    public void testSecurityLoggingEnabledLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
+            executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SECURITY_LOGGING_ENABLED_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setBoolean(true)
+                .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_SECURITY_LOGGING_ENABLED_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(false)
+                    .build());
+
+    }
+
     private void generateDummySecurityLogs() throws DeviceNotAvailableException {
         // Trigger security events of type TAG_ADB_SHELL_CMD.
         for (int i = 0; i < SECURITY_EVENTS_BATCH_SIZE; i++) {
@@ -516,6 +554,14 @@
         try {
             installAppAsUser(INTENT_RECEIVER_APK, mPrimaryUserId);
             executeDeviceOwnerTest("LockTaskTest");
+            assertMetricsLogged(getDevice(), () -> {
+                runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".LockTaskTest", "testStartLockTask",
+                        mPrimaryUserId);
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(true)
+                    .setStrings(DEVICE_OWNER_PKG)
+                    .build());
         } catch (AssertionError ex) {
             // STOPSHIP(b/32771855), remove this once we fixed the bug.
             executeShellCommand("dumpsys activity activities");
@@ -615,6 +661,36 @@
         executeDeviceOwnerTest("SystemUpdatePolicyTest");
     }
 
+    public void testSetSystemUpdatePolicyLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetAutomaticInstallPolicy");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setInt(TYPE_INSTALL_AUTOMATIC)
+                    .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetWindowedInstallPolicy");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setInt(TYPE_INSTALL_WINDOWED)
+                    .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetPostponeInstallPolicy");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setInt(TYPE_POSTPONE)
+                    .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetEmptytInstallPolicy");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setInt(TYPE_NONE)
+                    .build());
+    }
+
     public void testWifiConfigLockdown() throws Exception {
         final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
         if (hasWifi && mHasFeature) {
@@ -704,8 +780,26 @@
         if (!mHasFeature) {
             return;
         }
-
         executeDeviceOwnerTest("AdminActionBookkeepingTest");
+        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)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .build());
+    }
+
+    public void testRequestBugreportLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
+        }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
     }
 
     public void testBluetoothRestriction() throws Exception {
@@ -761,6 +855,29 @@
                 "testEnablingAndDisablingBackupService");
     }
 
+    public void testDeviceOwnerCanGetDeviceIdentifiers() throws Exception {
+        // The Device Owner should have access to all device identifiers.
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceTestMethod(".DeviceIdentifiersTest",
+                "testDeviceOwnerCanGetDeviceIdentifiersWithPermission");
+    }
+
+    public void testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+        // The Device Owner must have the READ_PHONE_STATE permission to get access to the device
+        // identifiers.
+        if (!mHasFeature) {
+            return;
+        }
+        // Revoke the READ_PHONE_STATE permission to ensure the device owner cannot access device
+        // identifiers without consent.
+        getDevice().executeShellCommand(
+                "pm revoke " + DEVICE_OWNER_PKG + " android.permission.READ_PHONE_STATE");
+        executeDeviceTestMethod(".DeviceIdentifiersTest",
+                "testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission");
+    }
+
     public void testPackageInstallCache() throws Exception {
         if (!mHasFeature) {
             return;
@@ -775,16 +892,28 @@
             runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
                     "testPackageInstall", mPrimaryUserId);
 
-            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
-                    "testKeepPackageCache", mPrimaryUserId);
+            assertMetricsLogged(getDevice(), () -> {
+                runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                        "testKeepPackageCache", mPrimaryUserId);
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEEP_UNINSTALLED_PACKAGES_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(false)
+                    .setStrings(TEST_APP_PKG)
+                    .build());
 
             // Remove the package in primary user
             runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
                     "testPackageUninstall", mPrimaryUserId);
 
-            // Should be able to enable the cached package in primary user
-            runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
-                    "testInstallExistingPackage", mPrimaryUserId);
+            assertMetricsLogged(getDevice(), () -> {
+                // Should be able to enable the cached package in primary user
+                runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+                        "testInstallExistingPackage", mPrimaryUserId);
+            }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_EXISTING_PACKAGE_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(false)
+                    .setStrings(TEST_APP_PKG)
+                    .build());
         } finally {
             String command = "rm " + TEST_APP_LOCATION + apk.getName();
             getDevice().executeShellCommand(command);
@@ -855,12 +984,104 @@
     }
 
     public void testOverrideApn() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !hasDeviceFeature("android.hardware.telephony")) {
             return;
         }
         executeDeviceOwnerTest("OverrideApnTest");
     }
 
+    public void testPrivateDnsPolicy() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("PrivateDnsPolicyTest");
+    }
+
+    public void testInstallUpdate() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        pushUpdateFileToDevice("wrongVersion.zip");
+        pushUpdateFileToDevice("notZip.zi");
+        pushUpdateFileToDevice("empty.zip");
+        pushUpdateFileToDevice("wrongPayload.zip");
+        pushUpdateFileToDevice("wrongHash.zip");
+        pushUpdateFileToDevice("wrongSize.zip");
+        executeDeviceOwnerTest("InstallUpdateTest");
+
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".InstallUpdateTest", "testInstallUpdate_failWrongHash");
+        }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_SYSTEM_UPDATE_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setBoolean(isDeviceAb())
+                .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.INSTALL_SYSTEM_UPDATE_ERROR_VALUE)
+                .setInt(UPDATE_ERROR_UPDATE_FILE_INVALID)
+                .build());
+    }
+
+    public void testInstallUpdateLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        pushUpdateFileToDevice("wrongHash.zip");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".InstallUpdateTest", "testInstallUpdate_failWrongHash");
+        }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_SYSTEM_UPDATE_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(isDeviceAb())
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.INSTALL_SYSTEM_UPDATE_ERROR_VALUE)
+                    .setInt(UPDATE_ERROR_UPDATE_FILE_INVALID)
+                    .build());
+    }
+
+    private boolean isDeviceAb() throws DeviceNotAvailableException {
+        final String result = getDevice().executeShellCommand("getprop ro.build.ab_update").trim();
+        return "true".equalsIgnoreCase(result);
+    }
+
+    private void pushUpdateFileToDevice(String fileName)
+            throws IOException, DeviceNotAvailableException {
+        File file = File.createTempFile(
+                fileName.split("\\.")[0], "." + fileName.split("\\.")[1]);
+        try (OutputStream outputStream = new FileOutputStream(file)) {
+            InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
+            ByteStreams.copy(inputStream, outputStream);
+        }
+
+        getDevice().pushFile(file, TEST_UPDATE_LOCATION + "/" + fileName);
+        file.delete();
+    }
+
+    public void testSetKeyguardDisabledLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetKeyguardDisabledLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
+    }
+
+    public void testSetStatusBarDisabledLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetStatusBarDisabledLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_STATUS_BAR_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(true)
+                    .build(),
+            new DevicePolicyEventWrapper.Builder(EventId.SET_STATUS_BAR_DISABLED_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .setBoolean(true)
+                    .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
new file mode 100644
index 0000000..960fbb2
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
@@ -0,0 +1,45 @@
+package com.android.cts.devicepolicy;
+
+import android.util.Log;
+
+/**
+ * Tests the DPC transfer functionality for COMP mode - managed profile on top of a device owner.
+ * Testing is done by having two dummy DPCs, CtsTransferOwnerOutgoingApp and
+ * CtsTransferOwnerIncomingApp. The former is the current DPC and the latter will be the new DPC
+ * after transfer. In order to run the tests from the correct process, first we setup some
+ * policies in the client side in CtsTransferOwnerOutgoingApp and then we verify the policies are
+ * still there in CtsTransferOwnerIncomingApp. Note that these tests are run on the profile owner
+ * user.
+ */
+public class DevicePlusProfileOwnerHostSideTransferTest
+    extends DeviceAndProfileOwnerHostSideTransferTest {
+
+    @Override
+    protected 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");
+        if (mHasFeature) {
+            installAppAsUser(TRANSFER_OWNER_OUTGOING_APK, mPrimaryUserId);
+            // First set up the device owner.
+            if (setDeviceOwner(TRANSFER_OWNER_OUTGOING_TEST_RECEIVER, mPrimaryUserId,
+                false)) {
+                mOutgoingTestClassName = TRANSFER_PROFILE_OWNER_OUTGOING_TEST;
+                mIncomingTestClassName = TRANSFER_PROFILE_OWNER_INCOMING_TEST;
+
+                // And then set up the managed profile on top of it.
+                final int profileUserId = setupManagedProfileOnDeviceOwner(
+                    TRANSFER_OWNER_OUTGOING_APK, TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+                setSameAffiliationId(profileUserId, TRANSFER_PROFILE_OWNER_OUTGOING_TEST);
+
+                installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mPrimaryUserId);
+                installAppAsUser(TRANSFER_OWNER_INCOMING_APK, profileUserId);
+                mUserId = profileUserId;
+            } else {
+                removeAdmin(TRANSFER_OWNER_OUTGOING_TEST_RECEIVER, mUserId);
+                getDevice().uninstallPackage(TRANSFER_OWNER_OUTGOING_PKG);
+                fail("Failed to set device owner");
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index 1596b3c..12624fa 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -31,6 +31,8 @@
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+    private static final String LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK =
+            "CtsNoLaunchableActivityApp.apk";
 
     private int mProfileUserId;
     private int mParentUserId;
@@ -67,6 +69,7 @@
         if (mHasFeature) {
             removeUser(mProfileUserId);
             uninstallTestApps();
+            getDevice().uninstallPackage(LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK);
         }
         super.tearDown();
     }
@@ -106,6 +109,23 @@
                 mProfileUserId);
     }
 
+    public void testNoHiddenActivityInProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Install app for all users.
+        installAppAsUser(LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK, mParentUserId);
+        installAppAsUser(LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK, mProfileUserId);
+
+        // Run tests to check SimpleApp exists in both profile and main user.
+        runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
+                LAUNCHER_TESTS_CLASS, "testNoInjectedActivityFound",
+                mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
+        runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
+                LAUNCHER_TESTS_CLASS, "testNoLaunchableActivityAppHasAppDetailsActivityInjected",
+                mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
+    }
+
     public void testLauncherCallbackPackageAddedProfile() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
new file mode 100644
index 0000000..7c80023
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.devicepolicy;
+
+import java.util.Collections;
+
+/**
+ * Set of tests for the limit app icon hiding feature.
+ */
+public class LimitAppIconHidingTest extends BaseLauncherAppsTest {
+
+    private static final String LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK =
+            "CtsNoLaunchableActivityApp.apk";
+
+    private boolean mHasLauncherApps;
+    private String mSerialNumber;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasLauncherApps = getDevice().getApiLevel() >= 21;
+
+        if (mHasLauncherApps) {
+            mSerialNumber = Integer.toString(getUserSerialNumber(USER_SYSTEM));
+            installTestApps();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasLauncherApps) {
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    @Override
+    protected void installTestApps() throws Exception {
+        super.installTestApps();
+        installAppAsUser(LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK, mPrimaryUserId);
+    }
+
+    @Override
+    protected void uninstallTestApps() throws Exception {
+        super.uninstallTestApps();
+        getDevice().uninstallPackage(LAUNCHER_TESTS_NO_LAUNCHABLE_ACTIVITY_APK);
+    }
+
+    public void testNoLaunchableActivityAppHasAppDetailsActivityInjected() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
+                LAUNCHER_TESTS_CLASS, "testNoLaunchableActivityAppHasAppDetailsActivityInjected",
+                mPrimaryUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
+    }
+
+    public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
+                LAUNCHER_TESTS_CLASS, "testNoSystemAppHasSyntheticAppDetailsActivityInjected",
+                mPrimaryUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 360e141..b83fbcd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -28,6 +31,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import android.stats.devicepolicy.EventId;
+
 /**
  * Set of tests for Managed Profile use cases.
  */
@@ -85,7 +90,7 @@
 
     private static final String PROFILE_CREDENTIAL = "1234";
     // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
-    private static final int PROFILE_TIMEOUT_DELAY_MS = 10_000;
+    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;
@@ -104,9 +109,9 @@
         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");
+        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
+        mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
+                && hasDeviceFeature("android.sofware.nfc.beam");
 
         if (mHasFeature) {
             removeTestUsers();
@@ -122,7 +127,7 @@
         }
     }
 
-    private void  waitForUserUnlock() throws Exception {
+    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) {
@@ -146,6 +151,14 @@
         super.tearDown();
     }
 
+    public void testManagedProfilesSupportedWithLockScreenOnly() throws Exception {
+        if (mHasFeature) {
+            // Managed profiles should be only supported if the device supports the secure lock
+            // screen feature.
+            assertTrue(mHasSecureLockScreen);
+        }
+    }
+
     public void testManagedProfileSetup() throws Exception {
         if (!mHasFeature) {
             return;
@@ -170,12 +183,46 @@
         // longer exists, verification should be run in primary user.
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
-                ".WipeDataWithReasonVerificationTest",
+                ".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());
+    }
+
+    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 separated from other tests.
+     * wipeData() test removes the managed profile, so it needs to be separated from other tests.
      */
     public void testWipeData() throws Exception {
         if (!mHasFeature) {
@@ -190,13 +237,13 @@
 
     private void sendWipeProfileBroadcast(String action) throws Exception {
         final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
-            + " -a " + action
-            + " com.android.cts.managedprofile/.WipeDataReceiver";
+                + " -a " + action
+                + " com.android.cts.managedprofile/.WipeDataReceiver";
         getDevice().executeShellCommand(cmd);
     }
 
     public void testLockNowWithKeyEviction() throws Exception {
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
         changeUserCredential("1234", null, mProfileUserId);
@@ -297,10 +344,12 @@
     }
 
     private void simulateUserInteraction(int timeMs) throws Exception {
+        final long endTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeMs);
         final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
-        for (int i = 0; i < timeMs; i += timeMs/10) {
-            Thread.sleep(timeMs/10);
+        while (System.nanoTime() < endTime) {
             helper.tapScreenCenter();
+            // Just in case to prevent busy loop.
+            Thread.sleep(100);
         }
     }
 
@@ -329,28 +378,6 @@
         }
     }
 
-    /**
-     * Verify that removing a managed profile will remove all networks owned by that profile.
-     */
-    public void testProfileWifiCleanup() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
-            return;
-        }
-        installAppAsUser(WIFI_CONFIG_CREATOR_APK, mProfileUserId);
-
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG, ".WifiTest", "testRemoveWifiNetworkIfExists", mParentUserId);
-
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG, ".WifiTest", "testAddWifiNetwork", mProfileUserId);
-
-        // Now delete the user - should undo the effect of testAddWifiNetwork.
-        removeUser(mProfileUserId);
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG, ".WifiTest", "testWifiNetworkDoesNotExist",
-                mParentUserId);
-    }
-
     public void testWifiMacAddress() throws Exception {
         if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
             return;
@@ -371,11 +398,21 @@
         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
+        String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
                 + "/.PrimaryUserFilterSetterActivity";
         CLog.d("Output for command " + command + ": "
-              + getDevice().executeShellCommand(command));
+                + getDevice().executeShellCommand(command));
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest", mParentUserId);
         // TODO: Test with startActivity
@@ -523,10 +560,13 @@
         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 */, mParentUserId);
+        installAppAsUser(INTENT_SENDER_APK, false /* grantPermissions */, mProfileUserId);
         installAppAsUser(INTENT_RECEIVER_APK, mParentUserId);
-        installAppAsUser(INTENT_SENDER_APK, mParentUserId);
         installAppAsUser(INTENT_RECEIVER_APK, mProfileUserId);
-        installAppAsUser(INTENT_SENDER_APK, mProfileUserId);
 
         // Test from parent to managed
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
@@ -598,6 +638,18 @@
                 Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
     }
 
+    public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(NOTIFICATION_APK, mProfileUserId);
+        installAppAsUser(NOTIFICATION_APK, mParentUserId);
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testSetAndGetPermittedCrossProfileNotificationListeners", mProfileUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+    }
+
     public void testCrossProfileCopyPaste() throws Exception {
         if (!mHasFeature) {
             return;
@@ -634,8 +686,6 @@
         if (shouldSucceed) {
             runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
                     "testCanReadAcrossProfiles", userId);
-            runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
-                    "testIsNotified", userId);
         } else {
             runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
                     "testCannotReadAcrossProfiles", userId);
@@ -695,7 +745,7 @@
     public void testBluetooth() throws Exception {
         boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
         if (!mHasFeature || !hasBluetooth) {
-            return ;
+            return;
         }
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
@@ -714,39 +764,12 @@
             return;
         }
         try {
-            setDeviceAdmin(MANAGED_PROFILE_PKG + "/.PrimaryUserDeviceAdmin", mParentUserId);
-
-            // Disable managed profile camera.
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
                     "testDisableCameraInManagedProfile",
                     mProfileUserId);
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testIsCameraEnabledInPrimaryProfile",
-                    mParentUserId);
-
-            // Enable managed profile camera.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
                     "testEnableCameraInManagedProfile",
                     mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testIsCameraEnabledInPrimaryProfile",
-                    mParentUserId);
-
-            // Disable primary profile camera.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testDisableCameraInPrimaryProfile",
-                    mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testIsCameraEnabledInManagedProfile",
-                    mProfileUserId);
-
-            // Enable primary profile camera.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testEnableCameraInPrimaryProfile",
-                    mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
-                    "testIsCameraEnabledInManagedProfile",
-                    mProfileUserId);
         } finally {
             final String adminHelperClass = ".PrimaryUserAdminHelper";
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
@@ -806,6 +829,32 @@
                     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
@@ -826,6 +875,13 @@
                 "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 {
@@ -836,12 +892,31 @@
                 mProfileUserId);
     }
 
+    public void testDevicePolicyManagerParentSupport() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG, ".DevicePolicyManagerParentSupportTest", mProfileUserId);
+    }
+
     public void testBluetoothContactSharingDisabled() throws Exception {
         if (!mHasFeature) {
             return;
         }
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                "testSetBluetoothContactSharingDisabled_setterAndGetter", mProfileUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testSetBluetoothContactSharingDisabled_setterAndGetter", mProfileUserId);
+        }, new DevicePolicyEventWrapper
+                    .Builder(EventId.SET_BLUETOOTH_CONTACT_SHARING_DISABLED_VALUE)
+                    .setAdminPackageName(MANAGED_PROFILE_PKG)
+                    .setBoolean(false)
+                    .build(),
+            new DevicePolicyEventWrapper
+                    .Builder(EventId.SET_BLUETOOTH_CONTACT_SHARING_DISABLED_VALUE)
+                    .setAdminPackageName(MANAGED_PROFILE_PKG)
+                    .setBoolean(true)
+                    .build());
     }
 
     public void testCannotSetProfileOwnerAgain() throws Exception {
@@ -941,6 +1016,39 @@
         }
     }
 
+    public void testCrossProfileWidgetsLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        try {
+            installAppAsUser(WIDGET_PROVIDER_APK, mProfileUserId);
+            installAppAsUser(WIDGET_PROVIDER_APK, mParentUserId);
+            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);
+        }
+    }
+
     public void testIsProvisioningAllowed() throws DeviceNotAvailableException {
         if (!mHasFeature) {
             return;
@@ -965,7 +1073,7 @@
                 + getDevice().executeShellCommand(command));
     }
 
-    public void testPhoneAccountVisibility() throws Exception  {
+    public void testPhoneAccountVisibility() throws Exception {
         if (!mHasFeature) {
             return;
         }
@@ -1013,7 +1121,7 @@
             return;
         }
         getDevice().setSetting(
-            mProfileUserId, "secure", "dialer_default_application", MANAGED_PROFILE_PKG);
+                mProfileUserId, "secure", "dialer_default_application", MANAGED_PROFILE_PKG);
 
         // Place a outgoing call through work phone account using TelecomManager and verify the
         // call is inserted properly.
@@ -1048,12 +1156,12 @@
         // Add an incoming missed call with parent user's phone account and verify the call is
         // inserted properly.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PhoneAccountTest",
-            "testIncomingMissedCall",
-            mProfileUserId);
+                "testIncomingMissedCall",
+                mProfileUserId);
         // Make sure the call is not inserted into parent user.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PhoneAccountTest",
-            "testEnsureCallNotInserted",
-            mParentUserId);
+                "testEnsureCallNotInserted",
+                mParentUserId);
     }
 
     private void givePackageWriteSettingsPermission(int userId, String pkg) throws Exception {
@@ -1103,7 +1211,7 @@
     }
 
     public void testTrustAgentInfo() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         // Set and get trust agent config using child dpm instance.
@@ -1153,8 +1261,32 @@
                 "testOppDisabledWhenRestrictionSet", mProfileUserId);
     }
 
+    public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
+        // The Profile Owner should have access to all device identifiers.
+        if (!mHasFeature) {
+            return;
+        }
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DeviceIdentifiersTest",
+                "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mProfileUserId);
+    }
+
+    public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // Revoke the READ_PHONE_STATE permission for the profile user ID to ensure the profile
+        // owner cannot access device identifiers without consent.
+        getDevice().executeShellCommand(
+                "pm revoke --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
+                        + " android.permission.READ_PHONE_STATE");
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DeviceIdentifiersTest",
+                "testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission", mProfileUserId);
+    }
+
     public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
 
@@ -1175,7 +1307,7 @@
      * the device lock.
      */
     public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
         final String devicePassword = "1234";
@@ -1202,11 +1334,11 @@
     }
 
     public void testIsUsingUnifiedPassword() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
 
-        // Freshly created profile profile has no separate challenge.
+        // Freshly created profile has no separate challenge.
         verifyUnifiedPassword(true);
 
         // Set separate challenge and verify that the API reports it correctly.
@@ -1214,6 +1346,268 @@
         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());
+    }
+
+    public void testSetProfileNameLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(
+                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".DevicePolicyLoggingTest",
+                    "testSetProfileNameLogged", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PROFILE_NAME_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .build());
+    }
+
     private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
         final String testMethod =
                 unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
@@ -1274,7 +1668,7 @@
                 + "--ei user-extra " + getUserSerialNumber(mProfileUserId)
                 + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
         CLog.d("Output for command " + command + ": "
-              + getDevice().executeShellCommand(command));
+                + getDevice().executeShellCommand(command));
     }
 
     private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
@@ -1368,7 +1762,7 @@
                     userId);
         }
 
-        // Enable / Disable cross profile caller id
+        // Enable / Disable
         public void setCallerIdEnabled(boolean enabled) throws DeviceNotAvailableException {
             if (enabled) {
                 runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
index 7aefc33..fa0e60b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
@@ -100,24 +100,4 @@
                 "testTransferAffiliatedProfileOwnershipCompleteCallbackIsCalled",
                 mUserId);
     }
-
-    private void assertAffiliationIdsAreIntact(int profileUserId,
-            String testClassName) throws Exception {
-        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
-                testClassName,
-                "testIsAffiliationId1", mPrimaryUserId);
-        runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
-                testClassName,
-                "testIsAffiliationId1", profileUserId);
-    }
-
-    private void setSameAffiliationId(int profileUserId, String testClassName)
-            throws Exception {
-        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
-                testClassName,
-                "testSetAffiliationId1", mPrimaryUserId);
-        runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
-                testClassName,
-                "testSetAffiliationId1", profileUserId);
-    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 01e55fe..da1bd7d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -16,13 +16,9 @@
 
 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 java.io.File;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Set of tests for device owner use cases that also apply to profile owners.
@@ -30,6 +26,9 @@
  */
 public class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
 
+    private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+    private static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -57,5 +56,45 @@
         super.tearDown();
     }
 
-    // All tests for this class are defined in DeviceAndProfileOwnerTest
+    public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        setUpDelegatedCertInstallerAndRunTests(() ->
+                runDeviceTestsAsUser("com.android.cts.certinstaller",
+                        ".DelegatedDeviceIdAttestationTest",
+                        "testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId));
+    }
+
+    protected List<String> getDelegationTests() {
+        List<String> result = super.getDelegationTests();
+        result.add(".NetworkLoggingDelegateTest");
+        return result;
+    }
+
+    protected List<String> getDelegationScopes() {
+        List<String> result = super.getDelegationScopes();
+        result.add(DELEGATION_NETWORK_LOGGING);
+        // PackageInstallation delegation is missing from this since it's explicitly tested below.
+        return result;
+    }
+
+    public void testDelegationPackageInstallation() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        final File apk = mBuildHelper.getTestFile(TEST_APP_APK);
+        try {
+            assertTrue(getDevice().pushFile(apk, TEST_APP_LOCATION + apk.getName()));
+
+            installAppAsUser(PACKAGE_INSTALLER_APK, mUserId);
+            setDelegatedScopes(PACKAGE_INSTALLER_PKG, Arrays.asList(DELEGATION_PACKAGE_INSTALLATION));
+            runDeviceTestsAsUser(PACKAGE_INSTALLER_PKG, ".SilentPackageInstallTest", mUserId);
+            // Uninstall of test packages happen in tearDown.
+        } finally {
+            String command = "rm " + TEST_APP_LOCATION + apk.getName();
+            getDevice().executeShellCommand(command);
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index b39e1ad..0417844 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,13 +16,15 @@
 
 package com.android.cts.devicepolicy;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
+
 /**
  * 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.
  */
 public class MixedManagedProfileOwnerTest extends DeviceAndProfileOwnerTest {
 
-    protected static final String CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS =
+    private static final String CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS =
             DEVICE_ADMIN_PKG + ".ClearProfileOwnerNegativeTest";
 
     private int mParentUserId = -1;
@@ -157,7 +159,7 @@
 
     @Override
     public void testResetPasswordWithToken() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
         // Execute the test method that's guaranteed to succeed. See also test in base class
@@ -170,4 +172,52 @@
     public void testSetSystemSetting() {
         // Managed profile owner cannot set currently whitelisted system settings.
     }
+
+    public void testCannotClearProfileOwner() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS, mUserId);
+    }
+
+    private void grantProfileOwnerDeviceIdsAccess() throws DeviceNotAvailableException {
+        getDevice().executeShellCommand(
+                String.format("dpm grant-profile-owner-device-ids-access --user %d '%s'",
+                    mUserId, DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS));
+
+    }
+
+    public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        setUpDelegatedCertInstallerAndRunTests(() -> {
+            runDeviceTestsAsUser("com.android.cts.certinstaller",
+                    ".DelegatedDeviceIdAttestationTest",
+                    "testGenerateKeyPairWithDeviceIdAttestationExpectingFailure", mUserId);
+
+            grantProfileOwnerDeviceIdsAccess();
+
+            runDeviceTestsAsUser("com.android.cts.certinstaller",
+                    ".DelegatedDeviceIdAttestationTest",
+                    "testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId);
+        });
+    }
+    public void testDeviceIdAttestationForProfileOwner() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // Test that Device ID attestation for the profile owner does not work without grant.
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdAttestationTest",
+                "testFailsWithoutProfileOwnerIdsGrant", mUserId);
+
+        // Test that Device ID attestation for the profile owner works with a grant.
+        grantProfileOwnerDeviceIdsAccess();
+
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdAttestationTest",
+                "testSucceedsWithProfileOwnerIdsGrant", mUserId);
+    }
+
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index fd5b2bf..47b3539 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -62,7 +62,7 @@
      */
     @Override
     public void testResetPassword() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasSecureLockScreen) {
             return;
         }
 
@@ -75,7 +75,7 @@
      */
     @Override
     public void testResetPasswordFbe() throws Exception {
-        if (!mHasFeature || !mSupportsFbe) {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
         }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
index 5143fb1..ca081fc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
@@ -24,10 +24,6 @@
  */
 public class MixedProfileOwnerHostSideTransferTest extends
         DeviceAndProfileOwnerHostSideTransferTest {
-    private static final String TRANSFER_PROFILE_OWNER_OUTGOING_TEST =
-            "com.android.cts.transferowner.TransferProfileOwnerOutgoingTest";
-    private static final String TRANSFER_PROFILE_OWNER_INCOMING_TEST =
-            "com.android.cts.transferowner.TransferProfileOwnerIncomingTest";
 
     @Override
     protected void setUp() throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index 803cf36..e1d50bd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.RequiresDevice;
-
 /**
  * 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.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
index b044441..0dcb82a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.RequiresDevice;
-
 /**
  * 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.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
new file mode 100644
index 0000000..958009f
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
@@ -0,0 +1,41 @@
+package com.android.cts.devicepolicy;
+
+/** Host-side tests to run the CtsPasswordComplexity device-side tests. */
+public class PasswordComplexityTest extends BaseDevicePolicyTest {
+
+    private static final String APP = "CtsPasswordComplexity.apk";
+    private static final String PKG = "com.android.cts.passwordcomplexity";
+    private static final String CLS = ".GetPasswordComplexityTest";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (!mHasSecureLockScreen) {
+          return;
+        }
+
+        if (!getDevice().executeShellCommand("cmd lock_settings verify")
+                .startsWith("Lock credential verified successfully")) {
+            fail("Please remove the device screen lock before running this test");
+        }
+
+        installAppAsUser(APP, mPrimaryUserId);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasSecureLockScreen) {
+            getDevice().uninstallPackage(PKG);
+        }
+
+        super.tearDown();
+    }
+
+    public void testGetPasswordComplexity() throws Exception {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+        runDeviceTestsAsUser(PKG, CLS, mPrimaryUserId);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index e9feb3e..0b5cdef 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -21,6 +21,7 @@
 public class ProfileOwnerTest extends BaseDevicePolicyTest {
     private static final String PROFILE_OWNER_PKG = "com.android.cts.profileowner";
     private static final String PROFILE_OWNER_APK = "CtsProfileOwnerApp.apk";
+    private static final String FEATURE_BACKUP = "android.software.backup";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             PROFILE_OWNER_PKG + ".BaseProfileOwnerTest$BasicAdminReceiver";
@@ -74,6 +75,15 @@
         executeProfileOwnerTest("AppUsageObserverTest");
     }
 
+    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.
+        if (!mHasFeature || !hasBackupService) {
+            return;
+        }
+        executeProfileOwnerTest("BackupServicePoliciesTest");
+    }
+
     @Override
     protected void tearDown() throws Exception {
         if (mHasFeature) {
@@ -92,4 +102,9 @@
         String testClass = PROFILE_OWNER_PKG + "." + testClassName;
         runDeviceTestsAsUser(PROFILE_OWNER_PKG, testClass, mPrimaryUserId);
     }
+
+    protected void executeProfileOwnerTestMethod(String className, String testName)
+            throws Exception {
+        runDeviceTestsAsUser(PROFILE_OWNER_PKG, className, testName, mUserId);
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index 90c35f4..96927a9 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -205,9 +205,9 @@
         runTests("userrestrictions.PrimaryProfileOwnerUserRestrictionsTest",
                 "testSetAllRestrictions", mDeviceOwnerUserId);
 
-        // Secondary users shouldn't see any of them.
+        // Secondary users shouldn't see any of them. Leaky user restrictions are excluded.
         runTests("userrestrictions.SecondaryProfileOwnerUserRestrictionsTest",
-                "testDefaultRestrictionsOnly", secondaryUserId);
+                "testDefaultAndLeakyRestrictions", secondaryUserId);
     }
 
     /**
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
new file mode 100644
index 0000000..3262632
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
@@ -0,0 +1,249 @@
+/*
+ * 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.devicepolicy.metrics;
+
+import static junit.framework.Assert.assertTrue;
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.google.common.io.Files;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Tests Statsd atoms.
+ * <p/>
+ * Uploads statsd event configs, retrieves logs from host side and validates them
+ * against specified criteria.
+ */
+class AtomMetricTester {
+    private static final String UPDATE_CONFIG_CMD = "cat %s | cmd stats config update %d";
+    private static final String DUMP_REPORT_CMD =
+            "cmd stats dump-report %d --include_current_bucket --proto";
+    private static final String REMOVE_CONFIG_CMD = "cmd stats config remove %d";
+    /** ID of the config, which evaluates to -1572883457. */
+    private static final long CONFIG_ID = "cts_config".hashCode();
+
+    private final ITestDevice mDevice;
+
+    AtomMetricTester(ITestDevice device) {
+        mDevice = device;
+    }
+
+    void cleanLogs() throws Exception {
+        if (isStatsdDisabled()) {
+            return;
+        }
+        removeConfig(CONFIG_ID);
+        getReportList(); // Clears data.
+    }
+
+    private static StatsdConfig.Builder createConfigBuilder() {
+        return StatsdConfig.newBuilder().setId(CONFIG_ID)
+                .addAllowedLogSource("AID_SYSTEM");
+    }
+
+    void createAndUploadConfig(int atomTag) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag);
+        uploadConfig(conf);
+    }
+
+    private void uploadConfig(StatsdConfig.Builder config) throws Exception {
+        uploadConfig(config.build());
+    }
+
+    private void uploadConfig(StatsdConfig config) throws Exception {
+        CLog.d("Uploading the following config:\n" + config.toString());
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        mDevice.pushFile(configFile, remotePath);
+        mDevice.executeShellCommand(String.format(UPDATE_CONFIG_CMD, remotePath, CONFIG_ID));
+        mDevice.executeShellCommand("rm " + remotePath);
+    }
+
+    private void removeConfig(long configId) throws Exception {
+        mDevice.executeShellCommand(String.format(REMOVE_CONFIG_CMD, configId));
+    }
+
+    /**
+     * Gets the statsd report and sorts it.
+     * Note that this also deletes that report from statsd.
+     */
+    List<EventMetricData> getEventMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        return getEventMetricDataList(reportList);
+    }
+
+    /**
+     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
+     * contain a single report).
+     */
+    private List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
+            throws Exception {
+        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        final ConfigMetricsReport report = reportList.getReports(0);
+        final List<StatsLogReport> metricsList = report.getMetricsList();
+        return metricsList.stream()
+                .flatMap(statsLogReport -> statsLogReport.getEventMetrics().getDataList().stream())
+                .sorted(Comparator.comparing(EventMetricData::getElapsedTimestampNanos))
+                .peek(eventMetricData -> {
+                    CLog.d("Atom at " + eventMetricData.getElapsedTimestampNanos()
+                            + ":\n" + eventMetricData.getAtom().toString());
+                })
+                .collect(Collectors.toList());
+    }
+
+    /** Gets the statsd report. Note that this also deletes that report from statsd. */
+    private ConfigMetricsReportList getReportList() throws Exception {
+        try {
+            return getDump(ConfigMetricsReportList.parser(),
+                    String.format(DUMP_REPORT_CMD, CONFIG_ID));
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            CLog.e("Failed to fetch and parse the statsd output report. "
+                    + "Perhaps there is not a valid statsd config for the requested "
+                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
+            throw (e);
+        }
+    }
+
+    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
+    private static FieldValueMatcher.Builder createFvm(int field) {
+        return FieldValueMatcher.newBuilder().setField(field);
+    }
+
+    private void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
+        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given keys.
+     *
+     * @param conf   configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
+     */
+    private void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            List<FieldValueMatcher.Builder> fvms) throws Exception {
+
+        final String atomName = "Atom" + System.nanoTime();
+        final String eventName = "Event" + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomTag);
+        if (fvms != null) {
+            for (FieldValueMatcher.Builder fvm : fvms) {
+                sam.addFieldValueMatcher(fvm);
+            }
+        }
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        conf.addEventMetric(EventMetric.newBuilder()
+                .setId(eventName.hashCode())
+                .setWhat(atomName.hashCode()));
+    }
+
+    /**
+     * Removes all elements from data prior to the first occurrence of an element for which
+     * the <code>atomMatcher</code> predicate returns <code>true</code>.
+     * After this method is called, the first element of data (if non-empty) is guaranteed to be
+     * an element in state.
+     *
+     * @param atomMatcher predicate that takes an Atom and returns <code>true</code> if it
+     * fits criteria.
+     */
+    static void dropWhileNot(List<EventMetricData> metricData, Predicate<Atom> atomMatcher) {
+        int firstStateIdx;
+        for (firstStateIdx = 0; firstStateIdx < metricData.size(); firstStateIdx++) {
+            final Atom atom = metricData.get(firstStateIdx).getAtom();
+            if (atomMatcher.test(atom)) {
+                break;
+            }
+        }
+        if (firstStateIdx == 0) {
+            // First first element already is in state, so there's nothing to do.
+            return;
+        }
+        metricData.subList(0, firstStateIdx).clear();
+    }
+
+    /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
+    private int getHostUid() throws DeviceNotAvailableException {
+        String strUid = "";
+        try {
+            strUid = mDevice.executeShellCommand("id -u");
+            return Integer.parseInt(strUid.trim());
+        } catch (NumberFormatException e) {
+            CLog.e("Failed to get host's uid via shell command. Found " + strUid);
+            // Fall back to alternative method...
+            if (mDevice.isAdbRoot()) {
+                return 0; // ROOT
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    /**
+     * Execute a shell command on device and get the results of
+     * that as a proto of the given type.
+     *
+     * @param parser A protobuf parser object. e.g. MyProto.parser()
+     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+     *
+     * @throws DeviceNotAvailableException If there was a problem communicating with
+     *      the test device.
+     * @throws InvalidProtocolBufferException If there was an error parsing
+     *      the proto. Note that a 0 length buffer is not necessarily an error.
+     */
+    private <T extends MessageLite> T getDump(Parser<T> parser, String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        mDevice.executeShellCommand(command, receiver);
+        return parser.parseFrom(receiver.getOutput());
+    }
+
+    boolean isStatsdDisabled() throws DeviceNotAvailableException {
+        // if ro.statsd.enable doesn't exist, statsd is running by default.
+        if ("false".equals(mDevice.getProperty("ro.statsd.enable"))
+                && "true".equals(mDevice.getProperty("ro.config.low_ram"))) {
+            CLog.d("Statsd is not enabled on the device");
+            return true;
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
new file mode 100644
index 0000000..264bd08
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.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.devicepolicy.metrics;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.device.ITestDevice;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Helper class to assert <code>DevicePolicyEvent</code> atoms were logged.
+ */
+public final class DevicePolicyEventLogVerifier {
+
+    public interface Action {
+        void apply() throws Exception;
+    }
+
+    private static final int WAIT_TIME_SHORT = 500;
+
+    /**
+     * Asserts that <code>expectedLogs</code> were logged as a result of executing
+     * <code>action</code>, in the same order.
+     */
+    public static void assertMetricsLogged(ITestDevice device, Action action,
+            DevicePolicyEventWrapper... expectedLogs) throws Exception {
+        final AtomMetricTester logVerifier = new AtomMetricTester(device);
+        if (logVerifier.isStatsdDisabled()) {
+            return;
+        }
+        try {
+            logVerifier.cleanLogs();
+            logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
+            action.apply();
+
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            final List<EventMetricData> data = logVerifier.getEventMetricDataList();
+            for (DevicePolicyEventWrapper expectedLog : expectedLogs) {
+                assertExpectedMetricLogged(data, expectedLog);
+            }
+        } finally {
+            logVerifier.cleanLogs();
+        }
+    }
+
+    private static void assertExpectedMetricLogged(List<EventMetricData> data,
+            DevicePolicyEventWrapper expectedLog) {
+        final List<DevicePolicyEventWrapper> closestMatches = new ArrayList<>();
+        AtomMetricTester.dropWhileNot(data, atom -> {
+            final DevicePolicyEventWrapper actualLog =
+                    DevicePolicyEventWrapper.fromDevicePolicyAtom(atom.getDevicePolicyEvent());
+            if (actualLog.getEventId() == expectedLog.getEventId()) {
+                closestMatches.add(actualLog);
+            }
+            return Objects.equals(actualLog, expectedLog);
+        });
+        assertWithMessage("Expected metric was not logged.")
+                .that(closestMatches).contains(expectedLog);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventWrapper.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventWrapper.java
new file mode 100644
index 0000000..71566d0
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventWrapper.java
@@ -0,0 +1,168 @@
+/*
+ * 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.devicepolicy.metrics;
+
+import android.stats.devicepolicy.StringList;
+import android.stats.devicepolicy.EventId;
+import com.android.os.AtomsProto.DevicePolicyEvent;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Wrapper over <code>DevicePolicyEvent</code> atom as defined in
+ * <code>frameworks/base/cmds/statsd/src/atoms.proto</code>.
+ * @see Builder
+ */
+public final class DevicePolicyEventWrapper {
+    private final int mEventId;
+    private final int mIntValue;
+    private final boolean mBooleanValue;
+    private final long mTimePeriodMs;
+    private final String[] mStringArrayValue;
+    private final String mAdminPackageName;
+
+    private DevicePolicyEventWrapper(Builder builder) {
+        mEventId = builder.mEventId;
+        mIntValue = builder.mIntValue;
+        mBooleanValue = builder.mBooleanValue;
+        mTimePeriodMs = builder.mTimePeriodMs;
+        mStringArrayValue = builder.mStringArrayValue;
+        mAdminPackageName = builder.mAdminPackageName;
+    }
+
+    /**
+     * Constructs a {@link DevicePolicyEventWrapper} from a <code>DevicePolicyEvent</code> atom.
+     */
+    static DevicePolicyEventWrapper fromDevicePolicyAtom(DevicePolicyEvent atom) {
+        return new Builder(atom.getEventId().getNumber())
+                .setAdminPackageName(atom.getAdminPackageName())
+                .setInt(atom.getIntegerValue())
+                .setBoolean(atom.getBooleanValue())
+                .setTimePeriod(atom.getTimePeriodMillis())
+                .setStrings(getStringArray(atom.getStringListValue()))
+                .build();
+    }
+
+    /**
+     * Converts a <code>StringList</code> proto object to <code>String[]</code>.
+     */
+    private static String[] getStringArray(StringList stringList) {
+        final int count = stringList.getStringValueCount();
+        if (count == 0) {
+            return null;
+        }
+        final String[] result = new String[count];
+        for (int i = 0; i < count; i++) {
+            result[i] = stringList.getStringValue(i);
+        }
+        return result;
+    }
+
+    int getEventId() {
+        return mEventId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DevicePolicyEventWrapper)) {
+            return false;
+        }
+        final DevicePolicyEventWrapper other = (DevicePolicyEventWrapper) obj;
+        return mEventId == other.mEventId
+                && mIntValue == other.mIntValue
+                && mBooleanValue == other.mBooleanValue
+                && mTimePeriodMs == other.mTimePeriodMs
+                && Objects.equals(mAdminPackageName, other.mAdminPackageName)
+                && Arrays.equals(mStringArrayValue, other.mStringArrayValue);
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.getDefault(), "{ eventId: %s(%d), intValue: %d, "
+                        + "booleanValue: %s, timePeriodMs: %d, adminPackageName: %s, "
+                        + "stringArrayValue: %s }",
+                EventId.forNumber(mEventId), mEventId, mIntValue, mBooleanValue,
+                mTimePeriodMs, mAdminPackageName, Arrays.toString(mStringArrayValue));
+    }
+
+    /**
+     * Builder class for {@link DevicePolicyEventWrapper}.
+     */
+    public static class Builder {
+        private final int mEventId;
+        private int mIntValue;
+        private boolean mBooleanValue;
+        private long mTimePeriodMs;
+        private String[] mStringArrayValue;
+        // Default value for Strings when getting dump is "", not null.
+        private String mAdminPackageName = "";
+
+        public Builder(int eventId) {
+            this.mEventId = eventId;
+        }
+
+        public Builder setInt(int value) {
+            mIntValue = value;
+            return this;
+        }
+
+        public Builder setBoolean(boolean value) {
+            mBooleanValue = value;
+            return this;
+        }
+
+        public Builder setTimePeriod(long timePeriodMs) {
+            mTimePeriodMs = timePeriodMs;
+            return this;
+        }
+
+        public Builder setStrings(String... values) {
+            mStringArrayValue = values;
+            return this;
+        }
+
+        public Builder setStrings(String value, String[] values) {
+            if (values == null) {
+                throw new IllegalArgumentException("values cannot be null.");
+            }
+            mStringArrayValue = new String[values.length + 1];
+            mStringArrayValue[0] = value;
+            System.arraycopy(values, 0, mStringArrayValue, 1, values.length);
+            return this;
+        }
+
+        public Builder setStrings(String value1, String value2, String[] values) {
+            if (values == null) {
+                throw new IllegalArgumentException("values cannot be null.");
+            }
+            mStringArrayValue = new String[values.length + 2];
+            mStringArrayValue[0] = value1;
+            mStringArrayValue[1] = value2;
+            System.arraycopy(values, 0, mStringArrayValue, 2, values.length);
+            return this;
+        }
+
+        public Builder setAdminPackageName(String packageName) {
+            mAdminPackageName = packageName;
+            return this;
+        }
+
+        public DevicePolicyEventWrapper build() {
+            return new DevicePolicyEventWrapper(this);
+        }
+    }
+}
diff --git a/hostsidetests/dumpsys/AndroidTest.xml b/hostsidetests/dumpsys/AndroidTest.xml
index 0ac7c2e..ead52be 100644
--- a/hostsidetests/dumpsys/AndroidTest.xml
+++ b/hostsidetests/dumpsys/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for the CTS dumpsys host tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- This module tests system service dumps, which is irrelevant for Instant Apps. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest">
         <option name="jar" value="CtsDumpsysHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
index c5c846b..bd6f0d73 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
@@ -32,6 +32,7 @@
     androidx.legacy_legacy-support-v4
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 24
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 32fffdb..5cc78d2 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -574,7 +574,7 @@
     }
 
     private void checkDataConnection(String[] parts) {
-        assertEquals(25, parts.length);
+        assertEquals(26, parts.length);
         assertInteger(parts[4]);  // none
         assertInteger(parts[5]);  // gprs
         assertInteger(parts[6]);  // edge
@@ -595,7 +595,8 @@
         assertInteger(parts[21]); // td_scdma
         assertInteger(parts[22]); // iwlan
         assertInteger(parts[23]); // lte_ca
-        assertInteger(parts[24]); // other
+        assertInteger(parts[24]); // nr
+        assertInteger(parts[25]); // other
     }
 
     private void checkWifiState(String[] parts) {
diff --git a/hostsidetests/edi/AndroidTest.xml b/hostsidetests/edi/AndroidTest.xml
index aef3086..b0d5d9f 100644
--- a/hostsidetests/edi/AndroidTest.xml
+++ b/hostsidetests/edi/AndroidTest.xml
@@ -15,7 +15,9 @@
 -->
 <configuration description="Config for CTS EDI host test cases">
     <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <option name="config-descriptor:metadata" key="component" value="deviceinfo" />
+    <!-- Included not for instant-app but to collect the required information for the run -->
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsEdiHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/edi/OWNERS b/hostsidetests/edi/OWNERS
new file mode 100644
index 0000000..73518d9
--- /dev/null
+++ b/hostsidetests/edi/OWNERS
@@ -0,0 +1,4 @@
+aaronholden@google.com
+agathaman@google.com
+nickrose@google.com
+samlin@google.com
diff --git a/hostsidetests/gputools/AndroidTest.xml b/hostsidetests/gputools/AndroidTest.xml
index 98e41a7..199f5e7 100644
--- a/hostsidetests/gputools/AndroidTest.xml
+++ b/hostsidetests/gputools/AndroidTest.xml
@@ -16,7 +16,7 @@
 
 <configuration description="Config for CtsGpuToolsHostTestCases">
     <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <option name="config-descriptor:metadata" key="component" value="graphics" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-DEBUG.apk" />
diff --git a/hostsidetests/gputools/apps/Android.mk b/hostsidetests/gputools/apps/Android.mk
index c913837..138b4e9 100644
--- a/hostsidetests/gputools/apps/Android.mk
+++ b/hostsidetests/gputools/apps/Android.mk
@@ -19,8 +19,9 @@
 LOCAL_SRC_FILES := \
     jni/CtsGpuToolsJniOnLoad.cpp \
     jni/android_gputools_cts_RootlessGpuDebug.cpp
-LOCAL_CFLAGS += -std=c++14 -Wall -Werror
+LOCAL_CFLAGS += -Wall -Werror
 LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan libEGL libGLESv3 liblog
 LOCAL_NDK_STL_VARIANT := c++_static
 LOCAL_SDK_VERSION := current
 include $(BUILD_SHARED_LIBRARY)
@@ -61,7 +62,8 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := \
 libctsgputools_jni \
-libVkLayer_nullLayerC
+libVkLayer_nullLayerC \
+libGLES_glesLayer3
 
 LOCAL_AAPT_FLAGS := \
 --rename-manifest-package android.rootlessgpudebug.RELEASE.app
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
index de8130f..fab44c3 100755
--- a/hostsidetests/gputools/apps/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.rootlessgpudebug.app">>
 
-    <application>
+    <application android:extractNativeLibs="true" >
         <activity android:name=".RootlessGpuDebugDeviceActivity" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
index 4fdddbc..bdbfd06 100644
--- a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
+++ b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
@@ -17,40 +17,46 @@
 
 #define LOG_TAG "RootlessGpuDebug"
 
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
 #include <android/log.h>
+#include <android/native_window.h>
 #include <jni.h>
-#include <string>
 #include <vulkan/vulkan.h>
+#include <string>
 
 #define ALOGI(msg, ...) \
     __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
 #define ALOGE(msg, ...) \
     __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGD(msg, ...) \
+    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, (msg), __VA_ARGS__)
 
 namespace {
 
-std::string initVulkan()
-{
+typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
+
+std::string initVulkan() {
     std::string result = "";
 
     const VkApplicationInfo app_info = {
         VK_STRUCTURE_TYPE_APPLICATION_INFO,
-        nullptr,            // pNext
-        "RootlessGpuDebug", // app name
-        0,                  // app version
-        nullptr,            // engine name
-        0,                  // engine version
+        nullptr,             // pNext
+        "RootlessGpuDebug",  // app name
+        0,                   // app version
+        nullptr,             // engine name
+        0,                   // engine version
         VK_API_VERSION_1_0,
     };
     const VkInstanceCreateInfo instance_info = {
         VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
-        nullptr,   // pNext
-        0,         // flags
+        nullptr,  // pNext
+        0,        // flags
         &app_info,
-        0,         // layer count
-        nullptr,   // layers
-        0,         // extension count
-        nullptr,   // extensions
+        0,        // layer count
+        nullptr,  // layers
+        0,        // extension count
+        nullptr,  // extensions
     };
     VkInstance instance;
     VkResult vkResult = vkCreateInstance(&instance_info, nullptr, &instance);
@@ -63,9 +69,50 @@
     return result;
 }
 
-jstring android_gputools_cts_RootlessGpuDebug_nativeInitVulkan(JNIEnv* env,
-    jclass /*clazz*/)
-{
+std::string initGLES() {
+    std::string result = "";
+
+    const EGLint attribs[] = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+                              EGL_BLUE_SIZE,    8,
+                              EGL_GREEN_SIZE,   8,
+                              EGL_RED_SIZE,     8,
+                              EGL_NONE};
+
+    // Create an EGL context
+    EGLDisplay display;
+    EGLConfig config;
+    EGLint numConfigs;
+    EGLint format;
+
+    if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
+        result = "eglGetDisplay() returned error " + std::to_string(eglGetError());
+        return result;
+    }
+
+    if (!eglInitialize(display, 0, 0)) {
+        result = "eglInitialize() returned error " + std::to_string(eglGetError());
+        return result;
+    }
+
+    if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs)) {
+        result =
+            "eglChooseConfig() returned error " + std::to_string(eglGetError());
+        return result;
+    }
+
+    if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
+        result =
+            "eglGetConfigAttrib() returned error " + std::to_string(eglGetError());
+        return result;
+    }
+
+    eglTerminate(display);
+
+    return result;
+}
+
+jstring android_gputools_cts_RootlessGpuDebug_nativeInitVulkan(
+        JNIEnv* env, jclass /*clazz*/) {
     std::string result;
 
     result = initVulkan();
@@ -73,15 +120,25 @@
     return env->NewStringUTF(result.c_str());
 }
 
-static JNINativeMethod gMethods[] = {
-    {    "nativeInitVulkan", "()Ljava/lang/String;",
-         (void*) android_gputools_cts_RootlessGpuDebug_nativeInitVulkan },
-};
+jstring android_gputools_cts_RootlessGpuDebug_nativeInitGLES(JNIEnv* env,
+                                                             jclass /*clazz*/) {
+    std::string result;
 
-} // anonymous namespace
+    result = initGLES();
+
+    return env->NewStringUTF(result.c_str());
+}
+
+static JNINativeMethod gMethods[] = {
+    {"nativeInitVulkan", "()Ljava/lang/String;",
+     (void*)android_gputools_cts_RootlessGpuDebug_nativeInitVulkan},
+    {"nativeInitGLES", "()Ljava/lang/String;",
+     (void*)android_gputools_cts_RootlessGpuDebug_nativeInitGLES}};
+}  // anonymous namespace
 
 int register_android_gputools_cts_RootlessGpuDebug(JNIEnv* env) {
-    jclass clazz = env->FindClass("android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity");
+    jclass clazz = env->FindClass(
+        "android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity");
     return env->RegisterNatives(clazz, gMethods,
-            sizeof(gMethods) / sizeof(JNINativeMethod));
+                                sizeof(gMethods) / sizeof(JNINativeMethod));
 }
diff --git a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
index ec5052b..a29a246 100644
--- a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
+++ b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
@@ -36,9 +36,15 @@
 
         String result = nativeInitVulkan();
         Log.i(TAG, result);
+
+        result = nativeInitGLES();
+        Log.i(TAG, result);
+
+        Log.i(TAG, "RootlessGpuDebug activity complete");
     }
 
     private static native String nativeInitVulkan();
+    private static native String nativeInitGLES();
 
 }
 
diff --git a/hostsidetests/gputools/layers/Android.mk b/hostsidetests/gputools/layers/Android.mk
index 201fafd..f91b0d3 100644
--- a/hostsidetests/gputools/layers/Android.mk
+++ b/hostsidetests/gputools/layers/Android.mk
@@ -19,8 +19,8 @@
 LOCAL_MODULE := libVkLayer_nullLayerA
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := jni/nullLayer.cpp
-LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="A"
-LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_CFLAGS += -Wall -Werror -fvisibility=hidden -DLAYERNAME="A"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan libEGL libGLESv3 liblog
 LOCAL_NDK_STL_VARIANT := c++_static
 LOCAL_SDK_VERSION := current
 include $(BUILD_SHARED_LIBRARY)
@@ -30,8 +30,8 @@
 LOCAL_MODULE := libVkLayer_nullLayerB
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := jni/nullLayer.cpp
-LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="B"
-LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_CFLAGS += -Wall -Werror -fvisibility=hidden -DLAYERNAME="B"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan libEGL libGLESv3 liblog
 LOCAL_NDK_STL_VARIANT := c++_static
 LOCAL_SDK_VERSION := current
 include $(BUILD_SHARED_LIBRARY)
@@ -41,13 +41,45 @@
 LOCAL_MODULE := libVkLayer_nullLayerC
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := jni/nullLayer.cpp
-LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="C"
-LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_CFLAGS += -Wall -Werror -fvisibility=hidden -DLAYERNAME="C"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan libEGL libGLESv3 liblog
 LOCAL_NDK_STL_VARIANT := c++_static
 LOCAL_SDK_VERSION := current
 include $(BUILD_SHARED_LIBRARY)
 
 
+include $(CLEAR_VARS)
+LOCAL_MODULE := libGLES_glesLayer1
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/glesLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME=1
+LOCAL_SHARED_LIBRARIES := libandroid libEGL libGLESv3 liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libGLES_glesLayer2
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/glesLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME=2
+LOCAL_SHARED_LIBRARIES := libandroid libEGL libGLESv3 liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libGLES_glesLayer3
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/glesLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME=3
+LOCAL_SHARED_LIBRARIES := libandroid libEGL libGLESv3 liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
 
 include $(CLEAR_VARS)
 
@@ -64,7 +96,10 @@
 LOCAL_JNI_SHARED_LIBRARIES := \
 libVkLayer_nullLayerA \
 libVkLayer_nullLayerB \
-libVkLayer_nullLayerC
+libVkLayer_nullLayerC \
+libGLES_glesLayer1 \
+libGLES_glesLayer2 \
+libGLES_glesLayer3
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
 
diff --git a/hostsidetests/gputools/layers/AndroidManifest.xml b/hostsidetests/gputools/layers/AndroidManifest.xml
index 957b047..2b187ad 100755
--- a/hostsidetests/gputools/layers/AndroidManifest.xml
+++ b/hostsidetests/gputools/layers/AndroidManifest.xml
@@ -18,7 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.rootlessgpudebug.LAYERS.app">
 
-    <application android:debuggable="false" android:hasCode="false">
+    <application android:extractNativeLibs="true" android:debuggable="false"
+        android:hasCode="false">
     </application>
 
 </manifest>
diff --git a/hostsidetests/gputools/layers/jni/glesLayer.cpp b/hostsidetests/gputools/layers/jni/glesLayer.cpp
new file mode 100644
index 0000000..a053de0
--- /dev/null
+++ b/hostsidetests/gputools/layers/jni/glesLayer.cpp
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+
+#include <android/log.h>
+#include <cstring>
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#include <string>
+#include <string.h>
+#include <unordered_map>
+
+#define xstr(a) str(a)
+#define str(a) #a
+
+#define LOG_TAG "glesLayer" xstr(LAYERNAME)
+
+#define ALOGI(msg, ...) \
+    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
+
+
+// Announce if anything loads this layer.  LAYERNAME is defined in Android.mk
+class StaticLogMessage {
+    public:
+        StaticLogMessage(const char* msg) {
+            ALOGI("%s", msg);
+    }
+};
+StaticLogMessage g_initMessage("glesLayer" xstr(LAYERNAME) " loaded");
+
+typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
+typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*);
+
+namespace {
+
+std::unordered_map<std::string, EGLFuncPointer> funcMap;
+
+EGLAPI void EGLAPIENTRY glesLayer_glCompileShaderA (GLuint shader) {
+    ALOGI("%s%u", "glesLayer_glCompileShaderA called with parameter ", shader);
+
+    if (funcMap.find("glCompileShader") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for glCompileShader");
+
+    EGLFuncPointer entry = funcMap["glCompileShader"];
+
+    PFNGLCOMPILESHADERPROC next = reinterpret_cast<PFNGLCOMPILESHADERPROC>(entry);
+
+    next(shader);
+}
+
+EGLAPI void EGLAPIENTRY glesLayer_glCompileShaderB (GLuint shader) {
+    ALOGI("%s%u", "glesLayer_CompileShaderB called with parameter ", shader);
+
+    if (funcMap.find("glCompileShader") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for glCompileShader");
+
+    EGLFuncPointer entry = funcMap["glCompileShader"];
+
+    PFNGLCOMPILESHADERPROC next = reinterpret_cast<PFNGLCOMPILESHADERPROC>(entry);
+
+    next(shader);
+}
+
+EGLAPI void EGLAPI glesLayer_glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
+    ALOGI("%s %i, %i, %i", "glesLayer_glDrawArraysInstanced called with parameters (minus GLenum):", first, count, instancecount);
+
+    if (funcMap.find("glDrawArraysInstanced") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for glDrawArraysInstanced");
+
+    EGLFuncPointer entry = funcMap["glDrawArraysInstanced"];
+
+    PFNGLDRAWARRAYSINSTANCEDPROC next = reinterpret_cast<PFNGLDRAWARRAYSINSTANCEDPROC>(entry);
+
+    next(mode, first, count, instancecount);
+}
+
+EGLAPI void EGLAPIENTRY glesLayer_glBindBuffer(GLenum target, GLuint buffer) {
+    ALOGI("%s %i", "glesLayer_glBindBuffer called with parameters (minus GLenum):", buffer);
+
+    if (funcMap.find("glBindBuffer") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for glBindBuffer");
+
+    EGLFuncPointer entry = funcMap["glBindBuffer"];
+
+    PFNGLBINDBUFFERPROC next = reinterpret_cast<PFNGLBINDBUFFERPROC>(entry);
+
+    next(target, buffer);
+}
+
+EGLAPI const GLubyte* EGLAPIENTRY glesLayer_glGetString(GLenum name) {
+    ALOGI("%s %lu", "glesLayer_glGetString called with parameters:", (unsigned long)name);
+
+    if (funcMap.find("glGetString") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for glGetString");
+
+    EGLFuncPointer entry = funcMap["glGetString"];
+
+    PFNGLGETSTRINGPROC next = reinterpret_cast<PFNGLGETSTRINGPROC>(entry);
+
+    return next(name);
+}
+
+EGLAPI EGLDisplay EGLAPIENTRY glesLayer_eglGetDisplay(EGLNativeDisplayType display_type) {
+    ALOGI("%s %lu", "glesLayer_eglGetDisplay called with parameters:", (unsigned long)display_type);
+
+    if (funcMap.find("eglGetDisplay") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for eglGetDisplay");
+
+    EGLFuncPointer entry = funcMap["eglGetDisplay"];
+
+    typedef EGLDisplay (*PFNEGLGETDISPLAYPROC)(EGLNativeDisplayType);
+    PFNEGLGETDISPLAYPROC next = reinterpret_cast<PFNEGLGETDISPLAYPROC>(entry);
+
+    return next(display_type);
+}
+
+EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor) {
+    ALOGI("%s %lu %li %li", "glesLayer_eglInitialize called with parameters:", (unsigned long)dpy, major ? (long)*major : 0, minor ? (long)*minor : 0);
+
+    if (funcMap.find("eglInitialize") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for eglInitialize");
+
+    EGLFuncPointer entry = funcMap["eglInitialize"];
+
+    typedef EGLBoolean (*PFNEGLINITIALIZEPROC)(EGLDisplay, EGLint*, EGLint*);
+    PFNEGLINITIALIZEPROC next = reinterpret_cast<PFNEGLINITIALIZEPROC>(entry);
+
+    return next(dpy, major, minor);
+}
+
+EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) {
+
+    const char* msg = "glesLayer_eglChooseConfig called in glesLayer" xstr(LAYERNAME);
+    ALOGI("%s", msg);
+
+    if (funcMap.find("eglChooseConfig") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for eglChooseConfig");
+
+    EGLFuncPointer entry = funcMap["eglChooseConfig"];
+
+    typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*);
+    PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry);
+
+    return next(dpy, attrib_list, configs, config_size, num_config);
+}
+
+EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglSwapBuffersWithDamageKHR (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects) {
+
+    const char* msg = "glesLayer_eglSwapBuffersWithDamageKHR called in glesLayer" xstr(LAYERNAME);
+    ALOGI("%s", msg);
+
+    if (funcMap.find("eglSwapBuffersWithDamageKHR") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for eglSwapBuffersWithDamageKHR");
+
+    EGLFuncPointer entry = funcMap["eglSwapBuffersWithDamageKHR"];
+
+    typedef EGLBoolean (*PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC)(EGLDisplay, EGLSurface, EGLint*, EGLint);
+    PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC next = reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC>(entry);
+
+    return next(dpy, surface, rects, n_rects);
+}
+
+EGLAPI void* EGLAPIENTRY glesLayer_eglGetProcAddress (const char* procname) {
+
+    const char* msg = "glesLayer_eglGetProcAddress called in glesLayer" xstr(LAYERNAME) " for:";
+    ALOGI("%s%s", msg, procname);
+
+    if (funcMap.find("eglGetProcAddress") == funcMap.end())
+        ALOGI("%s", "Unable to find funcMap entry for eglGetProcAddress");
+
+    EGLFuncPointer entry = funcMap["eglGetProcAddress"];
+
+    typedef void* (*PFNEGLGETPROCADDRESSPROC)(const char*);
+    PFNEGLGETPROCADDRESSPROC next = reinterpret_cast<PFNEGLGETPROCADDRESSPROC>(entry);
+
+    return next(procname);
+}
+
+EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) {
+
+#define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \
+ALOGI("%s%s%s", "Returning glesLayer_" #func " for ", funcName, " in eglGPA"); \
+return (EGLFuncPointer)glesLayer_##func; \
+}
+
+    if (strcmp("1", xstr(LAYERNAME)) == 0) {
+
+        const char* targetFunc = "glCompileShader";
+        if (strcmp(targetFunc, funcName) == 0) {
+            ALOGI("%s%s%s", "Returning glesLayer_glCompileShaderA for ", funcName, " in eglGPA");
+            return (EGLFuncPointer)glesLayer_glCompileShaderA;
+        }
+
+        GETPROCADDR(glDrawArraysInstanced);
+
+    } else if (strcmp("2", xstr(LAYERNAME)) == 0) {
+
+        const char* targetFunc = "glCompileShader";
+        if (strcmp(targetFunc, funcName) == 0) {
+            ALOGI("%s%s%s", "Returning glesLayer_glCompileShaderB for ", funcName, " in eglGPA");
+            return (EGLFuncPointer)glesLayer_glCompileShaderB;
+        }
+
+        GETPROCADDR(glBindBuffer);
+    }
+
+    GETPROCADDR(glGetString);
+    GETPROCADDR(eglGetDisplay);
+    GETPROCADDR(eglInitialize);
+    GETPROCADDR(eglChooseConfig);
+    GETPROCADDR(eglSwapBuffersWithDamageKHR);
+    GETPROCADDR(eglGetProcAddress);
+
+    // Don't return anything for unrecognized functions
+    return nullptr;
+}
+
+EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {
+    ALOGI("%s%llu%s%llu", "glesLayer_InitializeLayer called with layer_id (", (unsigned long long) layer_id,
+                              ") get_next_layer_proc_address (", (unsigned long long) get_next_layer_proc_address);
+
+    // Pick a real function to look up and test the pointer we've been handed
+    const char* func = "eglGetProcAddress";
+
+    ALOGI("%s%s%s%llu%s%llu%s", "Looking up address of ", func,
+                                " using get_next_layer_proc_address (", (unsigned long long) get_next_layer_proc_address,
+                                ") with layer_id (", (unsigned long long) layer_id,
+                                ")");
+
+    void* gpa = get_next_layer_proc_address(layer_id, func);
+
+    // Pick a fake function to look up and test the pointer we've been handed
+    func = "eglFoo";
+
+    ALOGI("%s%s%s%llu%s%llu%s", "Looking up address of ", func,
+                                " using get_next_layer_proc_address (", (unsigned long long) get_next_layer_proc_address,
+                                ") with layer_id (", (unsigned long long) layer_id,
+                                ")");
+
+    gpa = get_next_layer_proc_address(layer_id, func);
+
+    ALOGI("%s%llu%s%s", "Got back (", (unsigned long long) gpa, ") for ", func);
+}
+
+EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress(const char* funcName, EGLFuncPointer next) {
+
+    EGLFuncPointer entry = eglGPA(funcName);
+
+    if (entry != nullptr) {
+        ALOGI("%s%s%s%llu%s", "Setting up glesLayer version of ", funcName, " calling down with: next (", (unsigned long long) next, ")");
+        funcMap[std::string(funcName)] = next;
+        return entry;
+    }
+
+    // If the layer does not intercept the function, just return original func pointer
+    return next;
+}
+
+}  // namespace
+
+extern "C" {
+
+    __attribute((visibility("default"))) EGLAPI void InitializeLayer(void* layer_id,
+            PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) {
+        return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address);
+    }
+
+    __attribute((visibility("default"))) EGLAPI void* GetLayerProcAddress(const char *funcName, EGLFuncPointer next) {
+        return (void*)glesLayer_GetLayerProcAddress(funcName, next);
+    }
+}
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
index 02ad313..a3e51e1 100644
--- a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -53,7 +53,7 @@
         return mDevice;
     }
 
-    // This test ensures that the Vulkan loader can use Settings to load layer
+    // This test ensures that the Vulkan and GLES loaders can use Settings to load layers
     // from the base directory of debuggable applications.  Is also tests several
     // positive and negative scenarios we want to cover (listed below).
     //
@@ -67,7 +67,7 @@
     // The layers themselves are practically null, only enough functionality to
     // satisfy loader enumerating and loading.  They don't actually chain together.
     //
-    // Positive tests
+    // Positive Vulkan tests
     // - Ensure we can toggle the Enable Setting on and off (testDebugLayerLoadVulkan)
     // - Ensure we can set the debuggable app (testDebugLayerLoadVulkan)
     // - Ensure we can set the layer list (testDebugLayerLoadVulkan)
@@ -76,12 +76,30 @@
     // - 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)
-    // Negative tests
-    // - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoad)
-    // - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoad)
+    // - Ensure we can find layers in separate specified app (testDebugLayerLoadExternalVulkan)
+    // 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 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)
+    //
+    // Positive GLES tests
+    // - Ensure we can toggle the Enable Setting on and off (testDebugLayerLoadGLES)
+    // - Ensure we can set the debuggable app (testDebugLayerLoadGLES)
+    // - Ensure we can set the layer list (testDebugLayerLoadGLES)
+    // - Ensure we can push a layer to debuggable app (testDebugLayerLoadGLES)
+    // - 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)
+    // Negative GLES tests
+    // - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoadGLES)
+    // - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoadGLES)
+    // - Ensure we cannot enumerate layers from debuggable app's data directory if Setting not specified (testDebugNoEnumerateGLES)
+    // - Ensure we cannot enumerate layers without specifying the debuggable app (testDebugNoEnumerateGLES)
+
+
 
     private static final String CLASS = "RootlessGpuDebugDeviceActivity";
     private static final String ACTIVITY = "android.rootlessgpudebug.app.RootlessGpuDebugDeviceActivity";
@@ -97,6 +115,12 @@
     private static final String DEBUG_APP = "android.rootlessgpudebug.DEBUG.app";
     private static final String RELEASE_APP = "android.rootlessgpudebug.RELEASE.app";
     private static final String LAYERS_APP = "android.rootlessgpudebug.LAYERS.app";
+    private static final String SHIM_A = "glesLayer1";
+    private static final String SHIM_B = "glesLayer2";
+    private static final String SHIM_C = "glesLayer3";
+    private static final String SHIM_A_LIB = "libGLES_" + SHIM_A + ".so";
+    private static final String SHIM_B_LIB = "libGLES_" + SHIM_B + ".so";
+    private static final String SHIM_C_LIB = "libGLES_" + SHIM_C + ".so";
 
     // This is how long we'll scan the log for a result before giving up. This limit will only
     // be reached if something has gone wrong
@@ -175,7 +199,7 @@
                     result.found = true;
                     result.lineNumber = lineNumber;
                 }
-                if (line.contains("vkCreateInstance succeeded")) {
+                if (line.contains("RootlessGpuDebug activity complete")) {
                     // Once we've got output from the app, we've collected what we need
                     scanComplete= true;
                 }
@@ -196,10 +220,16 @@
         mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_A_LIB);
         mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_B_LIB);
         mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_C_LIB);
+        mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + SHIM_A_LIB);
+        mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + SHIM_B_LIB);
+        mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + SHIM_C_LIB);
         mDevice.executeAdbCommand("shell", "settings", "delete", "global", "enable_gpu_debug_layers");
         mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_app");
         mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layers");
+        mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layers_gles");
+        mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layer_app");
         mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers", "\'\'");
+        mDevice.executeAdbCommand("shell", "setprop", "debug.gles.layers", "\'\'");
     }
 
     /**
@@ -372,7 +402,7 @@
     }
 
     /**
-     * This test ensures we can still use properties if no layer found via Settings
+     * This test ensures we can still use properties if no layer specified via Settings
      */
     @Test
     public void testSystemPropertyEnableVulkan() throws Exception {
@@ -380,7 +410,9 @@
         // Set up layerA to be loaded, but not layerB or layerC
         mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
         mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
-        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+        // Switch back to delete once b/117555066 is fixed
+        //mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layers");
+        mDevice.executeShellCommand("settings put global gpu_debug_layers ''");
 
         // Enable layerC (which is packaged with the RELEASE app) with system properties
         mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers " + LAYER_C_NAME);
@@ -442,4 +474,305 @@
         LogScanResult resultB = scanLog(pid, searchStringB);
         Assert.assertFalse("LayerB was loaded", resultB.found);
     }
+
+    /**
+     *
+     */
+    @Test
+    public void testDebugLayerLoadExternalVulkan() throws Exception {
+
+        // Set up layers to be loaded
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_C_NAME);
+
+        // Specify the external app that hosts layers
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layer_app", LAYERS_APP);
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Check that our external layer was loaded
+        String searchStringC = "nullCreateInstance called in " + LAYER_C;
+        LogScanResult resultC = scanLog(pid, searchStringC);
+        Assert.assertTrue("LayerC was not loaded", resultC.found);
+    }
+
+
+    /**
+     * This test pushes GLES layers to our debuggable app and ensures they are
+     * loaded in the correct order.
+     */
+    @Test
+    public void testDebugLayerLoadGLES() throws Exception {
+
+        // Set up layers to be loaded
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_A_LIB + ":" + SHIM_B_LIB);
+
+        // Copy the layers from our LAYERS APK to tmp
+        setupLayer(SHIM_A_LIB);
+        setupLayer(SHIM_B_LIB);
+
+        // Copy them over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'");
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_B_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", SHIM_B_LIB, ";", "chmod", "700", SHIM_B_LIB + "\'");
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Check that both layers were loaded, in the correct order
+        String searchStringA = "glesLayer_eglChooseConfig called in " + SHIM_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertTrue("glesLayer1 was not loaded", resultA.found);
+
+        String searchStringB = "glesLayer_eglChooseConfig called in " + SHIM_B;
+        LogScanResult resultB = scanLog(pid, searchStringB);
+        Assert.assertTrue("glesLayer2 was not loaded", resultB.found);
+
+        Assert.assertTrue("glesLayer1 should be loaded before glesLayer2", resultA.lineNumber < resultB.lineNumber);
+    }
+
+    /**
+     * This test ensures that we cannot push a layer to a non-debuggable GLES app
+     * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+     */
+    @Test
+    public void testReleaseLayerLoadGLES() throws Exception {
+
+        // Set up a layers to be loaded for RELEASE app
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_A_LIB + ":" + SHIM_B_LIB);
+        // Remove this once b/117555066 has been fixed
+        mDevice.executeShellCommand("settings put global gpu_debug_layer_app ''");
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(SHIM_A_LIB);
+
+        // Attempt to copy them over to our RELEASE app (this should fail)
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", RELEASE_APP,
+                                   "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'", "||", "echo", "run-as", "failed");
+
+        // Kick off our RELEASE app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(RELEASE_APP);
+
+        // Ensure we don't load the layer in base dir
+        String searchStringA = SHIM_A + " loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse(SHIM_A + " was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable GLES apps do not enumerate layers in base
+     * directory if enable_gpu_debug_layers is not enabled.
+     */
+    @Test
+    public void testDebugNotEnabledGLES() throws Exception {
+
+        // Ensure the global layer enable settings is NOT enabled
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "0");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_A_LIB);
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(SHIM_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'");
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Ensure we don't load the layer in base dir
+        String searchStringA = SHIM_A + " loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse(SHIM_A + " was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable GLES apps do not enumerate layers in base
+     * directory if gpu_debug_app does not match.
+     */
+    @Test
+    public void testDebugWrongAppGLES() throws Exception {
+
+        // Ensure the gpu_debug_app does not match what we launch
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_A_LIB);
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(SHIM_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'");
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Ensure we don't load the layer in base dir
+        String searchStringA = SHIM_A + " loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("ShimA was enumerated", resultA.found);
+    }
+
+    /**
+     * This test ensures debuggable GLES apps do not enumerate layers in base
+     * directory if gpu_debug_layers are not set.
+     */
+    @Test
+    public void testDebugNoLayersEnabledGLES() throws Exception {
+
+        // Ensure the global layer enable settings is NOT enabled
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", "foo");
+
+        // Copy a layer from our LAYERS APK to tmp
+        setupLayer(SHIM_A_LIB);
+
+        // Copy it over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", DEBUG_APP,
+                                  "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'");
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Ensure layerA is not loaded
+        String searchStringA = "glesLayer_eglChooseConfig called in " + SHIM_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("ShimA was loaded", resultA.found);
+    }
+
+    /**
+     * This test ensures we can still use properties if no GLES layers are specified
+     */
+    @Test
+    public void testSystemPropertyEnableGLES() throws Exception {
+
+        // Set up layerA to be loaded, but not layerB or layerC
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+        // Switch back to delete once b/117555066 is fixed
+        //mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layers");
+        mDevice.executeShellCommand("settings put global gpu_debug_layers_gles ''");
+
+        // Enable layerC (which is packaged with the RELEASE app) with system properties
+        mDevice.executeAdbCommand("shell", "setprop", "debug.gles.layers " + SHIM_C_LIB);
+
+        // Kick off our RELEASE app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(RELEASE_APP);
+
+        // Check that both layers were loaded, in the correct order
+        String searchStringA = SHIM_A + "loaded";
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertFalse("ShimA was enumerated", resultA.found);
+
+        String searchStringC = "glesLayer_eglChooseConfig called in " + SHIM_C;
+        LogScanResult resultC = scanLog(pid, searchStringC);
+        Assert.assertTrue("ShimC was not loaded", resultC.found);
+    }
+
+    /**
+     * This test ensures system properties are ignored if Settings load a GLES layer
+     */
+    @Test
+    public void testSystemPropertyIgnoreGLES() throws Exception {
+
+        // Set up layerA to be loaded, but not layerB
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_A_LIB);
+
+        // Copy the layers from our LAYERS APK
+        setupLayer(SHIM_A_LIB);
+        setupLayer(SHIM_B_LIB);
+
+        // Copy them over to our DEBUG app
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_A_LIB, "|", "run-as", DEBUG_APP,
+                                 "sh", "-c", "\'cat", ">", SHIM_A_LIB, ";", "chmod", "700", SHIM_A_LIB + "\'");
+        mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + SHIM_B_LIB, "|", "run-as", DEBUG_APP,
+                                 "sh", "-c", "\'cat", ">", SHIM_B_LIB, ";", "chmod", "700", SHIM_B_LIB + "\'");
+
+        // Enable layerB with system properties
+        mDevice.executeAdbCommand("shell", "setprop", "debug.gles.layers " + SHIM_B_LIB);
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Ensure only layerA is loaded
+        String searchStringA = "glesLayer_eglChooseConfig called in " + SHIM_A;
+        LogScanResult resultA = scanLog(pid, searchStringA);
+        Assert.assertTrue("ShimA was not loaded", resultA.found);
+
+        String searchStringB = "glesLayer_eglChooseConfig called in " + SHIM_B;
+        LogScanResult resultB = scanLog(pid, searchStringB);
+        Assert.assertFalse("ShimB was loaded", resultB.found);
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testDebugLayerLoadExternalGLES() throws Exception {
+
+        // Set up layers to be loaded
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers_gles", SHIM_C_LIB);
+
+        // Specify the external app that hosts layers
+        mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layer_app", LAYERS_APP);
+
+        // Kick off our DEBUG app
+        mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+        // Give it a chance to start, then grab process ID
+        Thread.sleep(1000);
+        String pid = getPID(DEBUG_APP);
+
+        // Check that our external layer was loaded
+        String searchStringC = "glesLayer_eglChooseConfig called in " + SHIM_C;
+        LogScanResult resultC = scanLog(pid, searchStringC);
+        Assert.assertTrue("ShimC was not loaded", resultC.found);
+    }
+
 }
diff --git a/hostsidetests/incident/apps/errorsapp/AndroidManifest.xml b/hostsidetests/incident/apps/errorsapp/AndroidManifest.xml
index 41fde5c..f472e90 100644
--- a/hostsidetests/incident/apps/errorsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/errorsapp/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/incident/apps/errorsapp/src/com/android/server/cts/errors/ErrorsTests.java b/hostsidetests/incident/apps/errorsapp/src/com/android/server/cts/errors/ErrorsTests.java
index f5ce5d9..0c1ce19 100644
--- a/hostsidetests/incident/apps/errorsapp/src/com/android/server/cts/errors/ErrorsTests.java
+++ b/hostsidetests/incident/apps/errorsapp/src/com/android/server/cts/errors/ErrorsTests.java
@@ -26,13 +26,13 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Used by ErrorTest. Spawns misbehaving activities so reports will appear in Dropbox.
  */
@@ -57,6 +57,9 @@
         mDropbox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
         mResultsReceivedSignal = new CountDownLatch(1);
         mStartMs = System.currentTimeMillis();
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + mContext.getPackageName() + " android:get_usage_stats allow");
     }
 
     @Test
diff --git a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
index 53df6a9..342dc7f 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
@@ -232,9 +232,6 @@
     }
 
     private static void verifyActiveInstrumentationProto(ActiveInstrumentationProto aip, final int filterLevel) throws Exception {
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(aip.getArguments().isEmpty());
-        }
     }
 
     private static void verifyUidRecordProto(UidRecordProto urp, final int filterLevel) throws Exception {
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 9234387..115e1ad 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -350,21 +350,33 @@
         batteryOnScreenOff();
         installPackage(DEVICE_SIDE_TEST_APK, true);
         // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
         getDevice().executeShellCommand(String.format(
                 "settings put global location_background_throttle_package_whitelist %s",
                 DEVICE_SIDE_TEST_PACKAGE));
 
-        // Background test.
-        executeBackground(ACTION_GPS, 60_000);
-        assertValueRange("sr", gpsSensorNumber, 6, 1, 1); // count
-        assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
+        try {
+            // Background test.
+            executeBackground(ACTION_GPS, 60_000);
+            assertValueRange("sr", gpsSensorNumber, 6, 1, 1); // count
+            assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
 
-        // Foreground test.
-        executeForeground(ACTION_GPS, 60_000);
-        assertValueRange("sr", gpsSensorNumber, 6, 2, 2); // count
-        assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
-
-        batteryOffScreenOn();
+            // Foreground test.
+            executeForeground(ACTION_GPS, 60_000);
+            assertValueRange("sr", gpsSensorNumber, 6, 2, 2); // count
+            assertValueRange("sr", gpsSensorNumber, 7, 1, 1); // background_count
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+            batteryOffScreenOn();
+        }
     }
 
     public void testJobBgVsFg() throws Exception {
diff --git a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
index 7f77466..05699c7 100644
--- a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.cts;
 
-import com.android.server.fingerprint.FingerprintServiceDumpProto;
-import com.android.server.fingerprint.FingerprintUserStatsProto;
-import com.android.server.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
+import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
index 72d7cf4..0430e58 100644
--- a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -20,6 +20,7 @@
 import android.net.NetworkCapabilitiesProto;
 import android.net.NetworkRequestProto;
 import com.android.server.job.ConstantsProto;
+import com.android.server.job.ConstraintEnum;
 import com.android.server.job.DataSetProto;
 import com.android.server.job.JobPackageHistoryProto;
 import com.android.server.job.JobPackageTrackerDumpProto;
@@ -192,16 +193,16 @@
                 .contains(bp.getPolicy().getValueDescriptor()));
         assertTrue(0 <= bp.getInitialBackoffMs());
 
-        for (JobStatusDumpProto.Constraint c : jsd.getRequiredConstraintsList()) {
-            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+        for (ConstraintEnum c : jsd.getRequiredConstraintsList()) {
+            assertTrue(ConstraintEnum.getDescriptor().getValues()
                     .contains(c.getValueDescriptor()));
         }
-        for (JobStatusDumpProto.Constraint c : jsd.getSatisfiedConstraintsList()) {
-            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+        for (ConstraintEnum c : jsd.getSatisfiedConstraintsList()) {
+            assertTrue(ConstraintEnum.getDescriptor().getValues()
                     .contains(c.getValueDescriptor()));
         }
-        for (JobStatusDumpProto.Constraint c : jsd.getUnsatisfiedConstraintsList()) {
-            assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+        for (ConstraintEnum c : jsd.getUnsatisfiedConstraintsList()) {
+            assertTrue(ConstraintEnum.getDescriptor().getValues()
                     .contains(c.getValueDescriptor()));
         }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
index a9ae705..9c9d11e 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
@@ -105,7 +105,7 @@
 
         PackageServiceDumpProto.SharedUserProto systemUser = null;
         for (PackageServiceDumpProto.SharedUserProto user : dump.getSharedUsersList()) {
-            if (user.getUserId() == 1000) {
+            if (user.getUid() == 1000) {
                 systemUser = user;
                 break;
             }
diff --git a/hostsidetests/incident/src/com/android/server/cts/UsbIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/UsbIncidentTest.java
index f5ea527..71e0dd2 100644
--- a/hostsidetests/incident/src/com/android/server/cts/UsbIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/UsbIncidentTest.java
@@ -61,9 +61,11 @@
     }
 
     private static void verifyUsbAccessoryProto(UsbAccessoryProto uap, final int filterLevel) throws Exception {
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(uap.getUri().isEmpty());
+        if (filterLevel < PRIVACY_LOCAL) {
             assertTrue(uap.getSerial().isEmpty());
+            if (filterLevel == PRIVACY_AUTO) {
+                assertTrue(uap.getUri().isEmpty());
+            }
         }
     }
 
@@ -84,7 +86,7 @@
     }
 
     private static void verifyUsbDeviceProto(UsbDeviceProto udp, final int filterLevel) throws Exception {
-        if (filterLevel == PRIVACY_AUTO) {
+        if (filterLevel < PRIVACY_LOCAL) {
             assertTrue(udp.getSerialNumber().isEmpty());
         }
     }
diff --git a/hostsidetests/incident/src/com/android/server/cts/WindowManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/WindowManagerIncidentTest.java
index ffce223..907dc8d 100644
--- a/hostsidetests/incident/src/com/android/server/cts/WindowManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/WindowManagerIncidentTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.cts;
 
+import android.view.TransitionTypeEnum;
 import com.android.server.wm.AppTransitionProto;
 import com.android.server.wm.IdentifierProto;
 import com.android.server.wm.RootWindowContainerProto;
@@ -31,15 +32,11 @@
         verifyRootWindowContainerProto(dump.getRootWindowContainer(), filterLevel);
         verifyIdentifierProto(dump.getFocusedWindow(), filterLevel);
         verifyIdentifierProto(dump.getInputMethodWindow(), filterLevel);
-        verifyAppTransitionProto(dump.getAppTransition(), filterLevel);
     }
 
     private static void verifyWindowManagerPolicyProto(WindowManagerPolicyProto wmp, final int filterLevel) throws Exception {
         assertTrue(WindowManagerPolicyProto.UserRotationMode.getDescriptor().getValues()
                 .contains(wmp.getRotationMode().getValueDescriptor()));
-        verifyIdentifierProto(wmp.getFocusedWindow(), filterLevel);
-        verifyIdentifierProto(wmp.getTopFullscreenOpaqueWindow(), filterLevel);
-        verifyIdentifierProto(wmp.getTopFullscreenOpaqueOrDimmingWindow(), filterLevel);
     }
 
     private static void verifyIdentifierProto(IdentifierProto ip, final int filterLevel) throws Exception {
@@ -53,11 +50,4 @@
             verifyIdentifierProto(ip, filterLevel);
         }
     }
-
-    private static void verifyAppTransitionProto(AppTransitionProto atp, final int filterLevel) throws Exception {
-        assertTrue(AppTransitionProto.AppState.getDescriptor().getValues()
-                .contains(atp.getAppTransitionState().getValueDescriptor()));
-        assertTrue(AppTransitionProto.TransitionType.getDescriptor().getValues()
-                .contains(atp.getLastUsedAppTransition().getValueDescriptor()));
-    }
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/BusyWaitUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/BusyWaitUtils.java
index 399b16d..18fa0b8 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/BusyWaitUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/BusyWaitUtils.java
@@ -27,8 +27,18 @@
 
     private static final long POLLING_INTERVAL = TimeUnit.MILLISECONDS.toMillis(50);
 
+    /**
+     * Callback interface for {@link #pollingCheck(PollingCondition, long, String)} and
+     * {@link #waitFor(PollingCondition, long)}.
+     */
     @FunctionalInterface
     public interface PollingCondition {
+        /**
+         * Called back for polling check.
+         *
+         * @return {@code true} when the polling condition is met.
+         * @throws Exception when whatever unexpected problem happened.
+         */
         boolean check() throws Exception;
     }
 
@@ -43,8 +53,8 @@
      *                this message.
      * @throws Exception
      */
-    public static void pollingCheck(final PollingCondition condition, final long timeout,
-            final String message) throws Exception {
+    public static void pollingCheck(PollingCondition condition, long timeout, String message)
+            throws Exception {
         if (waitFor(condition, timeout)) {
             return;
         }
@@ -58,7 +68,7 @@
      * @return true when {@code condition} returns {@code true}, false when timed out.
      * @throws Exception
      */
-    static boolean waitFor(final PollingCondition condition, final long timeout) throws Exception {
+    static boolean waitFor(PollingCondition condition, long timeout) throws Exception {
         for (long remaining = timeout; remaining > 0; remaining -= POLLING_INTERVAL) {
             if (condition.check()) {
                 return true;
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java
index 507e84a..85b4b1c 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ComponentNameUtils.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common;
@@ -31,7 +31,7 @@
      * @return a component of {@code packageName/className} that can be used to specify component,
      *         for example, for {@code android.content.Intent}.
      */
-    static String buildComponentName(final String packageName, final String className) {
+    static String buildComponentName(String packageName, String className) {
         return packageName + "/" + (className.startsWith(packageName)
                 ? className.substring(packageName.length()) : className);
     }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
index 06a68f7..c751aa5 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common;
@@ -71,7 +71,7 @@
      */
     public enum DeviceEventType {
         /**
-         * {@link android.inputmethodservice.InputMethodService#onCreate() onCreate()} callback.
+         * {@link android.inputmethodservice.InputMethodService#onCreate()} callback.
          */
         ON_CREATE,
 
@@ -81,13 +81,15 @@
         ON_BIND_INPUT,
 
         /**
-         * {@link android.inputmethodservice.InputMethodService#onStartInput(android.view.inputmethod.EditorInfo,boolean) onStartInput(EditorInfo,boolean}
+         * {@link android.inputmethodservice.InputMethodService#onStartInput(
+         * android.view.inputmethod.EditorInfo, boolean)}
          * callback.
          */
         ON_START_INPUT,
 
         /**
-         * {@link android.inputmethodservice.InputMethodService#onStartInputView(android.view.inputmethod.EditorInfo, boolean) onStartInputView(EditorInfo,boolean}
+         * {@link android.inputmethodservice.InputMethodService#onStartInputView(
+         * android.view.inputmethod.EditorInfo, boolean)}
          */
         ON_START_INPUT_VIEW,
 
@@ -97,19 +99,19 @@
         ON_UNBIND_INPUT,
 
         /**
-         * {@link android.inputmethodservice.InputMethodService#onFinishInputView(boolean) onFinishInputView(boolean)}
+         * {@link android.inputmethodservice.InputMethodService#onFinishInputView(boolean)}
          * callback.
          */
         ON_FINISH_INPUT_VIEW,
 
         /**
-         * {@link android.inputmethodservice.InputMethodService#onFinishInput() onFinishInput()}
+         * {@link android.inputmethodservice.InputMethodService#onFinishInput()}
          * callback.
          */
         ON_FINISH_INPUT,
 
         /**
-         * {@link android.inputmethodservice.InputMethodService#onDestroy() onDestroy()} callback.
+         * {@link android.inputmethodservice.InputMethodService#onDestroy()} callback.
          */
         ON_DESTROY,
 
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java
index 9f6d4d1..b30ac84 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EditTextAppConstants.java
@@ -15,6 +15,9 @@
  */
 package android.inputmethodservice.cts.common;
 
+/**
+ * Constant table for test EditText app.
+ */
 public class EditTextAppConstants {
     // This is constants holding class, can't instantiate.
     private EditTextAppConstants() {}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java
index 3dc6ff1..89f56dd4 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime1Constants.java
@@ -11,19 +11,34 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common;
 
+/**
+ * Constant table for test IME 1.
+ */
 public final class Ime1Constants {
 
     // This is constants holding class, can't instantiate.
     private Ime1Constants() {}
 
+    /**
+     * Package name of test IME 1.
+     */
     public static final String PACKAGE = "android.inputmethodservice.cts.ime1";
+    /**
+     * Class name of test IME 1.
+     */
     public static final String CLASS =   "android.inputmethodservice.cts.ime1.CtsInputMethod1";
+    /**
+     * APK name that contains test IME 1.
+     */
     public static final String APK = "CtsInputMethod1.apk";
 
+    /**
+     * IME ID of test IME 1.
+     */
     public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java
index eeefae9..ff01635 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/Ime2Constants.java
@@ -11,19 +11,34 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common;
 
+/**
+ * Constant table for test IME 2.
+ */
 public final class Ime2Constants {
 
     // This is constants holding class, can't instantiate.
     private Ime2Constants() {}
 
+    /**
+     * Package name of test IME 2.
+     */
     public static final String PACKAGE = "android.inputmethodservice.cts.ime2";
+    /**
+     * Class name of test IME 2.
+     */
     public static final String CLASS =   "android.inputmethodservice.cts.ime2.CtsInputMethod2";
+    /**
+     * APK name that contains test IME 2.
+     */
     public static final String APK = "CtsInputMethod2.apk";
 
+    /**
+     * IME ID of test IME 2.
+     */
     public static final String IME_ID = ComponentNameUtils.buildComponentName(PACKAGE, CLASS);
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
index e17a7e2..15d8605 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/ImeCommandConstants.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common;
@@ -35,7 +35,8 @@
     public static final String EXTRA_ARG_INT1 = "arg_int1";
 
     /**
-     * This command has the mock IME call {@link android.view.inputmethod.InputConnection#commitText(CharSequence,int) InputConnection#commitText(CharSequence text, int newCursorPosition)}.
+     * This command has the mock IME call {@link
+     * android.view.inputmethod.InputConnection#commitText(CharSequence,int).
      * <ul>
      * <li>argument {@code text} needs to be specified by {@link #EXTRA_ARG_CHARSEQUENCE1}.</li>
      * <li>argument {@code newCursorPosition} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
@@ -44,7 +45,8 @@
     public static final String COMMAND_COMMIT_TEXT = "commitText";
 
     /**
-     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchInputMethod(String)} InputMethodService#switchInputMethod(String imeId)}.
+     * This command has the mock IME call {@link
+     * android.inputmethodservice.InputMethodService#switchInputMethod(String)}.
      * <ul>
      * <li>argument {@code imeId} needs to be specified by {@link #EXTRA_ARG_STRING1}.</li>
      * </ul>
@@ -52,25 +54,31 @@
     public static final String COMMAND_SWITCH_INPUT_METHOD = "switchInputMethod";
 
     /**
-     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchInputMethod(String, InputMethodSubtype)} InputMethodService#switchInputMethod(String imeId, InputMethodSubtype subtype)}.
+     * This command has the mock IME call {@link
+     * android.inputmethodservice.InputMethodService#switchInputMethod(String,
+     * android.view.inputmethod.InputMethodSubtype)}.
      * <ul>
      * <li>argument {@code imeId} needs to be specified by {@link #EXTRA_ARG_STRING1}.</li>
      * </ul>
      */
-    public static final String COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE = "switchInputMethodWithSubtype";
+    public static final String COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE =
+            "switchInputMethodWithSubtype";
 
     /**
-     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchToNextInputMethod(boolean)} InputMethodService#switchToNextInputMethod(boolean onlyCurrentIme)}.
+     * This command has the mock IME call {@link
+     * android.inputmethodservice.InputMethodService#switchToNextInputMethod(boolean)}.
      */
     public static final String COMMAND_SWITCH_TO_NEXT_INPUT = "switchToNextInput";
 
     /**
-     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#switchToPreviousInputMethod()} InputMethodService#switchToPreviousInputMethod()}.
+     * This command has the mock IME call {@link
+     * android.inputmethodservice.InputMethodService#switchToPreviousInputMethod()}.
      */
     public static final String COMMAND_SWITCH_TO_PREVIOUS_INPUT = "switchToPreviousInputMethod";
 
     /**
-     * This command has the mock IME call {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)} InputMethodService#requestHideSelf(int flags)}.
+     * This command has the mock IME call {@link
+     * android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
      * <ul>
      * <li>argument {@code flags} needs to be specified by {@link #EXTRA_ARG_INT1}.</li>
      * </ul>
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
index 9c448e2..b5aca9e 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
@@ -31,20 +31,56 @@
     /** APK file name. */
     public static final String APK = "CtsInputMethodServiceDeviceTests.apk";
 
-    /** Device test activity name. */
-    public static final String TEST_ACTIVITY_CLASS =
-            "android.inputmethodservice.cts.devicetest.InputMethodServiceTestActivity";
+    /**
+     * Device test class: InputMethodServiceDeviceTest.
+     */
+    private static final String SERVICE_TEST =
+            "android.inputmethodservice.cts.devicetest.InputMethodServiceDeviceTest";
+
+    public static final TestInfo TEST_CREATE_IME1 =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testCreateIme1");
+    public static final TestInfo TEST_SWITCH_IME1_TO_IME2 =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testSwitchIme1ToIme2");
+    public static final TestInfo TEST_SWITCH_INPUTMETHOD =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testSwitchInputMethod");
+    public static final TestInfo TEST_SWITCH_NEXT_INPUT =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testSwitchToNextInputMethod");
+    public static final TestInfo TEST_SWITCH_PREVIOUS_INPUT =
+            new TestInfo(PACKAGE, SERVICE_TEST, "switchToPreviousInputMethod");
+    public static final TestInfo TEST_INPUT_UNBINDS_ON_IME_STOPPED =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testInputUnbindsOnImeStopped");
+    public static final TestInfo TEST_INPUT_UNBINDS_ON_APP_STOPPED =
+            new TestInfo(PACKAGE, SERVICE_TEST, "testInputUnbindsOnAppStopped");
 
     /**
-     * Device test class name and methods name.
+     * Device test class: ShellCommandDeviceTest.
      */
-    public static final String TEST_CLASS =
-           "android.inputmethodservice.cts.devicetest.InputMethodServiceDeviceTest";
-    public static final String TEST_CREATE_IME1 = "testCreateIme1";
-    public static final String TEST_SWITCH_IME1_TO_IME2 = "testSwitchIme1ToIme2";
-    public static final String TEST_SWITCH_INPUTMETHOD = "testSwitchInputMethod";
-    public static final String TEST_SWITCH_NEXT_INPUT = "testSwitchToNextInputMethod";
-    public static final String TEST_SWITCH_PREVIOUS_INPUT = "switchToPreviousInputMethod";
-    public static final String TEST_INPUT_UNBINDS_ON_IME_STOPPED = "testInputUnbindsOnImeStopped";
-    public static final String TEST_INPUT_UNBINDS_ON_APP_STOPPED = "testInputUnbindsOnAppStopped";
+    private static final String SHELL_TEST =
+            "android.inputmethodservice.cts.devicetest.ShellCommandDeviceTest";
+
+    public static final TestInfo TEST_SHELL_COMMAND =
+            new TestInfo(PACKAGE, SHELL_TEST, "testShellCommand");
+    public static final TestInfo TEST_SHELL_COMMAND_IME =
+            new TestInfo(PACKAGE, SHELL_TEST, "testShellCommandIme");
+    public static final TestInfo TEST_SHELL_COMMAND_IME_LIST =
+            new TestInfo(PACKAGE, SHELL_TEST, "testShellCommandImeList");
+    public static final TestInfo TEST_SHELL_COMMAND_DUMP =
+            new TestInfo(PACKAGE, SHELL_TEST, "testShellCommandDump");
+    public static final TestInfo TEST_SHELL_COMMAND_HELP =
+            new TestInfo(PACKAGE, SHELL_TEST, "testShellCommandHelp");
+
+    /**
+     * Device test class: ShellCommandDeviceTest.
+     */
+    private static final String MANAGER_TEST =
+            "android.inputmethodservice.cts.devicetest.InputMethodManagerDeviceTest";
+
+    public static final TestInfo TEST_IME1_IN_INPUT_METHOD_LIST =
+            new TestInfo(PACKAGE, MANAGER_TEST, "testIme1InInputMethodList");
+    public static final TestInfo TEST_IME1_NOT_IN_INPUT_METHOD_LIST =
+            new TestInfo(PACKAGE, MANAGER_TEST, "testIme1NotInInputMethodList");
+    public static final TestInfo TEST_IME1_IMPLICITLY_ENABLED_SUBTYPE_EXISTS =
+            new TestInfo(PACKAGE, MANAGER_TEST, "testIme1ImplicitlyEnabledSubtypeExists");
+    public static final TestInfo TEST_IME1_IMPLICITLY_ENABLED_SUBTYPE_NOT_EXIST =
+            new TestInfo(PACKAGE, MANAGER_TEST, "testIme1ImplicitlyEnabledSubtypeNotExist");
 }
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
index 9bdb7ef..3c1cc20 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.common.test;
@@ -37,7 +37,7 @@
     }
 
     /** Command to set current IME to {@code imeId} synchronously */
-    public static String setCurrentImeSync(final String imeId) {
+    public static String setCurrentImeSync(String imeId) {
         return "ime set " + imeId;
     }
 
@@ -53,12 +53,12 @@
     }
 
     /** Command to enable IME of {@code imeId}. */
-    public static String enableIme(final String imeId) {
+    public static String enableIme(String imeId) {
         return "ime enable " + imeId;
     }
 
     /** Command to disable IME of {@code imeId}. */
-    public static String disableIme(final String imeId) {
+    public static String disableIme(String imeId) {
         return "ime disable " + imeId;
     }
 
@@ -68,7 +68,7 @@
     }
 
     /** Command to delete all records of IME event provider. */
-    public static String deleteContent(final String contentUri) {
+    public static String deleteContent(String contentUri) {
         return "content delete --uri " + contentUri;
     }
 
@@ -77,6 +77,52 @@
     }
 
     /**
+     * Command to uninstall {@code packageName} only for {@code userId}.
+     *
+     * @param packageName package name of the package to be uninstalled.
+     * @param userId user ID to specify the user.
+     */
+    public static String uninstallPackage(String packageName, int userId) {
+        return "pm uninstall --user " + userId + " " + packageName;
+    }
+
+    /**
+     * Command to get the last user ID that is specified to
+     * InputMethodManagerService.Lifecycle#onSwitchUser().
+     *
+     * @return the command to be passed to shell command.
+     */
+    public static String getLastSwitchUserId() {
+        return "cmd input_method get-last-switch-user-id";
+    }
+
+    /**
+     * Command to create a new profile user.
+     *
+     * @param parentUserId parent user to whom the new profile user should belong
+     * @param userName name of the new profile user.
+     * @return the command to be passed to shell command.
+     */
+    public static String createManagedProfileUser(int parentUserId, String userName) {
+        return "pm create-user --profileOf " + parentUserId + " --managed " + userName;
+    }
+
+    /** Command to turn on the display (if it's sleeping). */
+    public static String wakeUp() {
+        return "input keyevent KEYCODE_WAKEUP";
+    }
+
+    /** Command to dismiss Keyguard (if it's shown) */
+    public static String dismissKeyguard() {
+        return "wm dismiss-keyguard";
+    }
+
+    /** Command to close system dialogs (if shown) */
+    public static String closeSystemDialog() {
+        return "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS";
+    }
+
+    /**
      * Command to send broadcast {@code Intent}.
      *
      * @param action action of intent.
@@ -84,8 +130,7 @@
      * @param extras extra of intent, must be specified as triplet of option flag, key, and value.
      * @return shell command to send broadcast intent.
      */
-    public static String broadcastIntent(final String action, final String targetComponent,
-            final String... extras) {
+    public static String broadcastIntent(String action, String targetComponent, String... extras) {
         if (extras.length % 3 != 0) {
             throw new IllegalArgumentException(
                     "extras must be triplets: " + Arrays.toString(extras));
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/TestInfo.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/TestInfo.java
index 7805d6b..acdfbce 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/TestInfo.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/TestInfo.java
@@ -27,7 +27,7 @@
     public final String testClass;
     public final String testMethod;
 
-    public TestInfo(final String testPackage, final String testClass, final String testMethod) {
+    public TestInfo(String testPackage, String testClass, String testMethod) {
         this.testPackage = testPackage;
         this.testClass = testClass;
         this.testMethod = testMethod;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
index 1727ad7..a85b4d2 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.mk
@@ -41,5 +41,6 @@
 LOCAL_PACKAGE_NAME := CtsInputMethodServiceDeviceTests
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 19
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
index e585bcb..0293869 100755
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
@@ -16,13 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.devicetest">
+    package="android.inputmethodservice.cts.devicetest" android:targetSandboxVersion="2">
 
     <!--
       TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
       latest OS behaviors.
     -->
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
 
     <application
         android:label="CtsInputMethodServiceDeviceTests"
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/DirectShellCommand.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/DirectShellCommand.java
new file mode 100644
index 0000000..0643276
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/DirectShellCommand.java
@@ -0,0 +1,197 @@
+/*
+ * 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.inputmethodservice.cts.devicetest;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.StringJoiner;
+
+/**
+ * A utility class to invoke a hidden API {@code IBinder#shellCommand(FileDescriptor in,
+ * FileDescriptor out, FileDescriptor err, String[] args, ShellCallback shellCallback,
+ * ResultReceiver resultReceiver)} via reflection for any the system server.
+ */
+class DirectShellCommand {
+    /**
+     * A {@link java.util.concurrent.Future} style result object.
+     */
+    @FunctionalInterface
+    public interface FutureResult {
+        /**
+         * @param mills timeout in milliseconds.
+         * @return Non-{@code null} object is the {@link Result} becomes available within timeout.
+         *         Otherwise {@code null}.
+         */
+        Result get(long mills);
+    }
+
+    /**
+     * Represents general information about invocation result.
+     */
+    public static final class Result {
+        private final int mCode;
+        private final Bundle mData;
+        private final String mString;
+        private final Exception mException;
+
+        private Result(int code, Bundle data, String string, Exception exception) {
+            mCode = code;
+            mData = data;
+            mString = string;
+            mException = exception;
+        }
+
+        /**
+         * @return {@code 0} when the command is completed successfully.
+         */
+        public int getCode() {
+            return mCode;
+        }
+
+        /**
+         * @return {@link Bundle} returned from the system server as a response.
+         */
+        public Bundle getData() {
+            return mData;
+        }
+
+        /**
+         * @return Console output from the system server.
+         */
+        public String getString() {
+            return mString;
+        }
+
+        /**
+         * @return Any {@link Exception} thrown during the invocation.
+         */
+        public Exception getException() {
+            return mException;
+        }
+    }
+
+    private static final class MyResultReceiver extends ResultReceiver {
+        private final ParcelFileDescriptor mInputFileDescriptor;
+        private final Object mLock = new Object();
+
+        private Result mResult;
+
+        private static Looper createBackgroundLooper() {
+            final HandlerThread handlerThread = new HandlerThread("ShellCommandResultReceiver");
+            handlerThread.start();
+            return handlerThread.getLooper();
+        }
+
+        MyResultReceiver(ParcelFileDescriptor inputFileDescriptor) {
+            super(new Handler(createBackgroundLooper()));
+            mInputFileDescriptor = inputFileDescriptor;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            synchronized (mLock) {
+                final StringJoiner joiner = new StringJoiner("\n");
+                Exception resultException = null;
+                // Note that system server is expected to be using Charset.defaultCharset() hence
+                // we do not set the encoding here.
+                try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                        new ParcelFileDescriptor.AutoCloseInputStream(mInputFileDescriptor)))) {
+                    while (true) {
+                        final String line = reader.readLine();
+                        if (line == null) {
+                            break;
+                        }
+                        joiner.add(line);
+                    }
+                } catch (IOException e) {
+                    resultException = e;
+                }
+
+                mResult = new Result(resultCode, resultData, joiner.toString(), resultException);
+                mLock.notifyAll();
+                Looper.myLooper().quitSafely();
+            }
+        }
+
+        Result getResult(long millis) {
+            synchronized (mLock) {
+                if (mResult == null) {
+                    try {
+                        mLock.wait(millis);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                return mResult;
+            }
+        }
+    }
+
+    private static ParcelFileDescriptor createEmptyInput() throws IOException {
+        final ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createReliablePipe();
+        pipeFds[1].close();
+        return pipeFds[0];
+    }
+
+    private static FutureResult run(String serviceName, String[] args) throws Exception {
+        final Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
+        final Method getService = serviceManagerClass.getMethod("getService", String.class);
+        final IBinder service = (IBinder) getService.invoke(null, serviceName);
+
+        final Class<?> shellCallbackClass = Class.forName("android.os.ShellCallback");
+        final Method shellCommand = IBinder.class.getMethod("shellCommand",
+                FileDescriptor.class, FileDescriptor.class, FileDescriptor.class,
+                String[].class, shellCallbackClass, ResultReceiver.class);
+        shellCommand.setAccessible(true);
+        final ParcelFileDescriptor[] pipeFds = ParcelFileDescriptor.createReliablePipe();
+        final MyResultReceiver resultReceiver = new MyResultReceiver(pipeFds[0]);
+        try (ParcelFileDescriptor emptyInput = createEmptyInput();
+             ParcelFileDescriptor out = pipeFds[1];
+             ParcelFileDescriptor err = out.dup()) {
+            shellCommand.invoke(service, emptyInput.getFileDescriptor(),
+                    out.getFileDescriptor(), err.getFileDescriptor(), args, null, resultReceiver);
+        }
+        return resultReceiver::getResult;
+    }
+
+    /**
+     * Synchronously invoke {@code IBinder#shellCommand()} then return the result.
+     *
+     * @param serviceName internal system service name, e.g.
+     *                    {@link android.content.Context#INPUT_METHOD_SERVICE}.
+     * @param args commands to be passedto the system service.
+     * @param millis timeout in milliseconds.
+     * @return Non-{@code null} object if the command is not timed out.  Otherwise {@code null}.
+     */
+    static Result runSync(String serviceName, String[] args, long millis) {
+        try {
+            return run(serviceName, args).get(millis);
+        } catch (Exception e) {
+            return new Result(-1, null, null, e);
+        }
+    }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodManagerDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodManagerDeviceTest.java
new file mode 100644
index 0000000..8517099
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodManagerDeviceTest.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 android.inputmethodservice.cts.devicetest;
+
+import static android.inputmethodservice.cts.common.BusyWaitUtils.pollingCheck;
+
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.inputmethodservice.cts.common.Ime1Constants;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test public APIs defined in InputMethodManager.
+ */
+@RunWith(AndroidJUnit4.class)
+public class InputMethodManagerDeviceTest {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+    /**
+     * Make sure {@link InputMethodManager#getInputMethodList()} contains
+     * {@link Ime1Constants#IME_ID}.
+     */
+    @Test
+    public void testIme1InInputMethodList() throws Throwable {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+        pollingCheck(() -> imm.getInputMethodList().stream().anyMatch(
+                imi -> TextUtils.equals(imi.getId(), Ime1Constants.IME_ID)),
+                TIMEOUT, "Ime1 must exist.");
+    }
+
+    /**
+     * Make sure {@link InputMethodManager#getInputMethodList()} does not contain
+     * {@link Ime1Constants#IME_ID}.
+     */
+    @Test
+    public void testIme1NotInInputMethodList() throws Throwable {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+        Thread.sleep(EXPECTED_TIMEOUT);
+        assertFalse(imm.getInputMethodList().stream().anyMatch(
+                imi -> TextUtils.equals(imi.getId(), Ime1Constants.IME_ID)));
+    }
+
+    /**
+     * Make sure
+     * {@link InputMethodManager#getEnabledInputMethodSubtypeList(InputMethodInfo, boolean)} for
+     * {@link Ime1Constants#IME_ID} returns the implicitly enabled subtype.
+     */
+    @Test
+    public void testIme1ImplicitlyEnabledSubtypeExists() throws Throwable {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+        pollingCheck(() -> imm.getInputMethodList().stream()
+                        .filter(imi -> TextUtils.equals(imi.getId(), Ime1Constants.IME_ID))
+                        .flatMap(imi -> imm.getEnabledInputMethodSubtypeList(imi, true).stream())
+                        .anyMatch(InputMethodSubtype::overridesImplicitlyEnabledSubtype),
+                TIMEOUT, "Implicitly enabled Subtype must exist for IME1.");
+    }
+
+    /**
+     * Make sure
+     * {@link InputMethodManager#getEnabledInputMethodSubtypeList(InputMethodInfo, boolean)} for
+     * {@link Ime1Constants#IME_ID} does not return the implicitly enabled subtype.
+     */
+    @Test
+    public void testIme1ImplicitlyEnabledSubtypeNotExist() throws Throwable {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
+        Thread.sleep(EXPECTED_TIMEOUT);
+        assertFalse(imm.getInputMethodList().stream()
+                .filter(imi -> TextUtils.equals(imi.getId(), Ime1Constants.IME_ID))
+                .flatMap(imi -> imm.getEnabledInputMethodSubtypeList(imi, true).stream())
+                .anyMatch(InputMethodSubtype::overridesImplicitlyEnabledSubtype));
+    }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
index 348ea99..f065c58 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.devicetest;
@@ -20,22 +20,16 @@
 import static android.inputmethodservice.cts.DeviceEvent.isNewerThan;
 import static android.inputmethodservice.cts.DeviceEvent.isType;
 import static android.inputmethodservice.cts.common.BusyWaitUtils.pollingCheck;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
-        .ON_BIND_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_BIND_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
-
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
-        .ON_UNBIND_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_UNBIND_INPUT;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.ACTION_IME_COMMAND;
-import static android.inputmethodservice.cts.common.ImeCommandConstants
-        .COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD;
-import static android.inputmethodservice.cts.common.ImeCommandConstants
-        .COMMAND_SWITCH_TO_PREVIOUS_INPUT;
-import static android.inputmethodservice.cts.common.ImeCommandConstants
-        .COMMAND_SWITCH_TO_NEXT_INPUT;
+import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD_WITH_SUBTYPE;
+import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_TO_NEXT_INPUT;
+import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_TO_PREVIOUS_INPUT;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_COMMAND;
 import static android.inputmethodservice.cts.devicetest.MoreCollectors.startingFrom;
@@ -45,11 +39,11 @@
 import android.inputmethodservice.cts.common.EditTextAppConstants;
 import android.inputmethodservice.cts.common.Ime1Constants;
 import android.inputmethodservice.cts.common.Ime2Constants;
-import android.inputmethodservice.cts.common.test.DeviceTestConstants;
 import android.inputmethodservice.cts.common.test.ShellCommandUtils;
 import android.inputmethodservice.cts.devicetest.SequenceMatcher.MatchResult;
 import android.os.SystemClock;
 import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.InputMethodSubtype;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,6 +54,9 @@
 import java.util.function.Predicate;
 import java.util.stream.Collector;
 
+/**
+ * Test general lifecycle events around InputMethodService.
+ */
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceDeviceTest {
 
@@ -68,7 +65,7 @@
     /** Test to check CtsInputMethod1 receives onCreate and onStartInput. */
     @Test
     public void testCreateIme1() throws Throwable {
-        final TestHelper helper = new TestHelper(getClass(), DeviceTestConstants.TEST_CREATE_IME1);
+        final TestHelper helper = new TestHelper();
 
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
@@ -87,8 +84,7 @@
     /** Test to check IME is switched from CtsInputMethod1 to CtsInputMethod2. */
     @Test
     public void testSwitchIme1ToIme2() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
+        final TestHelper helper = new TestHelper();
 
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
@@ -129,10 +125,13 @@
                         + " in sequence");
     }
 
+    /**
+     * Test {@link android.inputmethodservice.InputMethodService#switchInputMethod(String,
+     * InputMethodSubtype)}.
+     */
     @Test
     public void testSwitchInputMethod() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SWITCH_INPUTMETHOD);
+        final TestHelper helper = new TestHelper();
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
                 EditTextAppConstants.URI);
@@ -157,10 +156,12 @@
                 TIMEOUT, "CtsInputMethod1.onDestroy is called");
     }
 
+    /**
+     * Test {@link android.inputmethodservice.InputMethodService#switchToNextInputMethod(boolean)}.
+     */
     @Test
     public void testSwitchToNextInputMethod() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SWITCH_NEXT_INPUT);
+        final TestHelper helper = new TestHelper();
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
                 EditTextAppConstants.URI);
@@ -181,10 +182,12 @@
                 TIMEOUT, "CtsInputMethod1 shouldn't be current IME");
     }
 
+    /**
+     * Test {@link android.inputmethodservice.InputMethodService#switchToPreviousInputMethod()}.
+     */
     @Test
     public void switchToPreviousInputMethod() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT);
+        final TestHelper helper = new TestHelper();
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
                 EditTextAppConstants.URI);
@@ -204,10 +207,13 @@
                 TIMEOUT, initialIme + " is current IME");
     }
 
+    /**
+     * Test if uninstalling the currently selected IME then selecting another IME triggers standard
+     * startInput/bindInput sequence.
+     */
     @Test
     public void testInputUnbindsOnImeStopped() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED);
+        final TestHelper helper = new TestHelper();
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
                 EditTextAppConstants.URI);
@@ -237,10 +243,13 @@
                 TIMEOUT, "CtsInputMethod2.onBindInput is called");
     }
 
+    /**
+     * Test if uninstalling the currently running IME client triggers
+     * {@link android.inputmethodservice.InputMethodService#onUnbindInput()}.
+     */
     @Test
     public void testInputUnbindsOnAppStopped() throws Throwable {
-        final TestHelper helper = new TestHelper(
-                getClass(), DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED);
+        final TestHelper helper = new TestHelper();
         final long startActivityTime = SystemClock.uptimeMillis();
         helper.launchActivity(EditTextAppConstants.PACKAGE, EditTextAppConstants.CLASS,
                 EditTextAppConstants.URI);
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
index 59692a6..ec64333 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
@@ -40,12 +40,12 @@
      * @return {@link Collector} object that collects elements ending at an element that is
      *          accepted by {@code predicate}.
      */
-    static <E> Collector<E, ?, Stream<E>> endingAt(final Predicate<E> predicate) {
+    static <E> Collector<E, ?, Stream<E>> endingAt(Predicate<E> predicate) {
         final BiConsumer<Builder<E>, E> endingAtAccumulator = new BiConsumer<Builder<E>, E>() {
             private boolean mFound = false;
 
             @Override
-            public void accept(final Builder<E> builder, final E element) {
+            public void accept(Builder<E> builder, E element) {
                 if (mFound) {
                     return;
                 }
@@ -77,12 +77,12 @@
      * @return {@link Collector} object that collects elements starting from an element that is
      *          accepted by {@code predicate}.
      */
-    static <E> Collector<E, ?, Stream<E>> startingFrom(final Predicate<E> predicate) {
+    static <E> Collector<E, ?, Stream<E>> startingFrom(Predicate<E> predicate) {
         final BiConsumer<Builder<E>, E> startingFromAccumulator = new BiConsumer<Builder<E>, E>() {
             private boolean mFound = false;
 
             @Override
-            public void accept(final Builder<E> builder, final E element) {
+            public void accept(Builder<E> builder, E element) {
                 if (mFound) {
                     builder.accept(element);
                     return;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
index 996dad9..9e60d20 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
@@ -38,7 +38,7 @@
         private final boolean mMatched;
         private final List<E> mMatchedSequence;
 
-        MatchResult(final boolean matched, final List<E> matchedSequence) {
+        MatchResult(boolean matched, List<E> matchedSequence) {
             mMatched = matched;
             mMatchedSequence = matchedSequence;
         }
@@ -61,11 +61,11 @@
         private final Predicate<E>[] mPredicates;
         private final List<E> mSequence = new ArrayList<>();
 
-        SequenceAccumulator(final Predicate<E>... predicates) {
+        SequenceAccumulator(Predicate<E>... predicates) {
             mPredicates = predicates;
         }
 
-        void accumulate(final E element) {
+        void accumulate(E element) {
             if (mSequence.isEmpty()) {
                 // Search for the first element of sequence.
                 if (mPredicates[0].test(element)) {
@@ -124,7 +124,7 @@
      * @param <E> type of stream element
      * @return {@link MatchResult<E>} of matcher sequence.
      */
-    static <E> Collector<E, ?, MatchResult<E>> of(final E... elements) {
+    static <E> Collector<E, ?, MatchResult<E>> of(E... elements) {
         if (elements == null || elements.length == 0) {
             throw new IllegalArgumentException("At least one element.");
         }
@@ -141,7 +141,7 @@
      * @param <E> type of stream element.
      * @return {@link MatchResult<E>} of matched sequence.
      */
-    static <E> Collector<E, ?, MatchResult<E>> of(final Predicate<E>... predicates) {
+    static <E> Collector<E, ?, MatchResult<E>> of(Predicate<E>... predicates) {
         if (predicates == null || predicates.length == 0) {
             throw new IllegalArgumentException("At least one Predicate.");
         }
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/ShellCommandDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/ShellCommandDeviceTest.java
new file mode 100644
index 0000000..544a170
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/ShellCommandDeviceTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.inputmethodservice.cts.devicetest;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test general shell commands implemented in InputMethodManagerService.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ShellCommandDeviceTest {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    private static void assertShellCommandThrowsException(String[] args) {
+        final DirectShellCommand.Result result = DirectShellCommand.runSync(
+                Context.INPUT_METHOD_SERVICE, args, TIMEOUT);
+        assertNotNull(result);
+        assertNotEquals(0, result.getCode());
+        assertNotNull(result.getException());
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{}, null, receiver)}
+     * returns {@link SecurityException}.
+     */
+    @Test
+    public void testShellCommand() {
+        assertShellCommandThrowsException(new String[]{});
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime"}, null, receiver)}
+     * returns {@link SecurityException}.
+     */
+    @Test
+    public void testShellCommandIme() {
+        assertShellCommandThrowsException(new String[]{"ime"});
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException}.
+     */
+    @Test
+    public void testShellCommandImeList() {
+        assertShellCommandThrowsException(new String[]{"ime", "list"});
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"dump"}, null, receiver)}
+     * returns {@link SecurityException}.
+     */
+    @Test
+    public void testShellCommandDump() {
+        assertShellCommandThrowsException(new String[]{"dump"});
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"help"}, null, receiver)}
+     * returns {@link SecurityException}.
+     */
+    @Test
+    public void testShellCommandHelp() {
+        assertShellCommandThrowsException(new String[]{"help"});
+    }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
index dda48a0..f47c213 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.devicetest;
@@ -29,14 +29,16 @@
 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
 import android.inputmethodservice.cts.common.test.TestInfo;
 import android.net.Uri;
-import androidx.annotation.IdRes;
 import android.support.test.InstrumentationRegistry;
 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 org.junit.Test;
+
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -56,14 +58,36 @@
     private final UiDevice mUiDevice;
 
     /**
-     * Construct a helper object of specified test method.
+     * @return {@link Method} that has {@link Test} annotation in the call stack.
      *
-     * @param testClass a {@link Class} of test.
-     * @param testMethod a name of test method.
+     * <p>Note: This can be removed once we switch to JUnit5, where org.junit.jupiter.api.TestInfo
+     * is supported in the framework level.</p>
      */
-    TestHelper(final Class<?> testClass, final String testMethod) {
+    private static Method getTestMethod() {
+        for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
+            try {
+                final Class<?> clazz = Class.forName(stackTraceElement.getClassName());
+                final Method method = clazz.getDeclaredMethod(stackTraceElement.getMethodName());
+                final Test annotation = method.getAnnotation(Test.class);
+                if (annotation != null) {
+                    return method;
+                }
+            } catch (ClassNotFoundException | NoSuchMethodException e) {
+            }
+        }
+        throw new IllegalStateException(
+                "Method that has @Test annotation is not found in the call stack.");
+    }
+
+    /**
+     * Construct a helper object of specified test method.
+     */
+    TestHelper() {
+        final Method testMethod = getTestMethod();
+        final Class<?> testClass = testMethod.getDeclaringClass();
         final Context testContext = InstrumentationRegistry.getContext();
-        mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(), testMethod);
+        mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(),
+                testMethod.getName());
         mResolver = testContext.getContentResolver();
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -75,7 +99,7 @@
      * @return command's standard output without ending newline.
      * @throws IOException
      */
-    String shell(final String command) throws IOException {
+    String shell(String command) throws IOException {
         return mUiDevice.executeShellCommand(command).trim();
     }
 
@@ -85,7 +109,7 @@
      * @param className activity's class name.
      * @param uri uri to be handled.
      */
-    void launchActivity(final String packageName, final String className, final String uri) {
+    void launchActivity(String packageName, String className, String uri) {
         final Intent intent = new Intent()
                 .setAction(Intent.ACTION_VIEW)
                 .addCategory(Intent.CATEGORY_BROWSABLE)
@@ -112,7 +136,7 @@
      * @return {@link Stream<DeviceEvent>} of all device events.
      */
     Stream<DeviceEvent> queryAllEvents() {
-        try (final Cursor cursor = mResolver.query(
+        try (Cursor cursor = mResolver.query(
                 DEVICE_EVENTS_CONTENT_URI,
                 null /* projection */,
                 null /* selection */,
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk b/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk
index 30fdb08..a089809 100644
--- a/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.mk
@@ -36,5 +36,6 @@
 LOCAL_PACKAGE_NAME := EditTextApp
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 28
 
 include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java b/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java
index 86e9e0a..6513a67 100644
--- a/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/src/android/inputmethodservice/cts/edittextapp/MainActivity.java
@@ -11,22 +11,20 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 package android.inputmethodservice.cts.edittextapp;
 
 import android.app.Activity;
-import android.content.Context;
-import android.widget.EditText;
 import android.os.Bundle;
-import android.view.inputmethod.InputMethodManager;
-import android.view.View;
-import android.view.View.OnFocusChangeListener;
 
+/**
+ * A test {@link Activity} that only hosts {@link android.widget.EditText}.
+ */
 public class MainActivity extends Activity {
     @Override
-    protected void onCreate(final Bundle savedInstanceState) {
+    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_inputmethod_test);
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk b/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk
index 850b89f..3b98a87 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/Android.mk
@@ -37,5 +37,6 @@
 LOCAL_PACKAGE_NAME := CtsInputMethod1
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 19
 
 include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml b/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml
index 3cbef44..fa6e408 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/res/xml/ime1.xml
@@ -17,4 +17,19 @@
 
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
     android:supportsSwitchingToNextInputMethod="true">
+    <subtype
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="true"
+        android:overridesImplicitlyEnabledSubtype="true"
+    />
+    <subtype
+        android:imeSubtypeLocale="en_US"
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="true"
+    />
+    <subtype
+        android:imeSubtypeLocale="ru_RU"
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="false"
+    />
 </input-method>
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java b/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java
index 4176014..e3f2b95 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/src/android/inputmethodservice/cts/ime1/CtsInputMethod1.java
@@ -19,6 +19,9 @@
 import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
 import android.view.View;
 
+/**
+ * Implementation of test IME 1.
+ */
 public final class CtsInputMethod1 extends CtsBaseInputMethod {
 
     @Override
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk b/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk
index fcd146c..7b9ebeb 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/Android.mk
@@ -37,5 +37,6 @@
 LOCAL_PACKAGE_NAME := CtsInputMethod2
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 19
 
 include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java b/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java
index 74a197d..c41d1f1 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/src/android/inputmethodservice/cts/ime2/CtsInputMethod2.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.ime2;
@@ -19,6 +19,9 @@
 import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
 import android.view.View;
 
+/**
+ * Implementation of test IME 2.
+ */
 public final class CtsInputMethod2 extends CtsBaseInputMethod {
 
     @Override
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
index a25cdfd..f1470a5 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -11,15 +11,15 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts;
 
 import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
 
@@ -34,9 +34,10 @@
 import android.inputmethodservice.cts.db.Field;
 import android.inputmethodservice.cts.db.Table;
 import android.os.SystemClock;
-import androidx.annotation.NonNull;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -58,8 +59,8 @@
      * @return an intent that has event {@code sender}, {@code type}, time from
      *         {@link SystemClock#uptimeMillis()}, and target component of event receiver.
      */
-    public static Intent newDeviceEventIntent(@NonNull final String sender,
-            @NonNull final DeviceEventType type) {
+    public static Intent newDeviceEventIntent(@NonNull String sender,
+            @NonNull DeviceEventType type) {
         return new Intent()
                 .setAction(ACTION_DEVICE_EVENT)
                 .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
@@ -74,7 +75,7 @@
      * @return {@link DeviceEvent} object that has event sender, type, and time form an
      *         {@code intent}.
      */
-    public static DeviceEvent newEvent(final Intent intent) {
+    public static DeviceEvent newEvent(Intent intent) {
         final String sender = intent.getStringExtra(EXTRA_EVENT_SENDER);
         if (sender == null) {
             throw new IllegalArgumentException(
@@ -101,7 +102,7 @@
      * @param event a {@link DeviceEvent} object to be converted.
      * @return a converted {@link ContentValues} object.
      */
-    public static ContentValues buildContentValues(final DeviceEvent event) {
+    public static ContentValues buildContentValues(DeviceEvent event) {
         return TABLE.buildContentValues(event);
     }
 
@@ -110,7 +111,7 @@
      * @param cursor a {@link Cursor} object to be converted.
      * @return a converted {@link Stream<DeviceEvent>} object.
      */
-    public static Stream<DeviceEvent> buildStream(final Cursor cursor) {
+    public static Stream<DeviceEvent> buildStream(Cursor cursor) {
         return TABLE.buildStream(cursor);
     }
 
@@ -120,7 +121,7 @@
      * @param sender event sender.
      * @return {@link Predicate<DeviceEvent>} object.
      */
-    public static Predicate<DeviceEvent> isFrom(final String sender) {
+    public static Predicate<DeviceEvent> isFrom(String sender) {
         return e -> e.sender.equals(sender);
     }
 
@@ -130,7 +131,7 @@
      * @param type a event type defined in {@link DeviceEventType}.
      * @return {@link Predicate<DeviceEvent>} object.
      */
-    public static Predicate<DeviceEvent> isType(final DeviceEventType type) {
+    public static Predicate<DeviceEvent> isType(DeviceEventType type) {
         return e -> e.type == type;
     }
 
@@ -141,7 +142,7 @@
      * @param time a time to compare against.
      * @return {@link Predicate<DeviceEvent>} object.
      */
-    public static Predicate<DeviceEvent> isNewerThan(final long time) {
+    public static Predicate<DeviceEvent> isNewerThan(long time) {
         return e -> e.time >= time;
     }
 
@@ -162,7 +163,7 @@
      */
     public final long time;
 
-    private DeviceEvent(final String sender, final DeviceEventType type, final long time) {
+    private DeviceEvent(String sender, DeviceEventType type, long time) {
         this.sender = sender;
         this.type = type;
         this.time = time;
@@ -180,27 +181,27 @@
 
         private static final String LOG_TAG = DeviceEventTable.class.getSimpleName();
 
-        private final Field SENDER;
-        private final Field TYPE;
-        private final Field TIME;
+        private final Field mSender;
+        private final Field mType;
+        private final Field mTime;
 
-        private DeviceEventTable(final String name) {
+        private DeviceEventTable(String name) {
             super(name, new Entity.Builder<DeviceEvent>()
                     .addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
                     .build());
-            SENDER = getField(EventTableConstants.SENDER);
-            TYPE = getField(EventTableConstants.TYPE);
-            TIME = getField(EventTableConstants.TIME);
+            mSender = getField(EventTableConstants.SENDER);
+            mType = getField(EventTableConstants.TYPE);
+            mTime = getField(EventTableConstants.TIME);
         }
 
         @Override
-        public ContentValues buildContentValues(final DeviceEvent event) {
+        public ContentValues buildContentValues(DeviceEvent event) {
             final ContentValues values = new ContentValues();
-            SENDER.putString(values, event.sender);
-            TYPE.putString(values, event.type.name());
-            TIME.putLong(values, event.time);
+            mSender.putString(values, event.sender);
+            mType.putString(values, event.type.name());
+            mTime.putLong(values, event.time);
             return values;
         }
 
@@ -212,12 +213,12 @@
             final Stream.Builder<DeviceEvent> builder = Stream.builder();
             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                 final DeviceEvent event = new DeviceEvent(
-                        SENDER.getString(cursor),
-                        DeviceEventType.valueOf(TYPE.getString(cursor)),
-                        TIME.getLong(cursor));
+                        mSender.getString(cursor),
+                        DeviceEventType.valueOf(mType.getString(cursor)),
+                        mTime.getLong(cursor));
                 builder.accept(event);
                 if (DEBUG_STREAM) {
-                    Log.d(LOG_TAG, " event=" +event);
+                    Log.d(LOG_TAG, " event=" + event);
                 }
             }
             return builder.build();
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Database.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Database.java
index 0ce3ee7..d34b4aa 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Database.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Database.java
@@ -21,6 +21,7 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
+
 import androidx.annotation.NonNull;
 
 import java.util.List;
@@ -32,13 +33,20 @@
 
     private final SQLiteOpenHelper mHelper;
 
-    public Database(final Context context, final String name, final int version) {
+    /**
+     * Create {@link Database}.
+     *
+     * @param context to use for locating paths to the the database.
+     * @param name of the database file, or null for an in-memory database.
+     * @param version number of the database (starting at 1).
+     */
+    public Database(Context context, String name, int version) {
         mHelper = new SQLiteOpenHelper(context, name, null /* factory */, version) {
             @Override
-            public void onCreate(final SQLiteDatabase db) {
+            public void onCreate(SQLiteDatabase db) {
                 db.beginTransaction();
                 try {
-                    for (final Table table : getTables()) {
+                    for (Table table : getTables()) {
                         db.execSQL(table.createTableSql());
                     }
                     db.setTransactionSuccessful();
@@ -48,8 +56,7 @@
             }
 
             @Override
-            public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
-                    final int newVersion) {
+            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                 // nothing to do so far.
             }
         };
@@ -58,26 +65,80 @@
     @NonNull
     protected abstract List<Table> getTables();
 
-    public Cursor query(final String table, final String[] projection, final String selection,
-            final String[] selectionArgs, final String orderBy) {
+    /**
+     * A wrapper method for {@link SQLiteDatabase#query(String, String[], String, String[], String,
+     * String, String)}.
+     *
+     * @param table the table name to compile the query against.
+     * @param projection a list of which columns to return. Passing {@code null} will return all
+     *                   columns, which is discouraged to prevent reading data from storage that
+     *                   isn't going to be used.
+     * @param selection a filter declaring which rows to return, formatted as an SQL WHERE clause
+     *                  (excluding the WHERE itself). Passing {@code null} will return all rows for
+     *                  the given table.
+     * @param selectionArgs you may include {@code ?}s in selection, which will be replaced by the
+     *                      values from selectionArgs, in order that they appear in the selection.
+     *                      The values will be bound as Strings.
+     * @param orderBy how to order the rows, formatted as an SQL ORDER BY clause (excluding the
+     *                ORDER BY itself). Passing null will use the default sort order, which may be
+     *                unordered.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     *         {@link Cursor}s are not synchronized, see the documentation for more details.
+     */
+    public Cursor query(String table, String[] projection, String selection, String[] selectionArgs,
+            String orderBy) {
         return mHelper.getReadableDatabase()
                 .query(table, projection, selection, selectionArgs, null /* groupBy */,
                         null /* having */, orderBy);
     }
 
-    public long insert(final String table, final ContentValues values) {
+    /**
+     * A wrapper method for {@link SQLiteDatabase#insert(String, String, ContentValues)}.
+     *
+     * @param table the table to insert the row into.
+     * @param values this map contains the initial column values for the row. The keys should be the
+     *               column names and the values the column values.
+     * @return the row ID of the newly inserted row, or {@code -1} if an error occurred.
+     */
+    public long insert(String table, ContentValues values) {
         return mHelper.getWritableDatabase().insert(table, null /* nullColumnHack */, values);
     }
 
-    public int delete(final String table, final String selection, final String[] selectionArgs) {
+    /**
+     * A wrapper method for {@link SQLiteDatabase#delete(String, String, String[])}.
+     *
+     * @param table the table to delete from.
+     * @param selection the optional WHERE clause to apply when deleting.  Passing {@code null} will
+     *                  delete all rows.
+     * @param selectionArgs You may include {@code ?}s in the where clause, which will be replaced
+     *                      by the values from whereArgs. The values will be bound as Strings.
+     * @return the number of rows affected if a whereClause is passed in, {@code 0} otherwise. To
+     *         remove all rows and get a count pass {@code 1} as the {@code selection}.
+     */
+    public int delete(String table, String selection, String[] selectionArgs) {
         return mHelper.getWritableDatabase().delete(table, selection, selectionArgs);
     }
 
-    public int update(final String table, final ContentValues values, final String selection,
-            final String[] selectionArgs) {
+    /**
+     * A wrapper method for {@link SQLiteDatabase#update(String, ContentValues, String, String[])}.
+     *
+     * @param table the table to update in
+     * @param values a map from column names to new column values. {@code null} is a valid value
+     *               that will be translated to NULL.
+     * @param selection the optional WHERE clause to apply when updating. Passing {@code null} will
+     *                  update all rows.
+     * @param selectionArgs You may include {@code ?}s in the where clause, which will be replaced
+     *                      by the values from whereArgs. The values will be bound as Strings.
+     * @return the number of rows affected
+     */
+    public int update(String table, ContentValues values, String selection,
+            String[] selectionArgs) {
         return mHelper.getWritableDatabase().update(table, values, selection, selectionArgs);
     }
 
+    /**
+     * Close any open database object.
+     */
     public void close() {
         mHelper.close();
     }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Entity.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Entity.java
index c66b36a..8ccfccb 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Entity.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Entity.java
@@ -18,6 +18,7 @@
 
 import android.database.Cursor;
 import android.provider.BaseColumns;
+
 import androidx.annotation.NonNull;
 
 import java.util.ArrayList;
@@ -27,13 +28,14 @@
 
 /**
  * Abstraction of SQLite database row.
+ * @param <E> type of entities.
  */
 public final class Entity<E> {
 
     private final List<Field> mFields;
     private final Map<String, Field> mFieldMap;
 
-    private Entity(final Builder<E> builder) {
+    private Entity(Builder<E> builder) {
         mFields = builder.mFields;
         mFieldMap = builder.mFieldMap;
     }
@@ -42,51 +44,67 @@
      * Returns SQL statement to create this entity/row, such that
      * "(_id INTEGER PRIMARY KEY AUTOINCREMENT, column2_name column2_type, ...)".
      */
-    public String createEntitySql() {
+    String createEntitySql() {
         final StringBuilder sb = new StringBuilder("(");
-        for (final Field field : mFields) {
-            if (field.pos > 0) sb.append(", ");
-            sb.append(field.name).append(" ").append(field.sqLiteType);
-            if (field.name.equals(BaseColumns._ID)) {
+        for (Field field : mFields) {
+            if (field.mPos > 0) sb.append(", ");
+            sb.append(field.mName).append(" ").append(field.mSqLiteType);
+            if (field.mName.equals(BaseColumns._ID)) {
                 sb.append(" PRIMARY KEY AUTOINCREMENT");
             }
         }
         return sb.append(")").toString();
     }
 
-    public Field getField(final String fieldName) {
+    Field getField(String fieldName) {
         return mFieldMap.get(fieldName);
     }
 
     /**
      * {@link Entity} builder.
+     * @param <E> type of entities.
      */
     public static final class Builder<E> {
         private final List<Field> mFields = new ArrayList<>();
         private final Map<String, Field> mFieldMap = new HashMap<>();
         private int mPos = 0;
 
+        /**
+         * Constructor or {@link Builder}.
+         */
         public Builder() {
             addFieldInternal(BaseColumns._ID, Cursor.FIELD_TYPE_INTEGER);
         }
 
-        public Builder<E> addField(@NonNull final String name, final int fieldType) {
+        /**
+         * Add a new field with given name and type.
+         *
+         * @param name name of the field
+         * @param fieldType type enum of the field
+         * @return this builder, useful for chaining
+         */
+        public Builder<E> addField(@NonNull String name, int fieldType) {
             addFieldInternal(name, fieldType);
             return this;
         }
 
+        /**
+         * Build {@link Entity}.
+         *
+         * @return a new instance of {@link Entity} built from this builder.
+         */
         public Entity<E> build() {
             return new Entity<>(this);
         }
 
-        private void addFieldInternal(final String name, final int fieldType) {
+        private void addFieldInternal(String name, int fieldType) {
             if (mFieldMap.containsKey(name)) {
                 throw new IllegalArgumentException("Field " + name + " already exists at "
-                        + mFieldMap.get(name).pos);
+                        + mFieldMap.get(name).mPos);
             }
             final Field field = Field.newInstance(mPos++, name, fieldType);
             mFields.add(field);
-            mFieldMap.put(field.name, field);
+            mFieldMap.put(field.mName, field);
         }
     }
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Field.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Field.java
index 2c69060..b5f4ad5 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Field.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Field.java
@@ -25,15 +25,15 @@
 public abstract class Field {
 
     /** Field position in a row. */
-    final int pos;
+    final int mPos;
 
     /** Column name of this field. */
-    final String name;
+    final String mName;
 
     /** Field type of SQLite. */
-    final String sqLiteType;
+    final String mSqLiteType;
 
-    public static Field newInstance(final int pos, final String name, final int fieldType) {
+    static Field newInstance(int pos, String name, int fieldType) {
         switch (fieldType) {
             case Cursor.FIELD_TYPE_INTEGER:
                 return new IntegerField(pos, name);
@@ -44,34 +44,58 @@
         }
     }
 
-    private Field(final int pos, final String name, final int fieldType) {
-        this.pos = pos;
-        this.name = name;
-        this.sqLiteType = toSqLiteType(fieldType);
+    private Field(int pos, String name, int fieldType) {
+        this.mPos = pos;
+        this.mName = name;
+        this.mSqLiteType = toSqLiteType(fieldType);
     }
 
-    public long getLong(final Cursor cursor) {
+    /**
+     * Read data from {@link Cursor}.
+     *
+     * @param cursor {@link Cursor} to read.
+     * @return long data read from {@code cursor}.
+     */
+    public long getLong(Cursor cursor) {
         throw buildException(Cursor.FIELD_TYPE_INTEGER);
     }
 
-    public String getString(final Cursor cursor) {
+    /**
+     * Read data from {@link Cursor}.
+     *
+     * @param cursor {@link Cursor} to read.
+     * @return {@link String} data read from {@code cursor}.
+     */
+    public String getString(Cursor cursor) {
         throw buildException(Cursor.FIELD_TYPE_STRING);
     }
 
-    public void putLong(final ContentValues values, final long value) {
+    /**
+     * Put data to {@link ContentValues}.
+     *
+     * @param values {@link ContentValues} to be updated.
+     * @param value long data to put.
+     */
+    public void putLong(ContentValues values, long value) {
         throw buildException(Cursor.FIELD_TYPE_INTEGER);
     }
 
-    public void putString(final ContentValues values, final String value) {
+    /**
+     * Put data to {@link ContentValues}.
+     *
+     * @param values {@link ContentValues} to be updated.
+     * @param value {@link String} data to put.
+     */
+    public void putString(ContentValues values, String value) {
         throw buildException(Cursor.FIELD_TYPE_STRING);
     }
 
-    private UnsupportedOperationException buildException(final int expectedFieldType) {
-        return new UnsupportedOperationException("Illegal type: " + name + " is " + sqLiteType
+    private UnsupportedOperationException buildException(int expectedFieldType) {
+        return new UnsupportedOperationException("Illegal type: " + mName + " is " + mSqLiteType
                 + ", expected " + toSqLiteType(expectedFieldType));
     }
 
-    private static String toSqLiteType(final int fieldType) {
+    private static String toSqLiteType(int fieldType) {
         switch (fieldType) {
             case Cursor.FIELD_TYPE_NULL:
                 return "NULL";
@@ -93,18 +117,18 @@
      */
     private static final class IntegerField extends Field {
 
-        IntegerField(final int pos, final String name) {
+        IntegerField(int pos, String name) {
             super(pos, name, Cursor.FIELD_TYPE_INTEGER);
         }
 
         @Override
-        public long getLong(final Cursor cursor) {
-            return cursor.getLong(pos);
+        public long getLong(Cursor cursor) {
+            return cursor.getLong(mPos);
         }
 
         @Override
-        public void putLong(final ContentValues values, final long value) {
-            values.put(name, value);
+        public void putLong(ContentValues values, long value) {
+            values.put(mName, value);
         }
     }
 
@@ -113,18 +137,18 @@
      */
     private static final class StringField extends Field {
 
-        StringField(final int pos, final String name) {
+        StringField(int pos, String name) {
             super(pos, name, Cursor.FIELD_TYPE_STRING);
         }
 
         @Override
-        public String getString(final Cursor cursor) {
-            return cursor.getString(pos);
+        public String getString(Cursor cursor) {
+            return cursor.getString(mPos);
         }
 
         @Override
-        public void putString(final ContentValues values, final String value) {
-            values.put(name, value);
+        public void putString(ContentValues values, String value) {
+            values.put(mName, value);
         }
     }
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Table.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Table.java
index b2e5bc7..a9552ca 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Table.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/db/Table.java
@@ -18,30 +18,47 @@
 
 import android.content.ContentValues;
 import android.database.Cursor;
+
 import androidx.annotation.NonNull;
 
 import java.util.stream.Stream;
 
 /**
  * Abstraction of SQLite database table.
+ * @param <E> type of table entities.
  */
 public abstract class Table<E> {
 
     public final String mName;
     private final Entity<E> mEntity;
 
-    protected Table(final String name, final Entity<E> entity) {
+    protected Table(String name, Entity<E> entity) {
         mName = name;
         mEntity = entity;
     }
 
+    /**
+     * @return name of this table.
+     */
     public String name() {
         return mName;
     }
 
-    public abstract ContentValues buildContentValues(final E entity);
+    /**
+     * Build {@link ContentValues} object from {@code entity}.
+     *
+     * @param entity an input data to be converted.
+     * @return a converted {@link ContentValues} object.
+     */
+    public abstract ContentValues buildContentValues(E entity);
 
-    public abstract Stream<E> buildStream(final Cursor cursor);
+    /**
+     * Build {@link Stream} object from {@link Cursor} comes from Content Provider.
+     *
+     * @param cursor a {@link Cursor} object to be converted.
+     * @return a converted {@link Stream} object.
+     */
+    public abstract Stream<E> buildStream(Cursor cursor);
 
     /**
      * Returns SQL statement to create this table, such that
@@ -53,7 +70,7 @@
         return "CREATE TABLE IF NOT EXISTS " + mName + " " + mEntity.createEntitySql();
     }
 
-    protected Field getField(final String fieldName) {
+    protected Field getField(String fieldName) {
         return mEntity.getField(fieldName);
     }
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
index 05d47bb..c3e082e 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -11,23 +11,19 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.ime;
 
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
-        .ON_BIND_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_BIND_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
-
-
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType
-        .ON_UNBIND_INPUT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_UNBIND_INPUT;
 
 import android.content.Intent;
 import android.inputmethodservice.InputMethodService;
@@ -40,6 +36,9 @@
 
 import java.util.function.Consumer;
 
+/**
+ * Base class to create test {@link InputMethodService}.
+ */
 public abstract class CtsBaseInputMethod extends InputMethodService implements ImeCommandCallbacks {
 
     protected static final boolean DEBUG = false;
@@ -140,7 +139,7 @@
     //
 
     @Override
-    public void commandCommitText(final CharSequence text, final int newCursorPosition) {
+    public void commandCommitText(CharSequence text, int newCursorPosition) {
         executeOnInputConnection(ic -> {
             // TODO: Log the return value of {@link InputConnection#commitText(CharSequence,int)}.
             ic.commitText(text, newCursorPosition);
@@ -148,16 +147,16 @@
     }
 
     @Override
-    public void commandSwitchInputMethod(final String imeId) {
+    public void commandSwitchInputMethod(String imeId) {
         switchInputMethod(imeId);
     }
 
     @Override
-    public void commandRequestHideSelf(final int flags) {
+    public void commandRequestHideSelf(int flags) {
         requestHideSelf(flags);
     }
 
-    private void executeOnInputConnection(final Consumer<InputConnection> consumer) {
+    private void executeOnInputConnection(Consumer<InputConnection> consumer) {
         final InputConnection ic = getCurrentInputConnection();
         // TODO: Check and log whether {@code ic} is null or equals to
         // {@link #getCurrentInputBindin().getConnection()}.
@@ -166,7 +165,7 @@
         }
     }
 
-    private void sendEvent(final DeviceEventType type, final Object... args) {
+    private void sendEvent(DeviceEventType type, Object... args) {
         final String sender = getClass().getName();
         final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
         // TODO: Send arbitrary {@code args} in {@code intent}.
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
index dd53765..02c1c56 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/ImeCommandReceiver.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package android.inputmethodservice.cts.ime;
@@ -40,30 +40,30 @@
          * @param text text to be committed via {@link android.view.inputmethod.InputConnection}.
          * @param newCursorPosition new cursor position after commit.
          */
-        void commandCommitText(final CharSequence text, final int newCursorPosition);
+        void commandCommitText(CharSequence text, int newCursorPosition);
 
         /**
          * Callback method for {@link ImeCommandConstants#COMMAND_SWITCH_INPUT_METHOD} intent.
          *
          * @param imeId IME id to switch.
          */
-        void commandSwitchInputMethod(final String imeId);
+        void commandSwitchInputMethod(String imeId);
 
         /**
          * Callback method for {@link ImeCommandConstants#COMMAND_REQUEST_HIDE_SELF} intent.
          */
-        void commandRequestHideSelf(final int flags);
+        void commandRequestHideSelf(int flags);
     }
 
     private T mIme;
 
-    void register(final T ime) {
+    void register(T ime) {
         mIme = ime;
         ime.registerReceiver(this, new IntentFilter(ImeCommandConstants.ACTION_IME_COMMAND));
     }
 
     @Override
-    public void onReceive(final Context context, final Intent intent) {
+    public void onReceive(Context context, Intent intent) {
         final String command = intent.getStringExtra(ImeCommandConstants.EXTRA_COMMAND);
         if (DEBUG) {
             Log.d(mIme.getClass().getSimpleName(), "onReceive: command=" + command);
@@ -106,15 +106,15 @@
         }
     }
 
-    private static CharSequence getCharSequence1(final Intent intent) {
+    private static CharSequence getCharSequence1(Intent intent) {
         return intent.getCharSequenceExtra(ImeCommandConstants.EXTRA_ARG_CHARSEQUENCE1);
     }
 
-    private static String getString1(final Intent intent) {
+    private static String getString1(Intent intent) {
         return intent.getStringExtra(ImeCommandConstants.EXTRA_ARG_STRING1);
     }
 
-    private static int getInt1(final Intent intent) {
+    private static int getInt1(Intent intent) {
         if (intent.hasExtra(ImeCommandConstants.EXTRA_ARG_INT1)) {
             return intent.getIntExtra(ImeCommandConstants.EXTRA_ARG_INT1, 0);
         }
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/Android.mk b/hostsidetests/inputmethodservice/deviceside/provider/Android.mk
index b8f308d..365f792 100644
--- a/hostsidetests/inputmethodservice/deviceside/provider/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/provider/Android.mk
@@ -36,5 +36,6 @@
 LOCAL_PACKAGE_NAME := CtsInputMethodServiceEventProvider
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 19
 
 include $(BUILD_PACKAGE)
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java
index cf20a95..7586816 100644
--- a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/EventProvider.java
@@ -22,14 +22,15 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.inputmethodservice.cts.DeviceEvent;
 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
 import android.inputmethodservice.cts.db.Database;
 import android.inputmethodservice.cts.db.Table;
-import android.inputmethodservice.cts.DeviceEvent;
 import android.net.Uri;
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import android.util.Log;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -66,9 +67,8 @@
     }
 
     @Override
-    public Cursor query(@NonNull final Uri uri, @Nullable final String[] projection,
-            final @Nullable String selection, @Nullable final String[] selectionArgs,
-            @Nullable final String orderBy) {
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+            @Nullable String[] selectionArgs, @Nullable String orderBy) {
         final UriHelper uriHelper = mUriFactory.newInstance(uri);
         if (DEBUG) {
             Log.d(TAG, "query:"
@@ -80,7 +80,7 @@
                     + " orderBy=" + orderBy);
         }
         final Cursor cursor = mDatabase.query(
-                uriHelper.table, projection, uriHelper.buildSelection(selection),
+                uriHelper.mTable, projection, uriHelper.buildSelection(selection),
                 uriHelper.buildSelectionArgs(selectionArgs), orderBy);
         if (DEBUG) {
             Log.d(TAG, "  query.count=" + cursor.getCount());
@@ -90,12 +90,12 @@
     }
 
     @Override
-    public Uri insert(@NonNull final Uri uri, @Nullable final ContentValues values) {
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
         final UriHelper uriHelper = mUriFactory.newInstance(uri);
         if (DEBUG) {
             Log.d(TAG, "insert: uri=" + uri + " values={" + values + "}");
         }
-        final long rowId = mDatabase.insert(uriHelper.table, values);
+        final long rowId = mDatabase.insert(uriHelper.mTable, values);
         final Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
         if (DEBUG) {
             Log.d(TAG, "  insert.uri=" + insertedUri);
@@ -105,8 +105,8 @@
     }
 
     @Override
-    public int delete(@NonNull final Uri uri, @Nullable final String selection,
-            @Nullable final String[] selectionArgs) {
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
         final UriHelper uriHelper = mUriFactory.newInstance(uri);
         if (DEBUG) {
             Log.d(TAG, "delete:"
@@ -115,7 +115,7 @@
                     + " selectionArgs=" + Arrays.toString(
                             uriHelper.buildSelectionArgs(selectionArgs)));
         }
-        final int count = mDatabase.delete(uriHelper.table, uriHelper.buildSelection(selection),
+        final int count = mDatabase.delete(uriHelper.mTable, uriHelper.buildSelection(selection),
                 uriHelper.buildSelectionArgs(selectionArgs));
         if (DEBUG) {
             Log.d(TAG, "  delete.count=" + count);
@@ -125,8 +125,8 @@
     }
 
     @Override
-    public int update(@NonNull final Uri uri, @Nullable final ContentValues values,
-            final @Nullable String selection, @Nullable final String[] selectionArgs) {
+    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
         final UriHelper uriHelper = mUriFactory.newInstance(uri);
         if (DEBUG) {
             Log.d(TAG, "update:"
@@ -136,7 +136,7 @@
                     + " selectionArgs=" + Arrays.toString(
                             uriHelper.buildSelectionArgs(selectionArgs)));
         }
-        final int count = mDatabase.update(uriHelper.table, values,
+        final int count = mDatabase.update(uriHelper.mTable, values,
                 uriHelper.buildSelection(selection), uriHelper.buildSelectionArgs(selectionArgs));
         if (DEBUG) {
             Log.d(TAG, "  update.count=" + count);
@@ -147,7 +147,7 @@
 
     @Override
     @Nullable
-    public String getType(@NonNull final Uri uri) {
+    public String getType(@NonNull Uri uri) {
         return mUriFactory.getTypeOf(uri);
     }
 
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java
index 3222684..55f2cd7 100644
--- a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/provider/UriHelper.java
@@ -19,11 +19,12 @@
 import android.content.UriMatcher;
 import android.net.Uri;
 import android.provider.BaseColumns;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 
 /**
@@ -46,13 +47,13 @@
             return new FactoryBuilder();
         }
 
-        private Factory(final FactoryBuilder builder) {
+        private Factory(FactoryBuilder builder) {
             mUriMatcher = builder.mUriMatcher;
             mUriTypeMap = builder.mUriTypeMap;
         }
 
         @NonNull
-        UriHelper newInstance(final Uri uri) {
+        UriHelper newInstance(Uri uri) {
             if (mUriMatcher.match(uri) == UriMatcher.NO_MATCH) {
                 throw new IllegalArgumentException("Unknown URI: " + uri);
             }
@@ -60,7 +61,7 @@
         }
 
         @Nullable
-        String getTypeOf(final Uri uri) {
+        String getTypeOf(Uri uri) {
             return mUriTypeMap.get(mUriMatcher.match(uri), null);
         }
     }
@@ -74,7 +75,7 @@
             mMatcherCode = 0;
         }
 
-        FactoryBuilder addUri(final String authority, final String path, final String type) {
+        FactoryBuilder addUri(String authority, String path, String type) {
             if (TextUtils.isEmpty(authority)) {
                 throw new IllegalArgumentException("Authority must not be empty");
             }
@@ -97,15 +98,15 @@
 
     /** Name of SQLite table specified by content uri. */
     @NonNull
-    final String table;
+    final String mTable;
 
     /** Primary id that is specified by content uri. Null if not. */
     @Nullable
     private final String mId;
 
-    private UriHelper(final Uri uri) {
+    private UriHelper(Uri uri) {
         final List<String> segments = uri.getPathSegments();
-        table = segments.get(0);
+        mTable = segments.get(0);
         mId = (segments.size() >= 2) ? segments.get(1) : null;
     }
 
@@ -118,7 +119,7 @@
      * @return composed selection SQL text, null if no selection specified.
      */
     @Nullable
-    String buildSelection(@Nullable final String selection) {
+    String buildSelection(@Nullable String selection) {
         if (mId == null) {
             return selection;
         }
@@ -141,7 +142,7 @@
      * @return composed selection argument array, null if selection argument is unnecessary.
      */
     @Nullable
-    String[] buildSelectionArgs(@Nullable final String[] selectionArgs) {
+    String[] buildSelectionArgs(@Nullable String[] selectionArgs) {
         if (mId == null) {
             return selectionArgs;
         }
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java
index 9b79605..c917f36 100644
--- a/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java
+++ b/hostsidetests/inputmethodservice/deviceside/provider/src/android/inputmethodservice/cts/receiver/EventReceiver.java
@@ -28,6 +28,9 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+/**
+ * An implementation of {@link BroadcastReceiver} to collect event logs on device.
+ */
 public final class EventReceiver extends BroadcastReceiver {
 
     private static final String TAG = EventReceiver.class.getSimpleName();
@@ -36,7 +39,7 @@
     private static final Uri CONTENT_URI = Uri.parse(EventTableConstants.CONTENT_URI);
 
     @Override
-    public void onReceive(final Context context, final Intent intent) {
+    public void onReceive(Context context, Intent intent) {
         // Since {@code intent} which comes from host has no
         // {@link DeviceEventConstants#EXTRA_EVENT_TIME EXTRA_EVENT_TIME} extra, here we record the
         // time.
diff --git a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index 1575659..07fa62e 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -18,6 +18,8 @@
 <configuration description="Config for CTS Input Method Service host test cases">
     <option name="test-suite-tag" value="cts" />
     <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" />
     <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" />
@@ -25,7 +27,6 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsInputMethodServiceEventProvider.apk" />
-        <option name="test-file-name" value="CtsInputMethodServiceDeviceTests.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsInputMethodServiceHostTestCases.jar" />
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index d778c70..49e793f 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -47,6 +47,9 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+/**
+ * Test general lifecycle events around InputMethodService.
+ */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class InputMethodServiceLifecycleTest extends BaseHostJUnit4Test {
 
@@ -54,14 +57,21 @@
     private static final long PACKAGE_OP_TIMEOUT = TimeUnit.SECONDS.toMillis(7);
     private static final long POLLING_INTERVAL = 100;
 
+    /**
+     * Set up test case.
+     */
     @Before
     public void setUp() throws Exception {
         // Skip whole tests when DUT has no android.software.input_methods feature.
         assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
         cleanUpTestImes();
+        installPackage(DeviceTestConstants.APK, "-r");
         shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI));
     }
 
+    /**
+     * Tear down test case.
+     */
     @After
     public void tearDown() throws Exception {
         shell(ShellCommandUtils.resetImes());
@@ -83,9 +93,9 @@
             String apkFileName, String packageName, String... options) throws Exception {
         installPackage(apkFileName, options);
         pollingCheck(() ->
-            shell(ShellCommandUtils.listPackage(packageName)).contains(packageName),
-            PACKAGE_OP_TIMEOUT,
-            packageName + " should be installed.");
+                        shell(ShellCommandUtils.listPackage(packageName)).contains(packageName),
+                PACKAGE_OP_TIMEOUT,
+                packageName + " should be installed.");
     }
 
     /**
@@ -102,7 +112,7 @@
     }
 
     private void installPossibleInstantPackage(
-        String apkFileName, String packageName, boolean instant) throws Exception {
+            String apkFileName, String packageName, boolean instant) throws Exception {
         if (instant) {
             installPackageSync(apkFileName, packageName, "-r", "--instant");
         } else {
@@ -111,11 +121,9 @@
     }
 
     private void testSwitchIme(boolean instant) throws Exception {
-        final TestInfo testSwitchIme1ToIme2 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
-        sendTestStartEvent(testSwitchIme1ToIme2);
+        sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
@@ -123,15 +131,21 @@
         waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testSwitchIme1ToIme2));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2));
     }
 
+    /**
+     * Test IME switching APIs for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testSwitchImeFull() throws Exception {
         testSwitchIme(false);
     }
 
+    /**
+     * Test IME switching APIs for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testSwitchImeInstant() throws Exception {
@@ -139,28 +153,32 @@
     }
 
     private void testUninstallCurrentIme(boolean instant) throws Exception {
-        final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
-        sendTestStartEvent(testCreateIme1);
+        sendTestStartEvent(DeviceTestConstants.TEST_CREATE_IME1);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
         waitUntilImesAreEnabled(Ime1Constants.IME_ID);
 
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
-        assertTrue(runDeviceTestMethod(testCreateIme1));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_CREATE_IME1));
 
         uninstallPackageSyncIfExists(Ime1Constants.PACKAGE);
         assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, WAIT_TIMEOUT);
     }
 
+    /**
+     * Test uninstalling the currently selected IME for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testUninstallCurrentImeFull() throws Exception {
         testUninstallCurrentIme(false);
     }
 
+    /**
+     * Test uninstalling the currently selected IME for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testUninstallCurrentImeInstant() throws Exception {
@@ -168,27 +186,31 @@
     }
 
     private void testDisableCurrentIme(boolean instant) throws Exception {
-        final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
-                DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
-        sendTestStartEvent(testCreateIme1);
+        sendTestStartEvent(DeviceTestConstants.TEST_CREATE_IME1);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
         waitUntilImesAreEnabled(Ime1Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
-        assertTrue(runDeviceTestMethod(testCreateIme1));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_CREATE_IME1));
 
         shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
         assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, WAIT_TIMEOUT);
     }
 
+    /**
+     * Test disabling the currently selected IME for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testDisableCurrentImeFull() throws Exception {
         testDisableCurrentIme(false);
     }
 
+    /**
+     * Test disabling the currently selected IME for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testDisableCurrentImeInstant() throws Exception {
@@ -196,12 +218,9 @@
     }
 
     private void testSwitchInputMethod(boolean instant) throws Exception {
-        final TestInfo testSetInputMethod = new TestInfo(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_SWITCH_INPUTMETHOD);
-        sendTestStartEvent(testSetInputMethod);
+        sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_INPUTMETHOD);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
@@ -209,15 +228,21 @@
         waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testSetInputMethod));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_INPUTMETHOD));
     }
 
+    /**
+     * Test "InputMethodService#switchInputMethod" API for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testSwitchInputMethodFull() throws Exception {
         testSwitchInputMethod(false);
     }
 
+    /**
+     * Test "InputMethodService#switchInputMethod" API for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testSwitchInputMethodInstant() throws Exception {
@@ -225,12 +250,9 @@
     }
 
     private void testSwitchToNextInput(boolean instant) throws Exception {
-        final TestInfo testSwitchInputs = new TestInfo(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_SWITCH_NEXT_INPUT);
-        sendTestStartEvent(testSwitchInputs);
+        sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_NEXT_INPUT);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
@@ -240,15 +262,21 @@
         waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testSwitchInputs));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_NEXT_INPUT));
     }
 
+    /**
+     * Test "InputMethodService#switchToNextInputMethod" API for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testSwitchToNextInputFull() throws Exception {
         testSwitchToNextInput(false);
     }
 
+    /**
+     * Test "InputMethodService#switchToNextInputMethod" API for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testSwitchToNextInputInstant() throws Exception {
@@ -256,12 +284,9 @@
     }
 
     private void testSwitchToPreviousInput(boolean instant) throws Exception {
-        final TestInfo testSwitchInputs = new TestInfo(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT);
-        sendTestStartEvent(testSwitchInputs);
+        sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
@@ -269,15 +294,21 @@
         waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testSwitchInputs));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT));
     }
 
+    /**
+     * Test "InputMethodService#switchToPreviousInputMethod" API for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testSwitchToPreviousInputFull() throws Exception {
         testSwitchToPreviousInput(false);
     }
 
+    /**
+     * Test "InputMethodService#switchToPreviousInputMethod" API for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testSwitchToPreviousInputInstant() throws Exception {
@@ -285,12 +316,9 @@
     }
 
     private void testInputUnbindsOnImeStopped(boolean instant) throws Exception {
-        final TestInfo testUnbind = new TestInfo(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED);
-        sendTestStartEvent(testUnbind);
+        sendTestStartEvent(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
@@ -298,15 +326,23 @@
         waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testUnbind));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED));
     }
 
+    /**
+     * Test if uninstalling the currently selected IME then selecting another IME triggers standard
+     * startInput/bindInput sequence for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testInputUnbindsOnImeStoppedFull() throws Exception {
         testInputUnbindsOnImeStopped(false);
     }
 
+    /**
+     * Test if uninstalling the currently selected IME then selecting another IME triggers standard
+     * startInput/bindInput sequence for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testInputUnbindsOnImeStoppedInstant() throws Exception {
@@ -314,33 +350,38 @@
     }
 
     private void testInputUnbindsOnAppStop(boolean instant) throws Exception {
-        final TestInfo testUnbind = new TestInfo(
-                DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_CLASS,
-                DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED);
-        sendTestStartEvent(testUnbind);
+        sendTestStartEvent(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED);
         installPossibleInstantPackage(
-            EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
+                EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant);
         installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID);
         shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
         waitUntilImesAreEnabled(Ime1Constants.IME_ID);
         shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID));
 
-        assertTrue(runDeviceTestMethod(testUnbind));
+        assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED));
     }
 
+    /**
+     * Test if uninstalling the currently running IME client triggers
+     * "InputMethodService#onUnbindInput" callback for full (non-instant) apps.
+     */
     @AppModeFull
     @Test
     public void testInputUnbindsOnAppStopFull() throws Exception {
         testInputUnbindsOnAppStop(false);
     }
 
+    /**
+     * Test if uninstalling the currently running IME client triggers
+     * "InputMethodService#onUnbindInput" callback for instant apps.
+     */
     @AppModeInstant
     @Test
     public void testInputUnbindsOnAppStopInstant() throws Exception {
         testInputUnbindsOnAppStop(true);
     }
 
-    private void sendTestStartEvent(final TestInfo deviceTest) throws Exception {
+    private void sendTestStartEvent(TestInfo deviceTest) throws Exception {
         final String sender = deviceTest.getTestName();
         // {@link EventType#EXTRA_EVENT_TIME} will be recorded at device side.
         shell(ShellCommandUtils.broadcastIntent(
@@ -349,11 +390,11 @@
                 "--es", EXTRA_EVENT_TYPE, TEST_START.name()));
     }
 
-    private boolean runDeviceTestMethod(final TestInfo deviceTest) throws Exception {
+    private boolean runDeviceTestMethod(TestInfo deviceTest) throws Exception {
         return runDeviceTests(deviceTest.testPackage, deviceTest.testClass, deviceTest.testMethod);
     }
 
-    private String shell(final String command) throws Exception {
+    private String shell(String command) throws Exception {
         return getDevice().executeShellCommand(command).trim();
     }
 
@@ -362,12 +403,11 @@
         uninstallPackageSyncIfExists(Ime2Constants.PACKAGE);
     }
 
-    private void uninstallPackageSyncIfExists(final String packageName) throws Exception {
+    private void uninstallPackageSyncIfExists(String packageName) throws Exception {
         if (isPackageInstalled(getDevice(), packageName)) {
             uninstallPackage(getDevice(), packageName);
-            pollingCheck(()-> !isPackageInstalled(getDevice(), packageName),
-                PACKAGE_OP_TIMEOUT,
-                packageName + " should be uninstalled.");
+            pollingCheck(() -> !isPackageInstalled(getDevice(), packageName), PACKAGE_OP_TIMEOUT,
+                    packageName + " should be uninstalled.");
         }
     }
 
@@ -408,14 +448,12 @@
     }
 
     private void waitUntilImesAreAvailableOrEnabled(
-        boolean shouldBeEnabled, String... imeIds) throws Exception {
-        final String cmd = shouldBeEnabled ?
-                ShellCommandUtils.getEnabledImes() : ShellCommandUtils.getAvailableImes();
+            boolean shouldBeEnabled, String... imeIds) throws Exception {
+        final String cmd = shouldBeEnabled
+                ? ShellCommandUtils.getEnabledImes() : ShellCommandUtils.getAvailableImes();
         for (String imeId : imeIds) {
-            pollingCheck(() ->
-                    shell(cmd).contains(imeId),
-                    PACKAGE_OP_TIMEOUT,
-                    imeId + " should be " + (shouldBeEnabled? "enabled." : "available."));
+            pollingCheck(() -> shell(cmd).contains(imeId), PACKAGE_OP_TIMEOUT,
+                    imeId + " should be " + (shouldBeEnabled ? "enabled." : "available."));
         }
     }
 }
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
new file mode 100644
index 0000000..0b77d7d
--- /dev/null
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.inputmethodservice.cts.hostside;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.inputmethodservice.cts.common.Ime1Constants;
+import android.inputmethodservice.cts.common.test.DeviceTestConstants;
+import android.inputmethodservice.cts.common.test.ShellCommandUtils;
+import android.inputmethodservice.cts.common.test.TestInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test IME APIs for multi-user environment.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MultiUserTest extends BaseHostJUnit4Test {
+    private static final long USER_SWITCH_TIMEOUT = TimeUnit.SECONDS.toMillis(60);
+    private static final long USER_SWITCH_POLLING_INTERVAL = TimeUnit.MILLISECONDS.toMillis(100);
+
+    /**
+     * A sleep time after calling {@link com.android.tradefed.device.ITestDevice#switchUser(int)}
+     * to see if the flakiness comes from race condition in UserManagerService#removeUser() or not.
+     *
+     * <p>TODO(Bug 122609784): Remove this once we figure out what is the root cause of flakiness.
+     * </p>
+     */
+    private static final long WAIT_AFTER_USER_SWITCH = TimeUnit.SECONDS.toMillis(10);
+
+    private ArrayList<Integer> mOriginalUsers;
+
+    /**
+     * Set up the test case
+     */
+    @Before
+    public void setUp() throws Exception {
+        // Skip whole tests when DUT has no android.software.input_methods feature.
+        assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
+        assumeTrue(getDevice().isMultiUserSupported());
+
+        mOriginalUsers = new ArrayList<>(getDevice().listUsers());
+        mOriginalUsers.forEach(
+                userId -> shell(ShellCommandUtils.uninstallPackage(Ime1Constants.PACKAGE, userId)));
+    }
+
+    /**
+     * Tear down the test case.
+     */
+    @After
+    public void tearDown() throws Exception {
+        getDevice().switchUser(getDevice().getPrimaryUserId());
+        // We suspect that the optimization made for Bug 38143512 was a bit unstable.  Let's see
+        // if adding a sleep improves the stability or not.
+        Thread.sleep(WAIT_AFTER_USER_SWITCH);
+
+        final ArrayList<Integer> newUsers = getDevice().listUsers();
+        for (int userId : newUsers) {
+            if (!mOriginalUsers.contains(userId)) {
+                getDevice().removeUser(userId);
+            }
+        }
+        shell(ShellCommandUtils.wakeUp());
+        shell(ShellCommandUtils.dismissKeyguard());
+        shell(ShellCommandUtils.closeSystemDialog());
+    }
+
+    /**
+     * Make sure that InputMethodManagerService automatically updates its internal IME list upon
+     * IME APK installation for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testSecondaryUserFull() throws Exception {
+        testSecondaryUser(false);
+    }
+
+    /**
+     * Make sure that InputMethodManagerService automatically updates its internal IME list upon
+     * IME APK installation for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testSecondaryUserInstant() throws Exception {
+        testSecondaryUser(true);
+    }
+
+    private void testSecondaryUser(boolean instant) throws Exception {
+        final int primaryUserId = getDevice().getPrimaryUserId();
+        final int secondaryUserId = getDevice().createUser(
+                "InputMethodMultiUserTest_secondaryUser" + System.currentTimeMillis());
+
+        getDevice().startUser(secondaryUserId);
+
+        installPossibleInstantPackage(DeviceTestConstants.APK, primaryUserId, instant);
+        installPossibleInstantPackage(DeviceTestConstants.APK, secondaryUserId, instant);
+
+        assertIme1NotExistInApiResult(primaryUserId);
+        assertIme1NotExistInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(secondaryUserId);
+
+        installPackageAsUser(Ime1Constants.APK, true, secondaryUserId, "-r");
+
+        assertIme1NotExistInApiResult(primaryUserId);
+        assertIme1ExistsInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeExists(secondaryUserId);
+
+        switchUser(secondaryUserId);
+
+        assertIme1NotExistInApiResult(primaryUserId);
+        assertIme1ExistsInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeExists(secondaryUserId);
+
+        switchUser(primaryUserId);
+
+        assertIme1NotExistInApiResult(primaryUserId);
+        assertIme1ExistsInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeExists(secondaryUserId);
+    }
+
+    /**
+     * Make sure that InputMethodManagerService automatically updates its internal IME list upon
+     * IME APK installation for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testProfileUserFull() throws Exception {
+        testProfileUser(false);
+    }
+
+    /**
+     * Make sure that InputMethodManagerService automatically updates its internal IME list upon
+     * IME APK installation for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testProfileUserInstant() throws Exception {
+        testProfileUser(true);
+    }
+
+    private void testProfileUser(boolean instant) throws Exception {
+        assumeTrue(getDevice().hasFeature("android.software.managed_users"));
+
+        final int primaryUserId = getDevice().getPrimaryUserId();
+        final int profileUserId = createProfile(primaryUserId);
+        final int secondaryUserId = getDevice().createUser(
+                "InputMethodMultiUserTest_secondaryUser" + System.currentTimeMillis());
+
+        getDevice().startUser(profileUserId);
+        getDevice().startUser(secondaryUserId);
+
+        installPossibleInstantPackage(DeviceTestConstants.APK, primaryUserId, instant);
+        installPossibleInstantPackage(DeviceTestConstants.APK, profileUserId, instant);
+        installPossibleInstantPackage(DeviceTestConstants.APK, secondaryUserId, instant);
+
+        assertIme1NotExistInApiResult(primaryUserId);
+        assertIme1NotExistInApiResult(profileUserId);
+        assertIme1NotExistInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(profileUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(secondaryUserId);
+
+        installPackageAsUser(Ime1Constants.APK, true, primaryUserId, "-r");
+
+        assertIme1ExistsInApiResult(primaryUserId);
+        assertIme1NotExistInApiResult(profileUserId);
+        assertIme1NotExistInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeExists(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(profileUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(secondaryUserId);
+
+        switchUser(secondaryUserId);
+
+        assertIme1ExistsInApiResult(primaryUserId);
+        assertIme1NotExistInApiResult(profileUserId);
+        assertIme1NotExistInApiResult(secondaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeExists(primaryUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(profileUserId);
+        assertIme1ImplicitlyEnabledSubtypeNotExist(secondaryUserId);
+    }
+
+    private String shell(String command) {
+        try {
+            return getDevice().executeShellCommand(command).trim();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * A convenient wrapper for {@link com.android.tradefed.device.ITestDevice#switchUser(int)}
+     * that also makes sure that InputMethodManagerService actually receives the new user ID.
+     *
+     * @param userId user ID to switch to.
+     */
+    private void switchUser(int userId) throws Exception {
+        getDevice().switchUser(userId);
+        final long initialTime = System.currentTimeMillis();
+        while (true) {
+            final CommandResult result = getDevice().executeShellV2Command(
+                    ShellCommandUtils.getLastSwitchUserId(), USER_SWITCH_TIMEOUT,
+                    TimeUnit.MILLISECONDS);
+            if (result.getStatus() != CommandStatus.SUCCESS) {
+                throw new IllegalStateException(
+                        "Failed to get last SwitchUser ID from InputMethodManagerService."
+                        + " result.getStatus()=" + result.getStatus());
+            }
+            final String[] lines = result.getStdout().split("\\r?\\n");
+            if (lines.length < 1) {
+                throw new IllegalStateException(
+                        "Failed to get last SwitchUser ID from InputMethodManagerService."
+                                + " result=" + result);
+            }
+            final int lastSwitchUserId = Integer.parseInt(lines[0], 10);
+            if (userId == lastSwitchUserId) {
+                // InputMethodManagerService.Lifecycle#onSwitchUser() gets called.  Ready to go.
+                return;
+            }
+            if (System.currentTimeMillis() > initialTime + USER_SWITCH_TIMEOUT) {
+                throw new TimeoutException(
+                        "Failed to get last SwitchUser ID from InputMethodManagerService.");
+            }
+            // InputMethodManagerService did not receive onSwitchUser() yet.
+            try {
+                Thread.sleep(USER_SWITCH_POLLING_INTERVAL);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException("Sleep interrupted while obtaining last SwitchUser"
+                        + " ID from InputMethodManagerService.");
+            }
+        }
+    }
+
+    private void installPossibleInstantPackage(String apkFileName, int userId, boolean instant)
+            throws Exception {
+        if (instant) {
+            installPackageAsUser(apkFileName, true, userId, "-r", "--instant");
+        } else {
+            installPackageAsUser(apkFileName, true, userId, "-r");
+        }
+    }
+
+    private int createProfile(int parentUserId) throws Exception {
+        final String command = ShellCommandUtils.createManagedProfileUser(parentUserId,
+                "InputMethodMultiUserTest_testProfileUser" + System.currentTimeMillis());
+        final String output = getDevice().executeShellCommand(command);
+
+        if (output.startsWith("Success")) {
+            try {
+                return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+            } catch (NumberFormatException e) {
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    private void assertIme1ExistsInApiResult(int userId) throws Exception  {
+        runTestAsUser(DeviceTestConstants.TEST_IME1_IN_INPUT_METHOD_LIST, userId);
+    }
+
+    private void assertIme1NotExistInApiResult(int userId) throws Exception  {
+        runTestAsUser(DeviceTestConstants.TEST_IME1_NOT_IN_INPUT_METHOD_LIST, userId);
+    }
+
+    private void assertIme1ImplicitlyEnabledSubtypeExists(int userId) throws Exception  {
+        runTestAsUser(DeviceTestConstants.TEST_IME1_IMPLICITLY_ENABLED_SUBTYPE_EXISTS, userId);
+    }
+
+    private void assertIme1ImplicitlyEnabledSubtypeNotExist(int userId) throws Exception  {
+        runTestAsUser(DeviceTestConstants.TEST_IME1_IMPLICITLY_ENABLED_SUBTYPE_NOT_EXIST, userId);
+    }
+
+    private void runTestAsUser(TestInfo testInfo, int userId) throws Exception {
+        runDeviceTests(new DeviceTestRunOptions(testInfo.testPackage)
+                .setDevice(getDevice())
+                .setTestClassName(testInfo.testClass)
+                .setTestMethodName(testInfo.testMethod)
+                .setUserId(userId));
+    }
+}
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java
new file mode 100644
index 0000000..17b31b5
--- /dev/null
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.inputmethodservice.cts.hostside;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.inputmethodservice.cts.common.test.DeviceTestConstants;
+import android.inputmethodservice.cts.common.test.ShellCommandUtils;
+import android.inputmethodservice.cts.common.test.TestInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test IInputMethodManager#shellComman verifies callers.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ShellCommandFromAppTest extends BaseHostJUnit4Test {
+    /**
+     * Run device test with disabling hidden API check.
+     *
+     * @param testInfo test to be executed.
+     * @param instant {@code true} when {@code testInfo} needs to be installed as an instant app.
+     */
+    private void runDeviceTestMethodWithoutHiddenApiCheck(TestInfo testInfo, boolean instant)
+            throws Exception {
+        if (instant) {
+            installPackage(DeviceTestConstants.APK, "-r", "--instant");
+        } else {
+            installPackage(DeviceTestConstants.APK, "-r");
+        }
+        runDeviceTests(new DeviceTestRunOptions(testInfo.testPackage)
+                .setDevice(getDevice())
+                .setDisableHiddenApiCheck(false)
+                .setTestClassName(testInfo.testClass)
+                .setTestMethodName(testInfo.testMethod));
+    }
+
+    /**
+     * Set up test case.
+     */
+    @Before
+    public void setUp() throws Exception {
+        // Skip whole tests when DUT has no android.software.input_methods feature.
+        assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{}, null, receiver)}
+     * returns {@link SecurityException} for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testShellCommandFull() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND, false);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{}, null, receiver)}
+     * returns {@link SecurityException} for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testShellCommandInstant() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND, true);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime"}, null, receiver)}
+     * returns {@link SecurityException} for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testShellCommandImeFull() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_IME, false);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime"}, null, receiver)}
+     * returns {@link SecurityException} for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testShellCommandImeInstant() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_IME, true);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testShellCommandImeListFull() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_IME_LIST,
+                false);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testShellCommandImeListInstant() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_IME_LIST,
+                true);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testShellCommandDumpFull() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_DUMP,
+                false);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testShellCommandDumpInstant() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_DUMP, true);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for full (non-instant) apps.
+     */
+    @AppModeFull
+    @Test
+    public void testShellCommandHelpFull() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_HELP,
+                false);
+    }
+
+    /**
+     * Make sure
+     * {@code IInputMethodManager#shellCommand(in, out, err, new String[]{"ime", "list"}, null,
+     * receiver)} returns {@link SecurityException} for instant apps.
+     */
+    @AppModeInstant
+    @Test
+    public void testShellCommandHelpInstant() throws Exception {
+        runDeviceTestMethodWithoutHiddenApiCheck(DeviceTestConstants.TEST_SHELL_COMMAND_HELP, true);
+    }
+}
diff --git a/hostsidetests/media/AndroidTest.xml b/hostsidetests/media/AndroidTest.xml
index e5818b5..3b40a0e 100644
--- a/hostsidetests/media/AndroidTest.xml
+++ b/hostsidetests/media/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS media host 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" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsMediaHostTestCases.jar" />
         <option name="runtime-hint" value="30m" />
diff --git a/hostsidetests/media/app/MediaSessionTest/Android.mk b/hostsidetests/media/app/MediaSessionTest/Android.mk
index 174a744..5be80de 100644
--- a/hostsidetests/media/app/MediaSessionTest/Android.mk
+++ b/hostsidetests/media/app/MediaSessionTest/Android.mk
@@ -35,5 +35,6 @@
 
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 26
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/media/bitstreams/DynamicConfig.xml b/hostsidetests/media/bitstreams/DynamicConfig.xml
index 028e3a5..dfe5cc9 100644
--- a/hostsidetests/media/bitstreams/DynamicConfig.xml
+++ b/hostsidetests/media/bitstreams/DynamicConfig.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <dynamicConfig>
+  <entry key="remote_config_required">
+    <value>false</value>
+  </entry>
   <entry key="h264/yuv420/8bit/bp/bitrate/crowd_1280x720p24f300_5000kbps_bp.mp4">
     <value>mime=video/avc,size=7930107,width=1280,height=720,frame-rate=24,profile=1,level=512,bitrate=5073888,package=full</value>
   </entry>
@@ -111,13 +114,13 @@
     <value>mime=video/avc,size=327741,width=1280,height=720,frame-rate=50,profile=1,level=2048,bitrate=4084648,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_128x96p50f32_200kbps_level_2_bp.mp4">
-    <value>mime=video/avc,size=13018,width=128,height=96,frame-rate=50,profile=1,level=32,bitrate=150624,package=standard</value>
+    <value>mime=video/avc,size=13018,width=128,height=96,frame-rate=50,profile=1,level=32,bitrate=150624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_16x16p50f32_200kbps_level_1_bp.mp4">
     <value>mime=video/avc,size=5193,width=16,height=16,frame-rate=50,profile=1,level=1,bitrate=52824,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_176x144p50f32_200kbps_level_2_bp.mp4">
-    <value>mime=video/avc,size=13097,width=176,height=144,frame-rate=50,profile=1,level=32,bitrate=151624,package=standard</value>
+    <value>mime=video/avc,size=13097,width=176,height=144,frame-rate=50,profile=1,level=32,bitrate=151624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_1920x1080p50f32_8200kbps_level_4.2_bp.mp4">
     <value>mime=video/avc,size=814724,width=1920,height=1080,frame-rate=50,profile=1,level=8192,bitrate=10171848,package=standard</value>
@@ -126,19 +129,19 @@
     <value>mime=video/avc,size=824354,width=1920,height=1088,frame-rate=50,profile=1,level=16384,bitrate=10292296,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_352x288p50f32_400kbps_level_2.1_bp.mp4">
-    <value>mime=video/avc,size=28452,width=352,height=288,frame-rate=50,profile=1,level=64,bitrate=343472,package=standard</value>
+    <value>mime=video/avc,size=28452,width=352,height=288,frame-rate=50,profile=1,level=64,bitrate=343472,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_3840x2160p50f32_24000kbps_level_5.2_bp.mp4">
     <value>mime=video/avc,size=2978730,width=3840,height=2160,frame-rate=50,profile=1,level=65536,bitrate=37221944,package=full</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_640x480p50f32_1200kbps_level_3.1_bp.mp4">
-    <value>mime=video/avc,size=96734,width=640,height=480,frame-rate=50,profile=1,level=512,bitrate=1197000,package=standard</value>
+    <value>mime=video/avc,size=96734,width=640,height=480,frame-rate=50,profile=1,level=512,bitrate=1197000,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_64x64p50f32_200kbps_level_2_bp.mp4">
-    <value>mime=video/avc,size=11979,width=64,height=64,frame-rate=50,profile=1,level=32,bitrate=137632,package=standard</value>
+    <value>mime=video/avc,size=11979,width=64,height=64,frame-rate=50,profile=1,level=32,bitrate=137632,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_720x480p50f32_1300kbps_level_3_bp.mp4">
-    <value>mime=video/avc,size=105729,width=720,height=480,frame-rate=50,profile=1,level=256,bitrate=1309496,package=standard</value>
+    <value>mime=video/avc,size=105729,width=720,height=480,frame-rate=50,profile=1,level=256,bitrate=1309496,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/levels/crowd_720x576p50f32_1600kbps_level_3.1_bp.mp4">
     <value>mime=video/avc,size=132868,width=720,height=576,frame-rate=50,profile=1,level=512,bitrate=1648680,package=standard</value>
@@ -813,16 +816,16 @@
     <value>mime=video/avc,size=454069,width=1280,height=960,frame-rate=50,profile=1,level=2048,bitrate=5663744,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_128x128p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13123,width=128,height=128,frame-rate=50,profile=1,level=16,bitrate=151944,package=standard</value>
+    <value>mime=video/avc,size=13123,width=128,height=128,frame-rate=50,profile=1,level=16,bitrate=151944,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_128x192p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13547,width=128,height=192,frame-rate=50,profile=1,level=16,bitrate=157248,package=standard</value>
+    <value>mime=video/avc,size=13547,width=128,height=192,frame-rate=50,profile=1,level=16,bitrate=157248,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_128x72p50f32_200kbps_bp.mp4">
     <value>mime=video/avc,size=12585,width=128,height=72,frame-rate=50,profile=1,level=16,bitrate=145200,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_128x96p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13018,width=128,height=96,frame-rate=50,profile=1,level=16,bitrate=150624,package=standard</value>
+    <value>mime=video/avc,size=13018,width=128,height=96,frame-rate=50,profile=1,level=16,bitrate=150624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_1440x1080p50f32_6200kbps_bp.mp4">
     <value>mime=video/avc,size=591640,width=1440,height=1080,frame-rate=50,profile=1,level=8192,bitrate=7383360,package=standard</value>
@@ -840,25 +843,25 @@
     <value>mime=video/avc,size=518511,width=1440,height=960,frame-rate=50,profile=1,level=8192,bitrate=6469272,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_144x144p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13183,width=144,height=144,frame-rate=50,profile=1,level=16,bitrate=152696,package=standard</value>
+    <value>mime=video/avc,size=13183,width=144,height=144,frame-rate=50,profile=1,level=16,bitrate=152696,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_144x176p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13598,width=144,height=176,frame-rate=50,profile=1,level=16,bitrate=157880,package=standard</value>
+    <value>mime=video/avc,size=13598,width=144,height=176,frame-rate=50,profile=1,level=16,bitrate=157880,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_144x192p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13579,width=144,height=192,frame-rate=50,profile=1,level=16,bitrate=157648,package=standard</value>
+    <value>mime=video/avc,size=13579,width=144,height=192,frame-rate=50,profile=1,level=16,bitrate=157648,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_144x256p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13643,width=144,height=256,frame-rate=50,profile=1,level=16,bitrate=158432,package=standard</value>
+    <value>mime=video/avc,size=13643,width=144,height=256,frame-rate=50,profile=1,level=16,bitrate=158432,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_144x96p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13137,width=144,height=96,frame-rate=50,profile=1,level=16,bitrate=152112,package=standard</value>
+    <value>mime=video/avc,size=13137,width=144,height=96,frame-rate=50,profile=1,level=16,bitrate=152112,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_1536x2048p50f32_12500kbps_bp.mp4">
     <value>mime=video/avc,size=1273812,width=1536,height=2048,frame-rate=50,profile=1,level=32768,bitrate=15910520,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_160x240p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13683,width=160,height=240,frame-rate=50,profile=1,level=16,bitrate=158944,package=standard</value>
+    <value>mime=video/avc,size=13683,width=160,height=240,frame-rate=50,profile=1,level=16,bitrate=158944,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_16x1080p50f32_200kbps_bp.mp4">
     <value>mime=video/avc,size=13341,width=16,height=1080,frame-rate=50,profile=1,level=64,bitrate=154656,package=standard</value>
@@ -876,13 +879,13 @@
     <value>mime=video/avc,size=13311,width=16,height=720,frame-rate=50,profile=1,level=16,bitrate=154280,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_176x144p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13097,width=176,height=144,frame-rate=50,profile=1,level=16,bitrate=151624,package=standard</value>
+    <value>mime=video/avc,size=13097,width=176,height=144,frame-rate=50,profile=1,level=16,bitrate=151624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_176x176p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13645,width=176,height=176,frame-rate=50,profile=1,level=16,bitrate=158472,package=standard</value>
+    <value>mime=video/avc,size=13645,width=176,height=176,frame-rate=50,profile=1,level=16,bitrate=158472,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_176x96p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=12950,width=176,height=96,frame-rate=50,profile=1,level=16,bitrate=149768,package=standard</value>
+    <value>mime=video/avc,size=12950,width=176,height=96,frame-rate=50,profile=1,level=16,bitrate=149768,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_1920x1080p50f32_8200kbps_bp.mp4">
     <value>mime=video/avc,size=814719,width=1920,height=1080,frame-rate=50,profile=1,level=8192,bitrate=10171848,package=standard</value>
@@ -909,19 +912,19 @@
     <value>mime=video/avc,size=27567,width=1920,height=64,frame-rate=50,profile=1,level=512,bitrate=332472,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_192x128p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13034,width=192,height=128,frame-rate=50,profile=1,level=16,bitrate=150832,package=standard</value>
+    <value>mime=video/avc,size=13034,width=192,height=128,frame-rate=50,profile=1,level=16,bitrate=150832,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_192x144p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13134,width=192,height=144,frame-rate=50,profile=1,level=16,bitrate=152080,package=standard</value>
+    <value>mime=video/avc,size=13134,width=192,height=144,frame-rate=50,profile=1,level=16,bitrate=152080,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_192x192p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13553,width=192,height=192,frame-rate=50,profile=1,level=16,bitrate=157320,package=standard</value>
+    <value>mime=video/avc,size=13553,width=192,height=192,frame-rate=50,profile=1,level=16,bitrate=157320,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_192x256p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13572,width=192,height=256,frame-rate=50,profile=1,level=16,bitrate=157544,package=standard</value>
+    <value>mime=video/avc,size=13572,width=192,height=256,frame-rate=50,profile=1,level=16,bitrate=157544,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_192x288p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13479,width=192,height=288,frame-rate=50,profile=1,level=16,bitrate=156384,package=standard</value>
+    <value>mime=video/avc,size=13479,width=192,height=288,frame-rate=50,profile=1,level=16,bitrate=156384,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_2048x1152p50f32_9400kbps_bp.mp4">
     <value>mime=video/avc,size=911171,width=2048,height=1152,frame-rate=50,profile=1,level=16384,bitrate=11377512,package=standard</value>
@@ -942,13 +945,13 @@
     <value>mime=video/avc,size=2649479,width=2304,height=4096,frame-rate=50,profile=1,level=65536,bitrate=33106360,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_240x160p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13592,width=240,height=160,frame-rate=50,profile=1,level=16,bitrate=157808,package=standard</value>
+    <value>mime=video/avc,size=13592,width=240,height=160,frame-rate=50,profile=1,level=16,bitrate=157808,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_240x240p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13504,width=240,height=240,frame-rate=50,profile=1,level=16,bitrate=156712,package=standard</value>
+    <value>mime=video/avc,size=13504,width=240,height=240,frame-rate=50,profile=1,level=16,bitrate=156712,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_240x320p50f32_300kbps_bp.mp4">
-    <value>mime=video/avc,size=21313,width=240,height=320,frame-rate=50,profile=1,level=64,bitrate=254312,package=standard</value>
+    <value>mime=video/avc,size=21313,width=240,height=320,frame-rate=50,profile=1,level=64,bitrate=254312,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_240x360p50f32_300kbps_bp.mp4">
     <value>mime=video/avc,size=21460,width=240,height=360,frame-rate=50,profile=1,level=64,bitrate=256136,package=standard</value>
@@ -969,13 +972,13 @@
     <value>mime=video/avc,size=2658588,width=2560,height=3840,frame-rate=50,profile=1,level=65536,bitrate=33220232,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_256x144p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13556,width=256,height=144,frame-rate=50,profile=1,level=16,bitrate=157344,package=standard</value>
+    <value>mime=video/avc,size=13556,width=256,height=144,frame-rate=50,profile=1,level=16,bitrate=157344,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_256x192p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13510,width=256,height=192,frame-rate=50,profile=1,level=16,bitrate=156768,package=standard</value>
+    <value>mime=video/avc,size=13510,width=256,height=192,frame-rate=50,profile=1,level=16,bitrate=156768,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_256x256p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13449,width=256,height=256,frame-rate=50,profile=1,level=64,bitrate=156000,package=standard</value>
+    <value>mime=video/avc,size=13449,width=256,height=256,frame-rate=50,profile=1,level=64,bitrate=156000,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_264x352p50f32_300kbps_bp.mp4">
     <value>mime=video/avc,size=21344,width=264,height=352,frame-rate=50,profile=1,level=64,bitrate=254672,package=standard</value>
@@ -984,31 +987,31 @@
     <value>mime=video/avc,size=2716331,width=2880,height=3840,frame-rate=50,profile=1,level=65536,bitrate=33942024,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_288x192p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13354,width=288,height=192,frame-rate=50,profile=1,level=16,bitrate=154824,package=standard</value>
+    <value>mime=video/avc,size=13354,width=288,height=192,frame-rate=50,profile=1,level=16,bitrate=154824,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_288x216p50f32_200kbps_bp.mp4">
     <value>mime=video/avc,size=13576,width=288,height=216,frame-rate=50,profile=1,level=64,bitrate=157584,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_288x288p50f32_300kbps_bp.mp4">
-    <value>mime=video/avc,size=20762,width=288,height=288,frame-rate=50,profile=1,level=64,bitrate=247408,package=standard</value>
+    <value>mime=video/avc,size=20762,width=288,height=288,frame-rate=50,profile=1,level=64,bitrate=247408,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_288x352p50f32_400kbps_bp.mp4">
-    <value>mime=video/avc,size=28900,width=288,height=352,frame-rate=50,profile=1,level=64,bitrate=349136,package=standard</value>
+    <value>mime=video/avc,size=28900,width=288,height=352,frame-rate=50,profile=1,level=64,bitrate=349136,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_288x512p50f32_500kbps_bp.mp4">
-    <value>mime=video/avc,size=37103,width=288,height=512,frame-rate=50,profile=1,level=256,bitrate=451672,package=standard</value>
+    <value>mime=video/avc,size=37103,width=288,height=512,frame-rate=50,profile=1,level=256,bitrate=451672,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_3072x4096p50f32_24000kbps_bp.mp4">
     <value>mime=video/avc,size=2790761,width=3072,height=4096,frame-rate=50,profile=1,level=65536,bitrate=34872384,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_320x240p50f32_300kbps_bp.mp4">
-    <value>mime=video/avc,size=20979,width=320,height=240,frame-rate=50,profile=1,level=64,bitrate=250136,package=standard</value>
+    <value>mime=video/avc,size=20979,width=320,height=240,frame-rate=50,profile=1,level=64,bitrate=250136,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_320x320p50f32_400kbps_bp.mp4">
-    <value>mime=video/avc,size=29141,width=320,height=320,frame-rate=50,profile=1,level=128,bitrate=352144,package=standard</value>
+    <value>mime=video/avc,size=29141,width=320,height=320,frame-rate=50,profile=1,level=128,bitrate=352144,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_320x480p50f32_600kbps_bp.mp4">
-    <value>mime=video/avc,size=44862,width=320,height=480,frame-rate=50,profile=1,level=256,bitrate=548656,package=standard</value>
+    <value>mime=video/avc,size=44862,width=320,height=480,frame-rate=50,profile=1,level=256,bitrate=548656,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_32x24p50f32_200kbps_bp.mp4">
     <value>mime=video/avc,size=8809,width=32,height=24,frame-rate=50,profile=1,level=16,bitrate=98024,package=standard</value>
@@ -1023,10 +1026,10 @@
     <value>mime=video/avc,size=20950,width=352,height=264,frame-rate=50,profile=1,level=64,bitrate=249744,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_352x288p50f32_400kbps_bp.mp4">
-    <value>mime=video/avc,size=28447,width=352,height=288,frame-rate=50,profile=1,level=64,bitrate=343472,package=standard</value>
+    <value>mime=video/avc,size=28447,width=352,height=288,frame-rate=50,profile=1,level=64,bitrate=343472,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_352x352p50f32_400kbps_bp.mp4">
-    <value>mime=video/avc,size=29170,width=352,height=352,frame-rate=50,profile=1,level=256,bitrate=352512,package=standard</value>
+    <value>mime=video/avc,size=29170,width=352,height=352,frame-rate=50,profile=1,level=256,bitrate=352512,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_356x638p50f32_900kbps_bp.mp4">
     <value>mime=video/avc,size=70522,width=356,height=638,frame-rate=50,profile=1,level=512,bitrate=869408,package=standard</value>
@@ -1071,7 +1074,7 @@
     <value>mime=video/avc,size=2886460,width=3840,height=3840,frame-rate=50,profile=1,level=65536,bitrate=36068632,package=full</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_384x512p50f32_700kbps_bp.mp4">
-    <value>mime=video/avc,size=53478,width=384,height=512,frame-rate=50,profile=1,level=256,bitrate=656360,package=standard</value>
+    <value>mime=video/avc,size=53478,width=384,height=512,frame-rate=50,profile=1,level=256,bitrate=656360,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_4096x2304p50f32_24000kbps_bp.mp4">
     <value>mime=video/avc,size=2580560,width=4096,height=2304,frame-rate=50,profile=1,level=65536,bitrate=32244872,package=standard</value>
@@ -1083,19 +1086,19 @@
     <value>mime=video/avc,size=62178,width=472,height=472,frame-rate=50,profile=1,level=512,bitrate=765112,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x320p50f32_600kbps_bp.mp4">
-    <value>mime=video/avc,size=45288,width=480,height=320,frame-rate=50,profile=1,level=256,bitrate=553984,package=standard</value>
+    <value>mime=video/avc,size=45288,width=480,height=320,frame-rate=50,profile=1,level=256,bitrate=553984,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x360p50f32_600kbps_bp.mp4">
     <value>mime=video/avc,size=45502,width=480,height=360,frame-rate=50,profile=1,level=256,bitrate=556648,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x480p50f32_900kbps_bp.mp4">
-    <value>mime=video/avc,size=70488,width=480,height=480,frame-rate=50,profile=1,level=512,bitrate=868984,package=standard</value>
+    <value>mime=video/avc,size=70488,width=480,height=480,frame-rate=50,profile=1,level=512,bitrate=868984,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x640p50f32_1200kbps_bp.mp4">
-    <value>mime=video/avc,size=96257,width=480,height=640,frame-rate=50,profile=1,level=512,bitrate=1191096,package=standard</value>
+    <value>mime=video/avc,size=96257,width=480,height=640,frame-rate=50,profile=1,level=512,bitrate=1191096,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x720p50f32_1300kbps_bp.mp4">
-    <value>mime=video/avc,size=105435,width=480,height=720,frame-rate=50,profile=1,level=512,bitrate=1305824,package=standard</value>
+    <value>mime=video/avc,size=105435,width=480,height=720,frame-rate=50,profile=1,level=512,bitrate=1305824,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_480x848p50f32_1600kbps_bp.mp4">
     <value>mime=video/avc,size=130172,width=480,height=848,frame-rate=50,profile=1,level=512,bitrate=1615032,package=standard</value>
@@ -1104,10 +1107,10 @@
     <value>mime=video/avc,size=11955,width=48,height=64,frame-rate=50,profile=1,level=16,bitrate=137344,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_512x288p50f32_500kbps_bp.mp4">
-    <value>mime=video/avc,size=37248,width=512,height=288,frame-rate=50,profile=1,level=256,bitrate=453480,package=standard</value>
+    <value>mime=video/avc,size=37248,width=512,height=288,frame-rate=50,profile=1,level=256,bitrate=453480,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_512x384p50f32_700kbps_bp.mp4">
-    <value>mime=video/avc,size=53597,width=512,height=384,frame-rate=50,profile=1,level=256,bitrate=657848,package=standard</value>
+    <value>mime=video/avc,size=53597,width=512,height=384,frame-rate=50,profile=1,level=256,bitrate=657848,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_512x512p50f32_1000kbps_bp.mp4">
     <value>mime=video/avc,size=79003,width=512,height=512,frame-rate=50,profile=1,level=512,bitrate=975432,package=standard</value>
@@ -1149,7 +1152,7 @@
     <value>mime=video/avc,size=71029,width=640,height=360,frame-rate=50,profile=1,level=512,bitrate=875736,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_640x480p50f32_1200kbps_bp.mp4">
-    <value>mime=video/avc,size=96729,width=640,height=480,frame-rate=50,profile=1,level=512,bitrate=1197000,package=standard</value>
+    <value>mime=video/avc,size=96729,width=640,height=480,frame-rate=50,profile=1,level=512,bitrate=1197000,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_640x640p50f32_1600kbps_bp.mp4">
     <value>mime=video/avc,size=132674,width=640,height=640,frame-rate=50,profile=1,level=512,bitrate=1646320,package=standard</value>
@@ -1173,13 +1176,13 @@
     <value>mime=video/avc,size=11463,width=64,height=48,frame-rate=50,profile=1,level=16,bitrate=131200,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_64x64p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=11979,width=64,height=64,frame-rate=50,profile=1,level=16,bitrate=137632,package=standard</value>
+    <value>mime=video/avc,size=11979,width=64,height=64,frame-rate=50,profile=1,level=16,bitrate=137632,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_64x720p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13766,width=64,height=720,frame-rate=50,profile=1,level=16,bitrate=159968,package=standard</value>
+    <value>mime=video/avc,size=13766,width=64,height=720,frame-rate=50,profile=1,level=16,bitrate=159968,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_64x96p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=12767,width=64,height=96,frame-rate=50,profile=1,level=16,bitrate=147480,package=standard</value>
+    <value>mime=video/avc,size=12767,width=64,height=96,frame-rate=50,profile=1,level=16,bitrate=147480,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_704x528p50f32_1400kbps_bp.mp4">
     <value>mime=video/avc,size=114746,width=704,height=528,frame-rate=50,profile=1,level=512,bitrate=1422224,package=standard</value>
@@ -1197,13 +1200,13 @@
     <value>mime=video/avc,size=13146,width=720,height=16,frame-rate=50,profile=1,level=16,bitrate=152224,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_720x480p50f32_1300kbps_bp.mp4">
-    <value>mime=video/avc,size=105729,width=720,height=480,frame-rate=50,profile=1,level=512,bitrate=1309496,package=standard</value>
+    <value>mime=video/avc,size=105729,width=720,height=480,frame-rate=50,profile=1,level=512,bitrate=1309496,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_720x576p50f32_1600kbps_bp.mp4">
     <value>mime=video/avc,size=132863,width=720,height=576,frame-rate=50,profile=1,level=512,bitrate=1648680,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_720x64p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13556,width=720,height=64,frame-rate=50,profile=1,level=16,bitrate=157344,package=standard</value>
+    <value>mime=video/avc,size=13556,width=720,height=64,frame-rate=50,profile=1,level=16,bitrate=157344,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_720x720p50f32_2000kbps_bp.mp4">
     <value>mime=video/avc,size=170358,width=720,height=720,frame-rate=50,profile=1,level=512,bitrate=2117368,package=standard</value>
@@ -1254,22 +1257,22 @@
     <value>mime=video/avc,size=322999,width=960,height=960,frame-rate=50,profile=1,level=1024,bitrate=4025384,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x128p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13156,width=96,height=128,frame-rate=50,profile=1,level=16,bitrate=152344,package=standard</value>
+    <value>mime=video/avc,size=13156,width=96,height=128,frame-rate=50,profile=1,level=16,bitrate=152344,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x144p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13301,width=96,height=144,frame-rate=50,profile=1,level=16,bitrate=154160,package=standard</value>
+    <value>mime=video/avc,size=13301,width=96,height=144,frame-rate=50,profile=1,level=16,bitrate=154160,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x176p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13579,width=96,height=176,frame-rate=50,profile=1,level=16,bitrate=157632,package=standard</value>
+    <value>mime=video/avc,size=13579,width=96,height=176,frame-rate=50,profile=1,level=16,bitrate=157632,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x64p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=12221,width=96,height=64,frame-rate=50,profile=1,level=16,bitrate=140656,package=standard</value>
+    <value>mime=video/avc,size=12221,width=96,height=64,frame-rate=50,profile=1,level=16,bitrate=140656,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x72p50f32_200kbps_bp.mp4">
     <value>mime=video/avc,size=12682,width=96,height=72,frame-rate=50,profile=1,level=16,bitrate=146408,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/resolutions/crowd_96x96p50f32_200kbps_bp.mp4">
-    <value>mime=video/avc,size=13050,width=96,height=96,frame-rate=50,profile=1,level=16,bitrate=151024,package=standard</value>
+    <value>mime=video/avc,size=13050,width=96,height=96,frame-rate=50,profile=1,level=16,bitrate=151024,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/bp/slices/crowd_1280x720p50f32/crowd_1280x720p50f32_1024byte_slices_bp.mp4">
     <value>mime=video/avc,size=276000,width=1280,height=720,frame-rate=50,profile=1,level=1024,bitrate=3437880,package=standard</value>
@@ -4341,13 +4344,13 @@
     <value>mime=video/avc,size=319856,width=1280,height=720,frame-rate=50,profile=2,level=2048,bitrate=3982320,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_128x96p50f32_200kbps_level_2_mp.mp4">
-    <value>mime=video/avc,size=12333,width=128,height=96,frame-rate=50,profile=2,level=32,bitrate=139320,package=standard</value>
+    <value>mime=video/avc,size=12333,width=128,height=96,frame-rate=50,profile=2,level=32,bitrate=139320,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_16x16p50f32_200kbps_level_1_mp.mp4">
     <value>mime=video/avc,size=5415,width=16,height=16,frame-rate=50,profile=2,level=1,bitrate=54544,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_176x144p50f32_200kbps_level_2_mp.mp4">
-    <value>mime=video/avc,size=12174,width=176,height=144,frame-rate=50,profile=2,level=32,bitrate=136824,package=standard</value>
+    <value>mime=video/avc,size=12174,width=176,height=144,frame-rate=50,profile=2,level=32,bitrate=136824,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_1920x1080p50f32_8200kbps_level_4.2_mp.mp4">
     <value>mime=video/avc,size=821392,width=1920,height=1080,frame-rate=50,profile=2,level=8192,bitrate=10251448,package=standard</value>
@@ -4356,19 +4359,19 @@
     <value>mime=video/avc,size=829236,width=1920,height=1088,frame-rate=50,profile=2,level=16384,bitrate=10349568,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_352x288p50f32_400kbps_level_2.1_mp.mp4">
-    <value>mime=video/avc,size=26857,width=352,height=288,frame-rate=50,profile=2,level=64,bitrate=320680,package=standard</value>
+    <value>mime=video/avc,size=26857,width=352,height=288,frame-rate=50,profile=2,level=64,bitrate=320680,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_3840x2160p50f32_24000kbps_level_5.2_mp.mp4">
     <value>mime=video/avc,size=3353147,width=3840,height=2160,frame-rate=50,profile=2,level=65536,bitrate=41898400,package=full</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_640x480p50f32_1200kbps_level_3.1_mp.mp4">
-    <value>mime=video/avc,size=91490,width=640,height=480,frame-rate=50,profile=2,level=512,bitrate=1127808,package=standard</value>
+    <value>mime=video/avc,size=91490,width=640,height=480,frame-rate=50,profile=2,level=512,bitrate=1127808,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_64x64p50f32_200kbps_level_2_mp.mp4">
-    <value>mime=video/avc,size=11818,width=64,height=64,frame-rate=50,profile=2,level=32,bitrate=135624,package=standard</value>
+    <value>mime=video/avc,size=11818,width=64,height=64,frame-rate=50,profile=2,level=32,bitrate=135624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_720x480p50f32_1300kbps_level_3_mp.mp4">
-    <value>mime=video/avc,size=100078,width=720,height=480,frame-rate=50,profile=2,level=256,bitrate=1235024,package=standard</value>
+    <value>mime=video/avc,size=100078,width=720,height=480,frame-rate=50,profile=2,level=256,bitrate=1235024,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/levels/crowd_720x576p50f32_1600kbps_level_3.1_mp.mp4">
     <value>mime=video/avc,size=125751,width=720,height=576,frame-rate=50,profile=2,level=512,bitrate=1555960,package=standard</value>
@@ -5352,16 +5355,16 @@
     <value>mime=video/avc,size=446270,width=1280,height=960,frame-rate=50,profile=2,level=2048,bitrate=5562496,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_128x128p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12097,width=128,height=128,frame-rate=50,profile=2,level=16,bitrate=135560,package=standard</value>
+    <value>mime=video/avc,size=12097,width=128,height=128,frame-rate=50,profile=2,level=16,bitrate=135560,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_128x192p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12424,width=128,height=192,frame-rate=50,profile=2,level=16,bitrate=139648,package=standard</value>
+    <value>mime=video/avc,size=12424,width=128,height=192,frame-rate=50,profile=2,level=16,bitrate=139648,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_128x72p50f32_200kbps_mp.mp4">
     <value>mime=video/avc,size=12180,width=128,height=72,frame-rate=50,profile=2,level=16,bitrate=137200,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_128x96p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12333,width=128,height=96,frame-rate=50,profile=2,level=16,bitrate=139320,package=standard</value>
+    <value>mime=video/avc,size=12333,width=128,height=96,frame-rate=50,profile=2,level=16,bitrate=139320,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_1440x1080p50f32_6200kbps_mp.mp4">
     <value>mime=video/avc,size=587354,width=1440,height=1080,frame-rate=50,profile=2,level=8192,bitrate=7326032,package=standard</value>
@@ -5379,25 +5382,25 @@
     <value>mime=video/avc,size=512376,width=1440,height=960,frame-rate=50,profile=2,level=8192,bitrate=6388824,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_144x144p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=11994,width=144,height=144,frame-rate=50,profile=2,level=16,bitrate=134672,package=standard</value>
+    <value>mime=video/avc,size=11994,width=144,height=144,frame-rate=50,profile=2,level=16,bitrate=134672,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_144x176p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12433,width=144,height=176,frame-rate=50,profile=2,level=16,bitrate=139856,package=standard</value>
+    <value>mime=video/avc,size=12433,width=144,height=176,frame-rate=50,profile=2,level=16,bitrate=139856,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_144x192p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12328,width=144,height=192,frame-rate=50,profile=2,level=16,bitrate=138648,package=standard</value>
+    <value>mime=video/avc,size=12328,width=144,height=192,frame-rate=50,profile=2,level=16,bitrate=138648,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_144x256p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12710,width=144,height=256,frame-rate=50,profile=2,level=16,bitrate=143512,package=standard</value>
+    <value>mime=video/avc,size=12710,width=144,height=256,frame-rate=50,profile=2,level=16,bitrate=143512,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_144x96p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12152,width=144,height=96,frame-rate=50,profile=2,level=16,bitrate=137056,package=standard</value>
+    <value>mime=video/avc,size=12152,width=144,height=96,frame-rate=50,profile=2,level=16,bitrate=137056,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_1536x2048p50f32_12500kbps_mp.mp4">
     <value>mime=video/avc,size=1314463,width=1536,height=2048,frame-rate=50,profile=2,level=32768,bitrate=16414920,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_160x240p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12753,width=160,height=240,frame-rate=50,profile=2,level=16,bitrate=143960,package=standard</value>
+    <value>mime=video/avc,size=12753,width=160,height=240,frame-rate=50,profile=2,level=16,bitrate=143960,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_16x1080p50f32_200kbps_mp.mp4">
     <value>mime=video/avc,size=12347,width=16,height=1080,frame-rate=50,profile=2,level=64,bitrate=138768,package=standard</value>
@@ -5415,13 +5418,13 @@
     <value>mime=video/avc,size=12456,width=16,height=720,frame-rate=50,profile=2,level=16,bitrate=140656,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_176x144p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12174,width=176,height=144,frame-rate=50,profile=2,level=16,bitrate=136824,package=standard</value>
+    <value>mime=video/avc,size=12174,width=176,height=144,frame-rate=50,profile=2,level=16,bitrate=136824,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_176x176p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12523,width=176,height=176,frame-rate=50,profile=2,level=16,bitrate=140984,package=standard</value>
+    <value>mime=video/avc,size=12523,width=176,height=176,frame-rate=50,profile=2,level=16,bitrate=140984,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_176x96p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12373,width=176,height=96,frame-rate=50,profile=2,level=16,bitrate=140120,package=standard</value>
+    <value>mime=video/avc,size=12373,width=176,height=96,frame-rate=50,profile=2,level=16,bitrate=140120,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_1920x1080p50f32_8200kbps_mp.mp4">
     <value>mime=video/avc,size=821387,width=1920,height=1080,frame-rate=50,profile=2,level=8192,bitrate=10251448,package=standard</value>
@@ -5448,19 +5451,19 @@
     <value>mime=video/avc,size=24539,width=1920,height=64,frame-rate=50,profile=2,level=512,bitrate=290872,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_192x128p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12219,width=192,height=128,frame-rate=50,profile=2,level=16,bitrate=137280,package=standard</value>
+    <value>mime=video/avc,size=12219,width=192,height=128,frame-rate=50,profile=2,level=16,bitrate=137280,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_192x144p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12593,width=192,height=144,frame-rate=50,profile=2,level=16,bitrate=143256,package=standard</value>
+    <value>mime=video/avc,size=12593,width=192,height=144,frame-rate=50,profile=2,level=16,bitrate=143256,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_192x192p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12501,width=192,height=192,frame-rate=50,profile=2,level=16,bitrate=140408,package=standard</value>
+    <value>mime=video/avc,size=12501,width=192,height=192,frame-rate=50,profile=2,level=16,bitrate=140408,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_192x256p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12863,width=192,height=256,frame-rate=50,profile=2,level=16,bitrate=145320,package=standard</value>
+    <value>mime=video/avc,size=12863,width=192,height=256,frame-rate=50,profile=2,level=16,bitrate=145320,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_192x288p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12650,width=192,height=288,frame-rate=50,profile=2,level=16,bitrate=142456,package=standard</value>
+    <value>mime=video/avc,size=12650,width=192,height=288,frame-rate=50,profile=2,level=16,bitrate=142456,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_2048x1152p50f32_9400kbps_mp.mp4">
     <value>mime=video/avc,size=917154,width=2048,height=1152,frame-rate=50,profile=2,level=16384,bitrate=11448560,package=standard</value>
@@ -5481,13 +5484,13 @@
     <value>mime=video/avc,size=2777432,width=2304,height=4096,frame-rate=50,profile=2,level=65536,bitrate=34702008,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_240x160p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12596,width=240,height=160,frame-rate=50,profile=2,level=16,bitrate=141696,package=standard</value>
+    <value>mime=video/avc,size=12596,width=240,height=160,frame-rate=50,profile=2,level=16,bitrate=141696,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_240x240p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12790,width=240,height=240,frame-rate=50,profile=2,level=16,bitrate=144424,package=standard</value>
+    <value>mime=video/avc,size=12790,width=240,height=240,frame-rate=50,profile=2,level=16,bitrate=144424,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_240x320p50f32_300kbps_mp.mp4">
-    <value>mime=video/avc,size=19720,width=240,height=320,frame-rate=50,profile=2,level=64,bitrate=230832,package=standard</value>
+    <value>mime=video/avc,size=19720,width=240,height=320,frame-rate=50,profile=2,level=64,bitrate=230832,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_240x360p50f32_300kbps_mp.mp4">
     <value>mime=video/avc,size=19801,width=240,height=360,frame-rate=50,profile=2,level=64,bitrate=231736,package=standard</value>
@@ -5508,13 +5511,13 @@
     <value>mime=video/avc,size=2796926,width=2560,height=3840,frame-rate=50,profile=2,level=65536,bitrate=34945696,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_256x144p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12529,width=256,height=144,frame-rate=50,profile=2,level=16,bitrate=140744,package=standard</value>
+    <value>mime=video/avc,size=12529,width=256,height=144,frame-rate=50,profile=2,level=16,bitrate=140744,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_256x192p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12550,width=256,height=192,frame-rate=50,profile=2,level=16,bitrate=141312,package=standard</value>
+    <value>mime=video/avc,size=12550,width=256,height=192,frame-rate=50,profile=2,level=16,bitrate=141312,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_256x256p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12620,width=256,height=256,frame-rate=50,profile=2,level=64,bitrate=142384,package=standard</value>
+    <value>mime=video/avc,size=12620,width=256,height=256,frame-rate=50,profile=2,level=64,bitrate=142384,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_264x352p50f32_300kbps_mp.mp4">
     <value>mime=video/avc,size=19625,width=264,height=352,frame-rate=50,profile=2,level=64,bitrate=229536,package=standard</value>
@@ -5523,28 +5526,28 @@
     <value>mime=video/avc,size=2880482,width=2880,height=3840,frame-rate=50,profile=2,level=65536,bitrate=35990144,package=full</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_288x192p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12507,width=288,height=192,frame-rate=50,profile=2,level=16,bitrate=140672,package=standard</value>
+    <value>mime=video/avc,size=12507,width=288,height=192,frame-rate=50,profile=2,level=16,bitrate=140672,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_288x216p50f32_200kbps_mp.mp4">
     <value>mime=video/avc,size=12750,width=288,height=216,frame-rate=50,profile=2,level=64,bitrate=144096,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_288x288p50f32_300kbps_mp.mp4">
-    <value>mime=video/avc,size=19640,width=288,height=288,frame-rate=50,profile=2,level=64,bitrate=230232,package=standard</value>
+    <value>mime=video/avc,size=19640,width=288,height=288,frame-rate=50,profile=2,level=64,bitrate=230232,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_288x352p50f32_400kbps_mp.mp4">
-    <value>mime=video/avc,size=26931,width=288,height=352,frame-rate=50,profile=2,level=64,bitrate=321072,package=standard</value>
+    <value>mime=video/avc,size=26931,width=288,height=352,frame-rate=50,profile=2,level=64,bitrate=321072,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_288x512p50f32_500kbps_mp.mp4">
-    <value>mime=video/avc,size=34424,width=288,height=512,frame-rate=50,profile=2,level=256,bitrate=414544,package=standard</value>
+    <value>mime=video/avc,size=34424,width=288,height=512,frame-rate=50,profile=2,level=256,bitrate=414544,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_320x240p50f32_300kbps_mp.mp4">
-    <value>mime=video/avc,size=19675,width=320,height=240,frame-rate=50,profile=2,level=64,bitrate=230272,package=standard</value>
+    <value>mime=video/avc,size=19675,width=320,height=240,frame-rate=50,profile=2,level=64,bitrate=230272,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_320x320p50f32_400kbps_mp.mp4">
-    <value>mime=video/avc,size=27750,width=320,height=320,frame-rate=50,profile=2,level=128,bitrate=331808,package=standard</value>
+    <value>mime=video/avc,size=27750,width=320,height=320,frame-rate=50,profile=2,level=128,bitrate=331808,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_320x480p50f32_600kbps_mp.mp4">
-    <value>mime=video/avc,size=41960,width=320,height=480,frame-rate=50,profile=2,level=256,bitrate=508832,package=standard</value>
+    <value>mime=video/avc,size=41960,width=320,height=480,frame-rate=50,profile=2,level=256,bitrate=508832,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_32x24p50f32_200kbps_mp.mp4">
     <value>mime=video/avc,size=8516,width=32,height=24,frame-rate=50,profile=2,level=16,bitrate=94344,package=standard</value>
@@ -5559,10 +5562,10 @@
     <value>mime=video/avc,size=19489,width=352,height=264,frame-rate=50,profile=2,level=64,bitrate=228432,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_352x288p50f32_400kbps_mp.mp4">
-    <value>mime=video/avc,size=26852,width=352,height=288,frame-rate=50,profile=2,level=64,bitrate=320680,package=standard</value>
+    <value>mime=video/avc,size=26852,width=352,height=288,frame-rate=50,profile=2,level=64,bitrate=320680,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_352x352p50f32_400kbps_mp.mp4">
-    <value>mime=video/avc,size=27834,width=352,height=352,frame-rate=50,profile=2,level=256,bitrate=332560,package=standard</value>
+    <value>mime=video/avc,size=27834,width=352,height=352,frame-rate=50,profile=2,level=256,bitrate=332560,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_356x638p50f32_900kbps_mp.mp4">
     <value>mime=video/avc,size=65821,width=356,height=638,frame-rate=50,profile=2,level=512,bitrate=806880,package=standard</value>
@@ -5604,7 +5607,7 @@
     <value>mime=video/avc,size=2837257,width=3840,height=2880,frame-rate=50,profile=2,level=65536,bitrate=35449832,package=full</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_384x512p50f32_700kbps_mp.mp4">
-    <value>mime=video/avc,size=50438,width=384,height=512,frame-rate=50,profile=2,level=256,bitrate=614720,package=standard</value>
+    <value>mime=video/avc,size=50438,width=384,height=512,frame-rate=50,profile=2,level=256,bitrate=614720,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_4096x2304p50f32_24000kbps_mp.mp4">
     <value>mime=video/avc,size=2713484,width=4096,height=2304,frame-rate=50,profile=2,level=65536,bitrate=33902656,package=standard</value>
@@ -5613,19 +5616,19 @@
     <value>mime=video/avc,size=58830,width=472,height=472,frame-rate=50,profile=2,level=512,bitrate=719800,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x320p50f32_600kbps_mp.mp4">
-    <value>mime=video/avc,size=42865,width=480,height=320,frame-rate=50,profile=2,level=256,bitrate=520344,package=standard</value>
+    <value>mime=video/avc,size=42865,width=480,height=320,frame-rate=50,profile=2,level=256,bitrate=520344,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x360p50f32_600kbps_mp.mp4">
     <value>mime=video/avc,size=42938,width=480,height=360,frame-rate=50,profile=2,level=256,bitrate=521144,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x480p50f32_900kbps_mp.mp4">
-    <value>mime=video/avc,size=66588,width=480,height=480,frame-rate=50,profile=2,level=512,bitrate=816784,package=standard</value>
+    <value>mime=video/avc,size=66588,width=480,height=480,frame-rate=50,profile=2,level=512,bitrate=816784,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x640p50f32_1200kbps_mp.mp4">
-    <value>mime=video/avc,size=90927,width=480,height=640,frame-rate=50,profile=2,level=512,bitrate=1120736,package=standard</value>
+    <value>mime=video/avc,size=90927,width=480,height=640,frame-rate=50,profile=2,level=512,bitrate=1120736,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x720p50f32_1300kbps_mp.mp4">
-    <value>mime=video/avc,size=100070,width=480,height=720,frame-rate=50,profile=2,level=512,bitrate=1235024,package=standard</value>
+    <value>mime=video/avc,size=100070,width=480,height=720,frame-rate=50,profile=2,level=512,bitrate=1235024,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_480x848p50f32_1600kbps_mp.mp4">
     <value>mime=video/avc,size=121917,width=480,height=848,frame-rate=50,profile=2,level=512,bitrate=1508112,package=standard</value>
@@ -5634,10 +5637,10 @@
     <value>mime=video/avc,size=11093,width=48,height=64,frame-rate=50,profile=2,level=16,bitrate=124608,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_512x288p50f32_500kbps_mp.mp4">
-    <value>mime=video/avc,size=35423,width=512,height=288,frame-rate=50,profile=2,level=256,bitrate=427632,package=standard</value>
+    <value>mime=video/avc,size=35423,width=512,height=288,frame-rate=50,profile=2,level=256,bitrate=427632,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_512x384p50f32_700kbps_mp.mp4">
-    <value>mime=video/avc,size=50309,width=512,height=384,frame-rate=50,profile=2,level=256,bitrate=613008,package=standard</value>
+    <value>mime=video/avc,size=50309,width=512,height=384,frame-rate=50,profile=2,level=256,bitrate=613008,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_512x512p50f32_1000kbps_mp.mp4">
     <value>mime=video/avc,size=74581,width=512,height=512,frame-rate=50,profile=2,level=512,bitrate=916496,package=standard</value>
@@ -5679,7 +5682,7 @@
     <value>mime=video/avc,size=66239,width=640,height=360,frame-rate=50,profile=2,level=512,bitrate=812224,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_640x480p50f32_1200kbps_mp.mp4">
-    <value>mime=video/avc,size=91485,width=640,height=480,frame-rate=50,profile=2,level=512,bitrate=1127808,package=standard</value>
+    <value>mime=video/avc,size=91485,width=640,height=480,frame-rate=50,profile=2,level=512,bitrate=1127808,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_640x640p50f32_1600kbps_mp.mp4">
     <value>mime=video/avc,size=125839,width=640,height=640,frame-rate=50,profile=2,level=512,bitrate=1557120,package=standard</value>
@@ -5703,13 +5706,13 @@
     <value>mime=video/avc,size=11198,width=64,height=48,frame-rate=50,profile=2,level=16,bitrate=126824,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_64x64p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=11818,width=64,height=64,frame-rate=50,profile=2,level=16,bitrate=135624,package=standard</value>
+    <value>mime=video/avc,size=11818,width=64,height=64,frame-rate=50,profile=2,level=16,bitrate=135624,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_64x720p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12619,width=64,height=720,frame-rate=50,profile=2,level=16,bitrate=141872,package=standard</value>
+    <value>mime=video/avc,size=12619,width=64,height=720,frame-rate=50,profile=2,level=16,bitrate=141872,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_64x96p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=11987,width=64,height=96,frame-rate=50,profile=2,level=16,bitrate=135280,package=standard</value>
+    <value>mime=video/avc,size=11987,width=64,height=96,frame-rate=50,profile=2,level=16,bitrate=135280,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_704x528p50f32_1400kbps_mp.mp4">
     <value>mime=video/avc,size=108268,width=704,height=528,frame-rate=50,profile=2,level=512,bitrate=1337480,package=standard</value>
@@ -5727,13 +5730,13 @@
     <value>mime=video/avc,size=12892,width=720,height=16,frame-rate=50,profile=2,level=16,bitrate=147208,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_720x480p50f32_1300kbps_mp.mp4">
-    <value>mime=video/avc,size=100078,width=720,height=480,frame-rate=50,profile=2,level=512,bitrate=1235024,package=standard</value>
+    <value>mime=video/avc,size=100078,width=720,height=480,frame-rate=50,profile=2,level=512,bitrate=1235024,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_720x576p50f32_1600kbps_mp.mp4">
     <value>mime=video/avc,size=125746,width=720,height=576,frame-rate=50,profile=2,level=512,bitrate=1555960,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_720x64p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12764,width=720,height=64,frame-rate=50,profile=2,level=16,bitrate=143880,package=standard</value>
+    <value>mime=video/avc,size=12764,width=720,height=64,frame-rate=50,profile=2,level=16,bitrate=143880,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_720x720p50f32_2000kbps_mp.mp4">
     <value>mime=video/avc,size=162293,width=720,height=720,frame-rate=50,profile=2,level=512,bitrate=2012800,package=standard</value>
@@ -5784,22 +5787,22 @@
     <value>mime=video/avc,size=313704,width=960,height=960,frame-rate=50,profile=2,level=1024,bitrate=3905432,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x128p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12441,width=96,height=128,frame-rate=50,profile=2,level=16,bitrate=140072,package=standard</value>
+    <value>mime=video/avc,size=12441,width=96,height=128,frame-rate=50,profile=2,level=16,bitrate=140072,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x144p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12096,width=96,height=144,frame-rate=50,profile=2,level=16,bitrate=135760,package=standard</value>
+    <value>mime=video/avc,size=12096,width=96,height=144,frame-rate=50,profile=2,level=16,bitrate=135760,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x176p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12277,width=96,height=176,frame-rate=50,profile=2,level=16,bitrate=137920,package=standard</value>
+    <value>mime=video/avc,size=12277,width=96,height=176,frame-rate=50,profile=2,level=16,bitrate=137920,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x64p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=11982,width=96,height=64,frame-rate=50,profile=2,level=16,bitrate=137672,package=standard</value>
+    <value>mime=video/avc,size=11982,width=96,height=64,frame-rate=50,profile=2,level=16,bitrate=137672,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x72p50f32_200kbps_mp.mp4">
     <value>mime=video/avc,size=12083,width=96,height=72,frame-rate=50,profile=2,level=16,bitrate=136368,package=standard</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/resolutions/crowd_96x96p50f32_200kbps_mp.mp4">
-    <value>mime=video/avc,size=12293,width=96,height=96,frame-rate=50,profile=2,level=16,bitrate=138912,package=standard</value>
+    <value>mime=video/avc,size=12293,width=96,height=96,frame-rate=50,profile=2,level=16,bitrate=138912,package=standard,enforce=true</value>
   </entry>
   <entry key="h264/yuv420/8bit/mp/slices/crowd_1280x720p50f32/crowd_1280x720p50f32_1024byte_slices_mp.mp4">
     <value>mime=video/avc,size=271360,width=1280,height=720,frame-rate=50,profile=2,level=1024,bitrate=3376120,package=standard</value>
@@ -25740,22 +25743,22 @@
     <value>mime=video/x-vnd.on2.vp8,size=36285942,width=3840,height=2160,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p24f300_1000kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1588213,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1588213,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p25f300_1000kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1523834,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1523834,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p30f300_1000kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1274889,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1274889,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p48f300_1500kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1213728,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1213728,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p50f300_1500kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1171849,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1171849,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_640x360p60f300_1500kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=976943,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=976943,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/bitrate/crowd_854x480p24f300_2500kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=3973482,width=854,height=480,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -26253,163 +26256,163 @@
     <value>mime=video/x-vnd.on2.vp8,size=4062168,width=3840,height=2160,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_0f_0s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_0f_5s_1t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_0f_5s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_15f_0s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_15f_5s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_5f_0s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_5f_5s_2t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_arnr_5f_5s_3t.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_autAltRef_8lag.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_autAltRef.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cbr.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=122296,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=122296,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cq10.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cq16.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cq32.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cq4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_cq63.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_downScale_10_noKey.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_downScale_20_noKey.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_downScale_50_noKey.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_downScale_80_noKey.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_errRes.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=122754,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=122754,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_keyFrmOnly.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=278508,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=278508,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_alternateKey_1x4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=278508,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=278508,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_key240_1x4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_key30_1x4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=139344,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=139344,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_key60_1x4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_key90_1x4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noKeyFrm.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_nonErrRes.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp0-10.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=1162750,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=1162750,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp0-63.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp0.webm">
     <value>mime=video/x-vnd.on2.vp8,size=3647597,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp10-20.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=702681,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=702681,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp20-30.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=435106,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=435106,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp30-40.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=261310,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=261310,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp32.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=382962,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=382962,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp40-50.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=138311,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=138311,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp50-63.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=50558,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=50558,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_Qp63.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=41786,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=41786,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp0.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp1.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127017,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127017,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp2.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127302,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127302,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp3.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127145,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127145,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp4.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127183,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127183,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp5.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127149,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127149,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp6.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=127092,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=127092,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_sharp7.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=126925,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=126925,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_skip5frm.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=121018,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=121018,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_upScale_100.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_upScale_10.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_upScale_20.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_upScale_50.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_vbr.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=124794,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/params/crowd_854x480p50f32/crowd_854x480p50f32_arnr_0f_0s_2t.webm">
     <value>mime=video/x-vnd.on2.vp8,size=214838,width=854,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26616,16 +26619,16 @@
     <value>mime=video/x-vnd.on2.vp8,size=544433,width=1280,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_128x128p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=22573,width=128,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=22573,width=128,height=128,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_128x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=25964,width=128,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=25964,width=128,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_128x72p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=19288,width=128,height=72,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=19288,width=128,height=72,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_128x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=21679,width=128,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=21679,width=128,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_1440x1080p50f32_3100kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=732471,width=1440,height=1080,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26643,25 +26646,25 @@
     <value>mime=video/x-vnd.on2.vp8,size=607624,width=1440,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_144x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=24431,width=144,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=24431,width=144,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_144x176p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=26701,width=144,height=176,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=26701,width=144,height=176,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_144x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=26749,width=144,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=26749,width=144,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_144x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=30324,width=144,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=30324,width=144,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_144x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=21810,width=144,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=21810,width=144,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_1536x2048p50f32_6200kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=1594581,width=1536,height=2048,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_160x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=30543,width=160,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=30543,width=160,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_16x1080p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=21812,width=16,height=1080,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26679,13 +26682,13 @@
     <value>mime=video/x-vnd.on2.vp8,size=20743,width=16,height=720,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_172x172p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=27116,width=172,height=172,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=27116,width=172,height=172,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_176x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=26536,width=176,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=26536,width=176,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_176x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=23213,width=176,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=23213,width=176,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_1920x1080p50f32_4100kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=957687,width=1920,height=1080,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26712,19 +26715,19 @@
     <value>mime=video/x-vnd.on2.vp8,size=47544,width=1920,height=64,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_192x128p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=26210,width=192,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=26210,width=192,height=128,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_192x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=27399,width=192,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=27399,width=192,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_192x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=30298,width=192,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=30298,width=192,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_192x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=35251,width=192,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=35251,width=192,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_192x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=38961,width=192,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=38961,width=192,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_2048x1152p50f32_4700kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=1196505,width=2048,height=1152,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26739,22 +26742,22 @@
     <value>mime=video/x-vnd.on2.vp8,size=2280972,width=2160,height=3840,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_216x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=44099,width=216,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=44099,width=216,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_2304x4096p50f32_18800kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=2638026,width=2304,height=4096,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_240x160p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=31009,width=240,height=160,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=31009,width=240,height=160,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_240x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=40814,width=240,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=40814,width=240,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_240x320p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=50030,width=240,height=320,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=50030,width=240,height=320,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_240x360p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=52756,width=240,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=52756,width=240,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_24x32p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=7659,width=24,height=32,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26772,34 +26775,34 @@
     <value>mime=video/x-vnd.on2.vp8,size=2762075,width=2560,height=3840,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_256x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=31222,width=256,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=31222,width=256,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_256x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=35669,width=256,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=35669,width=256,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_256x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=45820,width=256,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=45820,width=256,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_264x352p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=53957,width=264,height=352,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=53957,width=264,height=352,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_2880x3840p50f32_22100kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=3080416,width=2880,height=3840,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_288x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=39629,width=288,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=39629,width=288,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_288x216p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=44825,width=288,height=216,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=44825,width=288,height=216,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_288x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=49855,width=288,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=49855,width=288,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_288x352p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=54866,width=288,height=352,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=54866,width=288,height=352,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_288x512p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=58929,width=288,height=512,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=58929,width=288,height=512,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_3072x4096p50f32_24000kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=3468897,width=3072,height=4096,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -26808,13 +26811,13 @@
     <value>mime=video/x-vnd.on2.vp8,size=3546364,width=3120,height=4160,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_320x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=49907,width=320,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=49907,width=320,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_320x320p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=52171,width=320,height=320,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=52171,width=320,height=320,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_320x480p50f32_300kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=78944,width=320,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=78944,width=320,height=480,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_32x24p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=7192,width=32,height=24,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26823,46 +26826,46 @@
     <value>mime=video/x-vnd.on2.vp8,size=7411,width=32,height=32,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_350x350p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=52974,width=350,height=350,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=52974,width=350,height=350,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_352x264p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=53324,width=352,height=264,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=53324,width=352,height=264,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_352x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=53275,width=352,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=53275,width=352,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_352x352p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=56687,width=352,height=352,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=56687,width=352,height=352,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_356x638p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144407,width=356,height=638,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144407,width=356,height=638,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_356x640p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144233,width=356,height=640,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144233,width=356,height=640,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_358x636p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144934,width=358,height=636,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144934,width=358,height=636,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_358x640p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144989,width=358,height=640,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144989,width=358,height=640,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=52067,width=360,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=52067,width=360,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x360p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=56298,width=360,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=56298,width=360,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x480p50f32_300kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=80276,width=360,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=80276,width=360,height=480,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x636p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144803,width=360,height=636,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144803,width=360,height=636,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x638p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=145053,width=360,height=638,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=145053,width=360,height=638,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_360x640p50f32_400kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=109983,width=360,height=640,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=109983,width=360,height=640,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_3840x2160p50f32_16500kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=4061905,width=3840,height=2160,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -26904,10 +26907,10 @@
     <value>mime=video/x-vnd.on2.vp8,size=111263,width=472,height=472,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_480x320p50f32_300kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=79954,width=480,height=320,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=79954,width=480,height=320,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_480x360p50f32_300kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=81735,width=480,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=81735,width=480,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_480x480p50f32_400kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=111711,width=480,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26928,7 +26931,7 @@
     <value>mime=video/x-vnd.on2.vp8,size=6358861,width=5120,height=7680,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_512x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=58038,width=512,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=58038,width=512,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_512x384p50f32_300kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=84510,width=512,height=384,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -26958,25 +26961,25 @@
     <value>mime=video/x-vnd.on2.vp8,size=8098503,width=6144,height=8192,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_636x358p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144270,width=636,height=358,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144270,width=636,height=358,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_636x360p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144411,width=636,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144411,width=636,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_638x356p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144483,width=638,height=356,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144483,width=638,height=356,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_638x360p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=145021,width=638,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=145021,width=638,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_640x356p50f32_800kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=144416,width=640,height=356,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=144416,width=640,height=356,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_640x358p50f32_400kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=103745,width=640,height=358,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=103745,width=640,height=358,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_640x360p50f32_400kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=106663,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=106663,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_640x480p50f32_600kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=147651,width=640,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -27003,13 +27006,13 @@
     <value>mime=video/x-vnd.on2.vp8,size=17239,width=64,height=48,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_64x64p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=18514,width=64,height=64,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=18514,width=64,height=64,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_64x720p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=32501,width=64,height=720,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_64x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=19213,width=64,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=19213,width=64,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_704x528p50f32_700kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=172909,width=704,height=528,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -27042,10 +27045,10 @@
     <value>mime=video/x-vnd.on2.vp8,size=294296,width=720,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_72x128p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=21203,width=72,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=21203,width=72,height=128,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_72x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=20015,width=72,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=20015,width=72,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_7680x4320p50f32_24000kbps.webm">
     <value>mime=video/x-vnd.on2.vp8,size=8365736,width=7680,height=4320,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -27105,22 +27108,22 @@
     <value>mime=video/x-vnd.on2.vp8,size=418761,width=960,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x128p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=21046,width=96,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=21046,width=96,height=128,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=21094,width=96,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=21094,width=96,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x176p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=23140,width=96,height=176,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=23140,width=96,height=176,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x64p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=19097,width=96,height=64,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=19097,width=96,height=64,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x72p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=19164,width=96,height=72,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=19164,width=96,height=72,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp8/yuv420/8bit/resolution/crowd_96x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp8,size=20262,width=96,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp8,size=20262,width=96,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/10bit/bitrate/crowd_1280x720p24f300_3000kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=4825448,width=1280,height=720,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -30360,7 +30363,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=40409,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_frmPar_Qp63_1x2.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=9967,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=9967,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_frmPar_sharp0_1x2.webm">
     <value>mime=video/x-vnd.on2.vp9,size=40277,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -30387,7 +30390,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=40312,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_frmPar_skip5frm_1x2.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=34224,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=34224,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_frmPar_upScale_100_1x2.webm">
     <value>mime=video/x-vnd.on2.vp9,size=40277,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -30534,7 +30537,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=40457,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_Qp63_1x2.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=10122,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=10122,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_sharp0_1x2.webm">
     <value>mime=video/x-vnd.on2.vp9,size=40149,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -30561,7 +30564,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=39969,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_skip5frm_1x2.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=34188,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=34188,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/params/crowd_640x360p50f32/crowd_640x360p50f32_noFrmPar_upScale_100_1x2.webm">
     <value>mime=video/x-vnd.on2.vp9,size=40149,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -30978,10 +30981,10 @@
     <value>mime=video/x-vnd.on2.vp9,size=20315,width=128,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_128x72p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=22203,width=128,height=72,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=22203,width=128,height=72,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_128x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21764,width=128,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21764,width=128,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_1440x1080p50f32_3100kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=256607,width=1440,height=1080,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -30999,7 +31002,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=219548,width=1440,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_144x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21401,width=144,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21401,width=144,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_144x176p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=20035,width=144,height=176,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31008,10 +31011,10 @@
     <value>mime=video/x-vnd.on2.vp9,size=19508,width=144,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_144x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=19277,width=144,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=19277,width=144,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_144x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21945,width=144,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21945,width=144,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_1536x2048p50f32_6200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=491463,width=1536,height=2048,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31038,10 +31041,10 @@
     <value>mime=video/x-vnd.on2.vp9,size=20623,width=172,height=172,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_176x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21221,width=176,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21221,width=176,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_176x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21803,width=176,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21803,width=176,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_1920x1080p50f32_4100kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=347186,width=1920,height=1080,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31071,13 +31074,13 @@
     <value>mime=video/x-vnd.on2.vp9,size=21688,width=192,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_192x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21404,width=192,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21404,width=192,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_192x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=19632,width=192,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=19632,width=192,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_192x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=18824,width=192,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=18824,width=192,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_192x288p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=18313,width=192,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31095,7 +31098,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=1428214,width=2160,height=3840,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_216x288p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=17908,width=216,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=17908,width=216,height=288,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_2304x4096p50f32_18800kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=1667866,width=2304,height=4096,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -31104,13 +31107,13 @@
     <value>mime=video/x-vnd.on2.vp9,size=19372,width=240,height=160,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_240x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=17984,width=240,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=17984,width=240,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_240x320p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=16501,width=240,height=320,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=16501,width=240,height=320,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_240x360p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=16476,width=240,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=16476,width=240,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_24x32p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=13562,width=24,height=32,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31131,13 +31134,13 @@
     <value>mime=video/x-vnd.on2.vp9,size=19811,width=256,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_256x192p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=18844,width=256,height=192,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=18844,width=256,height=192,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_256x256p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=17692,width=256,height=256,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=17692,width=256,height=256,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_264x352p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=16651,width=264,height=352,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=16651,width=264,height=352,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_2880x3840p50f32_22100kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=1962521,width=2880,height=3840,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -31164,7 +31167,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=2135393,width=3120,height=4160,frame-rate=,profile=,level=,bitrate=,package=full</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_320x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=17161,width=320,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=17161,width=320,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_320x320p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=16450,width=320,height=320,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31188,7 +31191,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=16441,width=352,height=288,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_352x352p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=17348,width=352,height=352,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=17348,width=352,height=352,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_356x638p50f32_800kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=66226,width=356,height=638,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31203,13 +31206,13 @@
     <value>mime=video/x-vnd.on2.vp9,size=66220,width=358,height=640,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_360x240p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=16499,width=360,height=240,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=16499,width=360,height=240,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_360x360p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=18134,width=360,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=18134,width=360,height=360,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_360x480p50f32_300kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=24969,width=360,height=480,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=24969,width=360,height=480,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_360x636p50f32_800kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=66331,width=360,height=636,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31329,7 +31332,7 @@
     <value>mime=video/x-vnd.on2.vp9,size=75535,width=640,height=356,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_640x358p50f32_400kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=32962,width=640,height=358,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=32962,width=640,height=358,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_640x360p50f32_400kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=33152,width=640,height=360,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31359,13 +31362,13 @@
     <value>mime=video/x-vnd.on2.vp9,size=19429,width=64,height=48,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_64x64p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=20702,width=64,height=64,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=20702,width=64,height=64,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_64x720p50f32_200kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=18073,width=64,height=720,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_64x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=22194,width=64,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=22194,width=64,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_704x528p50f32_700kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=56606,width=704,height=528,frame-rate=,profile=,level=,bitrate=,package=standard</value>
@@ -31398,10 +31401,10 @@
     <value>mime=video/x-vnd.on2.vp9,size=104036,width=720,height=960,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_72x128p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21963,width=72,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21963,width=72,height=128,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_72x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=22305,width=72,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=22305,width=72,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_7680x4320p50f32_24000kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=2078023,width=7680,height=4320,frame-rate=,profile=,level=,bitrate=,package=full</value>
@@ -31464,19 +31467,19 @@
     <value>mime=video/x-vnd.on2.vp9,size=21565,width=96,height=128,frame-rate=,profile=,level=,bitrate=,package=standard</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_96x144p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21704,width=96,height=144,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21704,width=96,height=144,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_96x176p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=20834,width=96,height=176,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=20834,width=96,height=176,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_96x64p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21980,width=96,height=64,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21980,width=96,height=64,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_96x72p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=21496,width=96,height=72,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=21496,width=96,height=72,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv420/8bit/resolution/crowd_96x96p50f32_200kbps.webm">
-    <value>mime=video/x-vnd.on2.vp9,size=22764,width=96,height=96,frame-rate=,profile=,level=,bitrate=,package=standard</value>
+    <value>mime=video/x-vnd.on2.vp9,size=22764,width=96,height=96,frame-rate=,profile=,level=,bitrate=,package=standard,enforce=true</value>
   </entry>
   <entry key="vp9/yuv422/10bit/bitrate/crowd_1280x720p24f300_3000kbps.webm">
     <value>mime=video/x-vnd.on2.vp9,size=5005326,width=1280,height=720,frame-rate=,profile=,level=,bitrate=,package=full</value>
diff --git a/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
index 13b70a5..fbf7491 100644
--- a/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
+++ b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
@@ -61,6 +61,7 @@
     public static final String DYNAMIC_CONFIG_KEY = "key";
     public static final String DYNAMIC_CONFIG_VALUE = "value";
     public static final String DYNAMIC_CONFIG_PACKAGE = "package";
+    public static final String DYNAMIC_CONFIG_ENFORCE = "enforce";
 
     /* utilities */
     /**
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
index 457c636..72bb15a 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public H264Yuv420_8bitBpBitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
index 78cd436..5bcfe7e 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public H264Yuv420_8bitBpBitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
index 606a169..7db0198f 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public H264Yuv420_8bitHpBitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
index 792fa5e..6ba3822 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public H264Yuv420_8bitHpBitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
index ebacc3c..4811150 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public H264Yuv420_8bitMpBitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
index ce80c96..b7f2c67 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public H264Yuv420_8bitMpBitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
index 59ba131..e00283f 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public HevcYuv400BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
index 1680ff2..eb0fda7 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public HevcYuv420BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
index 0fd0c3c..1334c82 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public HevcYuv420BitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
index ee7a683..0efdad2 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public HevcYuv422BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
index f167cb6..0f564ea 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public HevcYuv444BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
index cb21724..3d16367 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
@@ -53,6 +53,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.xmlpull.v1.XmlPullParser;
@@ -109,6 +110,7 @@
 
     private BitstreamPackage mPackage = BitstreamPackage.FULL;
     private BitstreamPackage mPackageToRun = BitstreamPackage.STANDARD;
+    private boolean mEnforce = false;
 
     static class ConformanceEntry {
         final String mPath, mCodecName, mStatus;
@@ -177,6 +179,7 @@
                 String format = parser.getText();
                 String[] kvPairs = format.split(",");
                 BitstreamPackage curPackage = BitstreamPackage.FULL;
+                boolean enforce = false;
                 for (String kvPair : kvPairs) {
                     String[] kv = kvPair.split("=");
                     if (MediaBitstreams.DYNAMIC_CONFIG_PACKAGE.equals(kv[0])) {
@@ -186,10 +189,12 @@
                         } catch (Exception e) {
                             CLog.w(e);
                         }
+                    } else if (MediaBitstreams.DYNAMIC_CONFIG_ENFORCE.equals(kv[0])) {
+                        enforce = "true".equals(kv[1]);
                     }
                 }
                 if (curPackage.compareTo(packageToRun) <= 0) {
-                    entries.add(new Object[] {prefix, bitstream, curPackage, packageToRun});
+                    entries.add(new Object[] {prefix, bitstream, curPackage, packageToRun, enforce});
                 }
             }
             return entries;
@@ -201,10 +206,16 @@
 
     public MediaBitstreamsTest(String prefix, String path, BitstreamPackage pkg, BitstreamPackage packageToRun
             ) {
+        this(prefix, path, pkg, packageToRun, false);
+    }
+
+    public MediaBitstreamsTest(String prefix, String path, BitstreamPackage pkg, BitstreamPackage packageToRun,
+            boolean enforce) {
         mPrefix = prefix;
         mPath = path;
         mPackage = pkg;
         mPackageToRun = packageToRun;
+        mEnforce = enforce;
     }
 
     @Override
@@ -464,7 +475,19 @@
                 addConformanceEntry(curMethod, mPath, MediaBitstreams.K_UNAVAILABLE, e.toString());
             }
         }
-        // todo(robertshih): lookup conformance entry; pass/fail based on lookup result
+
+        if (mEnforce) {
+            if (!mResults.containsKey(mPath)) {
+                Assert.fail("no results captured for " + mPath);
+            }
+            List<ConformanceEntry> entries = mResults.get(mPath);
+            for (ConformanceEntry ce : entries) {
+                if (!"true".equals(ce.mStatus)) {
+                    Assert.fail(ce.toString());
+                }
+            }
+        }
+
     }
 
     private void testBitstreamsConformance(String prefix)
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
index 07b496c..36600eb 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -19,6 +19,7 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.testtype.AndroidJUnitTest;
@@ -123,6 +124,13 @@
         }
 
         @Override
+        public void testEnded(TestDescription test, HashMap<String, Metric> metrics) {
+            for (Entry<String, Metric> e: metrics.entrySet()) {
+                mMetrics.put(e.getKey(), e.getValue().getMeasurements().getSingleString());
+            }
+        }
+
+        @Override
         public void testFailed(TestDescription test, String trace) {
             mFailureStackTrace = trace;
         }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
index 7755b4a..bbfc2eb 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public Vp8BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
index de05f93..8587abd 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public Vp8BitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
index 747e3a9..d209011 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public Vp9Yuv420BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
index 76af826..9b048bb 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
@@ -29,8 +29,8 @@
     }
 
     public Vp9Yuv420BitstreamsTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
 }
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
index 2856e36..a03f1c4 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public Vp9Yuv422BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
index 46af097..22786fc 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
@@ -31,8 +31,8 @@
     }
 
     public Vp9Yuv444BitstreamsFullTest(String prefix, String path,
-            BitstreamPackage pkg, BitstreamPackage packageToRun) {
-        super(prefix, path, pkg, packageToRun);
+            BitstreamPackage pkg, BitstreamPackage packageToRun, boolean enforce) {
+        super(prefix, path, pkg, packageToRun, enforce);
     }
 
     @Test
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 0e141c0..7bf7bd4 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
@@ -175,6 +175,25 @@
         assertBackgroundNetworkAccess(true);
     }
 
+    public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
+        if (!isSupported()) return;
+
+        setAppIdle(true);
+        assertAppIdle(true);
+        assertBackgroundNetworkAccess(false);
+
+        addAppIdleWhitelist(mUid);
+        assertBackgroundNetworkAccess(true);
+
+        removeAppIdleWhitelist(mUid);
+        assertBackgroundNetworkAccess(false);
+
+        // Make sure whitelisting a random app doesn't affect the tested app.
+        addAppIdleWhitelist(mUid + 1);
+        assertBackgroundNetworkAccess(false);
+        removeAppIdleWhitelist(mUid + 1);
+    }
+
     public void testAppIdle_toast() throws Exception {
         if (!isSupported()) return;
 
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 5232372..c6e80a2 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
@@ -26,10 +26,6 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.NotificationManager;
@@ -53,6 +49,10 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Superclass for tests related to background network restrictions.
  */
@@ -92,7 +92,8 @@
     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     private static final int SECOND_IN_MS = 1000;
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
-    private static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+    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";
@@ -131,6 +132,8 @@
     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);
@@ -744,6 +747,20 @@
         assertRestrictBackground("restrict-background-blacklist", uid, expected);
     }
 
+    protected void addAppIdleWhitelist(int uid) throws Exception {
+        executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
+        assertAppIdleWhitelist(uid, true);
+    }
+
+    protected void removeAppIdleWhitelist(int uid) throws Exception {
+        executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
+        assertAppIdleWhitelist(uid, false);
+    }
+
+    protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
+        assertRestrictBackground("app-idle-whitelist", uid, expected);
+    }
+
     private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
         final int maxTries = 5;
         boolean actual = false;
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 87f9d77..74875cd 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
@@ -306,4 +306,84 @@
             setBatterySaverMode(false);
         }
     }
+
+    /**
+     * Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
+     */
+    public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
+        if (!isSupported()) {
+            return;
+        }
+
+        setDozeMode(true);
+        setAppIdle(true);
+
+        try {
+            assertBackgroundNetworkAccess(false);
+
+            // UID still shouldn't have access because of Doze.
+            addAppIdleWhitelist(mUid);
+            assertBackgroundNetworkAccess(false);
+
+            removeAppIdleWhitelist(mUid);
+            assertBackgroundNetworkAccess(false);
+        } finally {
+            setAppIdle(false);
+            setDozeMode(false);
+        }
+    }
+
+    public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+        if (!isSupported()) {
+            return;
+        }
+
+        setDozeMode(true);
+        setAppIdle(true);
+
+        try {
+            assertBackgroundNetworkAccess(false);
+
+            addAppIdleWhitelist(mUid);
+            assertBackgroundNetworkAccess(false);
+
+            addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+            assertBackgroundNetworkAccess(true);
+
+            // Wait until the whitelist duration is expired.
+            SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+            assertBackgroundNetworkAccess(false);
+        } finally {
+            setAppIdle(false);
+            setDozeMode(false);
+            removeAppIdleWhitelist(mUid);
+        }
+    }
+
+    public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+        if (!isSupported()) {
+            return;
+        }
+
+        setBatterySaverMode(true);
+        setAppIdle(true);
+
+        try {
+            assertBackgroundNetworkAccess(false);
+
+            addAppIdleWhitelist(mUid);
+            assertBackgroundNetworkAccess(false);
+
+            addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+            assertBackgroundNetworkAccess(true);
+
+            // Wait until the whitelist duration is expired.
+            SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+            assertBackgroundNetworkAccess(false);
+        } finally {
+            setAppIdle(false);
+            setBatterySaverMode(false);
+            removeAppIdleWhitelist(mUid);
+        }
+    }
 }
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index fe9d36c..5f5ea43 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -156,6 +156,11 @@
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    public void testAppIdleMetered_idleWhitelisted() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+                "testAppIdleNetworkAccess_idleWhitelisted");
+    }
+
     // TODO: currently power-save mode and idle uses the same whitelist, so this test would be
     // redundant (as it would be testing the same as testBatterySaverMode_reinstall())
     //    public void testAppIdle_reinstall() throws Exception {
@@ -181,6 +186,11 @@
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+                "testAppIdleNetworkAccess_idleWhitelisted");
+    }
+
     public void testAppIdleNonMetered_whenCharging() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testAppIdleNetworkAccess_whenCharging");
@@ -281,6 +291,21 @@
                 "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
     }
 
+    public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+                "testDozeAndAppIdle_appIdleWhitelist");
+    }
+
+    public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+                "testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
+    }
+
+    public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+                "testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
+    }
+
     /*******************
      * Helper methods. *
      *******************/
diff --git a/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java b/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
index 674dab2..353471f 100644
--- a/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
+++ b/hostsidetests/numberblocking/app/src/com/android/cts/numberblocking/hostside/CallBlockingTest.java
@@ -39,6 +39,7 @@
 public class CallBlockingTest extends BaseNumberBlockingClientTest {
     private static final String QUERY_CALL_THROUGH_OUR_CONNECTION_SERVICE = CallLog.Calls.NUMBER
             + " = ? AND " + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + " = ?";
+    public static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000;
 
     private static CountDownLatch callRejectionCountDownLatch;
 
@@ -58,11 +59,13 @@
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                 .build();
         mTelecomManager.registerPhoneAccount(phoneAccount);
+        assertPhoneAccountRegistered(phoneAccountHandle, true);
     }
 
     public void testUnregisterPhoneAccount() {
-        mTelecomManager.unregisterPhoneAccount(getPhoneAccountHandle());
-        assertNull(mTelecomManager.getPhoneAccount(getPhoneAccountHandle()));
+        PhoneAccountHandle handle = getPhoneAccountHandle();
+        mTelecomManager.unregisterPhoneAccount(handle);
+        assertPhoneAccountRegistered(handle, false);
     }
 
     public void testIncomingCallFromBlockedNumberIsRejected() throws Exception {
@@ -135,4 +138,46 @@
             return connection;
         }
     }
+
+    private void assertPhoneAccountRegistered(final PhoneAccountHandle handle,
+            boolean isRegistered) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
+                        return isRegistered ? phoneAccount != null : phoneAccount == null;
+                    }
+                },
+                WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+                "Phone account registered for " + handle
+        );
+    }
+
+    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());
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    protected interface Condition {
+        Object expected();
+        Object actual();
+    }
 }
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 2e61a61..68e99ef 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for the CTS OS host 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDeviceOsTestApp.apk" />
diff --git a/hostsidetests/os/src/android/os/cts/PowerManagerTests.java b/hostsidetests/os/src/android/os/cts/PowerManagerTests.java
new file mode 100644
index 0000000..d0f4413
--- /dev/null
+++ b/hostsidetests/os/src/android/os/cts/PowerManagerTests.java
@@ -0,0 +1,99 @@
+/*
+ * 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.os.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PowerManagerTests extends BaseHostJUnit4Test {
+    // We need a running test app to test cached processes releasing wake locks.
+    private static final String PACKAGE_NAME = "android.os.powermanagertests";
+    private static final String APK_NAME = "CtsHostPowerManagerTestApp.apk";
+    private static final String WAKE_LOCK = "WakeLockTest";
+
+    private static final String CHECK_CACHED_CMD = "dumpsys activity processes";
+    private static final String CACHED_REGEXP = "Proc # [0-9]+: cch.*" + PACKAGE_NAME;
+    private static final Pattern CACHED_PATTERN = Pattern.compile(CACHED_REGEXP);
+
+    private static final String GET_WAKE_LOCKS_CMD = "dumpsys power";
+    private static final String WAKE_LOCK_ACQUIRED_REGEXP =
+            "PARTIAL_WAKE_LOCK.*" + WAKE_LOCK + ".*ACQ";
+    private static final String WAKE_LOCK_DISABLED_REGEXP =
+                        "PARTIAL_WAKE_LOCK.*" + WAKE_LOCK + ".*DISABLED";
+    private static final Pattern WAKE_LOCK_ACQUIRED_PATTERN =
+            Pattern.compile(WAKE_LOCK_ACQUIRED_REGEXP);
+    private static final Pattern WAKE_LOCK_DISABLED_PATTERN =
+            Pattern.compile(WAKE_LOCK_DISABLED_REGEXP);
+
+    // A reference to the device under test, which gives us a handle to run commands.
+    private ITestDevice mDevice;
+
+    @Before
+    public synchronized void setUp() throws Exception {
+        mDevice = getDevice();
+        installPackage(APK_NAME);
+    }
+
+    /**
+     * Tests that cached process releases wake lock.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testCachedProcessReleasesWakeLock() throws Exception {
+        makeCachedProcess(PACKAGE_NAME);
+        // Short wait before checking cached process
+        Thread.sleep(1000);
+        String processes = mDevice.executeShellCommand(CHECK_CACHED_CMD);
+        assertTrue(CACHED_PATTERN.matcher(processes).find());
+
+        String wakelocks = mDevice.executeShellCommand(GET_WAKE_LOCKS_CMD);
+        assertTrue("Wake lock not acquired", WAKE_LOCK_ACQUIRED_PATTERN.matcher(wakelocks).find());
+        assertFalse("Wake lock disabled early",
+                WAKE_LOCK_DISABLED_PATTERN.matcher(wakelocks).find());
+
+        // ActivityManager will inform PowerManager of processes with idle uids.
+        // PowerManager will disable wakelocks held by such processes.
+        mDevice.executeShellCommand("am make-uid-idle " + PACKAGE_NAME);
+
+        wakelocks = mDevice.executeShellCommand(GET_WAKE_LOCKS_CMD);
+        assertFalse("Wake lock still acquired",
+                WAKE_LOCK_ACQUIRED_PATTERN.matcher(wakelocks).find());
+        assertTrue("Wake lock not disabled", WAKE_LOCK_DISABLED_PATTERN.matcher(wakelocks).find());
+    }
+
+    private void makeCachedProcess(String packageName) throws Exception {
+        String startAppTemplate = "am start -W -a android.intent.action.MAIN -p %s"
+                + " -c android.intent.category.LAUNCHER";
+        String viewUriTemplate = "am start -W -a android.intent.action.VIEW -d %s";
+        mDevice.executeShellCommand(String.format(startAppTemplate, packageName));
+        // Starting two random apps to make packageName cached
+        mDevice.executeShellCommand(String.format(viewUriTemplate, "mailto:"));
+        mDevice.executeShellCommand(String.format(viewUriTemplate, "tel:"));
+    }
+}
diff --git a/hostsidetests/os/test-apps/PowerManagerTestApp/Android.mk b/hostsidetests/os/test-apps/PowerManagerTestApp/Android.mk
new file mode 100644
index 0000000..3c6371f
--- /dev/null
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/Android.mk
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsHostPowerManagerTestApp
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
new file mode 100755
index 0000000..4fb0653
--- /dev/null
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.os.powermanagertests"
+    android:targetSandboxVersion="2">
+  <uses-permission android:name="android.permission.WAKE_LOCK" />
+  <application>
+    <activity android:name=".WakeLockTest">
+      <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/hostsidetests/os/test-apps/PowerManagerTestApp/src/android/os/powermanagertests/WakeLockTest.java b/hostsidetests/os/test-apps/PowerManagerTestApp/src/android/os/powermanagertests/WakeLockTest.java
new file mode 100644
index 0000000..b05770c
--- /dev/null
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/src/android/os/powermanagertests/WakeLockTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.os.powermanagertests;
+
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PowerManager.WakeLock;
+import android.os.PowerManager;
+
+public class WakeLockTest extends Activity {
+  private static final String TAG = WakeLockTest.class.getSimpleName();
+
+  @Override
+  public void onCreate(Bundle icicle) {
+    super.onCreate(icicle);
+    PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+    WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, TAG);
+    wl.acquire(300000 /* 5 mins */);
+  }
+}
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index 777479c..5539166 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Sample host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
+    <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" />
         <option name="test-file-name" value="CtsSampleDeviceApp.apk" />
diff --git a/hostsidetests/seccomp/app/Android.mk b/hostsidetests/seccomp/app/Android.mk
index 041d760..cf15eff 100644
--- a/hostsidetests/seccomp/app/Android.mk
+++ b/hostsidetests/seccomp/app/Android.mk
@@ -47,7 +47,7 @@
 
 LOCAL_PACKAGE_NAME := CtsSeccompDeviceApp
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_PACKAGE)
 
diff --git a/hostsidetests/seccomp/app/AndroidManifest.xml b/hostsidetests/seccomp/app/AndroidManifest.xml
index b8e97e3..5c62ab5 100644
--- a/hostsidetests/seccomp/app/AndroidManifest.xml
+++ b/hostsidetests/seccomp/app/AndroidManifest.xml
@@ -18,8 +18,14 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.seccomp.cts.app">
 
-    <application>
+    <application android:zygotePreloadName=".ZygotePreload">
         <uses-library android:name="android.test.runner" />
+        <service
+            android:name=".IsolatedService"
+            android:isolatedProcess="true"
+            android:exported="false"
+            android:useAppZygote="true">
+        </service>
     </application>
 
     <instrumentation
diff --git a/hostsidetests/seccomp/app/assets/syscalls_blocked.json b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
index 441c8da..a53319b 100644
--- a/hostsidetests/seccomp/app/assets/syscalls_blocked.json
+++ b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
@@ -13,8 +13,22 @@
     "mount": 21,
     "reboot": 88,
     "setdomainname": 121,
+    "setfsgid": 139,
+    "setfsuid": 138,
+    "setgid": 46,
+    "setgid32": 214,
+    "setgroups": 81,
+    "setgroups32": 206,
     "sethostname": 74,
+    "setregid": 71,
+    "setregid32": 204,
+    "setresgid": 170,
+    "setresgid32": 210,
+    "setreuid": 70,
+    "setreuid32": 203,
     "settimeofday": 79,
+    "setuid": 23,
+    "setuid32": 213,
     "swapoff": 115,
     "swapon": 87,
     "syslog": 103,
@@ -33,8 +47,16 @@
     "mount": 40,
     "reboot": 142,
     "setdomainname": 162,
+    "setfsgid": 152,
+    "setfsuid": 151,
+    "setgid": 144,
+    "setgroups": 159,
     "sethostname": 161,
+    "setregid": 143,
+    "setresgid": 149,
+    "setreuid": 145,
     "settimeofday": 170,
+    "setuid": 146,
     "swapoff": 225,
     "swapon": 224,
     "syslog": 116,
@@ -53,8 +75,16 @@
     "mount": 4021,
     "reboot": 4088,
     "setdomainname": 4121,
+    "setfsgid": 4139,
+    "setfsuid": 4138,
+    "setgid": 4046,
+    "setgroups": 4081,
     "sethostname": 4074,
+    "setregid": 4071,
+    "setresgid": 4190,
+    "setreuid": 4070,
     "settimeofday": 4079,
+    "setuid": 4023,
     "swapoff": 4115,
     "swapon": 4087,
     "syslog": 4103,
@@ -73,8 +103,16 @@
     "mount": 5160,
     "reboot": 5164,
     "setdomainname": 5166,
+    "setfsgid": 5121,
+    "setfsuid": 5120,
+    "setgid": 5104,
+    "setgroups": 5114,
     "sethostname": 5165,
+    "setregid": 5112,
+    "setresgid": 5117,
+    "setreuid": 5111,
     "settimeofday": 5159,
+    "setuid": 5103,
     "swapoff": 5163,
     "swapon": 5162,
     "syslog": 5101,
@@ -93,8 +131,22 @@
     "mount": 21,
     "reboot": 88,
     "setdomainname": 121,
+    "setfsgid": 139,
+    "setfsuid": 138,
+    "setgid": 46,
+    "setgid32": 214,
+    "setgroups": 81,
+    "setgroups32": 206,
     "sethostname": 74,
+    "setregid": 71,
+    "setregid32": 204,
+    "setresgid": 170,
+    "setresgid32": 210,
+    "setreuid": 70,
+    "setreuid32": 203,
     "settimeofday": 79,
+    "setuid": 23,
+    "setuid32": 213,
     "swapoff": 115,
     "swapon": 87,
     "syslog": 103,
@@ -113,8 +165,16 @@
     "mount": 165,
     "reboot": 169,
     "setdomainname": 171,
+    "setfsgid": 123,
+    "setfsuid": 122,
+    "setgid": 106,
+    "setgroups": 116,
     "sethostname": 170,
+    "setregid": 114,
+    "setresgid": 119,
+    "setreuid": 113,
     "settimeofday": 164,
+    "setuid": 105,
     "swapoff": 168,
     "swapon": 167,
     "syslog": 103,
diff --git a/hostsidetests/seccomp/app/gen_blacklist.py b/hostsidetests/seccomp/app/gen_blacklist.py
index cf36d11..e39fd9f 100755
--- a/hostsidetests/seccomp/app/gen_blacklist.py
+++ b/hostsidetests/seccomp/app/gen_blacklist.py
@@ -46,6 +46,20 @@
     'setdomainname': 'all',
     'sethostname': 'all',
     'settimeofday': 'all',
+    'setfsgid': 'all',
+    'setfsuid': 'all',
+    'setgid': 'all',
+    'setgid32': 'x86,arm',
+    'setgroups': 'all',
+    'setgroups32': 'x86,arm',
+    'setregid': 'all',
+    'setregid32': 'x86,arm',
+    'setresgid': 'all',
+    'setresgid32': 'x86,arm',
+    'setreuid': 'all',
+    'setreuid32': 'x86,arm',
+    'setuid': 'all',
+    'setuid32': 'x86,arm',
     'swapoff': 'all',
     'swapoff': 'all',
     'swapon': 'all',
diff --git a/hostsidetests/seccomp/app/jni/Android.mk b/hostsidetests/seccomp/app/jni/Android.mk
index 45fb135..3637f27 100644
--- a/hostsidetests/seccomp/app/jni/Android.mk
+++ b/hostsidetests/seccomp/app/jni/Android.mk
@@ -32,4 +32,5 @@
 
 LOCAL_CFLAGS := -Wall -Werror
 
+LOCAL_NDK_STL_VARIANT := c++_static
 include $(BUILD_SHARED_LIBRARY)
diff --git a/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
index 2257232..1aac723 100644
--- a/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
+++ b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
@@ -18,6 +18,7 @@
 
 #define LOG_TAG "SeccompTest"
 
+#include <functional>
 #include <android/log.h>
 #include <unistd.h>
 #include <sys/types.h>
@@ -37,12 +38,11 @@
  *        1 if blocked, else 0
  * Exceptions: None
  */
-static jboolean testSyscallBlocked(JNIEnv *, jobject, int nr) {
+static jboolean doTestSyscallBlocked(std::function<void()> execSyscall) {
     int pid = fork();
     if (pid == 0) {
-        ALOGI("Calling syscall %d", nr);
-        syscall(nr);
-        return false;
+        execSyscall();
+        exit(0);
     } else {
         int status;
         int ret = waitpid(pid, &status, 0);
@@ -72,9 +72,25 @@
     }
 }
 
+static jboolean testSyscallBlocked(JNIEnv *, jobject, jint nr) {
+    return doTestSyscallBlocked([&](){ ALOGI("Calling syscall %d", nr); syscall(nr); });
+}
+
+static jboolean testSetresuidBlocked(JNIEnv *, jobject, jint ruid, jint euid, jint suid) {
+    return doTestSyscallBlocked([&] {ALOGE("Calling setresuid\n"); setresuid(ruid, euid, suid);});
+}
+
+static jboolean testSetresgidBlocked(JNIEnv *, jobject, jint rgid, jint egid, jint sgid) {
+    return doTestSyscallBlocked([&] {ALOGE("Calling setresgid\n"); setresgid(rgid, egid, sgid);});
+}
+
 static JNINativeMethod gMethods[] = {
     { "testSyscallBlocked", "(I)Z",
             (void*) testSyscallBlocked },
+    { "testSetresuidBlocked", "(III)Z",
+            (void*) testSetresuidBlocked },
+    { "testSetresgidBlocked", "(III)Z",
+            (void*) testSetresgidBlocked },
 };
 
 int register_android_seccomp_cts_app_SeccompTest(JNIEnv* env)
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/IsolatedService.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/IsolatedService.java
new file mode 100644
index 0000000..2546c57
--- /dev/null
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/IsolatedService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.seccomp.cts.app;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IsolatedService extends Service {
+    static final String TAG = "IsolatedService";
+
+    static final int MSG_GET_SECCOMP_RESULT = 1;
+
+    static final int MSG_SECCOMP_RESULT = 2;
+    final Messenger mMessenger = new Messenger(new ServiceHandler());
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    static class ServiceHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case (MSG_GET_SECCOMP_RESULT):
+                    int result = ZygotePreload.getSeccomptestResult() ? 1 : 0;
+                    try {
+                        msg.replyTo.send(Message.obtain(null, MSG_SECCOMP_RESULT, result, 0));
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to send seccomp test result", e);
+                    }
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
index 42ea6c2..1f066dc 100644
--- a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
@@ -20,10 +20,23 @@
 import java.io.InputStream;
 import java.util.Iterator;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.res.AssetManager;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
 import com.android.compatibility.common.util.CpuFeatures;
 import org.json.JSONObject;
 import org.json.JSONException;
@@ -44,19 +57,39 @@
         System.loadLibrary("ctsseccomp_jni");
     }
 
+    static final String TAG = "SeccompDeviceTest";
+
+    protected Context mContext;
+
+    // This is flagged whenever we have obtained the test result from the isolated
+    // service; it allows us to block the test until the results are in.
+    private final ConditionVariable mResultCondition = new ConditionVariable();
+
+    private boolean mAppZygoteResult;
+    private Messenger mMessenger;
+    private HandlerThread mHandlerThread;
+
+    // The service start can take a long time, because seccomp denials will
+    // cause process crashes and dumps, which we waitpid() for sequentially.
+    private static final int SERVICE_START_TIMEOUT_MS = 120000;
+
     private JSONObject mAllowedSyscallMap;
     private JSONObject mBlockedSyscallMap;
 
     @Before
     public void initializeSyscallMap() throws IOException, JSONException {
-        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        AssetManager manager = context.getAssets();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        AssetManager manager = mContext.getAssets();
         try (InputStream is = manager.open("syscalls_allowed.json")) {
             mAllowedSyscallMap = new JSONObject(readInputStreamFully(is));
         }
         try (InputStream is = manager.open("syscalls_blocked.json")) {
             mBlockedSyscallMap = new JSONObject(readInputStreamFully(is));
         }
+        mHandlerThread = new HandlerThread("HandlerThread");
+        mHandlerThread.start();
+        Looper looper = mHandlerThread.getLooper();
+        mMessenger = new Messenger(new IncomingHandler(looper));
     }
 
     @Test
@@ -79,6 +112,84 @@
         }
     }
 
+    class IncomingHandler extends Handler {
+        IncomingHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case IsolatedService.MSG_SECCOMP_RESULT:
+                    mAppZygoteResult = (msg.arg1 == 1);
+                    mResultCondition.open();
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+	}
+
+    class IsolatedConnection implements ServiceConnection {
+        private final ConditionVariable mConnectedCondition = new ConditionVariable();
+        private Messenger mService;
+
+        public void onServiceConnected(ComponentName name, IBinder binder) {
+            mService = new Messenger(binder);
+            mConnectedCondition.open();
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mConnectedCondition.close();
+        }
+
+        public boolean getTestResult() {
+            boolean connected = mConnectedCondition.block(SERVICE_START_TIMEOUT_MS);
+            if (!connected) {
+               Log.e(TAG, "Failed to wait for IsolatedService to bind.");
+               return false;
+            }
+            Message msg = Message.obtain(null, IsolatedService.MSG_GET_SECCOMP_RESULT);
+            msg.replyTo = mMessenger;
+            try {
+                mService.send(msg);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send message to IsolatedService to get test result.", e);
+                return false;
+            }
+
+            // Wait for result and return it
+            mResultCondition.block();
+
+            return mAppZygoteResult;
+        }
+    }
+
+    private IsolatedConnection bindService(Class<?> cls) {
+        Intent intent = new Intent();
+        intent.setClass(mContext, cls);
+        IsolatedConnection conn = new IsolatedConnection();
+        mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
+
+        return conn;
+    }
+
+    @Test
+    public void testAppZygoteSyscalls() {
+        // Isolated services that spawn from the application Zygote are allowed
+        // to preload code in a security context that is allowed to use
+        // setresuid() / setresgid() in a limited range; this test enforces
+        // we allow calls within the range, and reject those outside them.
+        // This is done from the ZygotePreload class (which runs in the app zygote
+        // context); here we just wait for the service to come up, and ask it
+        // whether the tests were executed successfully. We have to ask the service
+        // because that is the only process that we can talk to that shares the
+        // same address space as the ZygotePreload class, which holds the test
+        // result.
+        IsolatedConnection conn = bindService(IsolatedService.class);
+        boolean testResult = conn.getTestResult();
+        Assert.assertTrue("seccomp tests in application zygote failed; see logs.", testResult);
+    }
+
     private static String getCurrentArch() {
         if (CpuFeatures.isArm64Cpu()) {
             return "arm64";
@@ -120,4 +231,6 @@
     }
 
     private static final native boolean testSyscallBlocked(int nr);
+    protected static final native boolean testSetresuidBlocked(int ruid, int euid, int suid);
+    protected static final native boolean testSetresgidBlocked(int rgid, int egid, int sgid);
 }
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/ZygotePreload.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/ZygotePreload.java
new file mode 100644
index 0000000..e7f5b25
--- /dev/null
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/ZygotePreload.java
@@ -0,0 +1,128 @@
+/*
+ * 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.seccomp.cts.app;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.util.Log;
+
+public class ZygotePreload implements android.app.ZygotePreload {
+    static final String TAG = "SeccompDeviceTest";
+
+    static volatile boolean sResult = false;
+
+    static private boolean testSetResUidGidBlocked(int rid, int eid, int sid) {
+        if (!SeccompDeviceTest.testSetresuidBlocked(rid, eid, sid)) {
+            Log.e(TAG, "setresuid( " + Integer.toString(rid) + ","
+                    + Integer.toString(eid) + "," + Integer.toString(sid) + ")"
+                    + " is wrongly allowed.");
+            return false;
+        }
+        if (!SeccompDeviceTest.testSetresgidBlocked(rid, eid, sid)) {
+            Log.e(TAG, "setresguid( " + Integer.toString(rid) + ","
+                    + Integer.toString(eid) + "," + Integer.toString(sid) + ")"
+                    + " is wrongly allowed.");
+            return false;
+        }
+
+        return true;
+    }
+
+    static private boolean testSetResUidGidAllowed(int rid, int eid, int sid) {
+        if (SeccompDeviceTest.testSetresuidBlocked(rid, eid, sid)) {
+            Log.e(TAG, "setresuid( " + Integer.toString(rid) + ","
+                    + Integer.toString(eid) + "," + Integer.toString(sid) + ")"
+                    + " is wrongly blocked.");
+            return false;
+        }
+        if (SeccompDeviceTest.testSetresgidBlocked(rid, eid, sid)) {
+            Log.e(TAG, "setresguid( " + Integer.toString(rid) + ","
+                    + Integer.toString(eid) + "," + Integer.toString(sid) + ")"
+                    + " is wrongly blocked.");
+            return false;
+        }
+
+        return true;
+    }
+
+    static synchronized public boolean getSeccomptestResult() {
+        return sResult;
+    }
+
+    /*
+     * This is called from the app_zygote security context, which has two seccomp
+     * filters in place:
+     * 1) The regular app seccomp filter (which allows setresuid/setresgid)
+     * 2) A setresuid/setresgid limiting filter, which restricts the calls to
+     *    setresuid/setresgid to be in a particular range.
+     *
+     * This test enforces 2) is in place.
+     */
+    @Override
+    synchronized public void doPreload(ApplicationInfo appInfo) {
+        boolean result = true;
+
+        // root uid
+        result &= testSetResUidGidBlocked(0, 0, 0);
+        // system uid
+        result &= testSetResUidGidBlocked(Process.SYSTEM_UID, Process.SYSTEM_UID,
+                Process.SYSTEM_UID);
+        // mix of uids
+        result &= testSetResUidGidBlocked(0, Process.SYSTEM_UID,
+                Process.SYSTEM_UID);
+
+        // an app uid
+        result &= testSetResUidGidBlocked(Process.FIRST_APPLICATION_UID,
+                Process.FIRST_APPLICATION_UID, Process.FIRST_APPLICATION_UID);
+        result &= testSetResUidGidBlocked(Process.LAST_APPLICATION_UID,
+                Process.LAST_APPLICATION_UID, Process.LAST_APPLICATION_UID);
+
+        // an isolated process uid
+        result &= testSetResUidGidBlocked(Process.FIRST_ISOLATED_UID,
+                Process.FIRST_ISOLATED_UID, Process.FIRST_ISOLATED_UID);
+        result &= testSetResUidGidBlocked(Process.LAST_ISOLATED_UID, Process.LAST_ISOLATED_UID,
+                Process.LAST_ISOLATED_UID);
+
+        // an allowed app zygote UID
+        // TODO this test assumes no other isolated app zygotes are currently running!
+        result &= testSetResUidGidAllowed(Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID, Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+
+        // off-by-one
+        result &= testSetResUidGidBlocked(Process.FIRST_APP_ZYGOTE_ISOLATED_UID - 1,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID - 1,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID - 1);
+
+        // mixed allowed rgid with dis-allowed euid and suid (and variants)
+        result &= testSetResUidGidBlocked(Process.FIRST_APP_ZYGOTE_ISOLATED_UID, 0, 0);
+        result &= testSetResUidGidBlocked(Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID, 0);
+        result &= testSetResUidGidBlocked(0, Process.FIRST_APP_ZYGOTE_ISOLATED_UID, 0);
+        result &= testSetResUidGidBlocked(0, Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+        result &= testSetResUidGidBlocked(0, 0, Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+        result &= testSetResUidGidBlocked(Process.FIRST_APP_ZYGOTE_ISOLATED_UID, 0,
+                Process.FIRST_APP_ZYGOTE_ISOLATED_UID);
+
+        // a disallowed app zygote UID
+        result &= testSetResUidGidBlocked(Process.LAST_APP_ZYGOTE_ISOLATED_UID,
+                Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.LAST_APP_ZYGOTE_ISOLATED_UID);
+
+        // Store result
+        sResult = result;
+    }
+}
diff --git a/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
index 2a7242b..1d845f9 100644
--- a/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
+++ b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
@@ -41,6 +41,7 @@
 
     private static final String TEST_CTS_SYSCALL_BLOCKED = "testCTSSyscallBlocked";
     private static final String TEST_CTS_SYSCALL_ALLOWED = "testCTSSyscallAllowed";
+    private static final String TEST_CTS_SYSCALL_APP_ZYGOTE = "testAppZygoteSyscalls";
 
     @Before
     public void setUp() throws Exception {
@@ -57,6 +58,11 @@
         Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SYSCALL_ALLOWED));
     }
 
+    @Test
+    public void testAppZygoteSyscalls() throws Exception {
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SYSCALL_APP_ZYGOTE));
+    }
+
     @After
     public void tearDown() throws Exception {
         uninstallPackage(getDevice(), TEST_PKG);
diff --git a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
index bec7a9c..525a3b4 100644
--- a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
@@ -111,7 +111,8 @@
     @CddTest(requirement="9.7")
     public void testConfigStackProtectorStrong() throws Exception {
         assertTrue("Linux kernel must have Stack Protector enabled: " +
-                "CONFIG_CC_STACKPROTECTOR_STRONG=y",
+                "CONFIG_STACKPROTECTOR_STRONG=y or CONFIG_CC_STACKPROTECTOR_STRONG=y",
+                configSet.contains("CONFIG_STACKPROTECTOR_STRONG=y") ||
                 configSet.contains("CONFIG_CC_STACKPROTECTOR_STRONG=y"));
     }
 
diff --git a/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
index 8443ef1..3463211 100644
--- a/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
@@ -81,6 +81,7 @@
     private File aospPcFile;
     private File aospSvcFile;
     private File devicePolicyFile;
+    private File deviceSystemPolicyFile;
     private File devicePlatSeappFile;
     private File deviceNonplatSeappFile;
     private File devicePlatFcFile;
@@ -163,6 +164,8 @@
                 deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
                         "/vendor/etc/selinux/vendor_file_contexts", "vendor_file_contexts");
             }
+            deviceSystemPolicyFile =
+                    android.security.cts.SELinuxHostTest.getDeviceSystemPolicyFile(mDevice);
         } else {
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/plat_file_contexts", "plat_file_contexts");
@@ -817,17 +820,32 @@
         libcpp.deleteOnExit();
     }
 
-    private void assertSepolicyTests(String test, String testExecutable) throws Exception {
+    private void assertSepolicyTests(String test, String testExecutable,
+            boolean includeVendorSepolicy) throws Exception {
         setupLibraries();
         sepolicyTests = copyResourceToTempFile(testExecutable);
         sepolicyTests.setExecutable(true);
-        ProcessBuilder pb = new ProcessBuilder(
-                sepolicyTests.getAbsolutePath(),
-                "-l", libsepolwrap.getAbsolutePath(),
-                "-f", devicePlatFcFile.getAbsolutePath(),
-                "-f", deviceNonplatFcFile.getAbsolutePath(),
-                "-p", devicePolicyFile.getAbsolutePath(),
-                "--test", test);
+
+        List<String> args = new ArrayList<String>();
+        args.add(sepolicyTests.getAbsolutePath());
+        args.add("-l");
+        args.add(libsepolwrap.getAbsolutePath());
+        args.add("-f");
+        args.add(devicePlatFcFile.getAbsolutePath());
+        args.add("--test");
+        args.add(test);
+
+        if (includeVendorSepolicy) {
+            args.add("-f");
+            args.add(deviceNonplatFcFile.getAbsolutePath());
+            args.add("-p");
+            args.add(devicePolicyFile.getAbsolutePath());
+        } else {
+            args.add("-p");
+            args.add(deviceSystemPolicyFile.getAbsolutePath());
+        }
+
+        ProcessBuilder pb = new ProcessBuilder(args);
         Map<String, String> env = pb.environment();
         if (isMac()) {
             env.put("DYLD_LIBRARY_PATH", System.getProperty("java.io.tmpdir"));
@@ -854,7 +872,8 @@
      * @throws Exception
      */
     public void testDataTypeViolators() throws Exception {
-        assertSepolicyTests("TestDataTypeViolations", "/sepolicy_tests");
+        assertSepolicyTests("TestDataTypeViolations", "/sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */);
     }
 
     /**
@@ -863,7 +882,8 @@
      * @throws Exception
      */
     public void testProcTypeViolators() throws Exception {
-        assertSepolicyTests("TestProcTypeViolations", "/sepolicy_tests");
+        assertSepolicyTests("TestProcTypeViolations", "/sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */);
     }
 
     /**
@@ -872,7 +892,8 @@
      * @throws Exception
      */
     public void testSysfsTypeViolators() throws Exception {
-        assertSepolicyTests("TestSysfsTypeViolations", "/sepolicy_tests");
+        assertSepolicyTests("TestSysfsTypeViolations", "/sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */);
     }
 
     /**
@@ -881,7 +902,8 @@
      * @throws Exception
      */
     public void testVendorTypeViolators() throws Exception {
-        assertSepolicyTests("TestVendorTypeViolations", "/sepolicy_tests");
+        assertSepolicyTests("TestVendorTypeViolations", "/sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */);
     }
 
     /**
@@ -892,7 +914,8 @@
      * @throws Exception
      */
     public void testCoredomainViolators() throws Exception {
-        assertSepolicyTests("CoredomainViolations", "/treble_sepolicy_tests");
+        assertSepolicyTests("CoredomainViolations", "/treble_sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 27) /* includeVendorSepolicy */);
     }
 
    /**
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index 1f29251..ae95f99 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -163,6 +163,11 @@
         <option name="push" value="CVE-2018-9490->/data/local/tmp/CVE-2018-9490" />
         <option name="push" value="CVE-2018-9515->/data/local/tmp/CVE-2018-9515" />
 
+        <!--__________________-->
+        <!-- Bulletin 2019-03 -->
+        <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
+        <option name="push" value="Bug-115739809->/data/local/tmp/Bug-115739809" />
+
         <option name="append-bitness" value="true" />
     </target_preparer>
 
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.mk b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.mk
new file mode 100755
index 0000000..cd2dbcd
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := Bug-115739809
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_SHARED_LIBRARIES := \
+        libbase \
+        libinput \
+        libutils \
+        liblog
+
+# 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_CPPFLAGS += -Wall -Werror -Wextra
+LOCAL_LDFLAGS += -fPIE -pie
+LOCAL_LDFLAGS += -rdynamic
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
new file mode 100755
index 0000000..83227af
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -0,0 +1,212 @@
+/**
+* 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.
+*/
+
+#define LOG_TAG "InputChannelTest"
+
+#include "../includes/common.h"
+
+#include <android-base/stringprintf.h>
+#include <input/InputTransport.h>
+
+using namespace android;
+using android::base::StringPrintf;
+
+static std::string memoryAsHexString(const void* const address, size_t numBytes) {
+    std::string str;
+    for (size_t i = 0; i < numBytes; i++) {
+        str += StringPrintf("%02X ", static_cast<const uint8_t* const>(address)[i]);
+    }
+    return str;
+}
+
+/**
+ * There could be non-zero bytes in-between InputMessage fields. Force-initialize the entire
+ * memory to zero, then only copy the valid bytes on a per-field basis.
+ * Input: message msg
+ * Output: cleaned message outMsg
+ */
+static void sanitizeMessage(const InputMessage& msg, InputMessage* outMsg) {
+    memset(outMsg, 0, sizeof(*outMsg));
+
+    // Write the header
+    outMsg->header.type = msg.header.type;
+
+    // Write the body
+    switch(msg.header.type) {
+        case InputMessage::TYPE_KEY: {
+            // uint32_t seq
+            outMsg->body.key.seq = msg.body.key.seq;
+            // nsecs_t eventTime
+            outMsg->body.key.eventTime = msg.body.key.eventTime;
+            // int32_t deviceId
+            outMsg->body.key.deviceId = msg.body.key.deviceId;
+            // int32_t source
+            outMsg->body.key.source = msg.body.key.source;
+            // int32_t displayId
+            outMsg->body.key.displayId = msg.body.key.displayId;
+            // int32_t action
+            outMsg->body.key.action = msg.body.key.action;
+            // int32_t flags
+            outMsg->body.key.flags = msg.body.key.flags;
+            // int32_t keyCode
+            outMsg->body.key.keyCode = msg.body.key.keyCode;
+            // int32_t scanCode
+            outMsg->body.key.scanCode = msg.body.key.scanCode;
+            // int32_t metaState
+            outMsg->body.key.metaState = msg.body.key.metaState;
+            // int32_t repeatCount
+            outMsg->body.key.repeatCount = msg.body.key.repeatCount;
+            // nsecs_t downTime
+            outMsg->body.key.downTime = msg.body.key.downTime;
+            break;
+        }
+        case InputMessage::TYPE_MOTION: {
+            // uint32_t seq
+            outMsg->body.motion.seq = msg.body.motion.seq;
+            // nsecs_t eventTime
+            outMsg->body.motion.eventTime = msg.body.motion.eventTime;
+            // int32_t deviceId
+            outMsg->body.motion.deviceId = msg.body.motion.deviceId;
+            // int32_t source
+            outMsg->body.motion.source = msg.body.motion.source;
+            // int32_t displayId
+            outMsg->body.motion.displayId = msg.body.motion.displayId;
+            // int32_t action
+            outMsg->body.motion.action = msg.body.motion.action;
+            // int32_t actionButton
+            outMsg->body.motion.actionButton = msg.body.motion.actionButton;
+            // int32_t flags
+            outMsg->body.motion.flags = msg.body.motion.flags;
+            // int32_t metaState
+            outMsg->body.motion.metaState = msg.body.motion.metaState;
+            // int32_t buttonState
+            outMsg->body.motion.buttonState = msg.body.motion.buttonState;
+            // MotionClassification classification
+            outMsg->body.motion.classification = msg.body.motion.classification;
+            // int32_t edgeFlags
+            outMsg->body.motion.edgeFlags = msg.body.motion.edgeFlags;
+            // nsecs_t downTime
+            outMsg->body.motion.downTime = msg.body.motion.downTime;
+            // float xOffset
+            outMsg->body.motion.xOffset = msg.body.motion.xOffset;
+            // float yOffset
+            outMsg->body.motion.yOffset = msg.body.motion.yOffset;
+            // float xPrecision
+            outMsg->body.motion.xPrecision = msg.body.motion.xPrecision;
+            // float yPrecision
+            outMsg->body.motion.yPrecision = msg.body.motion.yPrecision;
+            // uint32_t pointerCount
+            outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
+            //struct Pointer pointers[MAX_POINTERS]
+            for (size_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                // PointerProperties properties
+                outMsg->body.motion.pointers[i].properties.id =
+                        msg.body.motion.pointers[i].properties.id;
+                outMsg->body.motion.pointers[i].properties.toolType =
+                        msg.body.motion.pointers[i].properties.toolType;
+                // PointerCoords coords
+                outMsg->body.motion.pointers[i].coords.bits =
+                        msg.body.motion.pointers[i].coords.bits;
+                const uint32_t count = BitSet64::count(msg.body.motion.pointers[i].coords.bits);
+                memcpy(&outMsg->body.motion.pointers[i].coords.values[0],
+                        &msg.body.motion.pointers[i].coords.values[0],
+                        count * sizeof(msg.body.motion.pointers[i].coords.values[0]));
+            }
+            break;
+        }
+        case InputMessage::TYPE_FINISHED: {
+            outMsg->body.finished.seq = msg.body.finished.seq;
+            outMsg->body.finished.handled = msg.body.finished.handled;
+            break;
+        }
+    }
+}
+
+/**
+ * Return false if vulnerability is found for a given message type
+ */
+static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, int 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) {
+        serverMsg.body.motion.pointerCount = MAX_POINTERS;
+    }
+    status_t result = server->sendMessage(&serverMsg);
+    if (result != OK) {
+        ALOGE("Could not send message to the input channel");
+        return false;
+    }
+
+    InputMessage clientMsg;
+    result = client->receiveMessage(&clientMsg);
+    if (result != OK) {
+        ALOGE("Could not receive message from the input channel");
+        return false;
+    }
+    if (serverMsg.header.type != clientMsg.header.type) {
+        ALOGE("Types do not match");
+        return false;
+    }
+
+    if (clientMsg.header.padding != 0) {
+        ALOGE("Found padding to be uninitialized");
+        return false;
+    }
+
+    InputMessage sanitizedClientMsg;
+    sanitizeMessage(clientMsg, &sanitizedClientMsg);
+    if (memcmp(&clientMsg, &sanitizedClientMsg, clientMsg.size()) != 0) {
+        ALOGE("Client received un-sanitized message");
+        ALOGE("Received message: %s", memoryAsHexString(&clientMsg, clientMsg.size()).c_str());
+        ALOGE("Expected message: %s",
+                memoryAsHexString(&sanitizedClientMsg, clientMsg.size()).c_str());
+        return false;
+    }
+
+    return true;
+}
+
+/**
+ * Create an unsanitized message
+ * Send
+ * Receive
+ * Compare the received message to a sanitized expected message
+ * Do this for all message types
+ */
+int main() {
+    sp<InputChannel> server, client;
+
+    status_t result = InputChannel::openInputChannelPair("channel name", server, client);
+    if (result != OK) {
+        ALOGE("Could not open input channel pair");
+        return 0;
+    }
+
+    int types[] = {InputMessage::TYPE_KEY, InputMessage::TYPE_MOTION, InputMessage::TYPE_FINISHED};
+    for (int type : types) {
+        bool success = checkMessage(server, client, type);
+        if (!success) {
+            ALOGE("Check message failed for type %i", type);
+            return EXIT_VULNERABLE;
+        }
+    }
+
+    return 0;
+}
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.mk b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.mk
index aa77648..fc5d5dd 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.mk
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.mk
@@ -26,7 +26,9 @@
         $(TOP)/frameworks/av/include/media/ \
         $(TOP)/frameworks/av/services/ \
         $(TOP)/system/media/audio_utils/include/ \
-        $(TOP)/external/sonic/
+        $(TOP)/external/sonic/ \
+        $(TOP)/external/jsoncpp/include/ \
+        $(TOP)/frameworks/av/media/libnblog/include \
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts sts
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
index e09fe5e..3fc6329 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
@@ -16,7 +16,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>
-#include <audioflinger/AudioFlinger.h>
+#include <media/AudioSystem.h>
 #include <hardware/audio_effect.h>
 #include <media/IAudioFlinger.h>
 #include <media/IEffect.h>
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/poc.cpp
index 88dfad6..8f8125d 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/poc.cpp
@@ -37,7 +37,7 @@
   void *getBase() const { return NULL; }
   size_t getSize() const { return 4096 * 4096; }
   uint32_t getFlags() const { return 0; }
-  uint32_t getOffset() const { return 0; }
+  off_t getOffset() const { return 0; }
 
 private:
   mutable int mFd;
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index b9f3b2b..405cb5e 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -18,7 +18,6 @@
 
 import com.android.ddmlib.NullOutputReceiver;
 import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 
@@ -41,13 +40,17 @@
      * @return the console output from running the command
      */
     public static String runCommandLine(String command, ITestDevice device) throws Exception {
+        if ("reboot".equals(command)) {
+            throw new IllegalArgumentException(
+                    "You called a forbidden command! Please fix your tests.");
+        }
         return device.executeShellCommand(command);
     }
 
     /**
      * Pushes and runs a binary to the selected device
      *
-     * @param pathToPoc a string path to poc from the /res folder
+     * @param pocName a string path to poc from the /res folder
      * @param device device to be ran on
      * @return the console output from the binary
      */
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc17_03.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc17_03.java
index 0239883..5057ebe 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc17_03.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc17_03.java
@@ -96,8 +96,7 @@
         String bootCountAfter =
                 AdbUtils.runCommandLine("settings get global boot_count", getDevice());
         // Poc nukes the boot_count setting, reboot to restore it to a sane value
-        AdbUtils.runCommandLine("reboot", getDevice());
-        getDevice().waitForDeviceOnline(60 * 1000);
+        getDevice().reboot();
         updateKernelStartTime();
         assertEquals(bootCountBefore, bootCountAfter);
     }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_03.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_03.java
new file mode 100644
index 0000000..fe85e87
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_03.java
@@ -0,0 +1,34 @@
+/**
+ * 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.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import static org.junit.Assert.assertFalse;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SecurityTest
+public class Poc19_03 extends SecurityTestCase {
+    /**
+     * b/115739809
+     */
+    @SecurityTest(minPatchLevel = "2019-03")
+    public void testPocBug_115739809() throws Exception {
+        assertFalse(AdbUtils.runPocCheckExitCode("Bug-115739809", getDevice(), 30));
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
index b060940..2f1c7f0 100644
--- a/hostsidetests/shortcuts/hostside/AndroidTest.xml
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for the CTS ShortcutManager host tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't access ShortcutManager -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="android.cts.backup.BackupPreparer">
         <option name="enable-backup-if-needed" value="true" />
         <option name="select-local-transport" value="true" />
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
index 3caacad..7742864 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
@@ -126,16 +126,16 @@
 
         CLog.i("Making sure the local transport is selected...");
         assertContainsRegex(
-                "^Selected transport android/com.android.internal.backup.LocalTransport",
+                "^Selected transport com.android.localtransport/.LocalTransport",
                 executeShellCommandWithLog(
-                        "bmgr transport android/com.android.internal.backup.LocalTransport"));
+                        "bmgr transport com.android.localtransport/.LocalTransport"));
 
         executeShellCommandWithLog("dumpsys backup");
 
         assertContainsRegex(
                 "Wiped",
                 executeShellCommandWithLog(
-                        "bmgr wipe android/com.android.internal.backup.LocalTransport android"));
+                        "bmgr wipe com.android.localtransport/.LocalTransport android"));
 
         assertContainsRegex(
                 "Backup finished with result: Success",
diff --git a/hostsidetests/signedconfig/Android.mk b/hostsidetests/signedconfig/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/hostsidetests/signedconfig/Android.mk
@@ -0,0 +1,17 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/signedconfig/app/Android.mk b/hostsidetests/signedconfig/app/Android.mk
new file mode 100644
index 0000000..1e9b142
--- /dev/null
+++ b/hostsidetests/signedconfig/app/Android.mk
@@ -0,0 +1,61 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV1
+LOCAL_MANIFEST_FILE := version1_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV2
+LOCAL_MANIFEST_FILE := version2_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestApp2V2
+LOCAL_MANIFEST_FILE := version2_package2_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV1_badsignature
+LOCAL_MANIFEST_FILE := version1_badsignature_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV1_badb64_config
+LOCAL_MANIFEST_FILE := version1_badb64_config_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV1_badb64_signature
+LOCAL_MANIFEST_FILE := version1_badb64_signature_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV3_configv1
+LOCAL_MANIFEST_FILE := version3_configv1_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignedConfigTestAppV1_debug_key
+LOCAL_MANIFEST_FILE := version1_debug_key_AndroidManifest.xml
+include $(LOCAL_PATH)/build_signedconfig_apk.mk
diff --git a/hostsidetests/signedconfig/app/build_signedconfig_apk.mk b/hostsidetests/signedconfig/app/build_signedconfig_apk.mk
new file mode 100644
index 0000000..28673ec
--- /dev/null
+++ b/hostsidetests/signedconfig/app/build_signedconfig_apk.mk
@@ -0,0 +1,23 @@
+# 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.
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_APPS)
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/hostsidetests/signedconfig/app/res/values/strings.xml b/hostsidetests/signedconfig/app/res/values/strings.xml
new file mode 100755
index 0000000..454ddc6
--- /dev/null
+++ b/hostsidetests/signedconfig/app/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.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.
+-->
+<resources>
+  <string name="signed_config_v1">eyJjb25maWciOiBbeyJ2YWx1ZXMiOiB7ImhpZGRlbl9hcGlfYmxhY2tsaXN0X2V4ZW1wdGlvbnMiOiAiTENsYXNzMTstPm1ldGhvZDEoLExDbGFzczE7LT5maWVsZDE6In0sICJtYXhfc2RrIjogOTksICJtaW5fc2RrIjogMH1dLCAidmVyc2lvbiI6IDF9</string>
+  <string name="signed_config_signature_v1">MEUCIBx1XVpa8itMVos4oLSzb0zLsWT5mkbjSVrtRO6RnTjFAiEA+wI+G6YdxYdpolPliKXjUE6D0W9iRaqLeD29FZDb3Ic=</string>
+  <string name="signed_config_signature_v1_debug_key">MEQCIF7w8uUCYODYxYXyWvAnlPpLmn6Bv/OsW8fDqKfsGRPrAiA7t0jThzHkayAY841+UfKTA9gOH76Yu0e7BA1v9Fcx4g==</string>
+  <string name="signed_config_v2">eyJ2ZXJzaW9uIjogMiwgImNvbmZpZyI6IFt7InZhbHVlcyI6IHsiaGlkZGVuX2FwaV9ibGFja2xpc3RfZXhlbXB0aW9ucyI6ICJMQ2xhc3MyOy0+bWV0aG9kMigsTENsYXNzMjstPmZpZWxkMjoifSwgIm1heF9zZGsiOiA5OSwgIm1pbl9zZGsiOiAwfV19</string>
+  <string name="signed_config_signature_v2">MEYCIQCj78TNHymjNlewEYic/yQ9efvwmzRtOVDgW5qhWW6FRwIhAPDiwO2gkoZaBTzIxHGoU2eHDFMb5ydeJjQBuWl/pn+I</string>
+  <string name="invalid_b64">invalidb64</string>
+</resources>
diff --git a/hostsidetests/signedconfig/app/src/android/cts/test/signedconfig/app/Empty.java b/hostsidetests/signedconfig/app/src/android/cts/test/signedconfig/app/Empty.java
new file mode 100644
index 0000000..0c96d7d
--- /dev/null
+++ b/hostsidetests/signedconfig/app/src/android/cts/test/signedconfig/app/Empty.java
@@ -0,0 +1,20 @@
+/*
+ * 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.cts.test.signedconfig.app;
+
+public class Empty {
+
+}
diff --git a/hostsidetests/signedconfig/app/version1_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_AndroidManifest.xml
new file mode 100755
index 0000000..586afc7
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version1_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="1"
+    android:versionName="1">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v1"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v1"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version1_badb64_config_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_badb64_config_AndroidManifest.xml
new file mode 100755
index 0000000..aaab79a
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version1_badb64_config_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="1"
+    android:versionName="1">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/invalid_b64"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v1"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version1_badb64_signature_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_badb64_signature_AndroidManifest.xml
new file mode 100755
index 0000000..d635e15
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version1_badb64_signature_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="1"
+    android:versionName="1">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v1"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/invalid_b64"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version1_badsignature_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_badsignature_AndroidManifest.xml
new file mode 100755
index 0000000..9372495
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version1_badsignature_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="1"
+    android:versionName="1">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v1"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v2"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version1_debug_key_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_debug_key_AndroidManifest.xml
new file mode 100755
index 0000000..7a3462e
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version1_debug_key_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="1"
+    android:versionName="1">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v1"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v1_debug_key"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version2_AndroidManifest.xml b/hostsidetests/signedconfig/app/version2_AndroidManifest.xml
new file mode 100755
index 0000000..383457f
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version2_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="2"
+    android:versionName="2">
+  <application>
+      <meta-data android:name="android.settings.global"
+                 android:value="@string/signed_config_v2"/>
+      <meta-data android:name="android.settings.global.signature"
+                 android:value="@string/signed_config_signature_v2"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version2_package2_AndroidManifest.xml b/hostsidetests/signedconfig/app/version2_package2_AndroidManifest.xml
new file mode 100755
index 0000000..78aa7b7
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version2_package2_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app2"
+    android:versionCode="2"
+    android:versionName="2">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v2"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v2"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/app/version3_configv1_AndroidManifest.xml b/hostsidetests/signedconfig/app/version3_configv1_AndroidManifest.xml
new file mode 100755
index 0000000..0fb6a7a
--- /dev/null
+++ b/hostsidetests/signedconfig/app/version3_configv1_AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.signedconfig.app"
+    android:versionCode="3"
+    android:versionName="3">
+  <application>
+    <meta-data android:name="android.settings.global"
+               android:value="@string/signed_config_v1"/>
+    <meta-data android:name="android.settings.global.signature"
+               android:value="@string/signed_config_signature_v1"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/signedconfig/hostside/Android.mk b/hostsidetests/signedconfig/hostside/Android.mk
new file mode 100644
index 0000000..b23fb0d
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/Android.mk
@@ -0,0 +1,55 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsSignedConfigHostTestCases
+
+LOCAL_CTS_TEST_PACKAGE := android.signedconfig
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := \
+    tools-common-prebuilt \
+    cts-tradefed \
+    tradefed \
+    compatibility-host-util \
+    guava \
+    truth-prebuilt
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    hamcrest-library
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_TARGET_REQUIRED_MODULES := \
+    CtsSignedConfigTestAppV1 \
+    CtsSignedConfigTestAppV2 \
+    CtsSignedConfigTestApp2V2 \
+    CtsSignedConfigTestAppV1_badsignature \
+    CtsSignedConfigTestAppV1_badb64_config \
+    CtsSignedConfigTestAppV1_badb64_signature \
+    CtsSignedConfigTestAppV3_configv1 \
+    CtsSignedConfigTestAppV1_debug_key
+
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/signedconfig/hostside/AndroidTest.xml b/hostsidetests/signedconfig/hostside/AndroidTest.xml
new file mode 100644
index 0000000..8962507
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Config for CTS Signed Config 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" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am wait-for-broadcast-idle" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsSignedConfigHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/signedconfig/hostside/src/com/android/cts/signedconfig/SignedConfigHostTest.java b/hostsidetests/signedconfig/hostside/src/com/android/cts/signedconfig/SignedConfigHostTest.java
new file mode 100644
index 0000000..0d57fda
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/src/com/android/cts/signedconfig/SignedConfigHostTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.signedconfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.not;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Objects;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SignedConfigHostTest implements IDeviceTest, IBuildReceiver {
+
+    private static final String TEST_APP_PACKAGE_NAME = "android.cts.signedconfig.app";
+    private static final String TEST_APP_PACKAGE2_NAME = "android.cts.signedconfig.app2";
+    private static final String TEST_APP_APK_NAME_V1 = "CtsSignedConfigTestAppV1.apk";
+    private static final String TEST_APP_APK_NAME_V2 = "CtsSignedConfigTestAppV2.apk";
+    private static final String TEST_APP_APK_NAME_PACKAGE2_V2 = "CtsSignedConfigTestApp2V2.apk";
+    private static final String TEST_APP_APK_NAME_V1_BAD_SIGNATURE =
+            "CtsSignedConfigTestAppV1_badsignature.apk";
+    private static final String TEST_APP_APK_NAME_V1_BAD_B64_CONFIG =
+            "CtsSignedConfigTestAppV1_badb64_config.apk";
+    private static final String TEST_APP_APK_NAME_V1_BAD_B64_SIGNATURE =
+            "CtsSignedConfigTestAppV1_badb64_signature.apk";
+    private static final String TEST_APP_APK_NAME_V3_CONFIGV1 =
+            "CtsSignedConfigTestAppV3_configv1.apk";
+    private static final String TEST_APP_APK_NAME_V1_DEBUG_KEY =
+            "CtsSignedConfigTestAppV1_debug_key.apk";
+
+    private static final String SETTING_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
+    private static final String SETTING_SIGNED_CONFIG_VERSION = "signed_config_version";
+
+    private IBuildInfo mCtsBuild;
+    private ITestDevice mDevice;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    private File getTestApk(String name) throws FileNotFoundException {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        return buildHelper.getTestFile(name);
+    }
+
+    private void deleteSetting(String name) throws DeviceNotAvailableException {
+        String output = getDevice().executeShellCommand("settings delete global " + name);
+        assertThat(output).containsMatch("Deleted (0|1) rows");
+    }
+
+    private void deleteConfig() throws DeviceNotAvailableException {
+        deleteSetting(SETTING_BLACKLIST_EXEMPTIONS);
+        deleteSetting(SETTING_SIGNED_CONFIG_VERSION);
+    }
+
+    private void uninstallTestApps() throws DeviceNotAvailableException {
+        getDevice().uninstallPackage(TEST_APP_PACKAGE_NAME);
+        getDevice().uninstallPackage(TEST_APP_PACKAGE2_NAME);
+    }
+
+    private void waitUntilSettingMatches(String setting, String value) throws Exception {
+        int tries = 0;
+        String v;
+        do {
+            Thread.sleep(500);
+            v = getDevice().getSetting("global", setting);
+            tries++;
+        } while (tries < 10 && !Objects.equals(value, v));
+        assertThat(v).isEqualTo(value);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        deleteConfig();
+        waitForDevice();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallTestApps();
+        deleteConfig();
+    }
+
+    private void waitForDevice(int seconds) throws Exception {
+        Thread.sleep(seconds * 1000);
+    }
+
+    private void waitForDevice() throws Exception {
+        waitForDevice(1);
+    }
+
+    private void installPackage(String apkName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        assertThat(getDevice().installPackage(getTestApk(apkName), false)).isNull();
+    }
+
+    @Test
+    public void testConfigAppliedOnInstall() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "1");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass1;->method1(,LClass1;->field1:");
+    }
+
+    @Test
+    public void testConfigUpgradedOnInstall() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "1");
+        installPackage(TEST_APP_APK_NAME_V2);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "2");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass2;->method2(,LClass2;->field2:");
+    }
+
+    @Test
+    public void testConfigRemainsAfterUninstall() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "1");
+        getDevice().uninstallPackage(TEST_APP_PACKAGE_NAME);
+        waitForDevice(5);
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION)).isEqualTo("1");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass1;->method1(,LClass1;->field1:");
+    }
+
+    @Test
+    public void testConfigNotDowngraded() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V2);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "2");
+        installPackage(TEST_APP_APK_NAME_V3_CONFIGV1);
+        waitForDevice(5);
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION)).isEqualTo("2");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass2;->method2(,LClass2;->field2:");
+    }
+
+    @Test
+    public void testConfigUpgradedOnInstallOtherPackage() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "1");
+        installPackage(TEST_APP_APK_NAME_PACKAGE2_V2);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "2");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass2;->method2(,LClass2;->field2:");
+    }
+
+    @Test
+    public void testBadSignatureIgnored() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1_BAD_SIGNATURE);
+        waitForDevice(5);
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION))
+                .isEqualTo("null");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS))
+                .isEqualTo("null");
+    }
+
+    @Test
+    public void testBadBase64Config() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1_BAD_B64_CONFIG);
+        waitForDevice(5);
+        // This test is really testing that the system server doesn't crash, but
+        // this check should still do the trick.
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION))
+                .isEqualTo("null");
+    }
+
+    @Test
+    public void testBadBase64Signature() throws Exception {
+        installPackage(TEST_APP_APK_NAME_V1_BAD_B64_SIGNATURE);
+        waitForDevice(5);
+        // This test is really testing that the system server doesn't crash, but
+        // this check should still do the trick.
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION))
+                .isEqualTo("null");
+    }
+
+    @Test
+    public void testDebugKeyValidOnDebugBuild() throws Exception {
+        Assume.assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
+        installPackage(TEST_APP_APK_NAME_V1_DEBUG_KEY);
+        waitUntilSettingMatches(SETTING_SIGNED_CONFIG_VERSION, "1");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS)).isEqualTo(
+                "LClass1;->method1(,LClass1;->field1:");
+    }
+
+    @Test
+    public void testDebugKeyNotValidOnUserBuild() throws Exception {
+        Assume.assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
+        installPackage(TEST_APP_APK_NAME_V1_DEBUG_KEY);
+        waitForDevice(5);
+        assertThat(getDevice().getSetting("global", SETTING_SIGNED_CONFIG_VERSION))
+                .isEqualTo("null");
+        assertThat(getDevice().getSetting("global", SETTING_BLACKLIST_EXEMPTIONS))
+                .isEqualTo("null");
+    }
+}
diff --git a/hostsidetests/statsd/Android.mk b/hostsidetests/statsd/Android.mk
index 0be6af6..af7cb20 100644
--- a/hostsidetests/statsd/Android.mk
+++ b/hostsidetests/statsd/Android.mk
@@ -26,6 +26,9 @@
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util host-libprotobuf-java-full platformprotos
 
+LOCAL_COMPATIBILITY_SUPPORT_FILES := \
+	$(foreach file, $(call find-subdir-files, *.pbtxt), $(LOCAL_PATH)/$(file))
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
index 420ae75..a1beb7c 100644
--- a/hostsidetests/statsd/AndroidTest.xml
+++ b/hostsidetests/statsd/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Statsd host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="statsd" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsStatsdHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
new file mode 100644
index 0000000..8716e2b
--- /dev/null
+++ b/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
@@ -0,0 +1,23 @@
+id: 8835981461554930288
+count_metric {
+  id: 543749824321007836
+  what: 3909523419673092535
+  bucket: ONE_DAY
+}
+atom_matcher {
+  id: 3909523419673092535
+  simple_atom_matcher {
+    atom_id: 98
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
new file mode 100644
index 0000000..c9ab61d
--- /dev/null
+++ b/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
@@ -0,0 +1,35 @@
+id: 8835981461554930288
+count_metric {
+  id: 543749824321007836
+  what: 3909523419673092535
+  dimensions_in_what {
+    field: 100
+    child {
+      field: 1
+    },
+    child {
+      field: 2
+    },
+    child {
+      field: 3
+    }
+  }
+  bucket: ONE_DAY
+}
+atom_matcher {
+  id: 3909523419673092535
+  simple_atom_matcher {
+    atom_id: 100
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
new file mode 100644
index 0000000..46355e7
--- /dev/null
+++ b/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
@@ -0,0 +1,39 @@
+id: 8835981461554930288
+count_metric {
+  id: 543749824321007836
+  what: 3909523419673092535
+  dimensions_in_what {
+    field: 99
+    child {
+      field: 1
+    },
+    child {
+      field: 2
+    },
+    child {
+      field: 3
+    }
+  }
+  bucket: ONE_DAY
+}
+atom_matcher {
+  id: 3909523419673092535
+  simple_atom_matcher {
+    atom_id: 99
+    field_value_matcher: {
+      eq_int: 1
+      field: 4
+    }
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
new file mode 100644
index 0000000..41a8e0e
--- /dev/null
+++ b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
@@ -0,0 +1,69 @@
+# DURATION_PROCESS_STATE_IN_CACHED_EMPTY_PER_PROC_NAME_PACKAGE_NAME_VERSION
+id: -6109199674574072698
+duration_metric {
+  id: -7871805656933174442
+  what: -4194528603977557137
+  aggregation_type: SUM
+  dimensions_in_what {
+    field: 3
+    child {
+      field: 2
+    }
+    child {
+      field: 3
+    }
+    child {
+      field: 5
+    }
+  }
+  bucket: ONE_MINUTE
+}
+# PROC_STATE NOT IN CACHED_EMPTY
+atom_matcher {
+  id: -2354884036751182872
+  combination {
+    operation: NOT
+    matcher: -7794766650955623092
+  }
+}
+# PROC_STATE IN CACHED_EMPTY
+atom_matcher {
+  id: -7794766650955623092
+  simple_atom_matcher {
+    atom_id: 3
+    field_value_matcher {
+      field: 4
+      eq_int: 1018
+    }
+  }
+}
+predicate {
+  id: -4194528603977557137
+  simple_predicate {
+    start: -7794766650955623092
+    stop: -2354884036751182872
+    count_nesting: false
+    dimensions {
+      field: 3
+      child {
+        field: 2
+      }
+      child {
+        field: 3
+      }
+      child {
+        field: 5
+      }
+    }
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
new file mode 100644
index 0000000..e6efd55
--- /dev/null
+++ b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
@@ -0,0 +1,44 @@
+# VALUE_MAX_PSS_PER_PROC_NAME_PACKAGE_NAME_VERSION
+id: -6109199674574072698
+value_metric {
+  id: 1867856787681329178
+  what: -3480158308153459853
+  value_field {
+    field: 18
+    child {
+      field: 4
+    }
+  }
+  dimensions_in_what {
+    field: 18
+    child {
+      field: 2
+    }
+    child {
+      field: 3
+    }
+    child {
+      field: 9
+    }
+  }
+  bucket: ONE_MINUTE
+  aggregation_type: MAX
+}
+# PROCESS_MEMORY_STAT_REPORTED
+atom_matcher {
+  id: -3480158308153459853
+  simple_atom_matcher {
+    atom_id: 18
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
new file mode 100644
index 0000000..e17727a
--- /dev/null
+++ b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
@@ -0,0 +1,90 @@
+# DURATION_PROCESS_STATE_IN_TOP_PER_PROC_NAME_PACKAGE_NAME_VERSION
+id: -6109199674574072698
+duration_metric {
+  id: -1365360216258753370
+  what: -8800411078553365796
+  aggregation_type: SUM
+  dimensions_in_what {
+    field: 3
+    child {
+      field: 2
+    }
+    child {
+      field: 3
+    }
+    child {
+      field: 5
+    }
+  }
+  bucket: ONE_MINUTE
+}
+# PROC_STATE NOT IN TOP
+atom_matcher {
+  id: -7829668247086356765
+  combination {
+    operation: NOT
+    matcher: -2987742411590785849
+  }
+}
+# PROCESS_STATE TOP
+atom_matcher {
+  id: 509484152027467470
+  simple_atom_matcher {
+    atom_id: 3
+    field_value_matcher {
+      field: 4
+      eq_int: 1002
+    }
+  }
+}
+# PROCESS_STATE TOP_SLEEPING
+atom_matcher {
+  id: -3293304223207806916
+  simple_atom_matcher {
+    atom_id: 3
+    field_value_matcher {
+      field: 4
+      eq_int: 1011
+    }
+  }
+}
+# PROC_STATE IN TOP
+atom_matcher {
+  id: -2987742411590785849
+  combination {
+    operation: OR
+    matcher: 509484152027467470
+    matcher: -3293304223207806916
+  }
+}
+predicate {
+  id: -8800411078553365796
+  simple_predicate {
+    start: -2987742411590785849
+    stop: -7829668247086356765
+    count_nesting: false
+    dimensions {
+      field: 3
+      child {
+        field: 2
+      }
+      child {
+        field: 3
+      }
+      child {
+        field: 5
+      }
+    }
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
new file mode 100644
index 0000000..7d0820c
--- /dev/null
+++ b/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
@@ -0,0 +1,58 @@
+id: 8835981461554930288
+gauge_metric {
+  id: 543749824321007836
+  what: 3909523419673092535
+  gauge_fields_filter {
+    include_all: true
+  }
+  bucket: ONE_DAY
+  condition: -377136895
+  sampling_type: CONDITION_CHANGE_TO_TRUE
+  # Normal user should have <1000
+  max_num_gauge_atoms_per_bucket: 2000
+}
+atom_matcher {
+  id: 3909523419673092535
+  simple_atom_matcher {
+    atom_id: 10029
+  }
+}
+atom_matcher {
+  id: -1651300237
+  simple_atom_matcher {
+    atom_id: 47
+    field_value_matcher {
+      field: 2
+      eq_int: 1
+    }
+  }
+}
+atom_matcher {
+  id: -1651300236
+  simple_atom_matcher {
+    atom_id: 47
+    field_value_matcher {
+      field: 2
+      eq_int: 2
+    }
+  }
+}
+predicate {
+  id: -377136895
+  simple_predicate {
+    start: -1651300237
+    stop: -1651300236
+    count_nesting: false
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
new file mode 100644
index 0000000..7185963
--- /dev/null
+++ b/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
@@ -0,0 +1,58 @@
+id: 8835981461554930288
+gauge_metric {
+  id: 543749824321007836
+  what: 3909523419673092535
+  gauge_fields_filter {
+    include_all: true
+  }
+  bucket: ONE_DAY
+  condition: -377136895
+  sampling_type: CONDITION_CHANGE_TO_TRUE
+  # Normal user should have <1000
+  max_num_gauge_atoms_per_bucket: 2000
+}
+atom_matcher {
+  id: 3909523419673092535
+  simple_atom_matcher {
+    atom_id: 10034
+  }
+}
+atom_matcher {
+  id: -1651300237
+  simple_atom_matcher {
+    atom_id: 47
+    field_value_matcher {
+      field: 2
+      eq_int: 1
+    }
+  }
+}
+atom_matcher {
+  id: -1651300236
+  simple_atom_matcher {
+    atom_id: 47
+    field_value_matcher {
+      field: 2
+      eq_int: 2
+    }
+  }
+}
+predicate {
+  id: -377136895
+  simple_predicate {
+    start: -1651300237
+    stop: -1651300236
+    count_nesting: false
+  }
+}
+allowed_log_source: "AID_GRAPHICS"
+allowed_log_source: "AID_INCIDENTD"
+allowed_log_source: "AID_STATSD"
+allowed_log_source: "AID_RADIO"
+allowed_log_source: "com.android.systemui"
+allowed_log_source: "com.android.vending"
+allowed_log_source: "AID_SYSTEM"
+allowed_log_source: "AID_ROOT"
+allowed_log_source: "AID_BLUETOOTH"
+
+hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.mk b/hostsidetests/statsd/apps/statsdapp/Android.mk
index 71d9323..4218526 100644
--- a/hostsidetests/statsd/apps/statsdapp/Android.mk
+++ b/hostsidetests/statsd/apps/statsdapp/Android.mk
@@ -36,8 +36,7 @@
     compatibility-device-util \
     androidx.legacy_legacy-support-v4 \
     legacy-android-test \
-    android-support-test \
-    statsdprotolite
+    android-support-test
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
index 1e86fa7..592f0a7 100644
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -16,22 +16,23 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.server.cts.device.statsd" >
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.CAMERA"/>
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" />
     <uses-permission android:name="android.permission.DUMP" /> <!-- must be granted via pm grant -->
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
     <application android:label="@string/app_name">
         <uses-library android:name="android.test.runner" />
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 719dcb7..ab668b8 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
@@ -16,11 +16,14 @@
 
 package com.android.server.cts.device.statsd;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static org.junit.Assert.assertTrue;
+
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
-import android.app.Activity;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.bluetooth.BluetoothAdapter;
@@ -35,9 +38,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.location.Location;
 import android.location.LocationListener;
@@ -51,13 +53,11 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.support.test.InstrumentationRegistry;
 import android.util.Log;
-import android.util.StatsLog;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -336,6 +336,16 @@
     }
 
     @Test
+    public void testVibratorState() {
+        Context context = InstrumentationRegistry.getContext();
+        Vibrator vib = context.getSystemService(Vibrator.class);
+        if (vib.hasVibrator()) {
+            vib.vibrate(VibrationEffect.createOneShot(
+                    500 /* ms */, VibrationEffect.DEFAULT_AMPLITUDE));
+        }
+    }
+
+    @Test
     public void testWakelockState() {
         Context context = InstrumentationRegistry.getContext();
         PowerManager pm = context.getSystemService(PowerManager.class);
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 40c4f03..5e62558 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
@@ -17,6 +17,7 @@
 package com.android.server.cts.device.statsd;
 
 import android.net.wifi.WifiManager;
+import android.os.Vibrator;
 import android.support.test.InstrumentationRegistry;
 
 import static org.junit.Assert.assertTrue;
@@ -29,6 +30,12 @@
     private static final String TAG = Checkers.class.getSimpleName();
 
     @Test
+    public void checkVibratorSupported() {
+        Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
+        assertTrue(v.hasVibrator());
+    }
+
+    @Test
     public void checkWifiEnhancedPowerReportingSupported() {
         WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
         assertTrue(wm.isEnhancedPowerReportingSupported());
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
index ad72959..d0a05f4 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
@@ -19,6 +19,9 @@
 import com.android.server.cts.device.statsd.AtomTests;
 
 import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -37,11 +40,14 @@
     public static final String KEY_ACTION = "action";
     public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
     public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+    public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
     public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+    public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
     public static final String ACTION_CRASH = "action.crash";
 
     public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
     public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
+    public static final int LONG_SLEEP_WHILE_TOP = 60_000;
 
     @Override
     public void onCreate(Bundle bundle) {
@@ -61,11 +67,17 @@
                 finish();
                 break;
             case ACTION_SLEEP_WHILE_TOP:
-                doSleepWhileTop();
+                doSleepWhileTop(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
+                break;
+            case ACTION_LONG_SLEEP_WHILE_TOP:
+                doSleepWhileTop(LONG_SLEEP_WHILE_TOP);
                 break;
             case ACTION_SHOW_APPLICATION_OVERLAY:
                 doShowApplicationOverlay();
                 break;
+            case ACTION_SHOW_NOTIFICATION:
+                doShowNotification();
+                break;
             case ACTION_CRASH:
                 doCrash();
                 break;
@@ -76,11 +88,11 @@
     }
 
     /** Does nothing, but asynchronously. */
-    private void doSleepWhileTop() {
+    private void doSleepWhileTop(int sleepTime) {
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                AtomTests.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
+                AtomTests.sleep(sleepTime);
                 return null;
             }
 
@@ -121,6 +133,25 @@
         finish();
     }
 
+    private void doShowNotification() {
+        final int notificationId = R.layout.activity_main;
+        final String notificationChannel = "StatsdCtsChannel";
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        nm.createNotificationChannel(new NotificationChannel(notificationChannel, "Statsd Cts",
+                NotificationManager.IMPORTANCE_DEFAULT));
+
+        nm.notify(
+                notificationId,
+                new Notification.Builder(this, notificationChannel)
+                        .setSmallIcon(android.R.drawable.stat_notify_chat)
+                        .setContentTitle("StatsdCts")
+                        .setContentText("StatsdCts")
+                        .build());
+        nm.cancel(notificationId);
+        finish();
+    }
+
     @SuppressWarnings("ConstantOverflow")
     private void doCrash() {
         Log.e(TAG, "About to crash the app with 1/0 " + (long)1/0);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index e78a9cd..3109138 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -33,9 +33,9 @@
 import com.android.os.AtomsProto.AnomalyDetected;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.KernelWakelock;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.tradefed.log.LogUtil.CLog;
-
 import java.util.List;
 
 /**
@@ -46,7 +46,7 @@
     private static final String TAG = "Statsd.AnomalyDetectionTests";
 
     private static final boolean INCIDENTD_TESTS_ENABLED = false;
-    private static final boolean PERFETTO_TESTS_ENABLED = false;
+    private static final boolean PERFETTO_TESTS_ENABLED = true;
 
     private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
 
@@ -279,14 +279,15 @@
                 return;
         }
 
+        if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
+
         StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
                 .addSubscription(Subscription.newBuilder()
                         .setId(SUBSCRIPTION_ID_PERFETTO)
                         .setRuleType(Subscription.RuleType.ALERT)
                         .setRuleId(ALERT_ID)
                         .setPerfettoDetails(PerfettoDetails.newBuilder()
-                                .setTraceConfig(createPerfettoTraceConfig())
-                        )
+                                .setTraceConfig(getPerfettoConfig()))
                 )
                 .addValueMetric(ValueMetric.newBuilder()
                         .setId(METRIC_ID)
@@ -306,7 +307,7 @@
         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("Pefetto", didPerfettoStartSince(markTime));
+        if (PERFETTO_TESTS_ENABLED) assertFalse(isSystemTracingEnabled());
 
         doAppBreadcrumbReportedStart(14); // value = 14 > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
@@ -315,7 +316,7 @@
         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("No perfetto", didPerfettoStartSince(markTime));
+        if (PERFETTO_TESTS_ENABLED) assertTrue(isSystemTracingEnabled());
     }
 
     // Tests that anomaly detection for gauge works.
@@ -356,6 +357,54 @@
         if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
     }
 
+    // Test that anomaly detection for pulled metrics work.
+    public void testPulledAnomalyDetection() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        final int ATOM_ID = Atom.KERNEL_WAKELOCK_FIELD_NUMBER;  // A pulled atom
+        final int SLICE_BY_FIELD = KernelWakelock.NAME_FIELD_NUMBER;
+        final int VALUE_FIELD = KernelWakelock.VERSION_FIELD_NUMBER;  // Something that will be > 0.
+        final int ATOM_MATCHER_ID = 300;
+
+        StatsdConfig.Builder config = getBaseConfig(10, 20, 0 /* threshold: value > 0 */)
+                .addAllowedLogSource("AID_SYSTEM")
+                // Track the ATOM_ID pulled atom
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(ATOM_MATCHER_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(ATOM_ID)))
+                .addGaugeMetric(GaugeMetric.newBuilder()
+                        .setId(METRIC_ID)
+                        .setWhat(ATOM_MATCHER_ID)
+                        .setBucket(TimeUnit.CTS)
+                        .setSamplingType(GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
+                        // Slice by SLICE_BY_FIELD (typical usecase)
+                        .setDimensionsInWhat(FieldMatcher.newBuilder()
+                                .setField(ATOM_ID)
+                                .addChild(FieldMatcher.newBuilder().setField(SLICE_BY_FIELD))
+                        )
+                        // Track the VALUE_FIELD (anomaly detection requires exactly one field here)
+                        .setGaugeFieldsFilter(
+                                FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
+                                        .setField(ATOM_ID)
+                                        .addChild(FieldMatcher.newBuilder().setField(VALUE_FIELD))
+                                )
+                        )
+                );
+        uploadConfig(config);
+
+        Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
+
+        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());
+    }
+
+
     private final StatsdConfig.Builder getBaseConfig(int numBuckets,
                                                      int refractorySecs,
                                                      long triggerIfSumGt) throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index 02bc735..1fade2f 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -19,8 +19,9 @@
 import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
 
 import android.os.BatteryStatsProto;
+import android.os.StatsDataDumpProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
-import android.view.DisplayStateEnum;
+import android.service.procstats.ProcessStatsServiceDumpProto;
 
 import com.android.annotations.Nullable;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
@@ -36,16 +37,24 @@
 import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.AtomsProto.ProcessStatsPackageProto;
+import com.android.os.AtomsProto.ProcessStatsProto;
+import com.android.os.AtomsProto.ProcessStatsStateProto;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.DurationMetricData;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.CountMetricData;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.StatsLog.ValueMetricData;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
 
 import com.google.common.io.Files;
+import com.google.protobuf.ByteString;
 
 import java.io.File;
 import java.text.SimpleDateFormat;
@@ -57,25 +66,41 @@
 import java.util.Set;
 import java.util.function.Function;
 
-import perfetto.protos.PerfettoConfig.DataSourceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig.BufferConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig.DataSource;
-
 /**
  * Base class for testing Statsd atoms.
  * Validates reporting of statsd logging based on different events
  */
 public class AtomTestCase extends BaseTestCase {
 
+    /**
+     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
+     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
+     */
+    public static final boolean OPTIONAL_TESTS_ENABLED = false;
+
     public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
     public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
     public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
+    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
+    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
     public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
-    public static final String CONFIG_UID = "1000";
     /** ID of the config, which evaluates to -1572883457. */
     public static final long CONFIG_ID = "cts_config".hashCode();
 
+    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+    public static final String FEATURE_CAMERA = "android.hardware.camera";
+    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+    public static final String FEATURE_PC = "android.hardware.type.pc";
+    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+    public static final String FEATURE_WIFI = "android.hardware.wifi";
+
     protected static final int WAIT_TIME_SHORT = 500;
     protected static final int WAIT_TIME_LONG = 2_000;
 
@@ -115,32 +140,58 @@
         return log.contains(INCIDENTD_STARTED_STRING);
     }
 
-    /**
-     * Determines whether logcat indicates that perfetto fired since the given device date.
-     */
-    protected boolean didPerfettoStartSince(String date) throws Exception {
-        final String PERFETTO_TAG = "perfetto";
-        final String PERFETTO_STARTED_STRING = "Enabled tracing";
-        final String PERFETTO_STARTED_REGEX = ".*" + PERFETTO_STARTED_STRING + ".*";
-        // TODO: Do something more robust than this in case of delayed logging.
-        Thread.sleep(1000);
-        String log = getLogcatSince(date, String.format(
-                "-s %s -e %s", PERFETTO_TAG, PERFETTO_STARTED_REGEX));
-        return log.contains(PERFETTO_STARTED_STRING);
-    }
-
     protected boolean checkDeviceFor(String methodName) throws Exception {
         try {
             installPackage(DEVICE_SIDE_TEST_APK, true);
             runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
             // Test passes, meaning that the answer is true.
+            LogUtil.CLog.d(methodName + "() indicates true.");
             return true;
         } catch (AssertionError e) {
             // Method is designed to fail if the answer is false.
+            LogUtil.CLog.d(methodName + "() indicates false.");
             return false;
         }
     }
 
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel
+     * ftrace tracer with sched_switch for 10 seconds.
+     * See https://android.googlesource.com/platform/external/perfetto/+/master/docs/trace-config.md
+     * for details on how to generate this.
+     */
+    protected ByteString getPerfettoConfig() {
+        return ByteString.copyFrom(new byte[] { 0xa, 0x3, 0x8, (byte) 0x80, 0x1, 0x12, 0x23, 0xa,
+                        0x21, 0xa, 0xc, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x66, 0x74, 0x72, 0x61,
+                        0x63, 0x65, 0x10, 0x0, (byte) 0xa2, 0x6, 0xe, 0xa, 0xc, 0x73, 0x63, 0x68,
+                        0x65, 0x64, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x18, (byte) 0x90,
+                        0x4e });
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
+     * run too close of for too many times and hits the upload limit.
+     */
+    protected void resetPerfettoGuardrails() throws Exception {
+        final String cmd = "perfetto --reset-guardrails";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS)
+            throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
+    }
+
+    /**
+     * Determines whether perfetto enabled the kernel ftrace tracer.
+     */
+    protected boolean isSystemTracingEnabled() throws Exception {
+        final String path = "/sys/kernel/debug/tracing/tracing_on";
+        String tracing_on = getDevice().executeShellCommand("cat " + path);
+        if (tracing_on.startsWith("0"))
+            return false;
+        if (tracing_on.startsWith("1"))
+            return true;
+        throw new Exception(String.format("Unexpected state for %s = %s", path, tracing_on));
+    }
+
     protected static StatsdConfig.Builder createConfigBuilder() {
         return StatsdConfig.newBuilder().setId(CONFIG_ID)
                 .addAllowedLogSource("AID_SYSTEM")
@@ -179,6 +230,15 @@
     /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
     protected List<EventMetricData> getEventMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
+        return getEventMetricDataList(reportList);
+    }
+
+    /**
+     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
+     * contain a single report).
+     */
+    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
+            throws Exception {
         assertTrue("Expected one report", reportList.getReportsCount() == 1);
         ConfigMetricsReport report = reportList.getReports(0);
 
@@ -197,13 +257,15 @@
 
     protected List<Atom> getGaugeMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertTrue(reportList.getReportsCount() == 1);
+        assertTrue("Expected one report.", reportList.getReportsCount() == 1);
         // only config
         ConfigMetricsReport report = reportList.getReports(0);
+        assertEquals("Expected one metric in the report.", 1, report.getMetricsCount());
 
         List<Atom> data = new ArrayList<>();
         for (GaugeMetricData gaugeMetricData :
                 report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            assertTrue("Expected one bucket.", gaugeMetricData.getBucketInfoCount() == 1);
             for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) {
                 data.add(atom);
             }
@@ -216,6 +278,69 @@
         return data;
     }
 
+    /**
+     * Gets the statsd report and extract duration metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<DurationMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getDurationMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
+        for (DurationMetricData d : data) {
+            LogUtil.CLog.d("Duration " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract count metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<CountMetricData> getCountMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<CountMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getCountMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
+        for (CountMetricData d : data) {
+            LogUtil.CLog.d("Count " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract value metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<ValueMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getValueMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
+        for (ValueMetricData d : data) {
+            LogUtil.CLog.d("Value " + d);
+        }
+        return data;
+    }
+
     protected StatsLogReport getStatsLogReport() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
         assertTrue(reportList.getReportsCount() == 1);
@@ -230,12 +355,12 @@
         try {
             ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
                     String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
-                            "--proto"));
+                            "--include_current_bucket", "--proto"));
             return reportList;
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
             LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
                     + "Perhaps there is not a valid statsd config for the requested "
-                    + "uid=" + CONFIG_UID + ", id=" + CONFIG_ID + ".");
+                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
             throw (e);
         }
     }
@@ -253,24 +378,84 @@
         }
     }
 
+    /** Gets reports from the statsd data incident section from the stats dumpsys. */
+    protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
+        try {
+            StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
+                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
+            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
+            List<ConfigMetricsReportList> reports
+                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
+            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
+                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
+            }
+            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
+            return reports;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dumpsys stats proto");
+            throw (e);
+        }
+    }
+
+    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
+        try {
+
+            List<ProcessStatsProto> processStatsProtoList =
+                new ArrayList<ProcessStatsProto>();
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsNow();
+            for (android.service.procstats.ProcessStatsProto stats :
+                    sectionProto.getProcessStatsList()) {
+                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
+                    stats.toByteArray());
+                processStatsProtoList.add(procStats);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    /*
+     * Get all procstats package data in proto
+     */
+    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsOver24Hrs();
+            List<ProcessStatsPackageProto> processStatsProtoList =
+                new ArrayList<ProcessStatsPackageProto>();
+            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
+                sectionProto.getPackageStatsList()) {
+              ProcessStatsPackageProto pkgAtom =
+                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
+                processStatsProtoList.add(pkgAtom);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
     /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
     protected static FieldValueMatcher.Builder createFvm(int field) {
         return FieldValueMatcher.newBuilder().setField(field);
     }
 
-    protected static TraceConfig createPerfettoTraceConfig() {
-        return TraceConfig.newBuilder()
-            .addBuffers(BufferConfig.newBuilder().setSizeKb(32))
-            .addDataSources(DataSource.newBuilder()
-                .setConfig(DataSourceConfig.newBuilder()
-                    .setName("linux.ftrace")
-                    .setTargetBuffer(0)
-                    .build()
-                )
-            )
-            .build();
-    }
-
     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
         addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
     }
@@ -318,12 +503,12 @@
     /**
      * Adds an atom to a gauge metric of a config
      *
-     * @param conf      configuration
-     * @param atomId    atom id (from atoms.proto)
-     * @param dimension dimension is needed for most pulled atoms
+     * @param conf        configuration
+     * @param atomId      atom id (from atoms.proto)
+     * @param gaugeMetric the gauge metric to add
      */
     protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
-            @Nullable FieldMatcher.Builder dimension) throws Exception {
+            GaugeMetric.Builder gaugeMetric) throws Exception {
         final String atomName = "Atom" + System.nanoTime();
         final String gaugeName = "Gauge" + System.nanoTime();
         final String predicateName = "APP_BREADCRUMB";
@@ -362,17 +547,31 @@
                         .setCountNesting(false)
                 )
         );
-        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+        gaugeMetric
                 .setId(gaugeName.hashCode())
                 .setWhat(atomName.hashCode())
-                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
-                .setSamplingType(GaugeMetric.SamplingType.ALL_CONDITION_CHANGES)
-                .setBucket(TimeUnit.CTS)
                 .setCondition(predicateName.hashCode());
+        conf.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Adds an atom to a gauge metric of a config
+     *
+     * @param conf      configuration
+     * @param atomId    atom id (from atoms.proto)
+     * @param dimension dimension is needed for most pulled atoms
+     */
+    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
+            @Nullable FieldMatcher.Builder dimension) throws Exception {
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
+                .setMaxNumGaugeAtomsPerBucket(10000)
+                .setBucket(TimeUnit.CTS);
         if (dimension != null) {
             gaugeMetric.setDimensionsInWhat(dimension.build());
         }
-        conf.addGaugeMetric(gaugeMetric.build());
+        addGaugeAtom(conf, atomId, gaugeMetric);
     }
 
     /**
@@ -488,6 +687,10 @@
         }
     }
 
+    protected String getProperty(String prop) throws Exception {
+        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
+    }
+
     protected void turnScreenOn() throws Exception {
         getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
         getDevice().executeShellCommand("wm dismiss-keyguard");
@@ -517,6 +720,44 @@
         getDevice().executeShellCommand("cmd battery set wireless 1");
     }
 
+    protected void enableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+    }
+
+    protected void resetLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    protected void disableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+
+    protected void enableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
+    }
+
+    protected void resetBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
+    }
+
+    protected void disableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
+    }
+
+    protected void binderStatsNoSampling() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
+    }
+
+    protected void setUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    protected void cleanUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+
     public void setAppBreadcrumbPredicate() throws Exception {
         doAppBreadcrumbReportedStart(1);
     }
@@ -661,4 +902,11 @@
         return getDevice().doesFileExist(file);
     }
 
+    protected void turnOnAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+    }
+
+    protected void turnOffAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
index cd396ec..907f1d7 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -16,6 +16,8 @@
 
 package android.cts.statsd.atom;
 
+import android.cts.statsd.validation.ValidationTestUtil;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
@@ -58,6 +60,19 @@
         mCtsBuild = buildInfo;
     }
 
+    public IBuildInfo getBuild() {
+        return mCtsBuild;
+    }
+
+    /**
+     * Create and return {@link ValidationTestUtil} and give it the current build.
+     */
+    public ValidationTestUtil createValidationUtil() {
+        ValidationTestUtil util = new ValidationTestUtil();
+        util.setBuild(getBuild());
+        return util;
+    }
+
     /**
      * Call onto the device with an adb shell command and get the results of
      * that as a proto of the given type.
@@ -92,6 +107,10 @@
         assertNull("Failed to install " + appFileName + ": " + result, result);
     }
 
+    protected CompatibilityBuildHelper getBuildHelper() {
+        return new CompatibilityBuildHelper(mCtsBuild);
+    }
+
     /**
      * Run a device side test.
      *
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
index c14a89f..9f2685c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -30,13 +30,15 @@
  */
 public class DeviceAtomTestCase extends AtomTestCase {
 
-    protected static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
-    protected static final String DEVICE_SIDE_TEST_PACKAGE =
+    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
+    public static final String DEVICE_SIDE_TEST_PACKAGE =
             "com.android.server.cts.device.statsd";
-    protected static final long DEVICE_SIDE_TEST_PKG_HASH =
+    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
+            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
+    public static final long DEVICE_SIDE_TEST_PKG_HASH =
             Long.parseUnsignedLong("15694052924544098582");
 
-    protected static final String CONFIG_NAME = "cts_config";
+    public static final String CONFIG_NAME = "cts_config";
 
     @Override
     protected void setUp() throws Exception {
@@ -172,6 +174,13 @@
     }
 
     /**
+     * Uninstalls the test apk.
+     */
+    protected void uninstallPackage() throws Exception{
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    /**
      * Required to successfully start a background service from adb in O.
      */
     protected void allowBackgroundServices() throws Exception {
@@ -198,6 +207,24 @@
      */
     protected void runActivity(String activity, String actionKey, String actionValue,
             long waitTime) throws Exception {
+        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
+            Thread.sleep(waitTime);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
         String intentString = null;
         if (actionKey != null && actionValue != null) {
             intentString = actionKey + " " + actionValue;
@@ -210,15 +237,30 @@
                     "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
                             intentString);
         }
-
-        Thread.sleep(waitTime);
-        getDevice().executeShellCommand(
-                "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
-
-        Thread.sleep(WAIT_TIME_SHORT);
+        return () -> {
+            getDevice().executeShellCommand(
+                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
+            Thread.sleep(WAIT_TIME_SHORT);
+        };
     }
 
     protected void resetBatteryStats() throws Exception {
         getDevice().executeShellCommand("dumpsys batterystats --reset");
     }
+
+    protected void clearProcStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    protected void startProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --start-testing");
+    }
+
+    protected void stopProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
+    }
+
+    protected void commitProcStatsToDisk() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --commit");
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index cc3b47c..8d5e523 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -21,17 +21,15 @@
 import android.server.DeviceIdleModeEnum;
 import android.view.DisplayStateEnum;
 
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.BatterySaverModeStateChanged;
-import com.android.os.AtomsProto.FullBatteryCapacity;
-import com.android.os.AtomsProto.KernelWakelock;
-import com.android.os.AtomsProto.RemainingBatteryCapacity;
+import com.android.os.AtomsProto.BuildInformation;
+import com.android.os.AtomsProto.ConnectivityStateChanged;
+import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.EventMetricData;
 
-import java.io.File;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -44,11 +42,6 @@
 
     private static final String TAG = "Statsd.HostAtomTests";
 
-    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    private static final String FEATURE_WIFI = "android.hardware.wifi";
-    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    private static final String FEATURE_WATCH = "android.hardware.type.watch";
-
     // Either file must exist to read kernel wake lock stats.
     private static final String WAKE_LOCK_FILE = "/proc/wakelocks";
     private static final String WAKE_SOURCES_FILE = "/d/wakeup_sources";
@@ -339,11 +332,7 @@
         }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = getPulledConfig();
-        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
-            .setField(Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER)
-            .addChild(FieldMatcher.newBuilder()
-                .setField(RemainingBatteryCapacity.CHARGE_UAH_FIELD_NUMBER));
-        addGaugeAtom(config, Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER, dimension);
+        addGaugeAtomWithDimensions(config, Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER, null);
 
         uploadConfig(config);
 
@@ -355,8 +344,8 @@
 
         assertTrue(data.size() > 0);
         Atom atom = data.get(0);
-        assertTrue(atom.getRemainingBatteryCapacity().hasChargeUAh());
-        assertTrue(atom.getRemainingBatteryCapacity().getChargeUAh() > 0);
+        assertTrue(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour());
+        assertTrue(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour() > 0);
     }
 
     @RestrictedBuildTest
@@ -366,11 +355,7 @@
         }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = getPulledConfig();
-        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
-                .setField(Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER)
-                .addChild(FieldMatcher.newBuilder()
-                        .setField(FullBatteryCapacity.CAPACITY_UAH_FIELD_NUMBER));
-        addGaugeAtom(config, Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER, dimension);
+        addGaugeAtomWithDimensions(config, Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER, null);
 
         uploadConfig(config);
 
@@ -382,8 +367,77 @@
 
         assertTrue(data.size() > 0);
         Atom atom = data.get(0);
-        assertTrue(atom.getFullBatteryCapacity().hasCapacityUAh());
-        assertTrue(atom.getFullBatteryCapacity().getCapacityUAh() > 0);
+        assertTrue(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour());
+        assertTrue(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour() > 0);
+    }
+
+    public void testBatteryVoltage() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_VOLTAGE_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertTrue(data.size() > 0);
+        Atom atom = data.get(0);
+        assertTrue(atom.getBatteryVoltage().hasVoltageMillivolt());
+        assertTrue(atom.getBatteryVoltage().getVoltageMillivolt() > 0);
+    }
+
+    // This test is for the pulled battery level atom.
+    public void testBatteryLevel() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_LEVEL_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertTrue(data.size() > 0);
+        Atom atom = data.get(0);
+        assertTrue(atom.getBatteryLevel().hasBatteryLevel());
+        assertTrue(atom.getBatteryLevel().getBatteryLevel() > 0);
+        assertTrue(atom.getBatteryLevel().getBatteryLevel() <= 100);
+    }
+
+    // This test is for the pulled battery charge count atom.
+    public void testBatteryCycleCount() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.BATTERY_CYCLE_COUNT_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+
+        assertTrue(data.size() > 0);
+        Atom atom = data.get(0);
+        assertTrue(atom.getBatteryCycleCount().hasCycleCount());
+        assertTrue(atom.getBatteryCycleCount().getCycleCount() > 0);
     }
 
     public void testKernelWakelock() throws Exception {
@@ -391,11 +445,7 @@
             return;
         }
         StatsdConfig.Builder config = getPulledConfig();
-        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
-                .setField(Atom.KERNEL_WAKELOCK_FIELD_NUMBER)
-                .addChild(FieldMatcher.newBuilder()
-                        .setField(KernelWakelock.NAME_FIELD_NUMBER));
-        addGaugeAtom(config, Atom.KERNEL_WAKELOCK_FIELD_NUMBER, dimension);
+        addGaugeAtomWithDimensions(config, Atom.KERNEL_WAKELOCK_FIELD_NUMBER, null);
 
         uploadConfig(config);
 
@@ -410,7 +460,7 @@
         assertTrue(atom.getKernelWakelock().hasCount());
         assertTrue(atom.getKernelWakelock().hasVersion());
         assertTrue(atom.getKernelWakelock().getVersion() > 0);
-        assertTrue(atom.getKernelWakelock().hasTime());
+        assertTrue(atom.getKernelWakelock().hasTimeMicros());
     }
 
     // Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
@@ -431,7 +481,7 @@
         if (!checkDeviceFor("checkWifiEnhancedPowerReportingSupported")) return;
 
         StatsdConfig.Builder config = getPulledConfig();
-        addGaugeAtom(config, Atom.WIFI_ACTIVITY_INFO_FIELD_NUMBER, null);
+        addGaugeAtomWithDimensions(config, Atom.WIFI_ACTIVITY_INFO_FIELD_NUMBER, null);
 
         uploadConfig(config);
 
@@ -451,6 +501,55 @@
         }
     }
 
+    public void testBuildInformation() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.BUILD_INFORMATION_FIELD_NUMBER, null);
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> data = getGaugeMetricDataList();
+        assertTrue(data.size() > 0);
+        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());
+    }
+
+    public void testOnDevicePowerMeasurement() throws Exception {
+        if (!OPTIONAL_TESTS_ENABLED) return;
+        if (statsdDisabled()) {
+            return;
+        }
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.ON_DEVICE_POWER_MEASUREMENT_FIELD_NUMBER, null);
+
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> dataList = getGaugeMetricDataList();
+
+        for (Atom atom: dataList) {
+            assertTrue(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis() >= 0);
+            assertTrue(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs() >= 0);
+        }
+    }
+
     // Explicitly tests if the adb command to log a breadcrumb is working.
     public void testBreadcrumbAdb() throws Exception {
         if (statsdDisabled()) {
@@ -468,4 +567,75 @@
         assertTrue(atom.getLabel() == 1);
         assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
     }
+
+    // Test dumpsys stats --proto.
+    public void testDumpsysStats() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        doAppBreadcrumbReportedStart(1);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Get the stats incident section.
+        List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
+        assertTrue(listList.size() > 0);
+
+        // Extract the relevent report from the incident section.
+        ConfigMetricsReportList ourList = null;
+        int hostUid = getHostUid();
+        for (ConfigMetricsReportList list : listList) {
+            ConfigMetricsReportList.ConfigKey configKey = list.getConfigKey();
+            if (configKey.getUid() == hostUid && configKey.getId() == CONFIG_ID) {
+                ourList = list;
+                break;
+            }
+        }
+        assertNotNull("Could not find list for uid=" + hostUid
+                + " id=" + CONFIG_ID, ourList);
+
+        // 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);
+    }
+
+    public void testConnectivityStateChange() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+
+        final int atomTag = Atom.CONNECTIVITY_STATE_CHANGED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        turnOnAirplaneMode();
+        turnOffAirplaneMode();
+        // wait for long enough for device to restore connection
+        Thread.sleep(10_000);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        // at least 1 disconnect and 1 connect
+        assertTrue(data.size() >= 2);
+        boolean foundDisconnectEvent = false;
+        boolean foundConnectEvent = false;
+        for (EventMetricData d : data) {
+            ConnectivityStateChanged atom = d.getAtom().getConnectivityStateChanged();
+            if(atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.DISCONNECTED_VALUE) {
+                foundDisconnectEvent = true;
+            }
+            if(atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.CONNECTED_VALUE) {
+                foundConnectEvent = true;
+            }
+        }
+        assertTrue(foundConnectEvent && foundDisconnectEvent);
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
index cb0f758..f567cd9 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -31,29 +31,10 @@
 /**
  * Statsd atom tests that are done via app, for atoms that report a uid.
  */
-public class ProcStateAtomTests extends DeviceAtomTestCase {
+public class ProcStateAtomTests extends ProcStateTestCase {
 
     private static final String TAG = "Statsd.ProcStateAtomTests";
 
-    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT
-            = "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
-    private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
-            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
-    private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
-            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
-
-    // Constants from the device-side tests (not directly accessible here).
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
-    public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
-    public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
-
-    // Sleep times (ms) that actions invoke device-side.
-    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
-    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
-
     private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
     // ActivityManager can take a while to register screen state changes, mandating an extra delay.
     private static final int WAIT_TIME_FOR_CONFIG_AND_SCREEN_MS = 1_000;
@@ -270,38 +251,6 @@
                 ALL_STATES.contains(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE));
     }
 
-    /**
-     * Runs a (background) service to perform the given action.
-     * @param actionValue the action code constants indicating the desired action to perform.
-     */
-    private void executeBackgroundService(String actionValue) throws Exception {
-        allowBackgroundServices();
-        getDevice().executeShellCommand(String.format(
-                "am startservice -n '%s' -e %s %s",
-                DEVICE_SIDE_BG_SERVICE_COMPONENT,
-                KEY_ACTION, actionValue));
-    }
-
-    /**
-     * Runs an activity (in the foreground) to perform the given action.
-     * @param actionValue the action code constants indicating the desired action to perform.
-     */
-    private void executeForegroundActivity(String actionValue) throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "am start -n '%s' -e %s %s",
-                DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
-                KEY_ACTION, actionValue));
-    }
-
-    /**
-     * Runs a simple foreground service.
-     */
-    private void executeForegroundService() throws Exception {
-        executeForegroundActivity(ACTION_END_IMMEDIATELY);
-        getDevice().executeShellCommand(String.format(
-                "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
-    }
-
     /** Returns the a set containing elements of a that are not elements of b. */
     private Set<Integer> difference(Set<Integer> a, Set<Integer> b) {
         Set<Integer> result = new HashSet<Integer>(a);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
new file mode 100644
index 0000000..cd65a37
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
@@ -0,0 +1,90 @@
+/*
+ * 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.cts.statsd.atom;
+
+import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Base class for manipulating process states
+ */
+public class ProcStateTestCase extends DeviceAtomTestCase {
+
+  private static final String TAG = "Statsd.ProcStateTestCase";
+
+  private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT
+          = "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
+  private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
+  private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
+          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
+
+  // Constants from the device-side tests (not directly accessible here).
+  public static final String KEY_ACTION = "action";
+  public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+  public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+  public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+  public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+  public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+
+  // Sleep times (ms) that actions invoke device-side.
+  public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+  public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
+  public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+  public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+
+  /**
+   * Runs a (background) service to perform the given action.
+   * @param actionValue the action code constants indicating the desired action to perform.
+   */
+  protected void executeBackgroundService(String actionValue) throws Exception {
+    allowBackgroundServices();
+    getDevice().executeShellCommand(String.format(
+            "am startservice -n '%s' -e %s %s",
+            DEVICE_SIDE_BG_SERVICE_COMPONENT,
+            KEY_ACTION, actionValue));
+  }
+
+  /**
+   * Runs an activity (in the foreground) to perform the given action.
+   * @param actionValue the action code constants indicating the desired action to perform.
+   */
+  protected void executeForegroundActivity(String actionValue) throws Exception {
+    getDevice().executeShellCommand(String.format(
+            "am start -n '%s' -e %s %s",
+            DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+            KEY_ACTION, actionValue));
+  }
+
+  /**
+   * Runs a simple foreground service.
+   */
+  protected void executeForegroundService() throws Exception {
+    executeForegroundActivity(ACTION_END_IMMEDIATELY);
+    getDevice().executeShellCommand(String.format(
+            "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
+  }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 5de5df9..1a13135 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -22,26 +22,37 @@
 import android.os.WakeLockLevelEnum;
 import android.platform.test.annotations.RestrictedBuildTest;
 
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import com.android.os.AtomsProto.AppCrashOccurred;
 import com.android.os.AtomsProto.AppStartOccurred;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AudioStateChanged;
+import com.android.os.AtomsProto.BinderCalls;
 import com.android.os.AtomsProto.BleScanResultReceived;
 import com.android.os.AtomsProto.BleScanStateChanged;
 import com.android.os.AtomsProto.CameraStateChanged;
 import com.android.os.AtomsProto.CpuActiveTime;
-import com.android.os.AtomsProto.CpuTimePerUid;
+import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
+import com.android.os.AtomsProto.DeviceCalculatedPowerUse;
 import com.android.os.AtomsProto.FlashlightStateChanged;
 import com.android.os.AtomsProto.ForegroundServiceStateChanged;
 import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.LooperStats;
 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.ProcessMemoryState;
+import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
 import com.android.os.AtomsProto.ScheduledJobStateChanged;
 import com.android.os.AtomsProto.SyncStateChanged;
+import com.android.os.AtomsProto.VibratorStateChanged;
 import com.android.os.AtomsProto.WakelockStateChanged;
+import com.android.os.AtomsProto.WakeupAlarmOccurred;
 import com.android.os.AtomsProto.WifiLockStateChanged;
 import com.android.os.AtomsProto.WifiMulticastLockStateChanged;
 import com.android.os.AtomsProto.WifiScanStateChanged;
@@ -60,18 +71,6 @@
 
     private static final String TAG = "Statsd.UidAtomTests";
 
-    // These constants are those in PackageManager.
-    private static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
-    private static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
-    private static final String FEATURE_WIFI = "android.hardware.wifi";
-    private static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
-    private static final String FEATURE_CAMERA = "android.hardware.camera";
-    private static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
-    private static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
-    private static final String FEATURE_WATCH = "android.hardware.type.watch";
-    private static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
-    private static final String FEATURE_PC = "android.hardware.type.pc";
-
     private static final boolean DAVEY_ENABLED = false;
 
     @Override
@@ -79,6 +78,34 @@
         super.setUp();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        resetBatteryStatus();
+        super.tearDown();
+    }
+
+    public void testAppCrashOccurred() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runActivity("StatsdCtsForegroundActivity", "action", "action.crash");
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        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());
+    }
+
     public void testAppStartOccurred() throws Exception {
         if (statsdDisabled()) {
             return;
@@ -102,6 +129,37 @@
         assertTrue(atom.getTransitionDelayMillis() > 0);
     }
 
+    public void testAudioState() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
+
+        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testAudioState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // AudioStateChanged timestamp is fuzzed to 5min buckets
+        assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getAudioStateChanged().getState().getNumber());
+    }
+
     public void testBleScan() throws Exception {
         if (statsdDisabled()) {
             return;
@@ -216,11 +274,7 @@
         }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = getPulledConfig();
-        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
-                .setField(Atom.CPU_TIME_PER_UID_FIELD_NUMBER)
-                .addChild(FieldMatcher.newBuilder()
-                        .setField(CpuTimePerUid.UID_FIELD_NUMBER));
-        addGaugeAtom(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER, dimension);
+        addGaugeAtomWithDimensions(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER, null);
 
         uploadConfig(config);
 
@@ -257,7 +311,7 @@
                 .setField(Atom.CPU_ACTIVE_TIME_FIELD_NUMBER)
                 .addChild(FieldMatcher.newBuilder()
                         .setField(CpuActiveTime.UID_FIELD_NUMBER));
-        addGaugeAtom(config, Atom.CPU_ACTIVE_TIME_FIELD_NUMBER, dimension);
+        addGaugeAtomWithDimensions(config, Atom.CPU_ACTIVE_TIME_FIELD_NUMBER, dimension);
 
         uploadConfig(config);
 
@@ -282,6 +336,83 @@
         assertTrue("found uid " + uid, found);
     }
 
+    public void testDeviceCalculatedPowerUse() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        Atom atom = getGaugeMetricDataList().get(0);
+        assertTrue(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs() > 0);
+    }
+
+
+    public void testDeviceCalculatedPowerBlameUid() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config,
+                Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER, null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_LONG);
+
+        List<Atom> atomList = getGaugeMetricDataList();
+        boolean uidFound = false;
+        int uid = getUid();
+        long uidPower = 0;
+        for (Atom atom : atomList) {
+            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
+                if (item.getUid() == uid) {
+                assertFalse("Found multiple power values for uid " + uid, uidFound);
+                uidFound = true;
+                uidPower = item.getPowerNanoAmpSecs();
+            }
+        }
+        assertTrue("No power value for uid " + uid, uidFound);
+        assertTrue("Non-positive power value for uid " + uid, uidPower > 0);
+    }
+
+    public void testDavey() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!DAVEY_ENABLED ) return;
+        long MAX_DURATION = 2000;
+        long MIN_DURATION = 750;
+        final int atomTag = Atom.DAVEY_OCCURRED_FIELD_NUMBER;
+        createAndUploadConfig(atomTag, false); // UID is logged without attribution node
+
+        runActivity("DaveyActivity", null, null);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertTrue(data.size() == 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);
+    }
+
     public void testFlashlightState() throws Exception {
         if (statsdDisabled()) {
             return;
@@ -346,24 +477,65 @@
         }
         if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
         // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
         getDevice().executeShellCommand(String.format(
                 "settings put global location_background_throttle_package_whitelist %s",
                 DEVICE_SIDE_TEST_PACKAGE));
 
-        final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
-        final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
-        final int stateOn = GpsScanStateChanged.State.ON_VALUE;
-        final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
-        final int minTimeDiffMillis = 500;
-        final int maxTimeDiffMillis = 60_000;
+        try {
+            final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
+            final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
+            final int stateOn = GpsScanStateChanged.State.ON_VALUE;
+            final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
+            final int minTimeDiffMillis = 500;
+            final int maxTimeDiffMillis = 60_000;
 
-        List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
-                stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
+            List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
+                    stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
 
-        GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
-        GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
-        assertTrue(a0.getState().getNumber() == stateOn);
-        assertTrue(a1.getState().getNumber() == stateOff);
+            GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
+            GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
+            assertTrue(a0.getState().getNumber() == stateOn);
+            assertTrue(a1.getState().getNumber() == stateOff);
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+
+    public void testMediaCodecActivity() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runActivity("VideoPlayerActivity", "action", "action.play_video");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getMediaCodecStateChanged().getState().getNumber());
     }
 
     public void testOverlayState() throws Exception {
@@ -394,25 +566,37 @@
                 atom -> atom.getOverlayStateChanged().getState().getNumber());
     }
 
-    public void testDavey() throws Exception {
+    public void testPictureInPictureState() throws Exception {
         if (statsdDisabled()) {
             return;
         }
-        if (!DAVEY_ENABLED ) return;
-        long MAX_DURATION = 2000;
-        long MIN_DURATION = 750;
-        final int atomTag = Atom.DAVEY_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false); // UID is logged without attribution node
+        String supported = getDevice().executeShellCommand("am supports-multiwindow");
+        if (!hasFeature(FEATURE_WATCH, false) ||
+                !hasFeature(FEATURE_PICTURE_IN_PICTURE, true) ||
+                !supported.contains("true")) {
+            LogUtil.CLog.d("Skipping picture in picture atom test.");
+            return;
+        }
 
-        runActivity("DaveyActivity", null, null);
+        final int atomTag = Atom.PICTURE_IN_PICTURE_STATE_CHANGED_FIELD_NUMBER;
 
+        Set<Integer> entered = new HashSet<>(
+                Arrays.asList(PictureInPictureStateChanged.State.ENTERED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(entered);
+
+        createAndUploadConfig(atomTag, false);
+
+        LogUtil.CLog.d("Playing video in Picture-in-Picture mode");
+        runActivity("VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
+
+        // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = getEventMetricDataList();
-        assertTrue(data.size() == 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);
+
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+                atom -> atom.getPictureInPictureStateChanged().getState().getNumber());
     }
 
     public void testScheduledJobState() throws Exception {
@@ -507,6 +691,36 @@
                 atom -> atom.getSyncStateChanged().getState().getNumber());
     }
 
+    public void testVibratorState() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!checkDeviceFor("checkVibratorSupported")) return;
+
+        final int atomTag = Atom.VIBRATOR_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testVibratorState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        Thread.sleep(WAIT_TIME_LONG);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        assertStatesOccurred(stateSet, data, 300,
+                atom -> atom.getVibratorStateChanged().getState().getNumber());
+    }
+
     public void testWakelockState() throws Exception {
         if (statsdDisabled()) {
             return;
@@ -537,10 +751,10 @@
 
         for (EventMetricData event: data) {
             String tag = event.getAtom().getWakelockStateChanged().getTag();
-            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getLevel();
+            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,
+            assertTrue("Expected wakelock type: " + EXPECTED_LEVEL  + ", but got level: " + type,
                     type == EXPECTED_LEVEL);
         }
     }
@@ -557,8 +771,9 @@
         List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
         assertTrue(data.size() >= 1);
         for (int i = 0; i < data.size(); i++) {
-            String tag = data.get(i).getAtom().getWakeupAlarmOccurred().getTag();
-            assertTrue(tag.equals("*walarm*:android.cts.statsd.testWakeupAlarm"));
+            WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
+            assertEquals("*walarm*:android.cts.statsd.testWakeupAlarm", wao.getTag());
+            assertEquals(DEVICE_SIDE_TEST_PACKAGE, wao.getPackageName());
         }
     }
 
@@ -600,6 +815,8 @@
         Set<Integer> lockOff = new HashSet<>(
                 Arrays.asList(WifiMulticastLockStateChanged.State.OFF_VALUE));
 
+        final String EXPECTED_TAG = "StatsdCTSMulticastLock";
+
         // Add state sets to the list in order.
         List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
 
@@ -612,6 +829,11 @@
         // Assert that the events happened in the expected order.
         assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
                 atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
+
+        for (EventMetricData event: data) {
+            String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
+            assertEquals("Wrong tag.", EXPECTED_TAG, tag);
+        }
     }
 
     public void testWifiScan() throws Exception {
@@ -639,117 +861,246 @@
         assertTrue(a1.getState().getNumber() == stateOff);
     }
 
-    public void testAudioState() throws Exception {
+    public void testBinderStats() throws Exception {
         if (statsdDisabled()) {
             return;
         }
-        if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
+        try {
+            unplugDevice();
+            Thread.sleep(WAIT_TIME_SHORT);
+            enableBinderStats();
+            binderStatsNoSampling();
+            resetBinderStats();
+            StatsdConfig.Builder config = getPulledConfig();
+            addGaugeAtomWithDimensions(config, Atom.BINDER_CALLS_FIELD_NUMBER, null);
 
-        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
-        final String name = "testAudioState";
+            uploadConfig(config);
+            Thread.sleep(WAIT_TIME_SHORT);
 
-        Set<Integer> onState = new HashSet<>(
-                Arrays.asList(AudioStateChanged.State.ON_VALUE));
-        Set<Integer> offState = new HashSet<>(
-                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification",3_000);
 
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_SHORT);
 
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
+            boolean found = false;
+            int uid = getUid();
+            List<Atom> atomList = getGaugeMetricDataList();
+            for (Atom atom : atomList) {
+                BinderCalls calls = atom.getBinderCalls();
+                boolean classMatches = calls.getServiceClassName().contains(
+                        "com.android.server.notification.NotificationManagerService");
+                boolean methodMatches = calls.getServiceMethodName()
+                        .equals("createNotificationChannels");
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+                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);
+                }
+            }
 
-        Thread.sleep(WAIT_TIME_SHORT);
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
+            assertTrue("Did not find a matching atom for uid " + uid, found);
 
-        // AudioStateChanged timestamp is fuzzed to 5min buckets
-        assertStatesOccurred(stateSet, data, 0,
-                atom -> atom.getAudioStateChanged().getState().getNumber());
+        } finally {
+            disableBinderStats();
+            plugInAc();
+        }
     }
 
-    public void testMediaCodecActivity() throws Exception {
+    public void testLooperStats() throws Exception {
         if (statsdDisabled()) {
             return;
         }
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
+        try {
+            unplugDevice();
+            setUpLooperStats();
+            StatsdConfig.Builder config = getPulledConfig();
+            addGaugeAtomWithDimensions(config, Atom.LOOPER_STATS_FIELD_NUMBER, null);
+            uploadConfig(config);
+            Thread.sleep(WAIT_TIME_SHORT);
 
-        Set<Integer> onState = new HashSet<>(
-                Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
-        Set<Integer> offState = new HashSet<>(
-                Arrays.asList(MediaCodecStateChanged.State.OFF_VALUE));
+            runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification", 3_000);
 
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_SHORT);
 
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        Thread.sleep(WAIT_TIME_SHORT);
+            List<Atom> atomList = getGaugeMetricDataList();
 
-        runActivity("VideoPlayerActivity", "action", "action.play_video");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                atom -> atom.getMediaCodecStateChanged().getState().getNumber());
+            boolean found = false;
+            int uid = getUid();
+            for (Atom atom : atomList) {
+                LooperStats stats = atom.getLooperStats();
+                String notificationServiceFullName =
+                        "com.android.server.notification.NotificationManagerService";
+                boolean handlerMatches =
+                        stats.getHandlerClassName().equals(
+                                notificationServiceFullName + "$WorkerHandler");
+                boolean messageMatches =
+                        stats.getMessageName().equals(
+                                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);
+                }
+            }
+            assertTrue("Did not find a matching atom for uid " + uid, found);
+        } finally {
+            cleanUpLooperStats();
+            plugInAc();
+        }
     }
 
-    public void testPictureInPictureState() throws Exception {
+    public void testProcessMemoryState() throws Exception {
         if (statsdDisabled()) {
             return;
         }
-        String supported = getDevice().executeShellCommand("am supports-multiwindow");
-        if (!hasFeature(FEATURE_WATCH, false) ||
-            !hasFeature(FEATURE_PICTURE_IN_PICTURE, true) ||
-            !supported.contains("true")) {
-            LogUtil.CLog.d("Skipping picture in picture atom test.");
-            return;
+
+        // Get ProcessMemoryState as a simple gauge metric.
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            setAppBreadcrumbPredicate();
         }
+        Thread.sleep(WAIT_TIME_SHORT);
 
-        final int atomTag = Atom.PICTURE_IN_PICTURE_STATE_CHANGED_FIELD_NUMBER;
-
-        Set<Integer> entered = new HashSet<>(
-                Arrays.asList(PictureInPictureStateChanged.State.ENTERED_VALUE));
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(entered);
-
-        createAndUploadConfig(atomTag, false);
-
-        LogUtil.CLog.d("Playing video in Picture-in-Picture mode");
-        runActivity("VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
-                atom -> atom.getPictureInPictureStateChanged().getState().getNumber());
+        // Assert about ProcessMemoryState for the test app.
+        List<Atom> atoms = getGaugeMetricDataList();
+        int uid = getUid();
+        boolean found = false;
+        for (Atom atom : atoms) {
+            ProcessMemoryState state = atom.getProcessMemoryState();
+            if (state.getUid() != uid) {
+                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());
+        }
+        assertTrue("Did not find a matching atom for uid=" + uid, found);
     }
 
-    public void testAppCrashOccurred() throws Exception {
+    public void testNativeProcessMemoryState() throws Exception {
         if (statsdDisabled()) {
             return;
         }
-        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
-        createAndUploadConfig(atomTag, false);
+
+        // 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);
 
-        runActivity("StatsdCtsForegroundActivity", "action", "action.crash");
+        // Trigger new pull.
+        setAppBreadcrumbPredicate();
 
+        // 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);
+    }
+
+    public void testProcessMemoryHighWaterMark() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        // Get ProcessMemoryState 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);
-        // Sorted list of events in order in which they occurred.
-        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());
+        // Start test app and trigger a pull while its running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            setAppBreadcrumbPredicate();
+        }
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Assert about ProcessMemoryHighWaterMark 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) {
+            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);
+            } else if (state.getProcessName().contains("/statsd")) {
+                foundStatsd = true;
+                assertTrue("rss_high_water_mark_in_bytes should be positive",
+                        state.getRssHighWaterMarkInBytes() > 0);
+            } else if (state.getProcessName().equals("system")) {
+                foundSystemServer = true;
+                assertTrue("rss_high_water_mark_in_bytes should be positive",
+                        state.getRssHighWaterMarkInBytes() > 0);
+            }
+        }
+        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);
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
index 9340fd2..4351d4e 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -113,7 +113,9 @@
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      totalValue += (int) bucketInfo.getValue();
+      assertEquals(1, bucketInfo.getValuesCount());
+      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
     assertEquals(totalValue, 8);
   }
@@ -190,7 +192,9 @@
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      totalValue += (int) bucketInfo.getValue();
+      assertEquals(1, bucketInfo.getValuesCount());
+      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
     // At most we lose one full min bucket
     assertTrue(totalValue > (130_000 - 60_000));
@@ -272,7 +276,9 @@
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      totalValue += (int) bucketInfo.getValue();
+      assertEquals(1, bucketInfo.getValuesCount());
+      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
     // At most we lose one full min bucket
     assertTrue(totalValue > (GAP_INTERVAL*NUM_EVENTS - 60_000));
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
new file mode 100644
index 0000000..f8aa3b7
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.statsd.subscriber;
+
+import com.android.compatibility.common.util.CpuFeatures;
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.ShellConfig;
+import com.android.os.statsd.ShellDataProto;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.google.common.io.Files;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Statsd shell data subscription test.
+ */
+public class ShellSubscriberTest extends DeviceTestCase {
+    private int sizetBytes;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        sizetBytes = getSizetBytes();
+    }
+
+    private int getSizetBytes() {
+        try {
+            ITestDevice device = getDevice();
+            if (CpuFeatures.isArm64(device)) {
+                return 8;
+            }
+            if (CpuFeatures.isArm32(device)) {
+                return 4;
+            }
+            return -1;
+        } catch (DeviceNotAvailableException e) {
+            return -1;
+        }
+    }
+
+    // Tests that anomaly detection for count works.
+    // Also tests that anomaly detection works when spanning multiple buckets.
+    public void testShellSubscription() {
+        if (sizetBytes < 0) {
+            return;
+        }
+        // choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME).
+        // Testing pushed atom is a little trickier, because the executeShellCommand() is blocking
+        // and we cannot push a breadcrumb event at the same time when the shell subscription is
+        // running. So test pulled atom instead.
+        ShellConfig.ShellSubscription config = ShellConfig.ShellSubscription.newBuilder()
+                .addPulled(ShellConfig.PulledAtomSubscription.newBuilder().setMatcher(
+                        StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(AtomsProto.Atom.SYSTEM_UPTIME_FIELD_NUMBER).build())
+                        .setFreqMillis(2000).build()).build();
+        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        startSubscription(config, receiver, 10);
+        byte[] output = receiver.getOutput();
+        // There should be at lease some data returned.
+        assertTrue(output.length > sizetBytes);
+
+        int atomCount = 0;
+        int i = 0;
+        while (output.length > i + sizetBytes) {
+            int len = 0;
+            for (int j = 0; j < sizetBytes; j++) {
+                len += ((int) output[i + j] & 0xffL) << (sizetBytes * j);
+            }
+            LogUtil.CLog.d("received : " + output.length + " bytes, size : " + len);
+
+            if (output.length < i + sizetBytes + len) {
+                fail("Bad data received.");
+            }
+
+            try {
+                ShellDataProto.ShellData data =
+                        ShellDataProto.ShellData.parseFrom(
+                                Arrays.copyOfRange(output, i + sizetBytes, i + sizetBytes + len));
+                assertTrue(data.getAtomCount() > 0);
+                assertTrue(data.getAtom(0).hasSystemUptime());
+                atomCount++;
+                LogUtil.CLog.d("Received " + data.toString());
+            } catch (InvalidProtocolBufferException e) {
+                fail("Failed to parse proto");
+            }
+            i += (sizetBytes + len);
+        }
+
+        assertTrue(atomCount > 0);
+    }
+
+    private void startSubscription(ShellConfig.ShellSubscription config,
+                                   CollectingByteOutputReceiver receiver, int waitTimeSec) {
+        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
+        try {
+            File configFile = File.createTempFile("shellconfig", ".config");
+            configFile.deleteOnExit();
+            int length = config.toByteArray().length;
+            byte[] combined = new byte[sizetBytes + config.toByteArray().length];
+
+            System.arraycopy(IntToByteArrayLittleEndian(length), 0, combined, 0, sizetBytes);
+            System.arraycopy(config.toByteArray(), 0, combined, sizetBytes, length);
+
+            Files.write(combined, configFile);
+            String remotePath = "/data/local/tmp/" + configFile.getName();
+            getDevice().pushFile(configFile, remotePath);
+            LogUtil.CLog.d("waiting....................");
+
+            getDevice().executeShellCommand(
+                    String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe ",
+                            String.valueOf(waitTimeSec)), receiver);
+            getDevice().executeShellCommand("rm " + remotePath);
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    byte[] IntToByteArrayLittleEndian(int length) {
+        ByteBuffer b = ByteBuffer.allocate(sizetBytes);
+        b.order(ByteOrder.LITTLE_ENDIAN);
+        b.putInt(length);
+        return b.array();
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
new file mode 100644
index 0000000..830e079
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -0,0 +1,270 @@
+/*
+ * 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.cts.statsd.validation;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.os.BatteryStatsProto;
+import android.os.UidProto;
+import android.os.UidProto.Package;
+import android.os.UidProto.Package.Service;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
+import com.android.os.StatsLog.DimensionsValue;
+import com.android.os.StatsLog.CountMetricData;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.List;
+
+/**
+ * Side-by-side comparison between statsd and batterystats.
+ */
+public class BatteryStatsValidationTests extends DeviceAtomTestCase {
+
+    private static final String TAG = "Statsd.BatteryStatsValidationTests";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        resetBatteryStatus();
+        unplugDevice();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        plugInUsb();
+    }
+
+    public void testConnectivityStateChange() throws Exception {
+        if (!hasFeature(FEATURE_WIFI, true)) return;
+        if (!hasFeature(FEATURE_WATCH, false)) return;
+        final String fileName = "BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        turnOnAirplaneMode();
+        turnOffAirplaneMode();
+        // wait for long enough for device to restore connection
+        Thread.sleep(10_000);
+
+        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());
+    }
+
+    public void testPowerUse() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+        resetBatteryStats();
+        unplugDevice();
+
+        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_LONG);
+
+        setAppBreadcrumbPredicate();
+        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+        Thread.sleep(WAIT_TIME_LONG);
+        List<Atom> atomList = getGaugeMetricDataList();
+
+        // Extract statsd data
+        Atom atom = atomList.get(0);
+        long statsdPowerNas = atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs();
+        assertTrue("Statsd: Non-positive power value.", statsdPowerNas > 0);
+
+        // Extract BatteryStats data
+        double bsPowerNas = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah()
+                * 1_000_000L * 3600L; /* mAh to nAs */
+        assertTrue("BatteryStats: Non-positive power value.", bsPowerNas > 0);
+
+        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);
+    }
+
+    public void testPowerBlameUid() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
+        resetBatteryStats();
+        unplugDevice();
+
+        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER,
+                null);
+        uploadConfig(config);
+        unplugDevice();
+
+        Thread.sleep(WAIT_TIME_LONG);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
+        Thread.sleep(WAIT_TIME_LONG);
+
+        setAppBreadcrumbPredicate();
+        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+        Thread.sleep(WAIT_TIME_LONG);
+        List<Atom> atomList = getGaugeMetricDataList();
+
+        // Extract statsd data
+        boolean uidFound = false;
+        int uid = getUid();
+        long statsdUidPowerNas = 0;
+        for (Atom atom : atomList) {
+            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
+            if (item.getUid() == uid) {
+                assertFalse("Found multiple power values for uid " + uid, uidFound);
+                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);
+
+        // Extract batterystats data
+        double bsUidPowerNas = -1;
+        boolean hadUid = false;
+        for (UidProto uidProto : batterystatsProto.getUidsList()) {
+            if (uidProto.getUid() == uid) {
+                hadUid = true;
+                bsUidPowerNas = uidProto.getPowerUseItem().getComputedPowerMah()
+                        * 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);
+
+        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);
+    }
+
+    public void testServiceStartCount() throws Exception {
+        final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
+
+        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+        List<CountMetricData> countMetricData = getCountMetricDataList();
+        assertTrue(countMetricData.size() > 0);
+        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);
+                countFromStatsd = data.getBucketInfo(0).getCount();
+                assertTrue(countFromStatsd > 0);
+            }
+        }
+        long countFromBS = 0;
+        for (UidProto uidProto : batterystatsProto.getUidsList()) {
+            if (uidProto.getUid() == uid) {
+                for (Package pkg : uidProto.getPackagesList()) {
+                    if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
+                        for (Service svc : pkg.getServicesList()) {
+                            if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
+                                countFromBS = svc.getStartCount();
+                                assertTrue(countFromBS > 0);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        assertTrue(countFromStatsd > 0);
+        assertTrue(countFromBS > 0);
+        assertEquals(countFromBS, countFromStatsd);
+    }
+
+    public void testServiceLaunchCount() throws Exception {
+        final String fileName = "BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
+
+        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+        List<CountMetricData> countMetricData = getCountMetricDataList();
+        assertTrue(countMetricData.size() > 0);
+        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());
+                countFromStatsd = data.getBucketInfo(0).getCount();
+                assertTrue(countFromStatsd > 0);
+            }
+        }
+        long countFromBS = 0;
+        for (UidProto uidProto : batterystatsProto.getUidsList()) {
+            if (uidProto.getUid() == uid) {
+                for (Package pkg : uidProto.getPackagesList()) {
+                    if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
+                        for (Service svc : pkg.getServicesList()) {
+                            if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
+                                countFromBS = svc.getLaunchCount();
+                                assertTrue(countFromBS > 0);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        assertTrue(countFromStatsd > 0);
+        assertTrue(countFromBS > 0);
+        assertEquals(countFromBS, countFromStatsd);
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
new file mode 100644
index 0000000..cfa0a37
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -0,0 +1,466 @@
+/*
+ * 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.cts.statsd.validation;
+
+import static org.junit.Assert.assertTrue;
+
+import android.cts.statsd.atom.ProcStateTestCase;
+import android.service.procstats.ProcessState;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.ProcessStatsPackageProto;
+import com.android.os.AtomsProto.ProcessStatsProto;
+import com.android.os.AtomsProto.ProcessStatsStateProto;
+import com.android.os.StatsLog.DimensionsValue;
+import com.android.os.StatsLog.DurationBucketInfo;
+import com.android.os.StatsLog.DurationMetricData;
+import com.android.os.StatsLog.ValueBucketInfo;
+import com.android.os.StatsLog.ValueMetricData;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Side-by-side comparison between statsd and procstats.
+ */
+public class ProcStatsValidationTests extends ProcStateTestCase {
+
+    private static final String TAG = "Statsd.ProcStatsValidationTests";
+
+    private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
+
+    // Test process state top duration for test package.
+    // TODO: replace this with exclusive state for all states when statsd features are added
+    public void testProcessStateTopDuration() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final String fileName = "PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+        clearProcStats();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // foreground service
+        executeForegroundService();
+        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        // background
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        // top
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // foreground
+        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<DurationMetricData> statsdData = getDurationMetricDataList();
+
+        List<ProcessStatsProto> processStatsProtoList = getProcStatsProto();
+
+        LogUtil.CLog.d("======================");
+
+        String statsdPkgName = "com.android.server.cts.device.statsd";
+        double durationInTopStatsd = 0;
+        for (DurationMetricData d : statsdData) {
+            List<DimensionsValue> dimensionsValuesInWhat = d.getDimensionLeafValuesInWhatList();
+            if (dimensionsValuesInWhat.get(0).getValueStr().equals(statsdPkgName)
+                    && dimensionsValuesInWhat.get(1).getValueStr().equals(statsdPkgName)) {
+                LogUtil.CLog.d(d.toString());
+                for (DurationBucketInfo bucket : d.getBucketInfoList()) {
+                    durationInTopStatsd += bucket.getDurationNanos() / 1000 / 1000;
+                }
+            }
+        }
+
+        double durationInTopProcStats = 0;
+        for (ProcessStatsProto p : processStatsProtoList) {
+            if (p.getProcess().equals(statsdPkgName)) {
+                LogUtil.CLog.d(p.toString());
+                for (ProcessStatsStateProto s : p.getStatesList()) {
+                    if (s.getProcessState() == ProcessState.PROCESS_STATE_TOP) {
+                        durationInTopProcStats += s.getDurationMillis();
+                    }
+                }
+            }
+        }
+        // Verify that duration is within 1% difference
+        assertTrue(Math.abs(durationInTopStatsd - durationInTopProcStats) / durationInTopProcStats
+                < 0.1);
+    }
+
+    public void testProcessStateCachedEmptyDuration() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final String fileName = "PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+        clearProcStats();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // foreground service
+        executeForegroundService();
+        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        // background
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        // top
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // foreground
+        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<DurationMetricData> statsdData = getDurationMetricDataList();
+
+        List<ProcessStatsProto> processStatsProtoList = getProcStatsProto();
+
+        LogUtil.CLog.d("======================");
+
+        String statsdPkgName = "com.android.server.cts.device.statsd";
+        double durationInStatsd = 0;
+        for (DurationMetricData d : statsdData) {
+            List<DimensionsValue> dimensionsValuesInWhat = d.getDimensionLeafValuesInWhatList();
+            if (dimensionsValuesInWhat.get(0).getValueStr().equals(statsdPkgName)
+                    && dimensionsValuesInWhat.get(1).getValueStr().equals(statsdPkgName)) {
+                LogUtil.CLog.d(d.toString());
+                for (DurationBucketInfo bucket : d.getBucketInfoList()) {
+                    durationInStatsd += bucket.getDurationNanos() / 1000 / 1000;
+                }
+            }
+        }
+
+        double durationInProcStats = 0;
+        for (ProcessStatsProto p : processStatsProtoList) {
+            if (p.getProcess().equals(statsdPkgName)) {
+                LogUtil.CLog.d(p.toString());
+                for (ProcessStatsStateProto s : p.getStatesList()) {
+                    if (s.getProcessState() == ProcessState.PROCESS_STATE_CACHED_EMPTY) {
+                        durationInProcStats += s.getDurationMillis();
+                    }
+                }
+            }
+        }
+        // Verify that duration is within 1% difference
+        assertTrue(Math.abs(durationInStatsd - durationInProcStats) / durationInProcStats
+                < 0.1);
+    }
+
+    public void testProcessStatePssValue() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        final String fileName = "PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+        clearProcStats();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // foreground service
+        executeForegroundService();
+        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        // background
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        // top
+        executeForegroundActivity(ACTION_LONG_SLEEP_WHILE_TOP);
+        Thread.sleep(SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // foreground
+        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<ValueMetricData> statsdData = getValueMetricDataList();
+
+        List<ProcessStatsProto> processStatsProtoList = getProcStatsProto();
+
+        LogUtil.CLog.d("======================");
+
+        String statsdPkgName = "com.android.server.cts.device.statsd";
+        double valueInStatsd = 0;
+        for (ValueMetricData d : statsdData) {
+            List<DimensionsValue> dimensionsValuesInWhat = d.getDimensionLeafValuesInWhatList();
+            if (dimensionsValuesInWhat.get(0).getValueStr().equals(statsdPkgName)
+                    && dimensionsValuesInWhat.get(1).getValueStr().equals(statsdPkgName)) {
+                LogUtil.CLog.d(d.toString());
+                for (ValueBucketInfo bucket : d.getBucketInfoList()) {
+                    valueInStatsd = Math.max(bucket.getValues(0).getValueLong(), valueInStatsd);
+                }
+            }
+        }
+
+        double valueInProcStats = 0;
+        for (ProcessStatsProto p : processStatsProtoList) {
+            if (p.getProcess().equals(statsdPkgName)) {
+                LogUtil.CLog.d(p.toString());
+                for (ProcessStatsStateProto s : p.getStatesList()) {
+                    valueInProcStats = Math.max(s.getPss().getMax(), valueInProcStats);
+                }
+            }
+        }
+        assertTrue(valueInProcStats > 0);
+        assertTrue(valueInStatsd == valueInProcStats);
+    }
+
+    public void testProcessStateByPulling() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        startProcStatsTesting();
+        clearProcStats();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // foreground service
+        executeForegroundService();
+        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        // background
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        // top
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // foreground
+        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+
+        Thread.sleep(60_000);
+        uninstallPackage();
+        stopProcStatsTesting();
+        commitProcStatsToDisk();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final String fileName = "PROCSTATSQ_PULL.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<Atom> statsdData = getGaugeMetricDataList();
+
+        List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
+
+        // We pull directly from ProcessStatsService, so not necessary to compare every field.
+        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
+        LogUtil.CLog.d("======================");
+
+        String statsdPkgName = "com.android.server.cts.device.statsd";
+        long pssAvgStatsd = 0;
+        long ussAvgStatsd = 0;
+        long rssAvgStatsd = 0;
+        long durationStatsd = 0;
+        for (Atom d : statsdData) {
+            for (ProcessStatsPackageProto pkg : d.getProcStats().getProcStatsSection().getPackageStatsList()) {
+                if (pkg.getPackage().equals(statsdPkgName)) {
+                    LogUtil.CLog.d("Got proto from statsd:");
+                    LogUtil.CLog.d(pkg.toString());
+                    for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+                        for (ProcessStatsStateProto state : process.getStatesList()) {
+                            if (state.getProcessState()
+                                    == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                                durationStatsd = state.getDurationMillis();
+                                pssAvgStatsd = state.getPss().getAverage();
+                                ussAvgStatsd = state.getUss().getAverage();
+                                rssAvgStatsd = state.getRss().getAverage();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LogUtil.CLog.d("avg pss from statsd is " + pssAvgStatsd);
+
+        long pssAvgProcstats = 0;
+        long ussAvgProcstats = 0;
+        long rssAvgProcstats = 0;
+        long durationProcstats = 0;
+        for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
+            if (pkg.getPackage().equals(statsdPkgName)) {
+                LogUtil.CLog.d("Got proto from procstats dumpsys:");
+                LogUtil.CLog.d(pkg.toString());
+                for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+                    for (ProcessStatsStateProto state : process.getStatesList()) {
+                        if (state.getProcessState()
+                                == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                            durationProcstats = state.getDurationMillis();
+                            pssAvgProcstats = state.getPss().getAverage();
+                            ussAvgProcstats = state.getUss().getAverage();
+                            rssAvgProcstats = state.getRss().getAverage();
+                        }
+                    }
+                }
+            }
+        }
+
+        LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
+        assertTrue(durationStatsd > 0);
+        assertTrue(durationStatsd == durationProcstats);
+        assertTrue(pssAvgStatsd == pssAvgProcstats);
+        assertTrue(ussAvgStatsd == ussAvgProcstats);
+        assertTrue(rssAvgStatsd == rssAvgProcstats);
+    }
+
+    public void testProcStatsPkgProcStats() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        startProcStatsTesting();
+        clearProcStats();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // foreground service
+        executeForegroundService();
+        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        // background
+        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        // top
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // foreground
+        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+
+        Thread.sleep(60_000);
+        uninstallPackage();
+        stopProcStatsTesting();
+        commitProcStatsToDisk();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
+        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<Atom> statsdData = getGaugeMetricDataList();
+        assertTrue(statsdData.size() > 0);
+        assertTrue(statsdData.get(0).getProcStatsPkgProc().getProcStatsSection().getAvailablePagesList().size() > 0);
+
+        List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
+
+        // We pull directly from ProcessStatsService, so not necessary to compare every field.
+        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
+        LogUtil.CLog.d("======================");
+
+        String statsdPkgName = "com.android.server.cts.device.statsd";
+        long pssAvgStatsd = 0;
+        long ussAvgStatsd = 0;
+        long rssAvgStatsd = 0;
+        long durationStatsd = 0;
+        for (Atom d : statsdData) {
+            for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection().getPackageStatsList()) {
+                if (pkg.getPackage().equals(statsdPkgName)) {
+                    LogUtil.CLog.d("Got proto from statsd:");
+                    LogUtil.CLog.d(pkg.toString());
+                    for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+                        for (ProcessStatsStateProto state : process.getStatesList()) {
+                            if (state.getProcessState()
+                                    == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                                durationStatsd = state.getDurationMillis();
+                                pssAvgStatsd = state.getPss().getAverage();
+                                ussAvgStatsd = state.getUss().getAverage();
+                                rssAvgStatsd = state.getRss().getAverage();
+                            }
+                        }
+                    }
+                }
+                assertTrue(pkg.getServiceStatsCount() == 0);
+                assertTrue(pkg.getAssociationStatsCount() == 0);
+            }
+        }
+
+        LogUtil.CLog.d("avg pss from statsd is " + pssAvgStatsd);
+
+        long pssAvgProcstats = 0;
+        long ussAvgProcstats = 0;
+        long rssAvgProcstats = 0;
+        long durationProcstats = 0;
+        int serviceStatsCount = 0;
+        int associationStatsCount = 0;
+        for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
+            if (pkg.getPackage().equals(statsdPkgName)) {
+                LogUtil.CLog.d("Got proto from procstats dumpsys:");
+                LogUtil.CLog.d(pkg.toString());
+                for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+                    for (ProcessStatsStateProto state : process.getStatesList()) {
+                        if (state.getProcessState()
+                                == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                            durationProcstats = state.getDurationMillis();
+                            pssAvgProcstats = state.getPss().getAverage();
+                            ussAvgProcstats = state.getUss().getAverage();
+                            rssAvgProcstats = state.getRss().getAverage();
+                        }
+                    }
+                }
+            }
+            serviceStatsCount += pkg.getServiceStatsCount();
+            associationStatsCount += pkg.getAssociationStatsCount();
+        }
+        assertTrue(serviceStatsCount > 0);
+        assertTrue(associationStatsCount > 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);
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
new file mode 100644
index 0000000..d3e5bad
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cts.statsd.validation;
+
+import android.cts.statsd.atom.BaseTestCase;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ValidationTestUtil extends BaseTestCase {
+
+    private static final String TAG = "Statsd.ValidationTestUtil";
+
+    public StatsdConfig getConfig(String fileName) throws IOException {
+        try {
+            // TODO: Ideally, we should use real metrics that are also pushed to the fleet.
+            File configFile = getBuildHelper().getTestFile(fileName);
+            String configStr = FileUtil.readStringFromFile(configFile);
+            StatsdConfig.Builder builder = StatsdConfig.newBuilder();
+            TextFormat.merge(configStr, builder);
+            return builder.build();
+        } catch (ParseException e) {
+            LogUtil.CLog.e(
+                    "Failed to parse the config! line: " + e.getLine() + " col: " + e.getColumn(),
+                    e);
+        }
+        return null;
+    }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
index 8fe6965..ff4fc66 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
@@ -18,19 +18,18 @@
 import static org.junit.Assert.assertTrue;
 
 import android.cts.statsd.atom.DeviceAtomTestCase;
-import android.platform.test.annotations.RestrictedBuildTest;
+import android.os.BatteryPluggedStateEnum;
 import android.os.BatteryStatsProto;
 import android.os.UidProto;
 import android.os.UidProto.Wakelock;
-import android.os.BatteryPluggedStateEnum;
 import android.os.WakeLockLevelEnum;
+import android.platform.test.annotations.RestrictedBuildTest;
 import android.view.DisplayStateEnum;
 
-
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.DurationMetric;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
 import com.android.internal.os.StatsdConfigProto.LogicalOperation;
 import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.internal.os.StatsdConfigProto.Predicate;
@@ -42,15 +41,13 @@
 import com.android.os.AtomsProto.PluggedStateChanged;
 import com.android.os.AtomsProto.ScreenStateChanged;
 import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.DimensionsValue;
 import com.android.os.StatsLog.DurationBucketInfo;
 import com.android.os.StatsLog.DurationMetricData;
+import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil.CLog;
 
-
-
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -118,7 +115,7 @@
 
         for (EventMetricData event : data) {
             String tag = event.getAtom().getWakelockStateChanged().getTag();
-            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getLevel();
+            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,
@@ -193,10 +190,11 @@
         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,
+                        "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,
+                        "was too long. Expected " + MAX_DURATION + ", received " + statsdDurationMs,
                 statsdDurationMs <= MAX_DURATION);
 
         // Compare batterystats with statsd.
@@ -271,7 +269,8 @@
         //             long statsdDurationNs = statsdWakelockData.get(EXPECTED_UID).get(tag);
         //             long statsdDurationMs = statsdDurationNs / 1_000_000;
         //             long difference = Math.abs(statsdDurationMs - bsDurationMs);
-        //             assertTrue("Unusually large difference in wakelock duration for tag: " + tag +
+        //             assertTrue("Unusually large difference in wakelock duration for tag: " +
+        // tag +
         //                         ". Statsd had duration " + statsdDurationMs +
         //                         " and batterystats had duration " + bsDurationMs,
         //                         difference <= bsDurationMs / 10);
@@ -285,11 +284,11 @@
         // assertTrue("Could not find any wakelocks with uid " + EXPECTED_UID + " in statsd",
         //         statsdWakelockData.containsKey(EXPECTED_UID));
         // HashMap<String, Long> expectedWakelocks = statsdWakelockData.get(EXPECTED_UID);
-        // assertEquals("Expected " + NUM_THREADS + " wakelocks in statsd with UID " + EXPECTED_UID +
+        // assertEquals("Expected " + NUM_THREADS + " wakelocks in statsd with UID " +
+        // EXPECTED_UID +
         //         ". Received " + expectedWakelocks.size(), expectedWakelocks.size(), NUM_THREADS);
     }
 
-
     // Helper functions
     // TODO: Refactor these into some utils class.
 
@@ -432,219 +431,218 @@
         int deviceIsUnpluggedId = deviceIsUnpluggedName.hashCode();
 
 
-
         FieldMatcher.Builder dimensions = FieldMatcher.newBuilder()
-            .setField(atomTag)
-            .addChild(FieldMatcher.newBuilder()
-                .setField(WakelockStateChanged.TAG_FIELD_NUMBER))
-            .addChild(FieldMatcher.newBuilder()
-                .setField(1)
-                .setPosition(Position.FIRST)
+                .setField(atomTag)
                 .addChild(FieldMatcher.newBuilder()
-                    .setField(1)));
+                        .setField(WakelockStateChanged.TAG_FIELD_NUMBER))
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(1)
+                        .setPosition(Position.FIRST)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(1)));
 
         AtomMatcher.Builder wakelockAcquire = AtomMatcher.newBuilder()
-            .setId(partialWakelockAcquireId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(atomTag)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.LEVEL_FIELD_NUMBER)
-                    .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(WakelockStateChanged.State.ACQUIRE_VALUE)));
+                .setId(partialWakelockAcquireId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomTag)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(WakelockStateChanged.State.ACQUIRE_VALUE)));
 
         AtomMatcher.Builder wakelockChangeAcquire = AtomMatcher.newBuilder()
-            .setId(partialWakelockChangeAcquireId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(atomTag)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.LEVEL_FIELD_NUMBER)
-                    .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)));
+                .setId(partialWakelockChangeAcquireId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomTag)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)));
 
         AtomMatcher.Builder wakelockRelease = AtomMatcher.newBuilder()
-            .setId(partialWakelockReleaseId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(atomTag)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.LEVEL_FIELD_NUMBER)
-                    .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(WakelockStateChanged.State.RELEASE_VALUE)));
+                .setId(partialWakelockReleaseId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomTag)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(WakelockStateChanged.State.RELEASE_VALUE)));
 
         AtomMatcher.Builder wakelockChangeRelease = AtomMatcher.newBuilder()
-            .setId(partialWakelockChangeReleaseId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(atomTag)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.LEVEL_FIELD_NUMBER)
-                    .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)));
+                .setId(partialWakelockChangeReleaseId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomTag)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)));
 
         AtomMatcher.Builder wakelockOn = AtomMatcher.newBuilder()
-            .setId(partialWakelockOnId)
-            .setCombination(AtomMatcher.Combination.newBuilder()
-                .setOperation(LogicalOperation.OR)
-                .addMatcher(partialWakelockAcquireId)
-                .addMatcher(partialWakelockChangeAcquireId));
+                .setId(partialWakelockOnId)
+                .setCombination(AtomMatcher.Combination.newBuilder()
+                        .setOperation(LogicalOperation.OR)
+                        .addMatcher(partialWakelockAcquireId)
+                        .addMatcher(partialWakelockChangeAcquireId));
 
         AtomMatcher.Builder wakelockOff = AtomMatcher.newBuilder()
-            .setId(partialWakelockOffId)
-            .setCombination(AtomMatcher.Combination.newBuilder()
-                .setOperation(LogicalOperation.OR)
-                .addMatcher(partialWakelockReleaseId)
-                .addMatcher(partialWakelockChangeReleaseId));
+                .setId(partialWakelockOffId)
+                .setCombination(AtomMatcher.Combination.newBuilder()
+                        .setOperation(LogicalOperation.OR)
+                        .addMatcher(partialWakelockReleaseId)
+                        .addMatcher(partialWakelockChangeReleaseId));
 
 
         Predicate.Builder wakelockPredicate = Predicate.newBuilder()
-            .setId(partialWakelockIsOnId)
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                .setStart(partialWakelockOnId)
-                .setStop(partialWakelockOffId)
-                .setCountNesting(true)
-                .setDimensions(dimensions));
+                .setId(partialWakelockIsOnId)
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(partialWakelockOnId)
+                        .setStop(partialWakelockOffId)
+                        .setCountNesting(true)
+                        .setDimensions(dimensions));
 
         AtomMatcher.Builder pluggedStateBatteryPluggedNone = AtomMatcher.newBuilder()
-            .setId(pluggedStateBatteryPluggedNoneId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE)));
+                .setId(pluggedStateBatteryPluggedNoneId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE)));
 
         AtomMatcher.Builder pluggedStateBatteryPluggedAc = AtomMatcher.newBuilder()
-            .setId(pluggedStateBatteryPluggedAcId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE)));
+                .setId(pluggedStateBatteryPluggedAcId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE)));
 
         AtomMatcher.Builder pluggedStateBatteryPluggedUsb = AtomMatcher.newBuilder()
-            .setId(pluggedStateBatteryPluggedUsbId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE)));
+                .setId(pluggedStateBatteryPluggedUsbId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE)));
 
         AtomMatcher.Builder pluggedStateBatteryPluggedWireless = AtomMatcher.newBuilder()
-            .setId(pluggedStateBatteryPluggedWirelessId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE)));
+                .setId(pluggedStateBatteryPluggedWirelessId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE)));
 
         AtomMatcher.Builder pluggedStateBatteryPlugged = AtomMatcher.newBuilder()
-            .setId(pluggedStateBatteryPluggedId)
-            .setCombination(AtomMatcher.Combination.newBuilder()
-                .setOperation(LogicalOperation.OR)
-                .addMatcher(pluggedStateBatteryPluggedAcId)
-                .addMatcher(pluggedStateBatteryPluggedUsbId)
-                .addMatcher(pluggedStateBatteryPluggedWirelessId));
+                .setId(pluggedStateBatteryPluggedId)
+                .setCombination(AtomMatcher.Combination.newBuilder()
+                        .setOperation(LogicalOperation.OR)
+                        .addMatcher(pluggedStateBatteryPluggedAcId)
+                        .addMatcher(pluggedStateBatteryPluggedUsbId)
+                        .addMatcher(pluggedStateBatteryPluggedWirelessId));
 
         Predicate.Builder deviceIsUnplugged = Predicate.newBuilder()
-            .setId(deviceIsUnpluggedId)
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                .setStart(pluggedStateBatteryPluggedNoneId)
-                .setStop(pluggedStateBatteryPluggedId)
-                .setCountNesting(false));
+                .setId(deviceIsUnpluggedId)
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(pluggedStateBatteryPluggedNoneId)
+                        .setStop(pluggedStateBatteryPluggedId)
+                        .setCountNesting(false));
 
         AtomMatcher.Builder screenStateUnknown = AtomMatcher.newBuilder()
-            .setId(screenStateUnknownId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE)));
+                .setId(screenStateUnknownId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE)));
 
         AtomMatcher.Builder screenStateOff = AtomMatcher.newBuilder()
-            .setId(screenStateOffId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE)));
+                .setId(screenStateOffId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE)));
 
         AtomMatcher.Builder screenStateOn = AtomMatcher.newBuilder()
-            .setId(screenStateOnId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE)));
+                .setId(screenStateOnId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE)));
 
         AtomMatcher.Builder screenStateDoze = AtomMatcher.newBuilder()
-            .setId(screenStateDozeId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE)));
+                .setId(screenStateDozeId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE)));
 
         AtomMatcher.Builder screenStateDozeSuspend = AtomMatcher.newBuilder()
-            .setId(screenStateDozeSuspendId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE)));
+                .setId(screenStateDozeSuspendId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE)));
 
         AtomMatcher.Builder screenStateVr = AtomMatcher.newBuilder()
-            .setId(screenStateVrId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_VR_VALUE)));
+                .setId(screenStateVrId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_VR_VALUE)));
 
         AtomMatcher.Builder screenStateOnSuspend = AtomMatcher.newBuilder()
-            .setId(screenStateOnSuspendId)
-            .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                    .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                    .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE)));
+                .setId(screenStateOnSuspendId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
+                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE)));
 
 
         AtomMatcher.Builder screenTurnedOff = AtomMatcher.newBuilder()
-            .setId(screenTurnedOffId)
-            .setCombination(AtomMatcher.Combination.newBuilder()
-                .setOperation(LogicalOperation.OR)
-                .addMatcher(screenStateOffId)
-                .addMatcher(screenStateDozeId)
-                .addMatcher(screenStateDozeSuspendId)
-                .addMatcher(screenStateUnknownId));
+                .setId(screenTurnedOffId)
+                .setCombination(AtomMatcher.Combination.newBuilder()
+                        .setOperation(LogicalOperation.OR)
+                        .addMatcher(screenStateOffId)
+                        .addMatcher(screenStateDozeId)
+                        .addMatcher(screenStateDozeSuspendId)
+                        .addMatcher(screenStateUnknownId));
 
         AtomMatcher.Builder screenTurnedOn = AtomMatcher.newBuilder()
-            .setId(screenTurnedOnId)
-            .setCombination(AtomMatcher.Combination.newBuilder()
-                .setOperation(LogicalOperation.OR)
-                .addMatcher(screenStateOnId)
-                .addMatcher(screenStateOnSuspendId)
-                .addMatcher(screenStateVrId));
+                .setId(screenTurnedOnId)
+                .setCombination(AtomMatcher.Combination.newBuilder()
+                        .setOperation(LogicalOperation.OR)
+                        .addMatcher(screenStateOnId)
+                        .addMatcher(screenStateOnSuspendId)
+                        .addMatcher(screenStateVrId));
 
         Predicate.Builder screenIsOff = Predicate.newBuilder()
-            .setId(screenIsOffId)
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                .setStart(screenTurnedOffId)
-                .setStop(screenTurnedOnId)
-                .setCountNesting(false));
+                .setId(screenIsOffId)
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(screenTurnedOffId)
+                        .setStop(screenTurnedOnId)
+                        .setCountNesting(false));
 
 
         Predicate.Builder screenOffBatteryOn = Predicate.newBuilder()
-            .setId(screenOffBatteryOnId)
-            .setCombination(Predicate.Combination.newBuilder()
-                .setOperation(LogicalOperation.AND)
-                .addPredicate(screenIsOffId)
-                .addPredicate(deviceIsUnpluggedId));
+                .setId(screenOffBatteryOnId)
+                .setCombination(Predicate.Combination.newBuilder()
+                        .setOperation(LogicalOperation.AND)
+                        .addPredicate(screenIsOffId)
+                        .addPredicate(deviceIsUnpluggedId));
 
         StatsdConfig.Builder builder = createConfigBuilder();
         builder.addDurationMetric(DurationMetric.newBuilder()
@@ -653,30 +651,30 @@
                 .setCondition(screenOffBatteryOnId)
                 .setDimensionsInWhat(dimensions)
                 .setBucket(bucketsize))
-            .addAtomMatcher(wakelockAcquire)
-            .addAtomMatcher(wakelockChangeAcquire)
-            .addAtomMatcher(wakelockRelease)
-            .addAtomMatcher(wakelockChangeRelease)
-            .addAtomMatcher(wakelockOn)
-            .addAtomMatcher(wakelockOff)
-            .addAtomMatcher(pluggedStateBatteryPluggedNone)
-            .addAtomMatcher(pluggedStateBatteryPluggedAc)
-            .addAtomMatcher(pluggedStateBatteryPluggedUsb)
-            .addAtomMatcher(pluggedStateBatteryPluggedWireless)
-            .addAtomMatcher(pluggedStateBatteryPlugged)
-            .addAtomMatcher(screenStateUnknown)
-            .addAtomMatcher(screenStateOff)
-            .addAtomMatcher(screenStateOn)
-            .addAtomMatcher(screenStateDoze)
-            .addAtomMatcher(screenStateDozeSuspend)
-            .addAtomMatcher(screenStateVr)
-            .addAtomMatcher(screenStateOnSuspend)
-            .addAtomMatcher(screenTurnedOff)
-            .addAtomMatcher(screenTurnedOn)
-            .addPredicate(wakelockPredicate)
-            .addPredicate(deviceIsUnplugged)
-            .addPredicate(screenIsOff)
-            .addPredicate(screenOffBatteryOn);
+                .addAtomMatcher(wakelockAcquire)
+                .addAtomMatcher(wakelockChangeAcquire)
+                .addAtomMatcher(wakelockRelease)
+                .addAtomMatcher(wakelockChangeRelease)
+                .addAtomMatcher(wakelockOn)
+                .addAtomMatcher(wakelockOff)
+                .addAtomMatcher(pluggedStateBatteryPluggedNone)
+                .addAtomMatcher(pluggedStateBatteryPluggedAc)
+                .addAtomMatcher(pluggedStateBatteryPluggedUsb)
+                .addAtomMatcher(pluggedStateBatteryPluggedWireless)
+                .addAtomMatcher(pluggedStateBatteryPlugged)
+                .addAtomMatcher(screenStateUnknown)
+                .addAtomMatcher(screenStateOff)
+                .addAtomMatcher(screenStateOn)
+                .addAtomMatcher(screenStateDoze)
+                .addAtomMatcher(screenStateDozeSuspend)
+                .addAtomMatcher(screenStateVr)
+                .addAtomMatcher(screenStateOnSuspend)
+                .addAtomMatcher(screenTurnedOff)
+                .addAtomMatcher(screenTurnedOn)
+                .addPredicate(wakelockPredicate)
+                .addPredicate(deviceIsUnplugged)
+                .addPredicate(screenIsOff)
+                .addPredicate(screenOffBatteryOn);
 
         uploadConfig(builder);
     }
diff --git a/hostsidetests/sustainedperf/shadertoy_android/Android.mk b/hostsidetests/sustainedperf/shadertoy_android/Android.mk
index 2e1bbdd..190ce5d 100644
--- a/hostsidetests/sustainedperf/shadertoy_android/Android.mk
+++ b/hostsidetests/sustainedperf/shadertoy_android/Android.mk
@@ -31,6 +31,7 @@
 LOCAL_PACKAGE_NAME := CtsSustainedPerformanceTestCases
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 5
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/theme/app/Android.mk b/hostsidetests/theme/app/Android.mk
index de63355..ec63c13 100644
--- a/hostsidetests/theme/app/Android.mk
+++ b/hostsidetests/theme/app/Android.mk
@@ -26,7 +26,8 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  android-support-test
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -38,6 +39,6 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 2a03db9..9a8b7b2 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -18,9 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.theme.app">
 
-    <uses-sdk android:minSdkVersion="17" />
-
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application>
@@ -34,7 +31,7 @@
         </activity>
         <activity android:name=".GenerateImagesActivity"
                   android:screenOrientation="portrait"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
                   android:exported="true" />
     </application>
 
diff --git a/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
index 1bf4da1..3591410 100644
--- a/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
+++ b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
@@ -27,12 +27,16 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.util.Log;
+import android.util.Pair;
 import android.view.WindowManager.LayoutParams;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * Generates images by iterating through all themes and launching instances of
@@ -59,63 +63,65 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
-                | LayoutParams.FLAG_TURN_SCREEN_ON
-                | LayoutParams.FLAG_DISMISS_KEYGUARD);
+        // Useful for local testing. Not required for CTS harness.
+        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
 
+        mOutputDir = setupOutputDirectory();
+        if (mOutputDir == null) {
+            finish("Failed to create output directory " + mOutputDir.getAbsolutePath(), false);
+        }
+
+        // The activity has been created, but we don't want to start image generation until various
+        // asynchronous conditions are satisfied.
+        new ConditionCheck(this, () -> generateNextImage(), message -> finish(message, false))
+                .addCondition("Device is unlocked",
+                        () -> !getSystemService(KeyguardManager.class).isDeviceLocked())
+                .addCondition("Window is focused",
+                        () -> hasWindowFocus())
+                .start();
+    }
+
+    private File setupOutputDirectory() {
         mOutputDir = new File(Environment.getExternalStorageDirectory(), OUT_DIR);
         ThemeTestUtils.deleteDirectory(mOutputDir);
         mOutputDir.mkdirs();
 
-        if (!mOutputDir.exists()) {
-            finish("Failed to create output directory " + mOutputDir.getAbsolutePath(), false);
-            return;
+        if (mOutputDir.exists()) {
+            return mOutputDir;
         }
-
-        final boolean canDisableKeyguard = checkCallingOrSelfPermission(
-                permission.DISABLE_KEYGUARD) == PackageManager.PERMISSION_GRANTED;
-        if (!canDisableKeyguard) {
-            finish("Not granted permission to disable keyguard", false);
-            return;
-        }
-
-        new KeyguardCheck(this) {
-            @Override
-            public void onSuccess() {
-                generateNextImage();
-            }
-
-            @Override
-            public void onFailure() {
-                finish("Device is locked", false);
-            }
-        }.start();
+        return null;
     }
 
-    public boolean isFinishSuccess() {
-        return mFinishSuccess;
-    }
-
-    public String getFinishReason() {
-        return mFinishReason;
-    }
-
-    static abstract class KeyguardCheck implements Runnable {
+    /**
+     * Runnable that re-posts itself on a handler until either all of the conditions are satisfied
+     * or a retry threshold is exceeded.
+     */
+    class ConditionCheck implements Runnable {
         private static final int MAX_RETRIES = 3;
         private static final int RETRY_DELAY = 500;
 
         private final Handler mHandler;
-        private final KeyguardManager mKeyguard;
+        private final Runnable mOnSuccess;
+        private final Consumer<String> mOnFailure;
+        private final ArrayList<Pair<String, Supplier<Boolean>>> mConditions = new ArrayList<>();
 
-        private int mRetries;
+        private ArrayList<Pair<String, Supplier<Boolean>>> mRemainingConditions = new ArrayList<>();
+        private int mRemainingRetries;
 
-        public KeyguardCheck(Context context) {
+        ConditionCheck(Context context, Runnable onSuccess, Consumer<String> onFailure) {
             mHandler = new Handler(context.getMainLooper());
-            mKeyguard = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
+            mOnSuccess = onSuccess;
+            mOnFailure = onFailure;
+        }
+
+        public ConditionCheck addCondition(String summary, Supplier<Boolean> condition) {
+            mConditions.add(new Pair<>(summary, condition));
+            return this;
         }
 
         public void start() {
-            mRetries = 0;
+            mRemainingConditions = new ArrayList<>(mConditions);
+            mRemainingRetries = 0;
 
             mHandler.removeCallbacks(this);
             mHandler.post(this);
@@ -127,19 +133,35 @@
 
         @Override
         public void run() {
-            if (!mKeyguard.isKeyguardLocked()) {
-                onSuccess();
-            } else if (mRetries < MAX_RETRIES) {
-                mRetries++;
+            mRemainingConditions.removeIf(condition -> condition.second.get());
+            if (mRemainingConditions.isEmpty()) {
+                mOnSuccess.run();
+            } else if (mRemainingRetries < MAX_RETRIES) {
+                mRemainingRetries++;
+                mHandler.removeCallbacks(this);
                 mHandler.postDelayed(this, RETRY_DELAY);
             } else {
-                onFailure();
+                final StringBuffer buffer = new StringBuffer("Failed conditions:");
+                mRemainingConditions.forEach(condition ->
+                        buffer.append("\n").append(condition.first));
+                mOnFailure.accept(buffer.toString());
             }
-
         }
+    }
 
-        public abstract void onSuccess();
-        public abstract void onFailure();
+    /**
+     * @return whether the test finished successfully
+     */
+    public boolean isFinishSuccess() {
+        return mFinishSuccess;
+    }
+
+    /**
+     * @return user-visible string explaining why the test finished, may be {@code null} if the test
+     *         finished unexpectedly
+     */
+    public String getFinishReason() {
+        return mFinishReason;
     }
 
     /**
diff --git a/hostsidetests/theme/app/src/android/theme/app/LayoutModifier.java b/hostsidetests/theme/app/src/android/theme/app/LayoutModifier.java
index 844c578..e007129 100644
--- a/hostsidetests/theme/app/src/android/theme/app/LayoutModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/LayoutModifier.java
@@ -23,12 +23,19 @@
  */
 public interface LayoutModifier {
 
-    /** Actions to take before inflating the view. */
-    void prepare();
+    /**
+     * Modifies the view before it has been added to a parent. Useful for avoiding animations in
+     * response to setter calls.
+     *
+     * @param view the view inflated by the test activity
+     */
+    void modifyViewBeforeAdd(View view);
 
     /**
-     * @param view inflated by the test activity
-     * @return the same view or another view that will be snapshotted by the test
+     * Modifies the view after it has been added to a parent. Useful for running animations in
+     * response to setter calls.
+     *
+     * @param view the view inflated by the test activity
      */
-    View modifyView(View view);
+    void modifyViewAfterAdd(View view);
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java b/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java
index 6e0731d..2c3b018 100644
--- a/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java
+++ b/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java
@@ -16,26 +16,45 @@
 
 package android.theme.app;
 
-import android.test.ActivityInstrumentationTestCase2;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 
 import java.io.File;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
- * Activity test case used to instrument generation of reference images.
+ * Test used to instrument generation of reference images.
  */
-public class ReferenceImagesTest extends ActivityInstrumentationTestCase2<GenerateImagesActivity> {
+@RunWith(AndroidJUnit4.class)
+public class ReferenceImagesTest {
+    private Instrumentation mInstrumentation;
+    private GenerateImagesActivity mActivity;
 
-    /** Overall test timeout is 30 minutes. Should only take about 5. */
+    // Overall test timeout is 30 minutes. Should only take about 5.
     private static final int TEST_RESULT_TIMEOUT = 30 * 60 * 1000;
 
-    public ReferenceImagesTest() {
-        super(GenerateImagesActivity.class);
+    @Rule
+    public ActivityTestRule<GenerateImagesActivity> mActivityRule =
+        new ActivityTestRule<>(GenerateImagesActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInstrumentation.setInTouchMode(true);
+
+        mActivity = mActivityRule.getActivity();
     }
 
+    @Test
     public void testGenerateReferenceImages() throws Exception {
-        setActivityInitialTouchMode(true);
-
-        final GenerateImagesActivity activity = getActivity();
+        final GenerateImagesActivity activity = mActivity;
         assertTrue("Activity failed to complete within " + TEST_RESULT_TIMEOUT + " ms",
                 activity.waitForCompletion(TEST_RESULT_TIMEOUT));
         assertTrue(activity.getFinishReason(), activity.isFinishSuccess());
diff --git a/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
index 9469a0c..b30ffd3 100644
--- a/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
+++ b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
@@ -16,7 +16,9 @@
 
 package android.theme.app;
 
+import android.animation.ValueAnimator;
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Build;
@@ -28,6 +30,7 @@
 import android.theme.app.modifiers.ViewCheckedModifier;
 import android.theme.app.modifiers.ViewPressedModifier;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager.LayoutParams;
 import android.widget.DatePicker;
@@ -76,15 +79,20 @@
         mOutputDir = new File(outputDir);
         mTheme = THEMES[themeIndex];
 
-        setTheme(mTheme.id);
+        // Disable animations.
+        ValueAnimator.setDurationScale(0);
 
         // Force text scaling to 1.0 regardless of system default.
         Configuration config = new Configuration();
         config.fontScale = 1.0f;
-        getResources().updateConfiguration(config, null);
-        setContentView(R.layout.theme_test);
 
-        mViewGroup = (ReferenceViewGroup) findViewById(R.id.reference_view_group);
+        Context inflationContext = createConfigurationContext(config);
+        inflationContext.setTheme(mTheme.id);
+
+        LayoutInflater layoutInflater = LayoutInflater.from(inflationContext);
+        setContentView(layoutInflater.inflate(R.layout.theme_test, null));
+
+        mViewGroup = findViewById(R.id.reference_view_group);
 
         getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
                 | LayoutParams.FLAG_TURN_SCREEN_ON
@@ -139,24 +147,26 @@
         mViewGroup.removeAllViews();
 
         final Layout layout = LAYOUTS[mLayoutIndex++];
+        final LayoutModifier modifier = layout.modifier;
         final String layoutName = String.format("%s_%s", mTheme.name, layout.name);
-        final View view = getLayoutInflater().inflate(layout.id, mViewGroup, false);
-        if (layout.modifier != null) {
-            layout.modifier.modifyView(view);
+        final LayoutInflater layoutInflater = LayoutInflater.from(mViewGroup.getContext());
+        final View view = layoutInflater.inflate(layout.id, mViewGroup, false);
+        if (modifier != null) {
+            modifier.modifyViewBeforeAdd(view);
         }
+        view.setFocusable(false);
 
         mViewGroup.addView(view);
-        view.setFocusable(false);
+
+        if (modifier != null) {
+            modifier.modifyViewAfterAdd(view);
+        }
 
         Log.v(TAG, "Rendering layout " + layoutName
                 + " (" + mLayoutIndex + "/" + LAYOUTS.length + ")");
 
-        final Runnable generateBitmapRunnable = new Runnable() {
-            @Override
-            public void run() {
-                new BitmapTask(view, layoutName).execute();
-            }
-        };
+        final Runnable generateBitmapRunnable = () ->
+            new BitmapTask(view, layoutName, modifier).execute();
 
         if (view instanceof DatePicker && mTheme.spec == Theme.HOLO) {
             // The Holo-styled DatePicker uses a CalendarView that has a
@@ -168,8 +178,12 @@
     }
 
     private class BitmapTask extends GenerateBitmapTask {
-        public BitmapTask(View view, String name) {
+        private final LayoutModifier mLayoutModifier;
+
+        public BitmapTask(View view, String name, LayoutModifier modifier) {
             super(view, mOutputDir, name);
+
+            mLayoutModifier = modifier;
         }
 
         @Override
@@ -205,6 +219,7 @@
     }
 
     // List of themes to verify.
+    @SuppressWarnings("deprecation")
     static final Theme[] THEMES = {
             // Holo
             new Theme(Theme.HOLO, android.R.style.Theme_Holo,
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/AbstractLayoutModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/AbstractLayoutModifier.java
index 5c945ef..e9dca7a 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/AbstractLayoutModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/AbstractLayoutModifier.java
@@ -17,13 +17,18 @@
 package android.theme.app.modifiers;
 
 import android.theme.app.LayoutModifier;
+import android.view.View;
 
 /**
- * {@link LayoutModifier} that does nothing in {@link #prepare()}.
+ * {@link LayoutModifier} that does nothing.
  */
 abstract class AbstractLayoutModifier implements LayoutModifier {
 
     @Override
-    public void prepare() {
+    public void modifyViewBeforeAdd(View view) {
+    }
+
+    @Override
+    public void modifyViewAfterAdd(View view) {
     }
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/DatePickerModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/DatePickerModifier.java
index 26ccd67..f155fcb 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/DatePickerModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/DatePickerModifier.java
@@ -26,9 +26,8 @@
 public class DatePickerModifier extends AbstractLayoutModifier {
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewAfterAdd(View view) {
         DatePicker tp = (DatePicker) view;
         tp.updateDate(2011, 4, 20);
-        return view;
     }
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/ProgressBarModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/ProgressBarModifier.java
index 9849a64..dcd8c89 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/ProgressBarModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/ProgressBarModifier.java
@@ -16,6 +16,7 @@
 
 package android.theme.app.modifiers;
 
+import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.animation.Interpolator;
 import android.widget.ProgressBar;
@@ -23,10 +24,9 @@
 public class ProgressBarModifier extends AbstractLayoutModifier {
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewBeforeAdd(View view) {
         ProgressBar pb = (ProgressBar) view;
         pb.setInterpolator(new ZeroInterpolator());
-        return pb;
     }
 
     private static class ZeroInterpolator implements Interpolator {
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/SearchViewModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/SearchViewModifier.java
index 75dd20a..de341ce 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/SearchViewModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/SearchViewModifier.java
@@ -34,7 +34,7 @@
     }
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewBeforeAdd(View view) {
         SearchView searchView = (SearchView) view;
         Context context = view.getContext();
 
@@ -52,6 +52,5 @@
         }
 
         searchView.setIconifiedByDefault(false);
-        return searchView;
     }
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/TimePickerModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/TimePickerModifier.java
index b2ed4ef..8e06ad8 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/TimePickerModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/TimePickerModifier.java
@@ -22,11 +22,10 @@
 public class TimePickerModifier extends AbstractLayoutModifier {
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewBeforeAdd(View view) {
         TimePicker timePicker = (TimePicker) view;
         timePicker.setIs24HourView(true);
         timePicker.setCurrentHour(13);
         timePicker.setCurrentMinute(37);
-        return view;
     }
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewCheckedModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewCheckedModifier.java
index f55f057..7810219 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewCheckedModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewCheckedModifier.java
@@ -22,8 +22,7 @@
 public class ViewCheckedModifier extends AbstractLayoutModifier {
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewBeforeAdd(View view) {
         ((CheckBox) view).setChecked(true);
-        return view;
     }
 }
diff --git a/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewPressedModifier.java b/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewPressedModifier.java
index a94962d..78b329c 100644
--- a/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewPressedModifier.java
+++ b/hostsidetests/theme/app/src/android/theme/app/modifiers/ViewPressedModifier.java
@@ -21,8 +21,7 @@
 public class ViewPressedModifier extends AbstractLayoutModifier {
 
     @Override
-    public View modifyView(View view) {
+    public void modifyViewBeforeAdd(View view) {
         view.setPressed(true);
-        return view;
     }
 }
diff --git a/hostsidetests/theme/assets/Q/260dpi.zip b/hostsidetests/theme/assets/Q/260dpi.zip
index 68423a4..ba5d363 100644
--- a/hostsidetests/theme/assets/Q/260dpi.zip
+++ b/hostsidetests/theme/assets/Q/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/280dpi.zip b/hostsidetests/theme/assets/Q/280dpi.zip
index 9a09eb4..34ea14c 100644
--- a/hostsidetests/theme/assets/Q/280dpi.zip
+++ b/hostsidetests/theme/assets/Q/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/300dpi.zip b/hostsidetests/theme/assets/Q/300dpi.zip
index 35390b9..7595d24 100644
--- a/hostsidetests/theme/assets/Q/300dpi.zip
+++ b/hostsidetests/theme/assets/Q/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/340dpi.zip b/hostsidetests/theme/assets/Q/340dpi.zip
index a65cc9f..8ce960c 100644
--- a/hostsidetests/theme/assets/Q/340dpi.zip
+++ b/hostsidetests/theme/assets/Q/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/360dpi.zip b/hostsidetests/theme/assets/Q/360dpi.zip
index a7be2b6..aae3adc 100644
--- a/hostsidetests/theme/assets/Q/360dpi.zip
+++ b/hostsidetests/theme/assets/Q/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/400dpi.zip b/hostsidetests/theme/assets/Q/400dpi.zip
index f8e62bb..363d602 100644
--- a/hostsidetests/theme/assets/Q/400dpi.zip
+++ b/hostsidetests/theme/assets/Q/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/420dpi.zip b/hostsidetests/theme/assets/Q/420dpi.zip
index 3d70848..0f2ce47 100644
--- a/hostsidetests/theme/assets/Q/420dpi.zip
+++ b/hostsidetests/theme/assets/Q/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/440dpi.zip b/hostsidetests/theme/assets/Q/440dpi.zip
new file mode 100644
index 0000000..2328c61
--- /dev/null
+++ b/hostsidetests/theme/assets/Q/440dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/560dpi.zip b/hostsidetests/theme/assets/Q/560dpi.zip
index e1527ed..5f1bb0b 100644
--- a/hostsidetests/theme/assets/Q/560dpi.zip
+++ b/hostsidetests/theme/assets/Q/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/hdpi.zip b/hostsidetests/theme/assets/Q/hdpi.zip
index 59c6efc..6d82318 100644
--- a/hostsidetests/theme/assets/Q/hdpi.zip
+++ b/hostsidetests/theme/assets/Q/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/ldpi.zip b/hostsidetests/theme/assets/Q/ldpi.zip
index 8e408af..cc60027 100644
--- a/hostsidetests/theme/assets/Q/ldpi.zip
+++ b/hostsidetests/theme/assets/Q/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/mdpi.zip b/hostsidetests/theme/assets/Q/mdpi.zip
index 9aebe9d..66d41d4 100644
--- a/hostsidetests/theme/assets/Q/mdpi.zip
+++ b/hostsidetests/theme/assets/Q/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/tvdpi.zip b/hostsidetests/theme/assets/Q/tvdpi.zip
index 55525fb..b43032f 100644
--- a/hostsidetests/theme/assets/Q/tvdpi.zip
+++ b/hostsidetests/theme/assets/Q/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/xhdpi.zip b/hostsidetests/theme/assets/Q/xhdpi.zip
index 3fecabe..64905f3 100644
--- a/hostsidetests/theme/assets/Q/xhdpi.zip
+++ b/hostsidetests/theme/assets/Q/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/xxhdpi.zip b/hostsidetests/theme/assets/Q/xxhdpi.zip
index 14d7680..b2cb422 100644
--- a/hostsidetests/theme/assets/Q/xxhdpi.zip
+++ b/hostsidetests/theme/assets/Q/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Q/xxxhdpi.zip b/hostsidetests/theme/assets/Q/xxxhdpi.zip
index aa8d087..d00dbbd 100644
--- a/hostsidetests/theme/assets/Q/xxxhdpi.zip
+++ b/hostsidetests/theme/assets/Q/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 de107c7..199830f 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -83,13 +83,16 @@
 
     private ExecutorCompletionService<Pair<String, File>> mCompletionService;
 
+    // Density to which the device should be restored, or -1 if unnecessary.
+    private int mRestoreDensity;
+
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
         mDevice = getDevice();
-        mDevice.executeShellCommand("settings put system font_scale 1.0");
+        mRestoreDensity = resetDensityIfNeeded(mDevice);
         final String density = getDensityBucketForDevice(mDevice);
         final String referenceZipAssetPath = String.format("/%s.zip", density);
         mReferences = extractReferenceImages(referenceZipAssetPath);
@@ -139,6 +142,8 @@
         // Remove generated images.
         mDevice.executeShellCommand(CLEAR_GENERATED_CMD);
 
+        restoreDensityIfNeeded(mDevice, mRestoreDensity);
+
         super.tearDown();
     }
 
@@ -268,15 +273,26 @@
         return bucket;
     }
 
-    private static int getDensityForDevice(ITestDevice device) throws DeviceNotAvailableException {
+    private static int resetDensityIfNeeded(ITestDevice device) throws DeviceNotAvailableException {
         final String output = device.executeShellCommand(WM_DENSITY);
-        final Pattern p = Pattern.compile("Override density: (\\d+)");
-        final Matcher m = p.matcher(output);
-        if (m.find()) {
-            throw new RuntimeException("Cannot test device running at non-default density: "
-                    + Integer.parseInt(m.group(1)));
-        }
+         final Pattern p = Pattern.compile("Override density: (\\d+)");
+         final Matcher m = p.matcher(output);
+         if (m.find()) {
+             device.executeShellCommand(WM_DENSITY + " reset");
+             int restoreDensity = Integer.parseInt(m.group(1));
+             return restoreDensity;
+         }
+         return -1;
+    }
 
+    private static void restoreDensityIfNeeded(ITestDevice device, int restoreDensity)
+            throws DeviceNotAvailableException {
+        if (restoreDensity > 0) {
+            device.executeShellCommand(WM_DENSITY + " " + restoreDensity);
+        }
+    }
+
+    private static int getDensityForDevice(ITestDevice device) throws DeviceNotAvailableException {
         final String densityProp;
         if (device.getSerialNumber().startsWith("emulator-")) {
             densityProp = DENSITY_PROP_EMULATOR;
diff --git a/hostsidetests/ui/AndroidTest.xml b/hostsidetests/ui/AndroidTest.xml
index 88c515a..86e27bd 100644
--- a/hostsidetests/ui/AndroidTest.xml
+++ b/hostsidetests/ui/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS UI host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsUiHostTestCases.jar" />
         <option name="runtime-hint" value="2m" />
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index 7078705..9ccc7e1 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS App Usage host 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="instant_app" />
     <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/usb/AndroidTest.xml b/hostsidetests/usb/AndroidTest.xml
index bad4ff9..2318a4e 100644
--- a/hostsidetests/usb/AndroidTest.xml
+++ b/hostsidetests/usb/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS USB host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsUsbTests.jar" />
     </test>
diff --git a/hostsidetests/usb/SerialTestApp/Android.mk b/hostsidetests/usb/SerialTestApp/Android.mk
index 601c0ba..69bf34d 100644
--- a/hostsidetests/usb/SerialTestApp/Android.mk
+++ b/hostsidetests/usb/SerialTestApp/Android.mk
@@ -20,7 +20,8 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner \
+	compatibility-device-util
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
@@ -29,6 +30,7 @@
 LOCAL_PACKAGE_NAME := CtsUsbSerialTestApp
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 27
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
diff --git a/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java b/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
index d239095..a9ff1d5 100644
--- a/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
+++ b/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
@@ -20,6 +20,8 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,7 +34,9 @@
 
     @Test
     public void logSerial() {
-        Log.e(TAG, Build.getSerial());
+        // Since device identifiers are no longer accessible to third party apps invoke the
+        // Build#getSerial method with the shell identity's permissions.
+        Log.e(TAG, ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));
     }
 
     @Test(expected = SecurityException.class)
diff --git a/hostsidetests/webkit/AndroidTest.xml b/hostsidetests/webkit/AndroidTest.xml
index 414fad4..927d28f 100644
--- a/hostsidetests/webkit/AndroidTest.xml
+++ b/hostsidetests/webkit/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS WebView host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="webview" />
+    <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" />
         <option name="test-file-name" value="CtsWebViewStartupApp.apk" />
diff --git a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
index 692310b..80a8964 100644
--- a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
+++ b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
@@ -31,8 +31,9 @@
 import android.webkit.SslErrorHandler;
 import android.webkit.WebView;
 import android.webkit.cts.CtsTestServer;
-import android.webkit.cts.WebViewOnUiThread;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
+import android.webkit.cts.WebkitUtils;
 import android.webkit.WebView;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
@@ -100,16 +101,16 @@
         // Now create WebView and test that setting the cookie beforehand really worked.
         mActivity.createAndAttachWebView();
         WebView webView = mActivity.getWebView();
-        WebViewOnUiThread onUiThread = new WebViewOnUiThread(this, mActivity.getWebView());
-        webView.setWebViewClient(new WaitForLoadedClient(onUiThread) {
+        WebViewSyncLoader syncLoader = new WebViewSyncLoader(webView);
+        webView.setWebViewClient(new WaitForLoadedClient(syncLoader) {
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 // Not intended to verify server certificate, ignore the error.
                 if (error.getPrimaryError() == SslError.SSL_IDMISMATCH) handler.proceed();
             }
         });
-        onUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals("1|count=41", onUiThread.getTitle()); // outgoing cookie
+        syncLoader.loadUrlAndWaitForCompletion(url);
+        assertEquals("1|count=41", webView.getTitle()); // outgoing cookie
         CookieManager cookieManager = CookieManager.getInstance();
         String cookie = cookieManager.getCookie(url);
         assertNotNull(cookie);
@@ -117,6 +118,7 @@
         Matcher m = pat.matcher(cookie);
         assertTrue(m.matches());
         assertEquals("42", m.group(1)); // value got incremented
+        syncLoader.detach();
     }
 
     @UiThreadTest
@@ -140,11 +142,8 @@
             if (alreadyOnMainThread) {
                 mActivity.createAndAttachWebView();
             } else {
-                getInstrumentation().runOnMainSync(new Runnable() {
-                    @Override
-                    public void run() {
-                        mActivity.createAndAttachWebView();
-                    }
+                WebkitUtils.onMainThreadSync(() -> {
+                    mActivity.createAndAttachWebView();
                 });
             }
 
@@ -211,9 +210,10 @@
         // WebView is available, so try to call some WebView APIs to ensure they don't cause
         // strictmode violations
 
-        WebViewOnUiThread onUiThread = new WebViewOnUiThread(this, mActivity.getWebView());
-        onUiThread.loadUrlAndWaitForCompletion("about:blank");
-        onUiThread.loadUrlAndWaitForCompletion("");
+        WebViewSyncLoader syncLoader = new WebViewSyncLoader(mActivity.getWebView());
+        syncLoader.loadUrlAndWaitForCompletion("about:blank");
+        syncLoader.loadUrlAndWaitForCompletion("");
+        syncLoader.detach();
     }
 
     @UiThreadTest
@@ -232,16 +232,12 @@
         PackageManager pm = mActivity.getPackageManager();
         if (!pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) return;
 
-        WebView[] webviewHolder = new WebView[1];
         // Create the WebView on the UI thread and then ensure webview.getWebViewLooper() returns
         // the UI thread.
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                webviewHolder[0] = createAndCheckWebViewLooper();
-            }
+        WebView webView = WebkitUtils.onMainThreadSync(() -> {
+            return createAndCheckWebViewLooper();
         });
-        assertEquals(Looper.getMainLooper(), webviewHolder[0].getWebViewLooper());
+        assertEquals(Looper.getMainLooper(), webView.getWebViewLooper());
     }
 
     /**
diff --git a/hostsidetests/wifibroadcasts/Android.mk b/hostsidetests/wifibroadcasts/Android.mk
new file mode 100644
index 0000000..584cc4c
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_MODULE := CtsWifiBroadcastsHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/wifibroadcasts/AndroidTest.xml b/hostsidetests/wifibroadcasts/AndroidTest.xml
new file mode 100644
index 0000000..caa1d29
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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 WifiBroadcasts host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsWifiBroadcastsDeviceApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsWifiBroadcastsHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/wifibroadcasts/app/Android.mk b/hostsidetests/wifibroadcasts/app/Android.mk
new file mode 100644
index 0000000..ae4bcd1
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/app/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsWifiBroadcastsDeviceApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/wifibroadcasts/app/AndroidManifest.xml b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
new file mode 100644
index 0000000..7bc2d00
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.wifibroadcasts.app">
+
+    <application>
+        <activity android:name=".WifiBroadcastsDeviceActivity" >
+            <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/wifibroadcasts/app/src/android/wifibroadcasts/app/WifiBroadcastsDeviceActivity.java b/hostsidetests/wifibroadcasts/app/src/android/wifibroadcasts/app/WifiBroadcastsDeviceActivity.java
new file mode 100644
index 0000000..996f1df
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/app/src/android/wifibroadcasts/app/WifiBroadcastsDeviceActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.wifibroadcasts.app;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * Logs to Logcat when unexpected broadcasts are received
+ */
+public class WifiBroadcastsDeviceActivity extends Activity {
+
+    private static final String TAG = WifiBroadcastsDeviceActivity.class.getSimpleName();
+
+    private final Context mContext = this;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Toast.makeText(mContext, action, Toast.LENGTH_SHORT).show();
+            if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
+                Log.i(TAG, "UNEXPECTED WIFI BROADCAST RECEIVED - " + action);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        final IntentFilter filter = new IntentFilter();
+        String action = WifiManager.RSSI_CHANGED_ACTION;
+        filter.addAction(action);
+        mContext.registerReceiver(mReceiver, filter);
+        Log.i(TAG, "Registered " + action);
+        Toast.makeText(mContext, "Started", Toast.LENGTH_SHORT).show();
+    }
+
+}
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
new file mode 100644
index 0000000..e01d682
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -0,0 +1,157 @@
+/*
+ * 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.wifibroadcasts.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.util.CommandResult;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Scanner;
+
+/**
+ * Test to check the APK logs to Logcat.
+ *
+ * When this test builds, it also builds
+ * {@link android.wifibroadcasts.app.WifiBroadcastsDeviceActivity} into an
+ * APK which is then installed at runtime and started. That activity prints a message to
+ * Logcat if an unexpected broadcast is received.
+ *
+ * Instead of extending DeviceTestCase, this JUnit4 test extends IDeviceTest and is run with
+ * tradefed's DeviceJUnit4ClassRunner
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class WifiBroadcastsHostJUnit4Test implements IDeviceTest {
+
+    /**
+     * The package name of the APK.
+     */
+    private static final String PACKAGE = "android.wifibroadcasts.app";
+
+    /**
+     * The class name of the main activity in the APK.
+     */
+    private static final String CLASS = "WifiBroadcastsDeviceActivity";
+
+    /**
+     * The command to launch the main activity.
+     */
+    private static final String START_COMMAND = String.format(
+            "am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE, PACKAGE, CLASS);
+
+    /**
+     * The command to clear the main activity.
+     */
+    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+    /**
+     * The prohibited string to look for.
+     */
+    private static final String PROHIBITED_STRING = "UNEXPECTED WIFI BROADCAST RECEIVED";
+
+    /**
+     * The maximim number of times to attempt a ping
+     */
+    private static final int MAXIMUM_PING_TRIES = 30;
+
+    /**
+     * Name for wifi feature test
+     */
+    private static final String FEATURE_WIFI = "android.hardware.wifi";
+
+    private ITestDevice mDevice;
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Tests the string was not logged to Logcat from the activity.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testCleanLogcat() throws Exception {
+        ITestDevice device = getDevice();
+        assertNotNull("Device not set", device);
+        if (!device.hasFeature(FEATURE_WIFI)) {
+            return;
+        }
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // No mobile data or wifi or bluetooth to start with
+        device.executeShellCommand("svc data disable; svc wifi disable; svc bluetooth disable");
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Ensure the screen is on, so that rssi polling happens
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        // Start the APK
+        device.executeShellCommand(START_COMMAND);
+        // Bring up wifi
+        device.executeShellCommand("svc wifi enable; sleep 1");
+        // Make sure wifi comes up
+        String pingResult = "";
+        CommandResult pingCommandResult = null;
+        boolean pingSucceeded = false;
+        for (int tries = 0; tries < MAXIMUM_PING_TRIES; tries++) {
+            // 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(),
+                                          pingCommandResult.getStderr(),
+                                          pingCommandResult.getStatus().toString());
+            if (pingResult.contains("4 packets transmitted")) {
+                pingSucceeded = true;
+                break;
+            }
+            Thread.sleep(1000);
+        }
+        // Stop wifi
+        device.executeShellCommand("svc wifi disable");
+
+        assertTrue("Wi-Fi network unavailable - test could not complete " + pingResult,
+                pingSucceeded);
+
+        // Dump logcat.
+        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
+        // Search for prohibited string.
+        Scanner in = new Scanner(logs);
+        try {
+            while (in.hasNextLine()) {
+                String line = in.nextLine();
+                if (line.startsWith("I/" + CLASS)) {
+                    String payload = line.split(":")[1].trim();
+                    assertFalse(payload, payload.contains(PROHIBITED_STRING));
+                }
+            }
+        } finally {
+            in.close();
+        }
+    }
+}
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
index d20e1a4..e77f016 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -16,268 +16,180 @@
 
 package android.webkit.cts;
 
-import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.TestThread;
-
 import android.graphics.Bitmap;
 import android.graphics.Picture;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
+import android.net.http.SslCertificate;
 import android.os.Message;
-import android.os.SystemClock;
 import android.print.PrintDocumentAdapter;
-import android.support.test.rule.ActivityTestRule;
-import android.test.InstrumentationTestCase;
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.webkit.DownloadListener;
 import android.webkit.CookieManager;
+import android.webkit.DownloadListener;
 import android.webkit.ValueCallback;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
 import android.webkit.WebMessage;
 import android.webkit.WebMessagePort;
 import android.webkit.WebSettings;
+import android.webkit.WebView;
 import android.webkit.WebView.HitTestResult;
 import android.webkit.WebView.PictureListener;
 import android.webkit.WebView.VisualStateCallback;
-import android.webkit.WebView;
 import android.webkit.WebViewClient;
+import android.webkit.WebViewRendererClient;
 
-import junit.framework.Assert;
+import com.android.compatibility.common.util.PollingCheck;
 
-import java.io.File;
-import java.util.concurrent.Callable;
-import java.util.Map;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
 
 /**
  * Many tests need to run WebView code in the UI thread. This class
  * wraps a WebView so that calls are ensured to arrive on the UI thread.
  *
  * All methods may be run on either the UI thread or test thread.
+ *
+ * This should remain functionally equivalent to androidx.webkit.WebViewOnUiThread.
+ * Modifications to this class should be reflected in that class as necessary. See
+ * http://go/modifying-webview-cts.
  */
-public class WebViewOnUiThread {
-    /**
-     * The maximum time, in milliseconds (10 seconds) to wait for a load
-     * to be triggered.
-     */
-    private static final long LOAD_TIMEOUT = 10000;
-
-    /**
-     * Set to true after onPageFinished is called.
-     */
-    private boolean mLoaded;
-
-    /**
-     * Set to true after onNewPicture is called. Reset when onPageStarted
-     * is called.
-     */
-    private boolean mNewPicture;
-
-    /**
-     * The progress, in percentage, of the page load. Valid values are between
-     * 0 and 100.
-     */
-    private int mProgress;
-
-    /**
-     * The test that this class is being used in. Used for runTestOnUiThread.
-     */
-    private InstrumentationTestCase mTest;
-
-    /**
-     * The test rule that this class is being used in. Used for runTestOnUiThread.
-     */
-    private ActivityTestRule mActivityTestRule;
-
+public class WebViewOnUiThread extends WebViewSyncLoader {
     /**
      * The WebView that calls will be made on.
      */
     private WebView mWebView;
 
     /**
-     * Initializes the webView with a WebViewClient, WebChromeClient,
-     * and PictureListener to prepare for loadUrlAndWaitForCompletion.
+     * Wraps a WebView to ensure that methods are run on the UI thread.
      *
      * A new WebViewOnUiThread should be called during setUp so as to
      * reinitialize between calls.
      *
-     * @param test The test in which this is being run.
      * @param webView The webView that the methods should call.
-     * @see #loadDataAndWaitForCompletion(String, String, String)
-     * @deprecated Use {@link WebViewOnUiThread#WebViewOnUiThread(ActivityTestRule, WebView)}
      */
-    @Deprecated
-    public WebViewOnUiThread(InstrumentationTestCase test, WebView webView) {
-        mTest = test;
+    public WebViewOnUiThread(WebView webView) {
+        super(webView);
         mWebView = webView;
-        final WebViewClient webViewClient = new WaitForLoadedClient(this);
-        final WebChromeClient webChromeClient = new WaitForProgressClient(this);
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setWebViewClient(webViewClient);
-                mWebView.setWebChromeClient(webChromeClient);
-                mWebView.setPictureListener(new WaitForNewPicture());
-            }
-        });
     }
 
-    /**
-     * Initializes the webView with a WebViewClient, WebChromeClient,
-     * and PictureListener to prepare for loadUrlAndWaitForCompletion.
-     *
-     * A new WebViewOnUiThread should be called during setUp so as to
-     * reinitialize between calls.
-     *
-     * @param activityTestRule The test rule in which this is being run.
-     * @param webView The webView that the methods should call.
-     * @see #loadDataAndWaitForCompletion(String, String, String)
-     */
-    public WebViewOnUiThread(ActivityTestRule activityTestRule, WebView webView) {
-        mActivityTestRule = activityTestRule;
-        mWebView = webView;
-        final WebViewClient webViewClient = new WaitForLoadedClient(this);
-        final WebChromeClient webChromeClient = new WaitForProgressClient(this);
-        runOnUiThread(() -> {
-            mWebView.setWebViewClient(webViewClient);
-            mWebView.setWebChromeClient(webChromeClient);
-            mWebView.setPictureListener(new WaitForNewPicture());
-        });
-    }
-
-    /**
-     * Called after a test is complete and the WebView should be disengaged from
-     * the tests.
-     */
     public void cleanUp() {
-        clearHistory();
-        clearCache(true);
-        setPictureListener(null);
-        setWebChromeClient(null);
-        setWebViewClient(null);
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.destroy();
-            }
-        });
-    }
-
-    /**
-     * Called from WaitForNewPicture, this is used to indicate that
-     * the page has been drawn.
-     */
-    synchronized public void onNewPicture() {
-        mNewPicture = true;
-        this.notifyAll();
-    }
-
-    /**
-     * Called from WaitForLoadedClient, this is used to clear the picture
-     * draw state so that draws before the URL begins loading don't count.
-     */
-    synchronized public void onPageStarted() {
-        mNewPicture = false; // Earlier paints won't count.
-    }
-
-    /**
-     * Called from WaitForLoadedClient, this is used to indicate that
-     * the page is loaded, but not drawn yet.
-     */
-    synchronized public void onPageFinished() {
-        mLoaded = true;
-        this.notifyAll();
-    }
-
-    /**
-     * Called from the WebChrome client, this sets the current progress
-     * for a page.
-     * @param progress The progress made so far between 0 and 100.
-     */
-    synchronized public void onProgressChanged(int progress) {
-        mProgress = progress;
-        this.notifyAll();
+        super.destroy();
     }
 
     public void setWebViewClient(final WebViewClient webViewClient) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setWebViewClient(webViewClient);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setWebViewClient(webViewClient);
         });
     }
 
     public void setWebChromeClient(final WebChromeClient webChromeClient) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setWebChromeClient(webChromeClient);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setWebChromeClient(webChromeClient);
+        });
+    }
+
+    /**
+     * Set the webview renderer client for {@code mWebView}, on the UI thread.
+     */
+    public void setWebViewRendererClient(
+            final WebViewRendererClient webViewRendererClient) {
+        setWebViewRendererClient(mWebView, webViewRendererClient);
+    }
+
+    /**
+     * Set the webview renderer client for {@code webView}, on the UI thread.
+     */
+    public static void setWebViewRendererClient(
+            final WebView webView,
+            final WebViewRendererClient webViewRendererClient) {
+        WebkitUtils.onMainThreadSync(() ->
+                webView.setWebViewRendererClient(webViewRendererClient)
+        );
+    }
+
+    /**
+     * Set the webview renderer client for {@code mWebView}, on the UI thread, with callbacks
+     * executed by {@code executor}
+     */
+    public void setWebViewRendererClient(
+            final Executor executor, final WebViewRendererClient webViewRendererClient) {
+        setWebViewRendererClient(mWebView, executor, webViewRendererClient);
+    }
+
+    /**
+     * Set the webview renderer client for {@code webView}, on the UI thread, with callbacks
+     * executed by {@code executor}
+     */
+    public static void setWebViewRendererClient(
+            final WebView webView,
+            final Executor executor,
+            final WebViewRendererClient webViewRendererClient) {
+        WebkitUtils.onMainThreadSync(() ->
+                webView.setWebViewRendererClient(executor, webViewRendererClient)
+        );
+    }
+
+    /**
+     * Get the webview renderer client currently set on {@code mWebView}, on the UI thread.
+     */
+    public WebViewRendererClient getWebViewRendererClient() {
+        return getWebViewRendererClient(mWebView);
+    }
+
+    /**
+     * Get the webview renderer client currently set on {@code webView}, on the UI thread.
+     */
+    public static WebViewRendererClient getWebViewRendererClient(
+            final WebView webView) {
+        return WebkitUtils.onMainThreadSync(() -> {
+            return webView.getWebViewRendererClient();
         });
     }
 
     public void setPictureListener(final PictureListener pictureListener) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setPictureListener(pictureListener);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setPictureListener(pictureListener);
         });
     }
 
     public void setNetworkAvailable(final boolean available) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setNetworkAvailable(available);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setNetworkAvailable(available);
         });
     }
 
     public void setDownloadListener(final DownloadListener listener) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setDownloadListener(listener);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setDownloadListener(listener);
         });
     }
 
     public void setBackgroundColor(final int color) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setBackgroundColor(color);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setBackgroundColor(color);
         });
     }
 
     public void clearCache(final boolean includeDiskFiles) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.clearCache(includeDiskFiles);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.clearCache(includeDiskFiles);
         });
     }
 
     public void clearHistory() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.clearHistory();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.clearHistory();
         });
     }
 
     public void requestFocus() {
-        new PollingCheck(LOAD_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 requestFocusOnUiThread();
@@ -287,630 +199,364 @@
     }
 
     private void requestFocusOnUiThread() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.requestFocus();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.requestFocus();
         });
     }
 
     private boolean hasFocus() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.hasFocus();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.hasFocus();
         });
     }
 
     public boolean canZoomIn() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.canZoomIn();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.canZoomIn();
         });
     }
 
     public boolean canZoomOut() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.canZoomOut();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.canZoomOut();
         });
     }
 
     public boolean zoomIn() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.zoomIn();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.zoomIn();
         });
     }
 
     public boolean zoomOut() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.zoomOut();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.zoomOut();
         });
     }
 
     public void zoomBy(final float zoomFactor) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.zoomBy(zoomFactor);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.zoomBy(zoomFactor);
         });
     }
 
     public void setFindListener(final WebView.FindListener listener) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setFindListener(listener);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.setFindListener(listener);
         });
     }
 
     public void removeJavascriptInterface(final String interfaceName) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.removeJavascriptInterface(interfaceName);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.removeJavascriptInterface(interfaceName);
         });
     }
 
     public WebMessagePort[] createWebMessageChannel() {
-        return getValue(new ValueGetter<WebMessagePort[]>() {
-            @Override
-            public WebMessagePort[] capture() {
-                return mWebView.createWebMessageChannel();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.createWebMessageChannel();
         });
     }
 
     public void postWebMessage(final WebMessage message, final Uri targetOrigin) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.postWebMessage(message, targetOrigin);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.postWebMessage(message, targetOrigin);
         });
     }
 
     public void addJavascriptInterface(final Object object, final String name) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.addJavascriptInterface(object, name);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.addJavascriptInterface(object, name);
         });
     }
 
     public void flingScroll(final int vx, final int vy) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.flingScroll(vx, vy);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.flingScroll(vx, vy);
         });
     }
 
     public void requestFocusNodeHref(final Message hrefMsg) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.requestFocusNodeHref(hrefMsg);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.requestFocusNodeHref(hrefMsg);
         });
     }
 
     public void requestImageRef(final Message msg) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.requestImageRef(msg);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.requestImageRef(msg);
         });
     }
 
     public void setInitialScale(final int scaleInPercent) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
+        WebkitUtils.onMainThreadSync(() -> {
                 mWebView.setInitialScale(scaleInPercent);
-            }
         });
     }
 
     public void clearSslPreferences() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.clearSslPreferences();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.clearSslPreferences();
         });
     }
 
     public void clearClientCertPreferences(final Runnable onCleared) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                WebView.clearClientCertPreferences(onCleared);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            WebView.clearClientCertPreferences(onCleared);
         });
     }
 
     public void resumeTimers() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.resumeTimers();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.resumeTimers();
         });
     }
 
     public void findNext(final boolean forward) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.findNext(forward);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.findNext(forward);
         });
     }
 
     public void clearMatches() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.clearMatches();
-            }
-        });
-    }
-
-    /**
-     * Calls loadUrl on the WebView and then waits onPageFinished,
-     * onNewPicture and onProgressChange to reach 100.
-     * Test fails if the load timeout elapses.
-     * @param url The URL to load.
-     */
-    public void loadUrlAndWaitForCompletion(final String url) {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.loadUrl(url);
-            }
-        });
-    }
-
-    /**
-     * Calls loadUrl on the WebView and then waits onPageFinished,
-     * onNewPicture and onProgressChange to reach 100.
-     * Test fails if the load timeout elapses.
-     * @param url The URL to load.
-     * @param extraHeaders The additional headers to be used in the HTTP request.
-     */
-    public void loadUrlAndWaitForCompletion(final String url,
-            final Map<String, String> extraHeaders) {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.loadUrl(url, extraHeaders);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.clearMatches();
         });
     }
 
     public void loadUrl(final String url) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.loadUrl(url);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.loadUrl(url);
         });
     }
 
     public void stopLoading() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.stopLoading();
-            }
-        });
-    }
-
-    public void postUrlAndWaitForCompletion(final String url, final byte[] postData) {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.postUrl(url, postData);
-            }
-        });
-    }
-
-    public void loadDataAndWaitForCompletion(final String data,
-            final String mimeType, final String encoding) {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.loadData(data, mimeType, encoding);
-            }
-        });
-    }
-
-    public void loadDataWithBaseURLAndWaitForCompletion(final String baseUrl,
-            final String data, final String mimeType, final String encoding,
-            final String historyUrl) {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding,
-                        historyUrl);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.stopLoading();
         });
     }
 
     /**
-     * Reloads a page and waits for it to complete reloading. Use reload
-     * if it is a form resubmission and the onFormResubmission responds
-     * by telling WebView not to resubmit it.
-     */
-    public void reloadAndWaitForCompletion() {
-        callAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.reload();
-            }
-        });
-    }
-
-    /**
-     * Reload the previous URL. Use reloadAndWaitForCompletion unless
-     * it is a form resubmission and the onFormResubmission responds
-     * by telling WebView not to resubmit it.
+     * Reload the previous URL.
      */
     public void reload() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.reload();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.reload();
         });
     }
 
-    /**
-     * Use this only when JavaScript causes a page load to wait for the
-     * page load to complete. Otherwise use loadUrlAndWaitForCompletion or
-     * similar functions.
-     */
-    public void waitForLoadCompletion() {
-        waitForCriteria(LOAD_TIMEOUT,
-                new Callable<Boolean>() {
-                    @Override
-                    public Boolean call() {
-                        return isLoaded();
-                    }
-                });
-        clearLoad();
-    }
-
-    private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) {
-        if (isUiThread()) {
-            waitOnUiThread(timeout, doneCriteria);
-        } else {
-            waitOnTestThread(timeout, doneCriteria);
-        }
-    }
-
     public String getTitle() {
-        return getValue(new ValueGetter<String>() {
-            @Override
-            public String capture() {
-                return mWebView.getTitle();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getTitle();
         });
     }
 
     public WebSettings getSettings() {
-        return getValue(new ValueGetter<WebSettings>() {
-            @Override
-            public WebSettings capture() {
-                return mWebView.getSettings();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getSettings();
         });
     }
 
     public WebBackForwardList copyBackForwardList() {
-        return getValue(new ValueGetter<WebBackForwardList>() {
-            @Override
-            public WebBackForwardList capture() {
-                return mWebView.copyBackForwardList();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.copyBackForwardList();
         });
     }
 
     public Bitmap getFavicon() {
-        return getValue(new ValueGetter<Bitmap>() {
-            @Override
-            public Bitmap capture() {
-                return mWebView.getFavicon();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getFavicon();
         });
     }
 
     public String getUrl() {
-        return getValue(new ValueGetter<String>() {
-            @Override
-            public String capture() {
-                return mWebView.getUrl();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getUrl();
         });
     }
 
     public int getProgress() {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.getProgress();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getProgress();
         });
     }
 
     public int getHeight() {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.getHeight();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getHeight();
         });
     }
 
     public int getContentHeight() {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.getContentHeight();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getContentHeight();
         });
     }
 
     public boolean pageUp(final boolean top) {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.pageUp(top);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.pageUp(top);
         });
     }
 
     public boolean pageDown(final boolean bottom) {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.pageDown(bottom);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.pageDown(bottom);
         });
     }
 
+    /**
+     * Post a visual state listener callback for mWebView on the UI thread.
+     */
     public void postVisualStateCallback(final long requestId, final VisualStateCallback callback) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.postVisualStateCallback(requestId, callback);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.postVisualStateCallback(requestId, callback);
         });
     }
 
     public int[] getLocationOnScreen() {
         final int[] location = new int[2];
-        return getValue(new ValueGetter<int[]>() {
-            @Override
-            public int[] capture() {
-                mWebView.getLocationOnScreen(location);
-                return location;
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            mWebView.getLocationOnScreen(location);
+            return location;
         });
     }
 
     public float getScale() {
-        return getValue(new ValueGetter<Float>() {
-            @Override
-            public Float capture() {
-                return mWebView.getScale();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getScale();
         });
     }
 
     public boolean requestFocus(final int direction,
             final Rect previouslyFocusedRect) {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.requestFocus(direction, previouslyFocusedRect);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.requestFocus(direction, previouslyFocusedRect);
         });
     }
 
     public HitTestResult getHitTestResult() {
-        return getValue(new ValueGetter<HitTestResult>() {
-            @Override
-            public HitTestResult capture() {
-                return mWebView.getHitTestResult();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getHitTestResult();
         });
     }
 
     public int getScrollX() {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.getScrollX();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getScrollX();
         });
     }
 
     public int getScrollY() {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.getScrollY();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getScrollY();
         });
     }
 
     public final DisplayMetrics getDisplayMetrics() {
-        return getValue(new ValueGetter<DisplayMetrics>() {
-            @Override
-            public DisplayMetrics capture() {
-                return mWebView.getContext().getResources().getDisplayMetrics();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getContext().getResources().getDisplayMetrics();
         });
     }
 
     public boolean requestChildRectangleOnScreen(final View child,
             final Rect rect,
             final boolean immediate) {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return mWebView.requestChildRectangleOnScreen(child, rect,
-                        immediate);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.requestChildRectangleOnScreen(child, rect,
+                    immediate);
         });
     }
 
     public int findAll(final String find) {
-        return getValue(new ValueGetter<Integer>() {
-            @Override
-            public Integer capture() {
-                return mWebView.findAll(find);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.findAll(find);
         });
     }
 
     public Picture capturePicture() {
-        return getValue(new ValueGetter<Picture>() {
-            @Override
-            public Picture capture() {
-                return mWebView.capturePicture();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.capturePicture();
         });
     }
 
+    /**
+     * Execute javascript, returning a Future for the result.
+     */
+    public Future<String> evaluateJavascript(final String script) {
+        SettableFuture<String> future = SettableFuture.create();
+        WebkitUtils.onMainThread(() -> {
+            mWebView.evaluateJavascript(script, (String result) -> {
+                future.set(result);
+            });
+        });
+        return future;
+    }
+
     public void evaluateJavascript(final String script, final ValueCallback<String> result) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.evaluateJavascript(script, result);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.evaluateJavascript(script, result);
         });
     }
 
     public void saveWebArchive(final String basename, final boolean autoname,
                                final ValueCallback<String> callback) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.saveWebArchive(basename, autoname, callback);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.saveWebArchive(basename, autoname, callback);
+        });
+    }
+
+    public SslCertificate getCertificate() {
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.getCertificate();
         });
     }
 
     public WebView createWebView() {
-        return getValue(new ValueGetter<WebView>() {
-            @Override
-            public WebView capture() {
-                return new WebView(mWebView.getContext());
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return new WebView(mWebView.getContext());
         });
     }
 
     public PrintDocumentAdapter createPrintDocumentAdapter() {
-        return getValue(new ValueGetter<PrintDocumentAdapter>() {
-            @Override
-            public PrintDocumentAdapter capture() {
-                return mWebView.createPrintDocumentAdapter();
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return mWebView.createPrintDocumentAdapter();
         });
     }
 
     public void setLayoutHeightToMatchParent() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewParent parent = mWebView.getParent();
-                if (parent instanceof ViewGroup) {
-                    ((ViewGroup) parent).getLayoutParams().height =
-                        ViewGroup.LayoutParams.MATCH_PARENT;
-                }
-                mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
-                mWebView.requestLayout();
+        WebkitUtils.onMainThreadSync(() -> {
+            ViewParent parent = mWebView.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).getLayoutParams().height =
+                    ViewGroup.LayoutParams.MATCH_PARENT;
             }
+            mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
+            mWebView.requestLayout();
         });
     }
 
     public void setLayoutToMatchParent() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                setMatchParent((View) mWebView.getParent());
-                setMatchParent(mWebView);
-                mWebView.requestLayout();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            setMatchParent((View) mWebView.getParent());
+            setMatchParent(mWebView);
+            mWebView.requestLayout();
         });
     }
 
     public void setAcceptThirdPartyCookies(final boolean accept) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept);
         });
     }
 
     public boolean acceptThirdPartyCookies() {
-        return getValue(new ValueGetter<Boolean>() {
-            @Override
-            public Boolean capture() {
-                return CookieManager.getInstance().acceptThirdPartyCookies(mWebView);
-            }
+        return WebkitUtils.onMainThreadSync(() -> {
+            return CookieManager.getInstance().acceptThirdPartyCookies(mWebView);
         });
     }
 
     /**
-     * Helper for running code on the UI thread where an exception is
-     * a test failure. If this is already the UI thread then it runs
-     * the code immediately.
-     *
-     * @see InstrumentationTestCase#runTestOnUiThread(Runnable)
-     * @see ActivityTestRule#runOnUiThread(Runnable)
-     * @param r The code to run in the UI thread
-     */
-    public void runOnUiThread(Runnable r) {
-        try {
-            if (isUiThread()) {
-                r.run();
-            } else {
-                if (mActivityTestRule != null) {
-                    mActivityTestRule.runOnUiThread(r);
-                } else {
-                    mTest.runTestOnUiThread(r);
-                }
-            }
-        } catch (Throwable t) {
-            Assert.fail("Unexpected error while running on UI thread: "
-                    + t.getMessage());
-        }
-    }
-
-    /**
      * Accessor for underlying WebView.
      * @return The WebView being wrapped by this class.
      */
@@ -918,129 +564,6 @@
         return mWebView;
     }
 
-    private<T> T getValue(ValueGetter<T> getter) {
-        runOnUiThread(getter);
-        return getter.getValue();
-    }
-
-    private abstract class ValueGetter<T> implements Runnable {
-        private T mValue;
-
-        @Override
-        public void run() {
-            mValue = capture();
-        }
-
-        protected abstract T capture();
-
-        public T getValue() {
-           return mValue;
-        }
-    }
-
-    /**
-     * Returns true if the current thread is the UI thread based on the
-     * Looper.
-     */
-    private static boolean isUiThread() {
-        return (Looper.myLooper() == Looper.getMainLooper());
-    }
-
-    /**
-     * @return Whether or not the load has finished.
-     */
-    private synchronized boolean isLoaded() {
-        return mLoaded && mNewPicture && mProgress == 100;
-    }
-
-    /**
-     * Makes a WebView call, waits for completion and then resets the
-     * load state in preparation for the next load call.
-     * @param call The call to make on the UI thread prior to waiting.
-     */
-    private void callAndWait(Runnable call) {
-        Assert.assertTrue("WebViewOnUiThread.load*AndWaitForCompletion calls "
-                + "may not be mixed with load* calls directly on WebView "
-                + "without calling waitForLoadCompletion after the load",
-                !isLoaded());
-        clearLoad(); // clear any extraneous signals from a previous load.
-        runOnUiThread(call);
-        waitForLoadCompletion();
-    }
-
-    /**
-     * Called whenever a load has been completed so that a subsequent call to
-     * waitForLoadCompletion doesn't return immediately.
-     */
-    synchronized private void clearLoad() {
-        mLoaded = false;
-        mNewPicture = false;
-        mProgress = 0;
-    }
-
-    /**
-     * Uses a polling mechanism, while pumping messages to check when the
-     * criteria is met.
-     */
-    private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) {
-        new PollingCheck(timeout) {
-            @Override
-            protected boolean check() {
-                pumpMessages();
-                try {
-                    return doneCriteria.call();
-                } catch (Exception e) {
-                    Assert.fail("Unexpected error while checking the criteria: "
-                            + e.getMessage());
-                    return true;
-                }
-            }
-        }.run();
-    }
-
-    /**
-     * Uses a wait/notify to check when the criteria is met.
-     */
-    private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) {
-        try {
-            long waitEnd = SystemClock.uptimeMillis() + timeout;
-            long timeRemaining = timeout;
-            while (!doneCriteria.call() && timeRemaining > 0) {
-                this.wait(timeRemaining);
-                timeRemaining = waitEnd - SystemClock.uptimeMillis();
-            }
-            Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call());
-        } catch (InterruptedException e) {
-            // We'll just drop out of the loop and fail
-        } catch (Exception e) {
-            Assert.fail("Unexpected error while checking the criteria: "
-                    + e.getMessage());
-        }
-    }
-
-    /**
-     * Pumps all currently-queued messages in the UI thread and then exits.
-     * This is useful to force processing while running tests in the UI thread.
-     */
-    private void pumpMessages() {
-        class ExitLoopException extends RuntimeException {
-        }
-
-        // Force loop to exit when processing this. Loop.quit() doesn't
-        // work because this is the main Loop.
-        mWebView.getHandler().post(new Runnable() {
-            @Override
-            public void run() {
-                throw new ExitLoopException(); // exit loop!
-            }
-        });
-        try {
-            // Pump messages until our message gets through.
-            Looper.loop();
-        } catch (ExitLoopException e) {
-        }
-    }
-
     /**
      * Set LayoutParams to MATCH_PARENT.
      *
@@ -1052,65 +575,4 @@
         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
         view.setLayoutParams(params);
     }
-
-    /**
-     * A WebChromeClient used to capture the onProgressChanged for use
-     * in waitFor functions. If a test must override the WebChromeClient,
-     * it can derive from this class or call onProgressChanged
-     * directly.
-     */
-    public static class WaitForProgressClient extends WebChromeClient {
-        private WebViewOnUiThread mOnUiThread;
-
-        public WaitForProgressClient(WebViewOnUiThread onUiThread) {
-            mOnUiThread = onUiThread;
-        }
-
-        @Override
-        public void onProgressChanged(WebView view, int newProgress) {
-            super.onProgressChanged(view, newProgress);
-            mOnUiThread.onProgressChanged(newProgress);
-        }
-    }
-
-    /**
-     * A WebViewClient that captures the onPageFinished for use in
-     * waitFor functions. Using initializeWebView sets the WaitForLoadedClient
-     * into the WebView. If a test needs to set a specific WebViewClient and
-     * needs the waitForCompletion capability then it should derive from
-     * WaitForLoadedClient or call WebViewOnUiThread.onPageFinished.
-     */
-    public static class WaitForLoadedClient extends WebViewClient {
-        private WebViewOnUiThread mOnUiThread;
-
-        public WaitForLoadedClient(WebViewOnUiThread onUiThread) {
-            mOnUiThread = onUiThread;
-        }
-
-        @Override
-        public void onPageFinished(WebView view, String url) {
-            super.onPageFinished(view, url);
-            mOnUiThread.onPageFinished();
-        }
-
-        @Override
-        public void onPageStarted(WebView view, String url, Bitmap favicon) {
-            super.onPageStarted(view, url, favicon);
-            mOnUiThread.onPageStarted();
-        }
-    }
-
-    /**
-     * A PictureListener that captures the onNewPicture for use in
-     * waitForLoadCompletion. Using initializeWebView sets the PictureListener
-     * into the WebView. If a test needs to set a specific PictureListener and
-     * needs the waitForCompletion capability then it should call
-     * WebViewOnUiThread.onNewPicture.
-     */
-    private class WaitForNewPicture implements PictureListener {
-        @Override
-        public void onNewPicture(WebView view, Picture picture) {
-            WebViewOnUiThread.this.onNewPicture();
-        }
-    }
 }
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
new file mode 100644
index 0000000..2b27b52
--- /dev/null
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
@@ -0,0 +1,439 @@
+/*
+ * 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.webkit.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebView.PictureListener;
+import android.webkit.WebViewClient;
+
+import androidx.annotation.CallSuper;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import junit.framework.Assert;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Utility class to simplify tests that need to load data into a WebView and wait for completion
+ * conditions.
+ *
+ * May be used from any thread.
+ */
+public class WebViewSyncLoader {
+    /**
+     * Set to true after onPageFinished is called.
+     */
+    private boolean mLoaded;
+
+    /**
+     * Set to true after onNewPicture is called. Reset when onPageStarted
+     * is called.
+     */
+    private boolean mNewPicture;
+
+    /**
+     * The progress, in percentage, of the page load. Valid values are between
+     * 0 and 100.
+     */
+    private int mProgress;
+
+    /**
+     * The WebView that calls will be made on.
+     */
+    private WebView mWebView;
+
+
+    public WebViewSyncLoader(WebView webView) {
+        init(webView, new WaitForLoadedClient(this), new WaitForProgressClient(this),
+                new WaitForNewPicture(this));
+    }
+
+    public WebViewSyncLoader(
+            WebView webView,
+            WaitForLoadedClient waitForLoadedClient,
+            WaitForProgressClient waitForProgressClient,
+            WaitForNewPicture waitForNewPicture) {
+        init(webView, waitForLoadedClient, waitForProgressClient, waitForNewPicture);
+    }
+
+    private void init(
+            final WebView webView,
+            final WaitForLoadedClient waitForLoadedClient,
+            final WaitForProgressClient waitForProgressClient,
+            final WaitForNewPicture waitForNewPicture) {
+        if (!isUiThread()) {
+            WebkitUtils.onMainThreadSync(() -> {
+                init(webView, waitForLoadedClient, waitForProgressClient, waitForNewPicture);
+            });
+            return;
+        }
+        mWebView = webView;
+        mWebView.setWebViewClient(waitForLoadedClient);
+        mWebView.setWebChromeClient(waitForProgressClient);
+        mWebView.setPictureListener(waitForNewPicture);
+    }
+
+    /**
+     * Detach listeners from this WebView, undoing the changes made to enable sync loading.
+     */
+    public void detach() {
+        if (!isUiThread()) {
+            WebkitUtils.onMainThreadSync(this::detach);
+            return;
+        }
+        mWebView.setWebChromeClient(null);
+        mWebView.setWebViewClient(null);
+        mWebView.setPictureListener(null);
+        mWebView = null;
+    }
+
+    /**
+     * Detach listeners, and destroy this webview.
+     */
+    public void destroy() {
+        if (!isUiThread()) {
+            WebkitUtils.onMainThreadSync(this::destroy);
+            return;
+        }
+        WebView webView = mWebView;
+        detach();
+        webView.clearHistory();
+        webView.clearCache(true);
+        webView.destroy();
+    }
+
+    /**
+     * Accessor for underlying WebView.
+     * @return The WebView being wrapped by this class.
+     */
+    public WebView getWebView() {
+        return mWebView;
+    }
+
+    /**
+     * Called from WaitForNewPicture, this is used to indicate that
+     * the page has been drawn.
+     */
+    public synchronized void onNewPicture() {
+        mNewPicture = true;
+        this.notifyAll();
+    }
+
+    /**
+     * Called from WaitForLoadedClient, this is used to clear the picture
+     * draw state so that draws before the URL begins loading don't count.
+     */
+    public synchronized void onPageStarted() {
+        mNewPicture = false; // Earlier paints won't count.
+    }
+
+    /**
+     * Called from WaitForLoadedClient, this is used to indicate that
+     * the page is loaded, but not drawn yet.
+     */
+    public synchronized void onPageFinished() {
+        mLoaded = true;
+        this.notifyAll();
+    }
+
+    /**
+     * Called from the WebChrome client, this sets the current progress
+     * for a page.
+     * @param progress The progress made so far between 0 and 100.
+     */
+    public synchronized void onProgressChanged(int progress) {
+        mProgress = progress;
+        this.notifyAll();
+    }
+
+    /**
+     * Calls {@link WebView#loadUrl} on the WebView and then waits for completion.
+     *
+     * <p>Test fails if the load timeout elapses.
+     */
+    public void loadUrlAndWaitForCompletion(final String url) {
+        callAndWait(() -> mWebView.loadUrl(url));
+    }
+
+    /**
+     * Calls {@link WebView#loadUrl(String,Map<String,String>)} on the WebView and waits for
+     * completion.
+     *
+     * <p>Test fails if the load timeout elapses.
+     */
+    public void loadUrlAndWaitForCompletion(final String url,
+            final Map<String, String> extraHeaders) {
+        callAndWait(() -> mWebView.loadUrl(url, extraHeaders));
+    }
+
+    /**
+     * Calls {@link WebView#postUrl(String,byte[])} on the WebView and then waits for completion.
+     *
+     * <p>Test fails if the load timeout elapses.
+     *
+     * @param url The URL to load.
+     * @param postData the data will be passed to "POST" request.
+     */
+    public void postUrlAndWaitForCompletion(final String url, final byte[] postData) {
+        callAndWait(() -> mWebView.postUrl(url, postData));
+    }
+
+    /**
+     * Calls {@link WebView#loadData(String,String,String)} on the WebView and then waits for
+     * completion.
+     *
+     * <p>Test fails if the load timeout elapses.
+     */
+    public void loadDataAndWaitForCompletion(final String data,
+            final String mimeType, final String encoding) {
+        callAndWait(() -> mWebView.loadData(data, mimeType, encoding));
+    }
+
+    /**
+     * Calls {@link WebView#loadDataWithBaseUrl(String,String,String,String,String)} on the WebView
+     * and then waits for completion.
+     *
+     * <p>Test fails if the load timeout elapses.
+     */
+    public void loadDataWithBaseURLAndWaitForCompletion(final String baseUrl,
+            final String data, final String mimeType, final String encoding,
+            final String historyUrl) {
+        callAndWait(
+                () -> mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl));
+    }
+
+    /**
+     * Reloads a page and waits for it to complete reloading. Use reload
+     * if it is a form resubmission and the onFormResubmission responds
+     * by telling WebView not to resubmit it.
+     */
+    public void reloadAndWaitForCompletion() {
+        callAndWait(() -> mWebView.reload());
+    }
+
+    /**
+     * Use this only when JavaScript causes a page load to wait for the
+     * page load to complete. Otherwise use loadUrlAndWaitForCompletion or
+     * similar functions.
+     */
+    public void waitForLoadCompletion() {
+        waitForCriteria(WebkitUtils.TEST_TIMEOUT_MS,
+                new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() {
+                        return isLoaded();
+                    }
+                });
+        clearLoad();
+    }
+
+    private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) {
+        if (isUiThread()) {
+            waitOnUiThread(timeout, doneCriteria);
+        } else {
+            waitOnTestThread(timeout, doneCriteria);
+        }
+    }
+
+    /**
+     * @return Whether or not the load has finished.
+     */
+    private synchronized boolean isLoaded() {
+        return mLoaded && mNewPicture && mProgress == 100;
+    }
+
+    /**
+     * Makes a WebView call, waits for completion and then resets the
+     * load state in preparation for the next load call.
+     *
+     * <p>This method may be called on the UI thread.
+     *
+     * @param call The call to make on the UI thread prior to waiting.
+     */
+    private void callAndWait(Runnable call) {
+        Assert.assertTrue("WebViewSyncLoader.load*AndWaitForCompletion calls "
+                + "may not be mixed with load* calls directly on WebView "
+                + "without calling waitForLoadCompletion after the load",
+                !isLoaded());
+        clearLoad(); // clear any extraneous signals from a previous load.
+        if (isUiThread()) {
+            call.run();
+        } else {
+            WebkitUtils.onMainThread(call);
+        }
+        waitForLoadCompletion();
+    }
+
+    /**
+     * Called whenever a load has been completed so that a subsequent call to
+     * waitForLoadCompletion doesn't return immediately.
+     */
+    private synchronized void clearLoad() {
+        mLoaded = false;
+        mNewPicture = false;
+        mProgress = 0;
+    }
+
+    /**
+     * Uses a polling mechanism, while pumping messages to check when the
+     * criteria is met.
+     */
+    private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) {
+        new PollingCheck(timeout) {
+            @Override
+            protected boolean check() {
+                pumpMessages();
+                try {
+                    return doneCriteria.call();
+                } catch (Exception e) {
+                    Assert.fail("Unexpected error while checking the criteria: "
+                            + e.getMessage());
+                    return true;
+                }
+            }
+        }.run();
+    }
+
+    /**
+     * Uses a wait/notify to check when the criteria is met.
+     */
+    private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) {
+        try {
+            long waitEnd = SystemClock.uptimeMillis() + timeout;
+            long timeRemaining = timeout;
+            while (!doneCriteria.call() && timeRemaining > 0) {
+                this.wait(timeRemaining);
+                timeRemaining = waitEnd - SystemClock.uptimeMillis();
+            }
+            Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call());
+        } catch (InterruptedException e) {
+            // We'll just drop out of the loop and fail
+        } catch (Exception e) {
+            Assert.fail("Unexpected error while checking the criteria: "
+                    + e.getMessage());
+        }
+    }
+
+    /**
+     * Pumps all currently-queued messages in the UI thread and then exits.
+     * This is useful to force processing while running tests in the UI thread.
+     */
+    private void pumpMessages() {
+        class ExitLoopException extends RuntimeException {
+        }
+
+        // Force loop to exit when processing this. Loop.quit() doesn't
+        // work because this is the main Loop.
+        mWebView.getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                throw new ExitLoopException(); // exit loop!
+            }
+        });
+        try {
+            // Pump messages until our message gets through.
+            Looper.loop();
+        } catch (ExitLoopException e) {
+        }
+    }
+
+    /**
+     * Returns true if the current thread is the UI thread based on the
+     * Looper.
+     */
+    private static boolean isUiThread() {
+        return (Looper.myLooper() == Looper.getMainLooper());
+    }
+
+    /**
+     * A WebChromeClient used to capture the onProgressChanged for use
+     * in waitFor functions. If a test must override the WebChromeClient,
+     * it can derive from this class or call onProgressChanged
+     * directly.
+     */
+    public static class WaitForProgressClient extends WebChromeClient {
+        private WebViewSyncLoader mWebViewSyncLoader;
+
+        public WaitForProgressClient(WebViewSyncLoader webViewSyncLoader) {
+            mWebViewSyncLoader = webViewSyncLoader;
+        }
+
+        @Override
+        @CallSuper
+        public void onProgressChanged(WebView view, int newProgress) {
+            super.onProgressChanged(view, newProgress);
+            mWebViewSyncLoader.onProgressChanged(newProgress);
+        }
+    }
+
+    /**
+     * A WebViewClient that captures the onPageFinished for use in
+     * waitFor functions. Using initializeWebView sets the WaitForLoadedClient
+     * into the WebView. If a test needs to set a specific WebViewClient and
+     * needs the waitForCompletion capability then it should derive from
+     * WaitForLoadedClient or call WebViewSyncLoader.onPageFinished.
+     */
+    public static class WaitForLoadedClient extends WebViewClient {
+        private WebViewSyncLoader mWebViewSyncLoader;
+
+        public WaitForLoadedClient(WebViewSyncLoader webViewSyncLoader) {
+            mWebViewSyncLoader = webViewSyncLoader;
+        }
+
+        @Override
+        @CallSuper
+        public void onPageFinished(WebView view, String url) {
+            super.onPageFinished(view, url);
+            mWebViewSyncLoader.onPageFinished();
+        }
+
+        @Override
+        @CallSuper
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            super.onPageStarted(view, url, favicon);
+            mWebViewSyncLoader.onPageStarted();
+        }
+    }
+
+    /**
+     * A PictureListener that captures the onNewPicture for use in
+     * waitForLoadCompletion. Using initializeWebView sets the PictureListener
+     * into the WebView. If a test needs to set a specific PictureListener and
+     * needs the waitForCompletion capability then it should call
+     * WebViewSyncLoader.onNewPicture.
+     */
+    public static class WaitForNewPicture implements PictureListener {
+        private WebViewSyncLoader mWebViewSyncLoader;
+
+        public WaitForNewPicture(WebViewSyncLoader webViewSyncLoader) {
+            mWebViewSyncLoader = webViewSyncLoader;
+        }
+
+        @Override
+        @CallSuper
+        public void onNewPicture(WebView view, Picture picture) {
+            mWebViewSyncLoader.onNewPicture();
+        }
+    }
+}
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebkitUtils.java b/libs/deviceutillegacy/src/android/webkit/cts/WebkitUtils.java
new file mode 100644
index 0000000..eb9bdaa
--- /dev/null
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebkitUtils.java
@@ -0,0 +1,177 @@
+/*
+ * 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.webkit.cts;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper methods for common webkit test tasks.
+ *
+ * <p>
+ * This should remain functionally equivalent to androidx.webkit.WebkitUtils.
+ * Modifications to this class should be reflected in that class as necessary. See
+ * http://go/modifying-webview-cts.
+ */
+public final class WebkitUtils {
+
+    /**
+     * Arbitrary timeout for tests. This is intended to be used with {@link TimeUnit#MILLISECONDS}
+     * so that this can represent 20 seconds.
+     *
+     * <p class=note><b>Note:</b> only use this timeout value for the unexpected case, not for the
+     * correct case, as this exceeds the time recommendation for {@link
+     * androidx.test.filters.MediumTest}.
+     */
+    public static final long TEST_TIMEOUT_MS = 20000L; // 20s.
+
+    // A handler for the main thread.
+    private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
+
+    /**
+     * Executes a callable asynchronously on the main thread, returning a future for the result.
+     *
+     * @param callable the {@link Callable} to execute.
+     * @return a {@link Future} representing the result of {@code callable}.
+     */
+    public static <T> Future<T> onMainThread(final Callable<T> callable)  {
+        final SettableFuture<T> future = SettableFuture.create();
+        sMainHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    future.set(callable.call());
+                } catch (Throwable t) {
+                    future.setException(t);
+                }
+            }
+        });
+        return future;
+    }
+
+    /**
+     * Executes a runnable asynchronously on the main thread.
+     *
+     * @param runnable the {@link Runnable} to execute.
+     */
+    public static void onMainThread(final Runnable runnable)  {
+        sMainHandler.post(runnable);
+    }
+
+    /**
+     * Executes a callable synchronously on the main thread, returning its result. This rethrows any
+     * exceptions on the thread this is called from. This means callers may use {@link
+     * org.junit.Assert} methods within the {@link Callable} if they invoke this method from the
+     * instrumentation thread.
+     *
+     * <p class="note"><b>Note:</b> this should not be called from the UI thread.
+     *
+     * @param callable the {@link Callable} to execute.
+     * @return the result of the {@link Callable}.
+     */
+    public static <T> T onMainThreadSync(final Callable<T> callable) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new IllegalStateException("This cannot be called from the UI thread.");
+        }
+        return waitForFuture(onMainThread(callable));
+    }
+
+    /**
+     * Executes a {@link Runnable} synchronously on the main thread. This is similar to {@link
+     * android.app.Instrumentation#runOnMainSync(Runnable)}, with the main difference that this
+     * re-throws exceptions on the calling thread. This is useful if {@code runnable} contains any
+     * {@link org.junit.Assert} methods, or otherwise throws an Exception.
+     *
+     * <p class="note"><b>Note:</b> this should not be called from the UI thread.
+     *
+     * @param runnable the {@link Runnable} to execute.
+     */
+    public static void onMainThreadSync(final Runnable runnable) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new IllegalStateException("This cannot be called from the UI thread.");
+        }
+        final SettableFuture<Void> exceptionPropagatingFuture = SettableFuture.create();
+        onMainThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    runnable.run();
+                    exceptionPropagatingFuture.set(null);
+                } catch (Throwable t) {
+                    exceptionPropagatingFuture.setException(t);
+                }
+            }
+        });
+        waitForFuture(exceptionPropagatingFuture);
+    }
+
+
+    /**
+     * Waits for {@code future} and returns its value (or times out).
+     */
+    public static <T> T waitForFuture(Future<T> future) {
+        try {
+            return future.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException e) {
+            // Try to throw the cause itself, to avoid unnecessarily wrapping an unchecked
+            // throwable.
+            Throwable cause = e.getCause();
+            if (cause instanceof Error) throw (Error) cause;
+            if (cause instanceof RuntimeException) throw (RuntimeException) cause;
+            throw new RuntimeException(cause);
+        } catch (InterruptedException | TimeoutException e) {
+            // Don't handle InterruptedException specially, since it indicates that a different
+            // Thread was interrupted, not this one.
+            throw new RuntimeException(e.getCause());
+        }
+    }
+
+    /**
+     * Takes an element out of the {@link BlockingQueue} (or times out).
+     */
+    public static <T> T waitForNextQueueElement(BlockingQueue<T> queue) {
+        try {
+            T value = queue.poll(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            if (value == null) {
+                // {@code null} is the special value which means {@link BlockingQueue#poll} has
+                // timed out (also: there's no risk for collision with real values, because
+                // BlockingQueue does not allow null entries). Instead of returning this special
+                // value, let's throw a proper TimeoutException to stay consistent with {@link
+                // #waitForFuture}.
+                throw new TimeoutException(
+                        "Timeout while trying to take next entry from BlockingQueue");
+            }
+            return value;
+        } catch (TimeoutException | InterruptedException e) {
+            // Don't handle InterruptedException specially, since it indicates that a different
+            // Thread was interrupted, not this one.
+            throw new RuntimeException(e.getCause());
+        }
+    }
+
+    // Do not instantiate this class.
+    private WebkitUtils() {}
+}
diff --git a/libs/input/src/com/android/input/HidDevice.java b/libs/input/src/com/android/input/HidDevice.java
index f28edb5..173531a 100644
--- a/libs/input/src/com/android/input/HidDevice.java
+++ b/libs/input/src/com/android/input/HidDevice.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.input;
 
+import static android.os.FileUtils.closeQuietly;
+
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.hardware.input.InputManager;
@@ -127,17 +129,6 @@
         writeHidCommands(json.toString().getBytes());
     }
 
-    private static void closeQuietly(AutoCloseable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
     /**
      * Close the device, which would cause the associated input device to unregister.
      */
diff --git a/libs/input/src/com/android/input/HidJsonParser.java b/libs/input/src/com/android/input/HidJsonParser.java
index 6cd0528..be5f6bf 100644
--- a/libs/input/src/com/android/input/HidJsonParser.java
+++ b/libs/input/src/com/android/input/HidJsonParser.java
@@ -198,11 +198,7 @@
 
     private KeyEvent parseKeyEvent(JSONObject entry) throws JSONException {
         int action = keyActionFromString(entry.getString("action"));
-        String keyCodeStr = entry.getString("keycode");
-        if (!keyCodeStr.startsWith("KEYCODE_")) {
-            keyCodeStr = "KEYCODE_" + keyCodeStr;
-        }
-        int keyCode = KeyEvent.keyCodeFromString(keyCodeStr);
+        int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
         return new KeyEvent(action, keyCode);
     }
 
diff --git a/libs/json/src/com/android/json/stream/JsonWriter.java b/libs/json/src/com/android/json/stream/JsonWriter.java
index 66b21f0..851f26f 100644
--- a/libs/json/src/com/android/json/stream/JsonWriter.java
+++ b/libs/json/src/com/android/json/stream/JsonWriter.java
@@ -23,30 +23,31 @@
 import java.util.List;
 
 /**
- * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
- * encoded value to a stream, one token at a time. The stream includes both
- * literal values (strings, numbers, booleans and nulls) as well as the begin
- * and end delimiters of objects and arrays.
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) encoded value to a
+ * stream, one token at a time. The stream includes both literal values (strings, numbers, booleans
+ * and nulls) as well as the begin and end delimiters of objects and arrays.
  *
  * <h3>Encoding JSON</h3>
- * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
- * document must contain one top-level array or object. Call methods on the
- * writer as you walk the structure's contents, nesting arrays and objects as
- * necessary:
+ *
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON document must contain one
+ * top-level array or object. Call methods on the writer as you walk the structure's contents,
+ * nesting arrays and objects as necessary:
+ *
  * <ul>
- *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
- *       Write each of the array's elements with the appropriate {@link #value}
- *       methods or by nesting other arrays and objects. Finally close the array
- *       using {@link #endArray()}.
- *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
- *       Write each of the object's properties by alternating calls to
- *       {@link #name} with the property's value. Write property values with the
- *       appropriate {@link #value} method or by nesting other objects or arrays.
- *       Finally close the object using {@link #endObject()}.
+ *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. Write each of the
+ *       array's elements with the appropriate {@link #value} methods or by nesting other arrays and
+ *       objects. Finally close the array using {@link #endArray()}.
+ *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}. Write each of the
+ *       object's properties by alternating calls to {@link #name} with the property's value. Write
+ *       property values with the appropriate {@link #value} method or by nesting other objects or
+ *       arrays. Finally close the object using {@link #endObject()}.
  * </ul>
  *
  * <h3>Example</h3>
- * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ *
+ * Suppose we'd like to encode a stream of messages such as the following:
+ *
+ * <pre>{@code
  * [
  *   {
  *     "id": 912345678901,
@@ -66,77 +67,81 @@
  *       "followers_count": 2
  *     }
  *   }
- * ]}</pre>
- * This code encodes the above structure: <pre>   {@code
- *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
- *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
- *     writer.setIndent("  ");
- *     writeMessagesArray(writer, messages);
- *     writer.close();
+ * ]
+ * }</pre>
+ *
+ * This code encodes the above structure:
+ *
+ * <pre>{@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ *   JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *   writer.setIndent("  ");
+ *   writeMessagesArray(writer, messages);
+ *   writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ *   writer.beginArray();
+ *   for (Message message : messages) {
+ *     writeMessage(writer, message);
  *   }
+ *   writer.endArray();
+ * }
  *
- *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
- *     writer.beginArray();
- *     for (Message message : messages) {
- *       writeMessage(writer, message);
- *     }
- *     writer.endArray();
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *   writer.beginObject();
+ *   writer.name("id").value(message.getId());
+ *   writer.name("text").value(message.getText());
+ *   if (message.getGeo() != null) {
+ *     writer.name("geo");
+ *     writeDoublesArray(writer, message.getGeo());
+ *   } else {
+ *     writer.name("geo").nullValue();
  *   }
+ *   writer.name("user");
+ *   writeUser(writer, message.getUser());
+ *   writer.endObject();
+ * }
  *
- *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
- *     writer.beginObject();
- *     writer.name("id").value(message.getId());
- *     writer.name("text").value(message.getText());
- *     if (message.getGeo() != null) {
- *       writer.name("geo");
- *       writeDoublesArray(writer, message.getGeo());
- *     } else {
- *       writer.name("geo").nullValue();
- *     }
- *     writer.name("user");
- *     writeUser(writer, message.getUser());
- *     writer.endObject();
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ *   writer.beginObject();
+ *   writer.name("name").value(user.getName());
+ *   writer.name("followers_count").value(user.getFollowersCount());
+ *   writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ *   writer.beginArray();
+ *   for (Double value : doubles) {
+ *     writer.value(value);
  *   }
+ *   writer.endArray();
+ * }
+ * }</pre>
  *
- *   public void writeUser(JsonWriter writer, User user) throws IOException {
- *     writer.beginObject();
- *     writer.name("name").value(user.getName());
- *     writer.name("followers_count").value(user.getFollowersCount());
- *     writer.endObject();
- *   }
- *
- *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
- *     writer.beginArray();
- *     for (Double value : doubles) {
- *       writer.value(value);
- *     }
- *     writer.endArray();
- *   }}</pre>
- *
- * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
- * Instances of this class are not thread safe. Calls that would result in a
- * malformed JSON string will fail with an {@link IllegalStateException}.
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream. Instances of this class are
+ * not thread safe. Calls that would result in a malformed JSON string will fail with an {@link
+ * IllegalStateException}.
  */
-public final class JsonWriter implements Closeable {
+public class JsonWriter implements Closeable {
 
     /** The output data, containing at most one top-level array or object. */
-    private final Writer out;
+    protected final Writer mOut;
 
-    private final List<JsonScope> stack = new ArrayList<JsonScope>();
+    protected final List<JsonScope> mStack = new ArrayList<JsonScope>();
+
     {
-        stack.add(JsonScope.EMPTY_DOCUMENT);
+        mStack.add(JsonScope.EMPTY_DOCUMENT);
     }
 
     /**
-     * A string containing a full set of spaces for a single level of
-     * indentation, or null for no pretty printing.
+     * A string containing a full set of spaces for a single level of indentation, or null for no
+     * pretty printing.
      */
-    private String indent;
+    private String mIndent;
 
-    /**
-     * The name/value separator; either ":" or ": ".
-     */
-    private String separator = ":";
+    /** The name/value separator; either ":" or ": ". */
+    protected String mSeparator = ":";
 
     /**
      * Creates a new instance that writes a JSON-encoded stream to {@code out}.
@@ -147,7 +152,7 @@
         if (out == null) {
             throw new NullPointerException("out == null");
         }
-        this.out = out;
+        this.mOut = out;
     }
 
     /**
@@ -160,11 +165,11 @@
      */
     public void setIndent(String indent) {
         if (indent.isEmpty()) {
-            this.indent = null;
-            this.separator = ":";
+            this.mIndent = null;
+            this.mSeparator = ":";
         } else {
-            this.indent = indent;
-            this.separator = ": ";
+            this.mIndent = indent;
+            this.mSeparator = ": ";
         }
     }
 
@@ -212,8 +217,8 @@
      */
     private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
         beforeValue(true);
-        stack.add(empty);
-        out.write(openBracket);
+        mStack.add(empty);
+        mOut.write(openBracket);
         return this;
     }
 
@@ -225,29 +230,25 @@
             throws IOException {
         JsonScope context = peek();
         if (context != nonempty && context != empty) {
-            throw new IllegalStateException("Nesting problem: " + stack);
+            throw new IllegalStateException("Nesting problem: " + mStack);
         }
 
-        stack.remove(stack.size() - 1);
+        mStack.remove(mStack.size() - 1);
         if (context == nonempty) {
             newline();
         }
-        out.write(closeBracket);
+        mOut.write(closeBracket);
         return this;
     }
 
-    /**
-     * Returns the value on the top of the stack.
-     */
-    private JsonScope peek() {
-        return stack.get(stack.size() - 1);
+    /** Returns the value on the top of the stack. */
+    protected JsonScope peek() {
+        return mStack.get(mStack.size() - 1);
     }
 
-    /**
-     * Replace the value on the top of the stack with the given value.
-     */
-    private void replaceTop(JsonScope topOfStack) {
-        stack.set(stack.size() - 1, topOfStack);
+    /** Replace the value on the top of the stack with the given value. */
+    protected void replaceTop(JsonScope topOfStack) {
+        mStack.set(mStack.size() - 1, topOfStack);
     }
 
     /**
@@ -287,7 +288,7 @@
      */
     public JsonWriter nullValue() throws IOException {
         beforeValue(false);
-        out.write("null");
+        mOut.write("null");
         return this;
     }
 
@@ -298,7 +299,7 @@
      */
     public JsonWriter value(boolean value) throws IOException {
         beforeValue(false);
-        out.write(value ? "true" : "false");
+        mOut.write(value ? "true" : "false");
         return this;
     }
 
@@ -314,7 +315,7 @@
             throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
         }
         beforeValue(false);
-        out.append(Double.toString(value));
+        mOut.append(Double.toString(value));
         return this;
     }
 
@@ -325,7 +326,7 @@
      */
     public JsonWriter value(long value) throws IOException {
         beforeValue(false);
-        out.write(Long.toString(value));
+        mOut.write(Long.toString(value));
         return this;
     }
 
@@ -334,7 +335,7 @@
      * and flushes that writer.
      */
     public void flush() throws IOException {
-        out.flush();
+        mOut.flush();
     }
 
     /**
@@ -343,7 +344,7 @@
      * @throws IOException if the JSON document is incomplete.
      */
     public void close() throws IOException {
-        out.close();
+        mOut.close();
 
         if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
             throw new IOException("Incomplete document");
@@ -351,7 +352,7 @@
     }
 
     private void string(String value) throws IOException {
-        out.write("\"");
+        mOut.write("\"");
         for (int i = 0, length = value.length(); i < length; i++) {
             char c = value.charAt(i);
 
@@ -365,78 +366,77 @@
                 case '"':
                 case '\\':
                 case '/':
-                    out.write('\\');
-                    out.write(c);
+                    mOut.write('\\');
+                    mOut.write(c);
                     break;
 
                 case '\t':
-                    out.write("\\t");
+                    mOut.write("\\t");
                     break;
 
                 case '\b':
-                    out.write("\\b");
+                    mOut.write("\\b");
                     break;
 
                 case '\n':
-                    out.write("\\n");
+                    mOut.write("\\n");
                     break;
 
                 case '\r':
-                    out.write("\\r");
+                    mOut.write("\\r");
                     break;
 
                 case '\f':
-                    out.write("\\f");
+                    mOut.write("\\f");
                     break;
 
                 default:
                     if (c <= 0x1F) {
-                        out.write(String.format("\\u%04x", (int) c));
+                        mOut.write(String.format("\\u%04x", (int) c));
                     } else {
-                        out.write(c);
+                        mOut.write(c);
                     }
                     break;
             }
 
         }
-        out.write("\"");
+        mOut.write("\"");
     }
 
-    private void newline() throws IOException {
-        if (indent == null) {
+    protected void newline() throws IOException {
+        if (mIndent == null) {
             return;
         }
 
-        out.write("\n");
-        for (int i = 1; i < stack.size(); i++) {
-            out.write(indent);
+        mOut.write("\n");
+        for (int i = 1; i < mStack.size(); i++) {
+            mOut.write(mIndent);
         }
     }
 
     /**
-     * Inserts any necessary separators and whitespace before a name. Also
-     * adjusts the stack to expect the name's value.
+     * Inserts any necessary separators and whitespace before a name. Also adjusts the stack to
+     * expect the name's value.
      */
-    private void beforeName() throws IOException {
+    protected void beforeName() throws IOException {
         JsonScope context = peek();
         if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
-            out.write(',');
+            mOut.write(',');
         } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
-            throw new IllegalStateException("Nesting problem: " + stack);
+            throw new IllegalStateException("Nesting problem: " + mStack);
         }
         newline();
         replaceTop(JsonScope.DANGLING_NAME);
     }
 
     /**
-     * Inserts any necessary separators and whitespace before a literal value,
-     * inline array, or inline object. Also adjusts the stack to expect either a
-     * closing bracket or another element.
+     * Inserts any necessary separators and whitespace before a literal value, inline array, or
+     * inline object. Also adjusts the stack to expect either a closing bracket or another element.
      *
-     * @param root true if the value is a new array or object, the two values
-     *     permitted as top-level elements.
+     * @param root true if the value is a new array or object, the two values permitted as top-level
+     *     elements.
      */
-    private void beforeValue(boolean root) throws IOException {
+    protected void beforeValue(boolean root) throws IOException {
         switch (peek()) {
             case EMPTY_DOCUMENT: // first in document
                 if (!root) {
@@ -452,12 +452,12 @@
                 break;
 
             case NONEMPTY_ARRAY: // another in array
-                out.append(',');
+                mOut.append(',');
                 newline();
                 break;
 
             case DANGLING_NAME: // value for name
-                out.append(separator);
+                mOut.append(mSeparator);
                 replaceTop(JsonScope.NONEMPTY_OBJECT);
                 break;
 
@@ -466,7 +466,7 @@
                         "JSON must have only one top-level value.");
 
             default:
-                throw new IllegalStateException("Nesting problem: " + stack);
+                throw new IllegalStateException("Nesting problem: " + mStack);
         }
     }
 }
diff --git a/libs/json/src/com/android/json/stream/NewlineDelimitedJsonWriter.java b/libs/json/src/com/android/json/stream/NewlineDelimitedJsonWriter.java
new file mode 100644
index 0000000..7bb2dd3
--- /dev/null
+++ b/libs/json/src/com/android/json/stream/NewlineDelimitedJsonWriter.java
@@ -0,0 +1,194 @@
+/*
+ * 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.json.stream;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) encoded value to a
+ * stream, one token at a time. The stream includes both literal values (strings, numbers, booleans
+ * and nulls) as well as the begin and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ *
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON document must contain one
+ * top-level array or object. Call methods on the writer as you walk the structure's contents,
+ * nesting arrays and objects as necessary:
+ *
+ * <ul>
+ *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. Write each of the
+ *       array's elements with the appropriate {@link #value} methods or by nesting other arrays and
+ *       objects. Finally close the array using {@link #endArray()}.
+ *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}. Write each of the
+ *       object's properties by alternating calls to {@link #name} with the property's value. Write
+ *       property values with the appropriate {@link #value} method or by nesting other objects or
+ *       arrays. Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ *
+ * Suppose we'd like to encode a stream of messages such as the following:
+ *
+ * <pre>{@code
+ * [
+ *   {
+ *     "id": 912345678901,
+ *     "text": "How do I write JSON on Android?",
+ *     "geo": null,
+ *     "user": {
+ *       "name": "android_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     "id": 912345678902,
+ *     "text": "@android_newb just use android.util.JsonWriter!",
+ *     "geo": [50.454722, -104.606667],
+ *     "user": {
+ *       "name": "jesse",
+ *       "followers_count": 2
+ *     }
+ *   }
+ * ]
+ * }</pre>
+ *
+ * This code encodes the above structure:
+ *
+ * <pre>{@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ *   JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *   writer.setIndent("  ");
+ *   writeMessagesArray(writer, messages);
+ *   writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ *   writer.beginArray();
+ *   for (Message message : messages) {
+ *     writeMessage(writer, message);
+ *   }
+ *   writer.endArray();
+ * }
+ *
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *   writer.beginObject();
+ *   writer.name("id").value(message.getId());
+ *   writer.name("text").value(message.getText());
+ *   if (message.getGeo() != null) {
+ *     writer.name("geo");
+ *     writeDoublesArray(writer, message.getGeo());
+ *   } else {
+ *     writer.name("geo").nullValue();
+ *   }
+ *   writer.name("user");
+ *   writeUser(writer, message.getUser());
+ *   writer.endObject();
+ * }
+ *
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ *   writer.beginObject();
+ *   writer.name("name").value(user.getName());
+ *   writer.name("followers_count").value(user.getFollowersCount());
+ *   writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ *   writer.beginArray();
+ *   for (Double value : doubles) {
+ *     writer.value(value);
+ *   }
+ *   writer.endArray();
+ * }
+ * }</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream. Instances of this class are
+ * not thread safe. Calls that would result in a malformed JSON string will fail with an {@link
+ * IllegalStateException}.
+ */
+public class NewlineDelimitedJsonWriter extends JsonWriter {
+    private static final String NEW_LINE = "\n";
+    /**
+     * Creates a new instance that writes a JSON-encoded stream to {@code out}. For best
+     * performance, ensure {@link Writer} is buffered; wrapping in {@link java.io.BufferedWriter
+     * BufferedWriter} if necessary.
+     */
+    public NewlineDelimitedJsonWriter(Writer out) {
+        super(out);
+    }
+
+    /**
+     * Sets the indentation string is no opt for newline-delimited JSON
+     *
+     * @param indent a string containing only whitespace.
+     */
+    @Override
+    public void setIndent(String indent) {
+        // no opt on Indent
+        super.setIndent(null);
+    }
+
+    /** Sets newline-delimited for a JSON object. */
+    public void newlineDelimited() throws IOException {
+        mOut.write(NEW_LINE);
+    }
+
+    /**
+     * Flushes and closes this writer and the underlying {@link Writer}.
+     *
+     * @throws IOException if the JSON document is incomplete.
+     */
+    public void close() throws IOException {
+        mOut.close();
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a literal value, inline array, or
+     * inline object. Also adjusts the stack to expect either a closing bracket or another element.
+     *
+     * @param root true if the value is a new array or object, the two values permitted as top-level
+     *     elements.
+     */
+    @Override
+    protected void beforeValue(boolean root) throws IOException {
+        switch (peek()) {
+            case EMPTY_DOCUMENT: // first in document
+                if (!root) {
+                    throw new IllegalStateException("JSON must start with an array or an object.");
+                }
+                break;
+
+            case EMPTY_ARRAY: // first in array
+                replaceTop(JsonScope.NONEMPTY_ARRAY);
+                newline();
+                break;
+
+            case NONEMPTY_ARRAY: // another in array
+                mOut.append(',');
+                newline();
+                break;
+
+            case DANGLING_NAME: // value for name
+                mOut.append(mSeparator);
+                replaceTop(JsonScope.NONEMPTY_OBJECT);
+                break;
+
+            default:
+                throw new IllegalStateException("Nesting problem: " + mStack);
+        }
+    }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 36acd82..ecd9792 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -24,6 +24,7 @@
 
 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
+import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,6 +37,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
+import android.util.LongArray;
 
 import com.android.compatibility.common.util.AppStandbyUtils;
 
@@ -59,49 +61,93 @@
     private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
     private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
 
-    private static final long DEFAULT_WAIT = 4_000;
+    private static final long DEFAULT_WAIT = 2_000;
     private static final long POLL_INTERVAL = 200;
 
     // Tweaked alarm manager constants to facilitate testing
-    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 15_000;
-    private static final long MIN_FUTURITY = 2_000;
-    private static final long[] APP_STANDBY_DELAYS = {0, 10_000, 20_000, 30_000, 600_000};
+    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
+    private static final long MIN_FUTURITY = 1_000;
+
+    // Not touching ACTIVE and RARE parameters for this test
+    private static final int WORKING_INDEX = 0;
+    private static final int FREQUENT_INDEX = 1;
+    private static final int RARE_INDEX = 2;
     private static final String[] APP_BUCKET_TAGS = {
-            "active",
             "working_set",
             "frequent",
             "rare",
-            "never"
     };
-    private static final String[] APP_BUCKET_KEYS = {
-            "standby_active_delay",
+    private static final String[] APP_BUCKET_DELAY_KEYS = {
             "standby_working_delay",
             "standby_frequent_delay",
             "standby_rare_delay",
-            "standby_never_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",
+            "standby_frequent_quota",
+            "standby_rare_quota",
+    };
+    private static final int[] APP_STANDBY_QUOTAS = {
+            5,  // Working set
+            3,  // Frequent
+            1,  // Rare
+    };
+
+    // Settings common for all tests
+    private static final String COMMON_SETTINGS;
+
+    static {
+        final StringBuilder settings = new StringBuilder();
+        settings.append("min_futurity=");
+        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++) {
+            settings.append(",");
+            settings.append(APP_BUCKET_QUOTA_KEYS[i]);
+            settings.append("=");
+            settings.append(APP_STANDBY_QUOTAS[i]);
+        }
+        COMMON_SETTINGS = settings.toString();
+    }
 
     // Save the state before running tests to restore it after we finish testing.
     private static boolean sOrigAppStandbyEnabled;
+    // Test app's alarm history to help predict when a subsequent alarm is going to get deferred.
+    private static TestAlarmHistory sAlarmHistory;
 
     private Context mContext;
     private ComponentName mAlarmScheduler;
     private UiDevice mUiDevice;
     private AtomicInteger mAlarmCount;
-    private volatile long mLastAlarmTime;
 
     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mAlarmCount.getAndAdd(intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1));
-            mLastAlarmTime = SystemClock.elapsedRealtime();
-            Log.d(TAG, "No. of expirations: " + mAlarmCount
-                    + " elapsed: " + SystemClock.elapsedRealtime());
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            sAlarmHistory.addTime(nowElapsed);
+            Log.d(TAG, "No. of expirations: " + mAlarmCount + " elapsed: " + nowElapsed);
         }
     };
 
     @BeforeClass
     public static void setUpTests() throws Exception {
+        sAlarmHistory = new TestAlarmHistory();
         sOrigAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabledAtRuntime();
         if (!sOrigAppStandbyEnabled) {
             AppStandbyUtils.setAppStandbyEnabledAtRuntime(true);
@@ -117,16 +163,12 @@
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
         mAlarmCount = new AtomicInteger(0);
-        updateAlarmManagerConstants();
+        updateAlarmManagerConstants(true);
         setBatteryCharging(false);
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
         mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
-        setAppStandbyBucket("active");
-        scheduleAlarm(SystemClock.elapsedRealtime(), false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm not sent when app in active", waitForAlarms(1));
     }
 
     private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
@@ -140,50 +182,99 @@
         mContext.sendBroadcast(setAlarmIntent);
     }
 
+    private void scheduleAlarmClock(long triggerRTC) {
+        AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);
+
+        final Intent setAlarmClockIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM_CLOCK);
+        setAlarmClockIntent.setComponent(mAlarmScheduler);
+        setAlarmClockIntent.putExtra(TestAlarmScheduler.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
+        mContext.sendBroadcast(setAlarmClockIntent);
+    }
+
+
     private void testBucketDelay(int bucketIndex) throws Exception {
-        setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
-        final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        final long minTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[bucketIndex];
-        scheduleAlarm(triggerTime, false, 0);
+        setAppStandbyBucket("active");
+        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
+        scheduleAlarm(firstTrigger, false, 0);
         Thread.sleep(MIN_FUTURITY);
-        if (triggerTime + DEFAULT_WAIT < minTriggerTime) {
+        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",
-                    waitForAlarms(1));
-            Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
+                    waitForAlarm());
         }
+        Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
         assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
-                waitForAlarms(1));
+                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 {
-        testBucketDelay(1);
+        updateAlarmManagerConstants(false);
+        testBucketDelay(WORKING_INDEX);
     }
 
     @Test
     public void testFrequentDelay() throws Exception {
-        testBucketDelay(2);
+        updateAlarmManagerConstants(false);
+        testBucketDelay(FREQUENT_INDEX);
     }
 
     @Test
     public void testRareDelay() throws Exception {
-        testBucketDelay(3);
+        updateAlarmManagerConstants(false);
+        testBucketDelay(RARE_INDEX);
+    }
+
+    @Test
+    public void testNeverDelay() throws Exception {
+        updateAlarmManagerConstants(false);
+        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 {
-        setAppStandbyBucket(APP_BUCKET_TAGS[2]);
+        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 = mLastAlarmTime + APP_STANDBY_DELAYS[1];
+        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", waitForAlarms(1));
-        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
+        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",
-                waitForAlarms(1));
+                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.
@@ -192,61 +283,148 @@
      */
     @Test
     public void testBucketUpgradeToNoDelay() throws Exception {
-        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
-        final long triggerTime1 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        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",
-                waitForAlarms(1));
-        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
+                waitForAlarm());
+        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
         assertTrue("The alarm did not go off when app bucket upgraded to working_set",
-                waitForAlarms(1));
+                waitForAlarm());
 
         // Once more
-        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
-        final long triggerTime2 = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
+        final long triggerTime2 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
         scheduleAlarm(triggerTime2, false, 0);
-        setAppStandbyBucket(APP_BUCKET_TAGS[0]);
+        setAppStandbyBucket("active");
         Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
         assertTrue("The alarm did not go off as scheduled when the app was in active",
-                waitForAlarms(1));
+                waitForAlarm());
+    }
+
+    public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
+        setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
+        final int quota = APP_STANDBY_QUOTAS[bucketIndex];
+
+        long startElapsed = SystemClock.elapsedRealtime();
+        final long freshWindowPoint = sAlarmHistory.getLast(1) + APP_STANDBY_WINDOW + 1;
+        if (freshWindowPoint > startElapsed) {
+            Thread.sleep(freshWindowPoint - startElapsed);
+            startElapsed = freshWindowPoint;
+            // Now we should have no alarms in the past APP_STANDBY_WINDOW
+        }
+        final long desiredTrigger = startElapsed + APP_STANDBY_WINDOW;
+        final long firstTrigger = startElapsed + 4_000;
+        assertTrue("Quota too large for test",
+                firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
+        for (int i = 0; i < quota; i++) {
+            final long trigger = firstTrigger + (i * MIN_FUTURITY);
+            scheduleAlarm(trigger, false, 0);
+            Thread.sleep(trigger - SystemClock.elapsedRealtime());
+            assertTrue("Alarm within quota not firing as expected", waitForAlarm());
+        }
+
+        // Now quota is reached, any subsequent alarm should get deferred.
+        scheduleAlarm(desiredTrigger, false, 0);
+        Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
+        assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
+        final long minTrigger = firstTrigger + 1 + APP_STANDBY_WINDOW;
+        Thread.sleep(minTrigger - SystemClock.elapsedRealtime());
+        assertTrue("Alarm exceeding quota not delivered after expected delay", waitForAlarm());
+    }
+
+    @Test
+    public void testActiveQuota() throws Exception {
+        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 testWorkingQuota() throws Exception {
+        testSimpleQuotaDeferral(WORKING_INDEX);
+    }
+
+    @Test
+    public void testFrequentQuota() throws Exception {
+        testSimpleQuotaDeferral(FREQUENT_INDEX);
+    }
+
+    @Test
+    public void testRareQuota() throws Exception {
+        testSimpleQuotaDeferral(RARE_INDEX);
+    }
+
+    @Test
+    public void testNeverQuota() throws Exception {
+        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 testAlarmClockUnaffected() throws Exception {
+        setAppStandbyBucket("never");
+        final long trigger = System.currentTimeMillis() + MIN_FUTURITY;
+        scheduleAlarmClock(trigger);
+        Thread.sleep(MIN_FUTURITY);
+        assertTrue("Alarm clock not received as expected", waitForAlarm());
     }
 
     @Test
     public void testAllowWhileIdleAlarms() throws Exception {
+        updateAlarmManagerConstants(false);
+        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", waitForAlarms(1));
-        scheduleAlarm(mLastAlarmTime + 9_000, true, 0);
+        assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
+        scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
         // First check for the case where allow_while_idle delay should supersede app standby
-        setAppStandbyBucket(APP_BUCKET_TAGS[1]);
-        Thread.sleep(APP_STANDBY_DELAYS[1]);
-        assertFalse("allow_while_idle alarm went off before short time", waitForAlarms(1));
-        long expectedTriggerTime = mLastAlarmTime + ALLOW_WHILE_IDLE_SHORT_TIME;
+        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
+        Thread.sleep(APP_STANDBY_DELAYS[WORKING_INDEX]);
+        assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
+        long expectedTriggerTime = sAlarmHistory.getLast(1) + ALLOW_WHILE_IDLE_SHORT_TIME;
         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarms(1));
+        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(mLastAlarmTime + 12_000, true, 0);
-        setAppStandbyBucket(APP_BUCKET_TAGS[2]);
+        scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, 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[2]
-                + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
-        expectedTriggerTime = mLastAlarmTime + APP_STANDBY_DELAYS[2];
+        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[RARE_INDEX]
+                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
+        expectedTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[RARE_INDEX];
         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off even after " + APP_STANDBY_DELAYS[2]
-                + "ms, when in bucket " + APP_BUCKET_TAGS[2], waitForAlarms(1));
+        assertTrue("allow_while_idle alarm did not go off even after "
+                + APP_STANDBY_DELAYS[RARE_INDEX]
+                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
     }
 
     @Test
     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
-        setAppStandbyBucket(APP_BUCKET_TAGS[3]);
+        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
         setPowerWhitelisted(true);
         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
         scheduleAlarm(triggerTime, false, 0);
         Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarms(1));
+        assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
         setPowerWhitelisted(false);
     }
 
@@ -270,13 +448,11 @@
         }
     }
 
-    private void updateAlarmManagerConstants() throws IOException {
+    private void updateAlarmManagerConstants(boolean enableQuota) throws IOException {
         final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
-        cmd.append("min_futurity="); cmd.append(MIN_FUTURITY);
-        cmd.append(",allow_while_idle_short_time="); cmd.append(ALLOW_WHILE_IDLE_SHORT_TIME);
-        for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
-            cmd.append(",");
-            cmd.append(APP_BUCKET_KEYS[i]); cmd.append("="); cmd.append(APP_STANDBY_DELAYS[i]);
+        cmd.append(COMMON_SETTINGS);
+        if (!enableQuota) {
+            cmd.append(",app_standby_quotas_enabled=false");
         }
         executeAndLog(cmd.toString());
     }
@@ -311,8 +487,8 @@
         return output;
     }
 
-    private boolean waitForAlarms(final int numAlarms) throws InterruptedException {
-        final boolean success = waitUntil(() -> (mAlarmCount.get() == numAlarms), DEFAULT_WAIT);
+    private boolean waitForAlarm() throws InterruptedException {
+        final boolean success = waitUntil(() -> (mAlarmCount.get() == 1), DEFAULT_WAIT);
         mAlarmCount.set(0);
         return success;
     }
@@ -325,6 +501,24 @@
         return condition.isMet();
     }
 
+    private static final class TestAlarmHistory {
+        private LongArray mHistory = new LongArray();
+
+        private synchronized void addTime(long timestamp) {
+            mHistory.add(timestamp);
+        }
+
+        /**
+         * Get the xth alarm time from the end.
+         */
+        private synchronized long getLast(int x) {
+            if (x == 0 || x > mHistory.size()) {
+                return 0;
+            }
+            return mHistory.get(mHistory.size() - x);
+        }
+    }
+
     @FunctionalInterface
     interface Condition {
         boolean isMet();
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index a981e5a..f36b07f 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -136,7 +136,7 @@
     @Test
     public void testAlarmClockNotBlocked() throws Exception {
         final long nowRTC = System.currentTimeMillis();
-        final long waitInterval = 10_000;
+        final long waitInterval = 3_000;
         final long triggerRTC = nowRTC + waitInterval;
         scheduleAlarmClock(triggerRTC);
         Thread.sleep(waitInterval);
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index 4820aa2..83365d7 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -21,6 +21,10 @@
         <option name="test-file-name" value="CtsJobSchedulerTestCases.apk" />
         <option name="test-file-name" value="CtsJobTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.jobscheduler.cts" />
         <option name="runtime-hint" value="2m" />
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
index f31e052..6a521f1 100644
--- a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -34,6 +34,8 @@
 
     public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
     public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
+    public static final String EXTRA_REQUIRE_NETWORK_ANY = PACKAGE_NAME
+            + ".extra.REQUIRE_NETWORK_ANY";
     public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
     public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
     public static final int JOB_INITIAL_BACKOFF = 10_000;
@@ -50,10 +52,14 @@
             case ACTION_SCHEDULE_JOB:
                 final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
                 final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
+                final boolean network = intent.getBooleanExtra(EXTRA_REQUIRE_NETWORK_ANY, false);
                 JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
                         .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
                         .setOverrideDeadline(0)
                         .setImportantWhileForeground(allowInIdle);
+                if (network) {
+                    jobBuilder = jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+                }
                 final int result = jobScheduler.schedule(jobBuilder.build());
                 if (result != JobScheduler.RESULT_SUCCESS) {
                     Log.e(TAG, "Could not schedule job " + jobId);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 4486c15..0d300be 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -96,24 +96,9 @@
         }
 
         // Ensure that we leave WiFi in its previous state.
-        if (mWifiManager.isWifiEnabled() == mInitialWiFiState) {
-            return;
+        if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+            setWifiState(mInitialWiFiState, mContext, mCm, mWifiManager);
         }
-        NetworkInfo.State expectedState = mInitialWiFiState ?
-                NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED;
-        ConnectivityActionReceiver receiver =
-                new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
-                        expectedState);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        assertTrue(mWifiManager.setWifiEnabled(mInitialWiFiState));
-        receiver.waitForStateChange();
-        assertTrue("Failure to restore previous WiFi state",
-                mWifiManager.isWifiEnabled() == mInitialWiFiState);
-
-        mContext.unregisterReceiver(receiver);
     }
 
     // --------------------------------------------------------------------------------------------
@@ -129,7 +114,7 @@
             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
             return;
         }
-        connectToWiFi();
+        connectToWifi();
 
         kTestEnvironment.setExpectedExecutions(1);
         mJobScheduler.schedule(
@@ -150,7 +135,7 @@
             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
             return;
         }
-        connectToWiFi();
+        connectToWifi();
 
         kTestEnvironment.setExpectedExecutions(1);
         mJobScheduler.schedule(
@@ -246,7 +231,7 @@
             Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
             return;
         }
-        connectToWiFi();
+        connectToWifi();
 
         kTestEnvironment.setExpectedExecutions(0);
         mJobScheduler.schedule(
@@ -282,22 +267,43 @@
 
     /**
      * Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
+     */
+    private void connectToWifi()
+            throws InterruptedException {
+        setWifiState(true, mContext, mCm, mWifiManager);
+    }
+
+    /**
+     * Ensure WiFi is disabled, and block until we've verified that we are in fact disconnected.
+     */
+    private void disconnectFromWifi()
+            throws InterruptedException {
+        setWifiState(false, mContext, mCm, mWifiManager);
+    }
+
+    /**
+     * Set Wifi connection to specific state , and block until we've verified
+     * that we are in the state.
      * Taken from {@link android.net.http.cts.ApacheHttpClientTest}.
      */
-    private void connectToWiFi() throws InterruptedException {
-        if (!mWifiManager.isWifiEnabled()) {
+    static void setWifiState(boolean enable, Context context, ConnectivityManager cm,
+            WifiManager wm) throws InterruptedException  {
+        if (enable != wm.isWifiEnabled()) {
+            NetworkInfo.State expectedState = enable ?
+                    NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED;
             ConnectivityActionReceiver receiver =
                     new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
-                            NetworkInfo.State.CONNECTED);
+                            expectedState, cm);
             IntentFilter filter = new IntentFilter();
             filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-            mContext.registerReceiver(receiver, filter);
+            context.registerReceiver(receiver, filter);
 
-            assertTrue(mWifiManager.setWifiEnabled(true));
-            assertTrue("Wifi must be configured to connect to an access point for this test.",
+            assertTrue(wm.setWifiEnabled(enable));
+            assertTrue("Wifi must be configured to " + (enable ? "connect" : "disconnect")
+                            + " to an access point for this test.",
                     receiver.waitForStateChange());
 
-            mContext.unregisterReceiver(receiver);
+            context.unregisterReceiver(receiver);
         }
     }
 
@@ -311,24 +317,18 @@
      */
     private void disconnectWifiToConnectToMobile() throws InterruptedException {
         if (mHasWifi && mWifiManager.isWifiEnabled()) {
+            disconnectFromWifi();
             ConnectivityActionReceiver connectMobileReceiver =
                     new ConnectivityActionReceiver(ConnectivityManager.TYPE_MOBILE,
-                            NetworkInfo.State.CONNECTED);
-            ConnectivityActionReceiver disconnectWifiReceiver =
-                    new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
-                            NetworkInfo.State.DISCONNECTED);
+                            NetworkInfo.State.CONNECTED, mCm);
             IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
             mContext.registerReceiver(connectMobileReceiver, filter);
-            mContext.registerReceiver(disconnectWifiReceiver, filter);
 
-            assertTrue(mWifiManager.setWifiEnabled(false));
-            assertTrue("Failure disconnecting from WiFi.",
-                    disconnectWifiReceiver.waitForStateChange());
+
             assertTrue("Device must have access to a metered network for this test.",
                     connectMobileReceiver.waitForStateChange());
 
             mContext.unregisterReceiver(connectMobileReceiver);
-            mContext.unregisterReceiver(disconnectWifiReceiver);
         }
     }
 
@@ -343,7 +343,7 @@
     }
 
     /** Capture the last connectivity change's network type and state. */
-    private class ConnectivityActionReceiver extends BroadcastReceiver {
+    private static class ConnectivityActionReceiver extends BroadcastReceiver {
 
         private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
 
@@ -351,9 +351,13 @@
 
         private final NetworkInfo.State mExpectedState;
 
-        ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState) {
+        private final ConnectivityManager mCm;
+
+        ConnectivityActionReceiver(int networkType, NetworkInfo.State expectedState,
+                ConnectivityManager cm) {
             mNetworkType = networkType;
             mExpectedState = expectedState;
+            mCm = cm;
         }
 
         public void onReceive(Context context, Intent intent) {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
deleted file mode 100644
index e624a62..0000000
--- a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
+++ /dev/null
@@ -1,348 +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.jobscheduler.cts;
-
-import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
-import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.jobscheduler.cts.jobtestapp.TestActivity;
-import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiDevice;
-import android.util.Log;
-
-import com.android.compatibility.common.util.AppStandbyUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that temp whitelisted apps can run jobs if all the other constraints are met
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class DeviceIdleJobsTest {
-    private static final String TAG = DeviceIdleJobsTest.class.getSimpleName();
-    private static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp";
-    private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver";
-    private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity";
-    private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
-    private static final long POLL_INTERVAL = 500;
-    private static final long DEFAULT_WAIT_TIMEOUT = 1000;
-    private static final long SHELL_TIMEOUT = 3_000;
-
-    enum Bucket {
-        ACTIVE,
-        WORKING_SET,
-        FREQUENT,
-        RARE,
-        NEVER
-    }
-
-    private Context mContext;
-    private UiDevice mUiDevice;
-    private PowerManager mPowerManager;
-    private long mTempWhitelistExpiryElapsed;
-    private int mTestJobId;
-    private int mTestPackageUid;
-    private boolean mDeviceInDoze;
-    private boolean mDeviceIdleEnabled;
-    private boolean mAppStandbyEnabled;
-
-    /* accesses must be synchronized on itself */
-    private final TestJobStatus mTestJobStatus = new TestJobStatus();
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Log.d(TAG, "Received action " + intent.getAction());
-            switch (intent.getAction()) {
-                case ACTION_JOB_STARTED:
-                case ACTION_JOB_STOPPED:
-                    final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
-                    Log.d(TAG, "JobId: " + params.getJobId());
-                    synchronized (mTestJobStatus) {
-                        mTestJobStatus.running = ACTION_JOB_STARTED.equals(intent.getAction());
-                        mTestJobStatus.jobId = params.getJobId();
-                    }
-                    break;
-                case ACTION_DEVICE_IDLE_MODE_CHANGED:
-                case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
-                    synchronized (DeviceIdleJobsTest.this) {
-                        mDeviceInDoze = mPowerManager.isDeviceIdleMode();
-                        Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
-                    }
-                    break;
-            }
-        }
-    };
-
-    private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception {
-        final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim();
-        return Integer.parseInt(output) != 0;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        mPowerManager = mContext.getSystemService(PowerManager.class);
-        mDeviceInDoze = mPowerManager.isDeviceIdleMode();
-        mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
-        mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
-        mTestJobStatus.reset();
-        mTempWhitelistExpiryElapsed = -1;
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_JOB_STARTED);
-        intentFilter.addAction(ACTION_JOB_STOPPED);
-        intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
-        intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
-        mContext.registerReceiver(mReceiver, intentFilter);
-        assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
-        makeTestPackageIdle();
-        mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice);
-        mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled();
-        if (mAppStandbyEnabled) {
-            setTestPackageStandbyBucket(Bucket.ACTIVE);
-        } else {
-            Log.w(TAG, "App standby not enabled on test device");
-        }
-    }
-
-    @Test
-    public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
-        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-
-        toggleDeviceIdleState(true);
-        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
-        sendScheduleJobBroadcast(true);
-        assertFalse("Job started without being tempwhitelisted", awaitJobStart(5_000));
-        tempWhitelistTestApp(5_000);
-        assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @Test
-    public void testForegroundJobsStartImmediately() throws Exception {
-        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-
-        sendScheduleJobBroadcast(false);
-        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        toggleDeviceIdleState(true);
-        assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
-        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
-        startAndKeepTestActivity();
-        toggleDeviceIdleState(false);
-        assertTrue("Job for foreground app did not start immediately when device exited doze",
-                awaitJobStart(3_000));
-    }
-
-    @Test
-    public void testBackgroundJobsDelayed() throws Exception {
-        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-
-        sendScheduleJobBroadcast(false);
-        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        toggleDeviceIdleState(true);
-        assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
-        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
-        toggleDeviceIdleState(false);
-        assertFalse("Job for background app started immediately when device exited doze",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
-        assertTrue("Job for background app did not start after the expected delay of "
-                + BACKGROUND_JOBS_EXPECTED_DELAY + "ms", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @Test
-    public void testJobsInNeverApp() throws Exception {
-        assumeTrue("app standby not enabled", mAppStandbyEnabled);
-
-        enterFakeUnpluggedState();
-        setTestPackageStandbyBucket(Bucket.NEVER);
-        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
-        sendScheduleJobBroadcast(false);
-        assertFalse("New job started in NEVER standby", awaitJobStart(3_000));
-        resetFakeUnpluggedState();
-    }
-
-    @Test
-    public void testUidActiveBypassesStandby() throws Exception {
-        enterFakeUnpluggedState();
-        setTestPackageStandbyBucket(Bucket.NEVER);
-        tempWhitelistTestApp(6_000);
-        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
-        sendScheduleJobBroadcast(false);
-        assertTrue("New job in uid-active app failed to start in NEVER standby",
-                awaitJobStart(4_000));
-        resetFakeUnpluggedState();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mDeviceIdleEnabled) {
-            toggleDeviceIdleState(false);
-        }
-        final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS);
-        cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
-        cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.sendBroadcast(cancelJobsIntent);
-        mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
-        mContext.unregisterReceiver(mReceiver);
-        Thread.sleep(500); // To avoid any race between unregister and the next register in setUp
-        waitUntilTestAppNotInTempWhitelist();
-    }
-
-    private boolean isTestAppTempWhitelisted() throws Exception {
-        final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
-        for (String line : output.split("\n")) {
-            if (line.contains("UID="+mTestPackageUid)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void startAndKeepTestActivity() {
-        final Intent testActivity = new Intent();
-        testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        testActivity.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
-        mContext.startActivity(testActivity);
-    }
-
-    private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
-        final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
-        scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mTestJobId);
-        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
-        scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
-        mContext.sendBroadcast(scheduleJobIntent);
-    }
-
-    private void toggleDeviceIdleState(final boolean idle) throws Exception {
-        mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
-        assertTrue("Could not change device idle state to " + idle,
-                waitUntilTrue(SHELL_TIMEOUT, () -> {
-                    synchronized (DeviceIdleJobsTest.this) {
-                        return mDeviceInDoze == idle;
-                    }
-                }));
-    }
-
-    private void tempWhitelistTestApp(long duration) throws Exception {
-        mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
-                + " " + TEST_APP_PACKAGE);
-        mTempWhitelistExpiryElapsed = SystemClock.elapsedRealtime() + duration;
-    }
-
-    private void makeTestPackageIdle() throws Exception {
-        mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
-    }
-
-    private void setTestPackageStandbyBucket(Bucket bucket) throws Exception {
-        final String bucketName;
-        switch (bucket) {
-            case ACTIVE: bucketName = "active"; break;
-            case WORKING_SET: bucketName = "working"; break;
-            case FREQUENT: bucketName = "frequent"; break;
-            case RARE: bucketName = "rare"; break;
-            case NEVER: bucketName = "never"; break;
-            default:
-                throw new IllegalArgumentException("Requested unknown bucket " + bucket);
-        }
-        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE
-                + " " + bucketName);
-    }
-
-    private void enterFakeUnpluggedState() throws Exception {
-        mUiDevice.executeShellCommand("dumpsys battery unplug");
-    }
-
-    private void resetFakeUnpluggedState() throws Exception  {
-        mUiDevice.executeShellCommand("dumpsys battery reset");
-    }
-
-    private boolean waitUntilTestAppNotInTempWhitelist() throws Exception {
-        long now;
-        boolean interrupted = false;
-        while ((now = SystemClock.elapsedRealtime()) < mTempWhitelistExpiryElapsed) {
-            try {
-                Thread.sleep(mTempWhitelistExpiryElapsed - now);
-            } catch (InterruptedException iexc) {
-                interrupted = true;
-            }
-        }
-        if (interrupted) {
-            Thread.currentThread().interrupt();
-        }
-        return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
-    }
-
-    private boolean awaitJobStart(long maxWait) throws Exception {
-        return waitUntilTrue(maxWait, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
-            }
-        });
-    }
-
-    private boolean awaitJobStop(long maxWait) throws Exception {
-        return waitUntilTrue(maxWait, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running;
-            }
-        });
-    }
-
-    private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
-        final long deadLine = SystemClock.uptimeMillis() + maxWait;
-        do {
-            Thread.sleep(POLL_INTERVAL);
-        } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
-        return condition.isTrue();
-    }
-
-    private static final class TestJobStatus {
-        int jobId;
-        boolean running;
-        private void reset() {
-            running = false;
-        }
-    }
-
-    private interface Condition {
-        boolean isTrue() throws Exception;
-    }
-}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
new file mode 100644
index 0000000..4c68551
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.jobscheduler.cts;
+
+import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
+import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED;
+import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
+import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
+import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.job.JobParameters;
+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.jobscheduler.cts.jobtestapp.TestActivity;
+import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.Temperature;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.ThermalUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests related to job throttling -- device idle, app standby and battery saver.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class JobThrottlingTest {
+    private static final String TAG = JobThrottlingTest.class.getSimpleName();
+    private static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp";
+    private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver";
+    private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity";
+    private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
+    private static final long POLL_INTERVAL = 500;
+    private static final long DEFAULT_WAIT_TIMEOUT = 1000;
+    private static final long SHELL_TIMEOUT = 3_000;
+
+    enum Bucket {
+        ACTIVE,
+        WORKING_SET,
+        FREQUENT,
+        RARE,
+        NEVER
+    }
+
+    private Context mContext;
+    private UiDevice mUiDevice;
+    private PowerManager mPowerManager;
+    private long mTempWhitelistExpiryElapsed;
+    private int mTestJobId;
+    private int mTestPackageUid;
+    private boolean mDeviceInDoze;
+    private boolean mDeviceIdleEnabled;
+    private boolean mAppStandbyEnabled;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mCm;
+    /** Whether the device running these tests supports WiFi. */
+    private boolean mHasWifi;
+    /** Track whether WiFi was enabled in case we turn it off. */
+    private boolean mInitialWiFiState;
+
+    /* accesses must be synchronized on itself */
+    private final TestJobStatus mTestJobStatus = new TestJobStatus();
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG, "Received action " + intent.getAction());
+            switch (intent.getAction()) {
+                case ACTION_JOB_STARTED:
+                case ACTION_JOB_STOPPED:
+                    final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
+                    Log.d(TAG, "JobId: " + params.getJobId());
+                    synchronized (mTestJobStatus) {
+                        mTestJobStatus.running = ACTION_JOB_STARTED.equals(intent.getAction());
+                        mTestJobStatus.jobId = params.getJobId();
+                    }
+                    break;
+                case ACTION_DEVICE_IDLE_MODE_CHANGED:
+                case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+                    synchronized (JobThrottlingTest.this) {
+                        mDeviceInDoze = mPowerManager.isDeviceIdleMode();
+                        Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
+                    }
+                    break;
+            }
+        }
+    };
+
+    private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception {
+        final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim();
+        return Integer.parseInt(output) != 0;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mDeviceInDoze = mPowerManager.isDeviceIdleMode();
+        mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
+        mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
+        mTestJobStatus.reset();
+        mTempWhitelistExpiryElapsed = -1;
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_JOB_STARTED);
+        intentFilter.addAction(ACTION_JOB_STOPPED);
+        intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
+        intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(mReceiver, intentFilter);
+        assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
+        makeTestPackageIdle();
+        mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice);
+        mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled();
+        if (mAppStandbyEnabled) {
+            setTestPackageStandbyBucket(Bucket.ACTIVE);
+        } else {
+            Log.w(TAG, "App standby not enabled on test device");
+        }
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mHasWifi = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
+        mInitialWiFiState = mWifiManager.isWifiEnabled();
+    }
+
+    @Test
+    public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDeviceIdleState(true);
+        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
+        sendScheduleJobBroadcast(true);
+        assertFalse("Job started without being tempwhitelisted", awaitJobStart(5_000));
+        tempWhitelistTestApp(5_000);
+        assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
+                awaitJobStart(5_000));
+    }
+
+    @Test
+    public void testForegroundJobsStartImmediately() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        sendScheduleJobBroadcast(false);
+        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        toggleDeviceIdleState(true);
+        assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+        startAndKeepTestActivity();
+        toggleDeviceIdleState(false);
+        assertTrue("Job for foreground app did not start immediately when device exited doze",
+                awaitJobStart(3_000));
+    }
+
+    @Test
+    public void testBackgroundJobsDelayed() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        sendScheduleJobBroadcast(false);
+        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        toggleDeviceIdleState(true);
+        assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+        toggleDeviceIdleState(false);
+        assertFalse("Job for background app started immediately when device exited doze",
+                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
+        assertTrue("Job for background app did not start after the expected delay of "
+                + BACKGROUND_JOBS_EXPECTED_DELAY + "ms", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testBackgroundConnectivityJobsThrottled() throws Exception {
+        if (!mHasWifi) {
+            Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
+            return;
+        }
+        setWifiState(true, mContext, mCm, mWifiManager);
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+        sendScheduleJobBroadcast(false, true);
+        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+        assertTrue("Job did not stop on thermal throttling", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+        ThermalUtils.overrideThermalNotThrottling();
+        assertTrue("Job did not start back from throttling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testJobsInNeverApp() throws Exception {
+        assumeTrue("app standby not enabled", mAppStandbyEnabled);
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        setTestPackageStandbyBucket(Bucket.NEVER);
+        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
+        sendScheduleJobBroadcast(false);
+        assertFalse("New job started in NEVER standby", awaitJobStart(3_000));
+    }
+
+    @Test
+    public void testUidActiveBypassesStandby() throws Exception {
+        BatteryUtils.runDumpsysBatteryUnplug();
+        setTestPackageStandbyBucket(Bucket.NEVER);
+        tempWhitelistTestApp(6_000);
+        Thread.sleep(DEFAULT_WAIT_TIMEOUT);
+        sendScheduleJobBroadcast(false);
+        assertTrue("New job in uid-active app failed to start in NEVER standby",
+                awaitJobStart(4_000));
+    }
+
+    @Test
+    public void testBatterySaverOff() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(false);
+        sendScheduleJobBroadcast(false);
+        assertTrue("New job failed to start with battery saver OFF", awaitJobStart(3_000));
+    }
+
+    @Test
+    public void testBatterySaverOn() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        sendScheduleJobBroadcast(false);
+        assertFalse("New job started with battery saver ON", awaitJobStart(3_000));
+    }
+
+    @Test
+    public void testUidActiveBypassesBatterySaverOn() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        tempWhitelistTestApp(6_000);
+        sendScheduleJobBroadcast(false);
+        assertTrue("New job in uid-active app failed to start with battery saver OFF",
+                awaitJobStart(3_000));
+    }
+
+    @Test
+    public void testBatterySaverOnThenUidActive() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+
+        // Enable battery saver, and schedule a job. It shouldn't run.
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        sendScheduleJobBroadcast(false);
+        assertFalse("New job started with battery saver ON", awaitJobStart(3_000));
+
+
+        // Then make the UID active. Now the job should run.
+        tempWhitelistTestApp(120_000);
+        assertTrue("New job in uid-active app failed to start with battery saver OFF",
+                awaitJobStart(120_000));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Lock thermal service to not throttling
+        ThermalUtils.overrideThermalNotThrottling();
+        if (mDeviceIdleEnabled) {
+            toggleDeviceIdleState(false);
+        }
+        final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS);
+        cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
+        cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.sendBroadcast(cancelJobsIntent);
+        mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+        mContext.unregisterReceiver(mReceiver);
+        BatteryUtils.runDumpsysBatteryReset();
+
+        Thread.sleep(500); // To avoid any race between unregister and the next register in setUp
+        waitUntilTestAppNotInTempWhitelist();
+
+        // Ensure that we leave WiFi in its previous state.
+        if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+            setWifiState(mInitialWiFiState, mContext, mCm, mWifiManager);
+        }
+    }
+
+    private boolean isTestAppTempWhitelisted() throws Exception {
+        final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
+        for (String line : output.split("\n")) {
+            if (line.contains("UID="+mTestPackageUid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void startAndKeepTestActivity() {
+        final Intent testActivity = new Intent();
+        testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        testActivity.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
+        mContext.startActivity(testActivity);
+    }
+
+    private void sendScheduleJobBroadcast(boolean allowWhileIdle, boolean needNetwork)
+            throws Exception {
+        final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
+        scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mTestJobId);
+        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
+        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_REQUIRE_NETWORK_ANY, needNetwork);
+        scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
+        mContext.sendBroadcast(scheduleJobIntent);
+    }
+
+    private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
+        sendScheduleJobBroadcast(allowWhileIdle, false);
+    }
+
+    private void toggleDeviceIdleState(final boolean idle) throws Exception {
+        mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
+        assertTrue("Could not change device idle state to " + idle,
+                waitUntilTrue(SHELL_TIMEOUT, () -> {
+                    synchronized (JobThrottlingTest.this) {
+                        return mDeviceInDoze == idle;
+                    }
+                }));
+    }
+
+    private void tempWhitelistTestApp(long duration) throws Exception {
+        mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
+                + " " + TEST_APP_PACKAGE);
+        mTempWhitelistExpiryElapsed = SystemClock.elapsedRealtime() + duration;
+    }
+
+    private void makeTestPackageIdle() throws Exception {
+        mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
+    }
+
+    private void setTestPackageStandbyBucket(Bucket bucket) throws Exception {
+        final String bucketName;
+        switch (bucket) {
+            case ACTIVE: bucketName = "active"; break;
+            case WORKING_SET: bucketName = "working"; break;
+            case FREQUENT: bucketName = "frequent"; break;
+            case RARE: bucketName = "rare"; break;
+            case NEVER: bucketName = "never"; break;
+            default:
+                throw new IllegalArgumentException("Requested unknown bucket " + bucket);
+        }
+        mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE
+                + " " + bucketName);
+    }
+
+    private boolean waitUntilTestAppNotInTempWhitelist() throws Exception {
+        long now;
+        boolean interrupted = false;
+        while ((now = SystemClock.elapsedRealtime()) < mTempWhitelistExpiryElapsed) {
+            try {
+                Thread.sleep(mTempWhitelistExpiryElapsed - now);
+            } catch (InterruptedException iexc) {
+                interrupted = true;
+            }
+        }
+        if (interrupted) {
+            Thread.currentThread().interrupt();
+        }
+        return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
+    }
+
+    private boolean awaitJobStart(long maxWait) throws Exception {
+        return waitUntilTrue(maxWait, () -> {
+            synchronized (mTestJobStatus) {
+                return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
+            }
+        });
+    }
+
+    private boolean awaitJobStop(long maxWait) throws Exception {
+        return waitUntilTrue(maxWait, () -> {
+            synchronized (mTestJobStatus) {
+                return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running;
+            }
+        });
+    }
+
+    private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
+        final long deadLine = SystemClock.uptimeMillis() + maxWait;
+        do {
+            Thread.sleep(POLL_INTERVAL);
+        } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
+        return condition.isTrue();
+    }
+
+    private static final class TestJobStatus {
+        int jobId;
+        boolean running;
+        private void reset() {
+            running = false;
+        }
+    }
+
+    private interface Condition {
+        boolean isTrue() throws Exception;
+    }
+}
diff --git a/tests/JobSchedulerSharedUid/AndroidTest.xml b/tests/JobSchedulerSharedUid/AndroidTest.xml
index db88860..183551d 100644
--- a/tests/JobSchedulerSharedUid/AndroidTest.xml
+++ b/tests/JobSchedulerSharedUid/AndroidTest.xml
@@ -23,6 +23,10 @@
         <option name="test-file-name" value="CtsJobSchedulerSharedUid.apk" />
         <option name="test-file-name" value="CtsJobSharedUidTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.jobscheduler.cts.shareduidtests" />
         <option name="runtime-hint" value="2m" />
diff --git a/tests/JobSchedulerSharedUid/jobperm/Android.mk b/tests/JobSchedulerSharedUid/jobperm/Android.mk
index 8be235f..a95f0b3 100644
--- a/tests/JobSchedulerSharedUid/jobperm/Android.mk
+++ b/tests/JobSchedulerSharedUid/jobperm/Android.mk
@@ -20,6 +20,7 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-annotations \
     compatibility-device-util \
 
 LOCAL_SRC_FILES := \
diff --git a/tests/JobSchedulerSharedUid/jobperm/src/android/jobscheduler/cts/shareduid/jobperm/JobPermProvider.java b/tests/JobSchedulerSharedUid/jobperm/src/android/jobscheduler/cts/shareduid/jobperm/JobPermProvider.java
index 5c6b4b2..330ccac 100644
--- a/tests/JobSchedulerSharedUid/jobperm/src/android/jobscheduler/cts/shareduid/jobperm/JobPermProvider.java
+++ b/tests/JobSchedulerSharedUid/jobperm/src/android/jobscheduler/cts/shareduid/jobperm/JobPermProvider.java
@@ -16,8 +16,6 @@
 
 package android.jobscheduler.cts.shareduid.jobperm;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -25,6 +23,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileNotFoundException;
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index da993ed..6063e0b 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -54,6 +54,12 @@
             <meta-data android:name="android.accessibilityservice"
                        android:resource="@xml/speaking_and_vibrating_accessibilityservice" />
         </service>
+
+        <activity
+            android:label="@string/some_description"
+            android:name=".DummyActivity"
+            android:screenOrientation="locked"/>
+
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibility/AndroidTest.xml b/tests/accessibility/AndroidTest.xml
index 6d5bac2..7783a25 100644
--- a/tests/accessibility/AndroidTest.xml
+++ b/tests/accessibility/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Accessibility 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="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <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" />
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index a58a53e..249c381 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -16,10 +16,12 @@
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
     android:accessibilityEventTypes="typeAllMask"
     android:accessibilityFeedbackType="feedbackSpoken"
-    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagReportViewIds|flagRequestFilterKeyEvents"
+    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagReportViewIds|flagRequestFilterKeyEvents|flagRequestShortcutWarningDialogSpokenFeedback"
     android:canRetrieveWindowContent="true"
     android:canRequestTouchExplorationMode="true"
     android:canRequestFilterKeyEvents="true"
     android:settingsActivity="foo.bar.Activity"
     android:description="@string/some_description"
-    android:summary="@string/some_summary" />
+    android:summary="@string/some_summary"
+    android:nonInteractiveUiTimeout="1000"
+    android:interactiveUiTimeout="6000"/>
\ No newline at end of file
diff --git a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
index e1b9706..93d9f0d 100644
--- a/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/vibrating_accessibilityservice.xml
@@ -18,4 +18,6 @@
     android:accessibilityFeedbackType="feedbackHaptic"
     android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode"
     android:canRetrieveWindowContent="true"
-    android:canRequestTouchExplorationMode="true" />
+    android:canRequestTouchExplorationMode="true"
+    android:nonInteractiveUiTimeout="2000"
+    android:interactiveUiTimeout="5000"/>
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
new file mode 100644
index 0000000..a3efbe8
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.view.accessibility.cts;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.LinearLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests AccessibilityDelegate functionality
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityDelegateTest {
+
+    private LinearLayout mParentView;
+    private View mChildView;
+
+    @Rule
+    public ActivityTestRule<DummyActivity> mActivityRule =
+            new ActivityTestRule<>(DummyActivity.class, false, false);
+
+    @Before
+    public void setUp() throws Exception {
+        Activity activity = mActivityRule.launchActivity(null);
+        LinearLayout grandparent = new LinearLayout(activity);
+        mParentView = new LinearLayout(activity);
+        mChildView = new View(activity);
+        grandparent.addView(mParentView);
+        mParentView.addView(mChildView);
+    }
+
+    @Test
+    public void testAccessibilityDelegateGetAndSet() {
+        AccessibilityDelegate delegate = new AccessibilityDelegate();
+        mParentView.setAccessibilityDelegate(delegate);
+        assertThat(mParentView.getAccessibilityDelegate(), is(equalTo(delegate)));
+    }
+
+    @Test
+    public void testViewDelegatesToAccessibilityDelegate() {
+        AccessibilityDelegate mockDelegate = mock(AccessibilityDelegate.class);
+        mParentView.setAccessibilityDelegate(mockDelegate);
+        final AccessibilityEvent event = AccessibilityEvent.obtain();
+
+        mParentView.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+        verify(mockDelegate).sendAccessibilityEvent(
+                mParentView, AccessibilityEvent.TYPE_ANNOUNCEMENT);
+
+        mParentView.sendAccessibilityEventUnchecked(event);
+        verify(mockDelegate).sendAccessibilityEventUnchecked(mParentView, event);
+
+        mParentView.dispatchPopulateAccessibilityEvent(event);
+        verify(mockDelegate).dispatchPopulateAccessibilityEvent(mParentView, event);
+
+        mParentView.onPopulateAccessibilityEvent(event);
+        verify(mockDelegate).onPopulateAccessibilityEvent(mParentView, event);
+
+        mParentView.onInitializeAccessibilityEvent(event);
+        verify(mockDelegate).onInitializeAccessibilityEvent(mParentView, event);
+
+        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mParentView.onInitializeAccessibilityNodeInfo(info);
+        verify(mockDelegate).onInitializeAccessibilityNodeInfo(mParentView, info);
+
+        mParentView.requestSendAccessibilityEvent(mChildView, event);
+        verify(mockDelegate).onRequestSendAccessibilityEvent(mParentView, mChildView, event);
+
+        mParentView.getAccessibilityNodeProvider();
+        verify(mockDelegate).getAccessibilityNodeProvider(mParentView);
+
+        final Bundle bundle = new Bundle();
+        mParentView.performAccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, bundle);
+        verify(mockDelegate).performAccessibilityAction(
+                mParentView, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, bundle);
+    }
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index 0cf2c19..700e830 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -16,12 +16,32 @@
 
 package android.view.accessibility.cts;
 
+import static android.view.accessibility.cts.ServiceControlUtils.TIMEOUT_FOR_SERVICE_ENABLE;
+import static android.view.accessibility.cts.ServiceControlUtils.getEnabledServices;
+import static android.view.accessibility.cts.ServiceControlUtils.waitForConditionWithServiceStateChange;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
 import android.app.Service;
+import android.app.UiAutomation;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
-import android.test.InstrumentationTestCase;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
@@ -32,10 +52,19 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityManager}.
  */
-public class AccessibilityManagerTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityManagerTest {
+
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
 
     private static final String SPEAKING_ACCESSIBLITY_SERVICE_NAME =
         "android.view.accessibility.cts.SpeakingAccessibilityService";
@@ -46,28 +75,35 @@
     private static final String MULTIPLE_FEEDBACK_TYPES_ACCESSIBILITY_SERVICE_NAME =
         "android.view.accessibility.cts.SpeakingAndVibratingAccessibilityService";
 
+    public static final String ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS =
+            "accessibility_non_interactive_ui_timeout_ms";
+
+    public static final String ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS =
+            "accessibility_interactive_ui_timeout_ms";
+
     private AccessibilityManager mAccessibilityManager;
 
     private Context mTargetContext;
 
     private Handler mHandler;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
         mAccessibilityManager = (AccessibilityManager)
-                getInstrumentation().getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
-        mTargetContext = getInstrumentation().getTargetContext();
+                sInstrumentation.getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
+        mTargetContext = sInstrumentation.getTargetContext();
         mHandler = new Handler(mTargetContext.getMainLooper());
         // In case the test runner started a UiAutomation, destroy it to start with a clean slate.
-        getInstrumentation().getUiAutomation().destroy();
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        sInstrumentation.getUiAutomation().destroy();
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
     }
 
+    @Test
     public void testAddAndRemoveAccessibilityStateChangeListener() throws Exception {
         AccessibilityStateChangeListener listener = (state) -> {
                 /* do nothing */
@@ -77,6 +113,7 @@
         assertFalse(mAccessibilityManager.removeAccessibilityStateChangeListener(listener));
     }
 
+    @Test
     public void testAddAndRemoveTouchExplorationStateChangeListener() throws Exception {
         TouchExplorationStateChangeListener listener = (boolean enabled) -> {
             // Do nothing.
@@ -86,8 +123,9 @@
         assertFalse(mAccessibilityManager.removeTouchExplorationStateChangeListener(listener));
     }
 
+    @Test
     public void testIsTouchExplorationEnabled() throws Exception {
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         new PollingCheck() {
             @Override
             protected boolean check() {
@@ -96,6 +134,7 @@
         }.run();
     }
 
+    @Test
     public void testGetInstalledAccessibilityServicesList() throws Exception {
         List<AccessibilityServiceInfo> installedServices =
             mAccessibilityManager.getInstalledAccessibilityServiceList();
@@ -119,8 +158,9 @@
         assertTrue("The vibrating service should be installed.", vibratingServiceInstalled);
     }
 
+    @Test
     public void testGetEnabledAccessibilityServiceList() throws Exception {
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         List<AccessibilityServiceInfo> enabledServices =
             mAccessibilityManager.getEnabledAccessibilityServiceList(
                     AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
@@ -143,8 +183,9 @@
         assertTrue("The vibrating service should be enabled.", vibratingServiceEnabled);
     }
 
+    @Test
     public void testGetEnabledAccessibilityServiceListForType() throws Exception {
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         List<AccessibilityServiceInfo> enabledServices =
             mAccessibilityManager.getEnabledAccessibilityServiceList(
                     AccessibilityServiceInfo.FEEDBACK_SPOKEN);
@@ -161,10 +202,11 @@
         fail("The speaking service is not enabled.");
     }
 
+    @Test
     public void testGetEnabledAccessibilityServiceListForTypes() throws Exception {
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         // For this test, also enable a service with multiple feedback types
-        ServiceControlUtils.enableMultipleFeedbackTypesService(getInstrumentation());
+        ServiceControlUtils.enableMultipleFeedbackTypesService(sInstrumentation);
 
         List<AccessibilityServiceInfo> enabledServices =
                 mAccessibilityManager.getEnabledAccessibilityServiceList(
@@ -199,6 +241,7 @@
     }
 
     @SuppressWarnings("deprecation")
+    @Test
     public void testGetAccessibilityServiceList() throws Exception {
         List<ServiceInfo> services = mAccessibilityManager.getAccessibilityServiceList();
         boolean speakingServiceInstalled = false;
@@ -219,23 +262,26 @@
         assertTrue("The vibrating service should be installed.", vibratingServiceInstalled);
     }
 
+    @Test
     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.
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         waitForAccessibilityEnabled();
         mAccessibilityManager.interrupt();
     }
 
+    @Test
     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.
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         waitForAccessibilityEnabled();
         mAccessibilityManager.sendAccessibilityEvent(AccessibilityEvent.obtain(
                 AccessibilityEvent.TYPE_VIEW_CLICKED));
     }
 
+    @Test
     public void testTouchExplorationListenerNoHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -247,12 +293,12 @@
             }
         };
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         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());
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
         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",
@@ -260,6 +306,7 @@
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
     }
 
+    @Test
     public void testTouchExplorationListenerWithHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -271,12 +318,12 @@
             }
         };
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
-        ServiceControlUtils.enableSpeakingAndVibratingServices(getInstrumentation());
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
         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());
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
         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",
@@ -284,6 +331,7 @@
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
     }
 
+    @Test
     public void testAccessibilityStateListenerNoHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -295,12 +343,12 @@
             }
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
-        ServiceControlUtils.enableMultipleFeedbackTypesService(getInstrumentation());
+        ServiceControlUtils.enableMultipleFeedbackTypesService(sInstrumentation);
         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());
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Accessibility state listener not called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -308,6 +356,7 @@
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
     }
 
+    @Test
     public void testAccessibilityStateListenerWithHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -319,12 +368,12 @@
             }
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
-        ServiceControlUtils.enableMultipleFeedbackTypesService(getInstrumentation());
+        ServiceControlUtils.enableMultipleFeedbackTypesService(sInstrumentation);
         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());
-        ServiceControlUtils.turnAccessibilityOff(getInstrumentation());
+        ServiceControlUtils.turnAccessibilityOff(sInstrumentation);
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Accessibility state listener not called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -332,11 +381,99 @@
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
     }
 
+    @AppModeFull
+    @Test
+    public void testGetRecommendedTimeoutMillis() throws Exception {
+        ServiceControlUtils.enableSpeakingAndVibratingServices(sInstrumentation);
+        waitForAccessibilityEnabled();
+        UiAutomation automan = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        try {
+            // SpeakingA11yService interactive/nonInteractive timeout is 6000/1000
+            // vibratingA11yService interactive/nonInteractive timeout is 5000/2000
+            turnOffRecommendedUiTimoutSettings(automan);
+            PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 2000));
+            turnOnRecommendedUiTimoutSettings(automan, 7000, 0);
+            PollingCheck.waitFor(() -> sameRecommendedTimeout(7000, 2000));
+            turnOnRecommendedUiTimoutSettings(automan, 0, 4000);
+            PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 4000));
+            turnOnRecommendedUiTimoutSettings(automan, 9000, 8000);
+            PollingCheck.waitFor(() -> sameRecommendedTimeout(9000, 8000));
+            turnOffRecommendedUiTimoutSettings(automan);
+            PollingCheck.waitFor(() -> sameRecommendedTimeout(6000, 2000));
+            assertEquals("Should return original timeout", 3000,
+                    mAccessibilityManager.getRecommendedTimeoutMillis(3000,
+                            AccessibilityManager.FLAG_CONTENT_ICONS));
+            assertEquals("Should return original timeout", 7000,
+                    mAccessibilityManager.getRecommendedTimeoutMillis(7000,
+                            AccessibilityManager.FLAG_CONTENT_CONTROLS));
+        } finally {
+            automan.destroy();
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void performShortcut_withoutPermission_fails() {
+        UiAutomation uiAutomation = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+        String originalShortcut = configureShortcut(
+                uiAutomation, SpeakingAccessibilityService.COMPONENT_NAME.flattenToString());
+        try {
+            mAccessibilityManager.performAccessibilityShortcut();
+            fail("No security exception thrown when performing shortcut without permission");
+        } catch (SecurityException e) {
+            // Expected
+        } finally {
+            configureShortcut(uiAutomation, originalShortcut);
+            uiAutomation.destroy();
+        }
+        assertTrue(TextUtils.isEmpty(getEnabledServices(mTargetContext.getContentResolver())));
+    }
+
+    @AppModeFull
+    @Test
+    public void performShortcut_withPermission_succeeds() {
+        UiAutomation uiAutomation = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+        String originalShortcut = configureShortcut(
+                uiAutomation, SpeakingAccessibilityService.COMPONENT_NAME.flattenToString());
+        try {
+            runWithShellPermissionIdentity(uiAutomation,
+                    () -> mAccessibilityManager.performAccessibilityShortcut());
+            // Make sure the service starts up
+            waitOn(SpeakingAccessibilityService.sWaitObjectForConnecting,
+                    () -> SpeakingAccessibilityService.sConnectedInstance != null,
+                    TIMEOUT_FOR_SERVICE_ENABLE, "Speaking accessibility service starts up");
+        } finally {
+            configureShortcut(uiAutomation, originalShortcut);
+            uiAutomation.destroy();
+        }
+    }
+
+    private String configureShortcut(UiAutomation uiAutomation, String shortcutService) {
+        String currentService = Settings.Secure.getString(mTargetContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+        putSecureSetting(uiAutomation, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                shortcutService);
+        if (shortcutService != null) {
+            runWithShellPermissionIdentity(uiAutomation, () ->
+                    waitForConditionWithServiceStateChange(mTargetContext, () -> TextUtils.equals(
+                            mAccessibilityManager.getAccessibilityShortcutService(),
+                            shortcutService),
+                            TIMEOUT_FOR_SERVICE_ENABLE,
+                            "accessibility shortcut set to test service"));
+        }
+        return currentService;
+    }
+
     private void assertAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
             boolean expectedValue, Object waitObject, String message)
             throws Exception {
         long timeoutTime =
-                System.currentTimeMillis() + ServiceControlUtils.TIMEOUT_FOR_SERVICE_ENABLE;
+                System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         synchronized (waitObject) {
             while ((atomicBoolean.get() != expectedValue)
                     && (System.currentTimeMillis() < timeoutTime)) {
@@ -356,7 +493,7 @@
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
         long timeoutTime =
-                System.currentTimeMillis() + ServiceControlUtils.TIMEOUT_FOR_SERVICE_ENABLE;
+                System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         synchronized (waitObject) {
             while (!mAccessibilityManager.isEnabled()
                     && (System.currentTimeMillis() < timeoutTime)) {
@@ -366,4 +503,37 @@
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
         assertTrue("Timed out enabling accessibility", mAccessibilityManager.isEnabled());
     }
+
+    private void turnOffRecommendedUiTimoutSettings(UiAutomation automan) {
+        putSecureSetting(automan, ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, null);
+        putSecureSetting(automan, ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, null);
+    }
+
+    private void turnOnRecommendedUiTimoutSettings(UiAutomation automan,
+            int interactiveUiTimeout, int nonInteractiveUiTimeout) {
+        putSecureSetting(automan, ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
+                Integer.toString(interactiveUiTimeout));
+        putSecureSetting(automan, ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
+                Integer.toString(nonInteractiveUiTimeout));
+    }
+
+    private boolean sameRecommendedTimeout(int interactiveUiTimeout,
+            int nonInteractiveUiTimeout) {
+        final int currentInteractiveUiTimeout = mAccessibilityManager
+                .getRecommendedTimeoutMillis(0, AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        final int currentNonInteractiveUiTimeout = mAccessibilityManager
+                .getRecommendedTimeoutMillis(0, AccessibilityManager.FLAG_CONTENT_ICONS);
+        return (currentInteractiveUiTimeout == interactiveUiTimeout
+                && currentNonInteractiveUiTimeout == nonInteractiveUiTimeout);
+    }
+
+    private void putSecureSetting(UiAutomation automan, String name, String value) {
+        ContentResolver cr = mTargetContext.getContentResolver();
+        automan.adoptShellPermissionIdentity();
+        try {
+            Settings.Secure.putString(cr, name, value);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 720a4e0..0923a92 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -17,6 +17,7 @@
 package android.view.accessibility.cts;
 
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
@@ -24,6 +25,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.InputType;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -31,6 +33,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
 import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -249,11 +252,12 @@
         info.setTraversalBefore(new View(getContext()));
         info.setTraversalAfter(new View(getContext()));
 
-        // Populate 2 fields
+        // Populate 3 fields
         info.setLabeledBy(new View(getContext()));
         info.setLabelFor(new View(getContext()));
+        populateTouchDelegateTargetMap(info);
 
-        // And Boolean properties are another field. Total is 33
+        // And Boolean properties are another field. Total is 34
 
         // 10 Boolean properties
         info.setCheckable(true);
@@ -279,9 +283,47 @@
         info.setImportantForAccessibility(true);
         info.setScreenReaderFocusable(true);
 
-        // 2 Boolean properties, for a total of 22
+        // 3 Boolean properties, for a total of 23
         info.setShowingHintText(true);
         info.setHeading(true);
+        info.setTextEntryKey(true);
+    }
+
+    /**
+     * Populates touch delegate target map.
+     */
+    private void populateTouchDelegateTargetMap(AccessibilityNodeInfo info) {
+        final ArrayMap<Region, View> targetMap = new ArrayMap<>(3);
+        final Rect rect1 = new Rect(1, 1, 10, 10);
+        final Rect rect2 = new Rect(2, 2, 20, 20);
+        final Rect rect3 = new Rect(3, 3, 30, 30);
+        targetMap.put(new Region(rect1), new View(getContext()));
+        targetMap.put(new Region(rect2), new View(getContext()));
+        targetMap.put(new Region(rect3), new View(getContext()));
+
+        final TouchDelegateInfo touchDelegateInfo = new TouchDelegateInfo(targetMap);
+        info.setTouchDelegateInfo(touchDelegateInfo);
+    }
+
+    private static void assertEqualsTouchDelegateInfo(String message,
+            AccessibilityNodeInfo.TouchDelegateInfo expected,
+            AccessibilityNodeInfo.TouchDelegateInfo actual) {
+        if (expected == actual) return;
+        assertEquals(message, expected.getRegionCount(), actual.getRegionCount());
+        for (int i = 0; i < expected.getRegionCount(); i++) {
+            final Region expectedRegion = expected.getRegionAt(i);
+            final Long expectedId = expected.getAccessibilityIdForRegion(expectedRegion);
+            boolean matched = false;
+            for (int j = 0; j < actual.getRegionCount(); j++) {
+                final Region actualRegion = actual.getRegionAt(j);
+                final Long actualId = actual.getAccessibilityIdForRegion(actualRegion);
+                if (expectedRegion.equals(actualRegion) && expectedId.equals(actualId)) {
+                    matched = true;
+                    break;
+                }
+            }
+            assertTrue(message, matched);
+        }
     }
 
     /**
@@ -400,6 +442,10 @@
                     receivedItemInfo.getRowSpan());
         }
 
+        assertEqualsTouchDelegateInfo("TouchDelegate target map has incorrect value",
+                expectedInfo.getTouchDelegateInfo(),
+                receivedInfo.getTouchDelegateInfo());
+
         // And the boolean properties are another field, for a total of 26
         // Missing parent: Tested end-to-end in AccessibilityWindowTraversalTest#testObjectContract
         //                 (getting a child is also checked there)
@@ -459,11 +505,13 @@
         assertSame("isScreenReaderFocusable has incorrect value",
                 expectedInfo.isScreenReaderFocusable(), receivedInfo.isScreenReaderFocusable());
 
-        // 2 Boolean properties, for a total of 22
+        // 3 Boolean properties, for a total of 23
         assertSame("isShowingHint has incorrect value",
                 expectedInfo.isShowingHintText(), receivedInfo.isShowingHintText());
         assertSame("isHeading has incorrect value",
                 expectedInfo.isHeading(), receivedInfo.isHeading());
+        assertSame("isTextEntryKey has incorrect value",
+                expectedInfo.isTextEntryKey(), receivedInfo.isTextEntryKey());
     }
 
     /**
@@ -503,12 +551,13 @@
         assertEquals("Text selection end not properly recycled", -1, info.getTextSelectionEnd());
         assertEquals("Live region not properly recycled", 0, info.getLiveRegion());
 
-        // Check 5 fields
+        // Check 6 fields
         assertEquals("Extras not properly recycled", 0, info.getExtras().keySet().size());
         assertEquals("Input type not properly recycled", 0, info.getInputType());
         assertNull("Range info not properly recycled", info.getRangeInfo());
         assertNull("Collection info not properly recycled", info.getCollectionInfo());
         assertNull("Collection item info not properly recycled", info.getCollectionItemInfo());
+        assertNull("TouchDelegate target map not recycled", info.getTouchDelegateInfo());
 
         // And Boolean properties brings up to 26 fields
         // Missing:
@@ -546,8 +595,9 @@
                 info.isImportantForAccessibility());
         assertFalse("ScreenReaderFocusable not properly recycled", info.isScreenReaderFocusable());
 
-        // 2 Boolean properties
+        // 3 Boolean properties
         assertFalse("isShowingHint not properly reset", info.isShowingHintText());
         assertFalse("isHeading not properly reset", info.isHeading());
+        assertFalse("isTextEntryKey not properly reset", info.isTextEntryKey());
     }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
index 799a43b..cf3fb15 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
@@ -60,11 +60,12 @@
         AccessibilityServiceInfo speakingService = enabledServices.get(0);
         assertSame(AccessibilityEvent.TYPES_ALL_MASK, speakingService.eventTypes);
         assertSame(AccessibilityServiceInfo.FEEDBACK_SPOKEN, speakingService.feedbackType);
-        assertSame(AccessibilityServiceInfo.DEFAULT
+        assertEquals(AccessibilityServiceInfo.DEFAULT
                 | AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
                 | AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
                 | AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS
-                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS,
+                | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
+                | AccessibilityServiceInfo.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK,
                 speakingService.flags);
         assertSame(0l, speakingService.notificationTimeout);
         assertEquals("Some description", speakingService.getDescription());
@@ -80,5 +81,7 @@
         assertEquals("Some summary", speakingService.loadSummary(
                 getInstrumentation().getContext().getPackageManager()));
         assertNotNull(speakingService.getResolveInfo());
+        assertEquals(6000, speakingService.getInteractiveUiTimeoutMillis());
+        assertEquals(1000, speakingService.getNonInteractiveUiTimeoutMillis());
     }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/DummyActivity.java b/tests/accessibility/src/android/view/accessibility/cts/DummyActivity.java
new file mode 100644
index 0000000..b184fe8
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/DummyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.view.accessibility.cts;
+
+import android.app.Activity;
+
+public class DummyActivity extends Activity {
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
index 5e8755f..437c35e 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
@@ -16,16 +16,13 @@
 
 package android.view.accessibility.cts;
 
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertThat;
+import static com.android.compatibility.common.util.TestUtils.waitOn;
 
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.view.accessibility.AccessibilityManager;
 
@@ -33,6 +30,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
 
 /**
  * Utility methods for enabling and disabling the services used in this package
@@ -60,9 +58,7 @@
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
 
         // Change the settings to enable the two services
-        ContentResolver cr = context.getContentResolver();
-        String alreadyEnabledServices = Settings.Secure.getString(
-                cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        String alreadyEnabledServices = getEnabledServices(context.getContentResolver());
         ParcelFileDescriptor fd = uiAutomation.executeShellCommand("settings --user cur put secure "
                 + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " "
                 + alreadyEnabledServices + ":"
@@ -73,42 +69,14 @@
         uiAutomation.destroy();
 
         // Wait for speaking service to be connected
-        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
-        boolean speakingServiceStarted = false;
-        while (!speakingServiceStarted && (SystemClock.uptimeMillis() < timeoutTimeMillis)) {
-            synchronized (SpeakingAccessibilityService.sWaitObjectForConnecting) {
-                if (SpeakingAccessibilityService.sConnectedInstance != null) {
-                    speakingServiceStarted = true;
-                    break;
-                }
-                if (!speakingServiceStarted) {
-                    try {
-                        SpeakingAccessibilityService.sWaitObjectForConnecting.wait(
-                                timeoutTimeMillis - SystemClock.uptimeMillis());
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-        }
-        if (!speakingServiceStarted) {
-            throw new RuntimeException("Speaking accessibility service not starting");
-        }
+        waitOn(SpeakingAccessibilityService.sWaitObjectForConnecting,
+                () -> SpeakingAccessibilityService.sConnectedInstance != null,
+                TIMEOUT_FOR_SERVICE_ENABLE, "Speaking accessibility service starts up");
 
         // Wait for vibrating service to be connected
-        while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized (VibratingAccessibilityService.sWaitObjectForConnecting) {
-                if (VibratingAccessibilityService.sConnectedInstance != null) {
-                    return;
-                }
-
-                try {
-                    VibratingAccessibilityService.sWaitObjectForConnecting.wait(
-                            timeoutTimeMillis - SystemClock.uptimeMillis());
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-        throw new RuntimeException("Vibrating accessibility service not starting");
+        waitOn(VibratingAccessibilityService.sWaitObjectForConnecting,
+                () -> VibratingAccessibilityService.sConnectedInstance != null,
+                TIMEOUT_FOR_SERVICE_ENABLE, "Vibrating accessibility service starts up");
     }
 
     /**
@@ -125,9 +93,7 @@
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
 
         // Change the settings to enable the services
-        ContentResolver cr = context.getContentResolver();
-        String alreadyEnabledServices = Settings.Secure.getString(
-                cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        String alreadyEnabledServices = getEnabledServices(context.getContentResolver());
         ParcelFileDescriptor fd = uiAutomation.executeShellCommand("settings --user cur put secure "
                 + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " "
                 + alreadyEnabledServices + ":"
@@ -138,28 +104,10 @@
         uiAutomation.destroy();
 
         // Wait for the service to be connected
-        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
-        boolean multipleFeedbackTypesServiceEnabled = false;
-        while (!multipleFeedbackTypesServiceEnabled && (SystemClock.uptimeMillis()
-                < timeoutTimeMillis)) {
-            synchronized (SpeakingAndVibratingAccessibilityService.sWaitObjectForConnecting) {
-                if (SpeakingAndVibratingAccessibilityService.sConnectedInstance != null) {
-                    multipleFeedbackTypesServiceEnabled = true;
-                    break;
-                }
-                if (!multipleFeedbackTypesServiceEnabled) {
-                    try {
-                        SpeakingAndVibratingAccessibilityService.sWaitObjectForConnecting.wait(
-                                timeoutTimeMillis - SystemClock.uptimeMillis());
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-        }
-        if (!multipleFeedbackTypesServiceEnabled) {
-            throw new RuntimeException(
-                    "Multiple feedback types accessibility service not starting");
-        }
+        waitOn(SpeakingAndVibratingAccessibilityService.sWaitObjectForConnecting,
+                () -> SpeakingAndVibratingAccessibilityService.sConnectedInstance != null,
+                TIMEOUT_FOR_SERVICE_ENABLE,
+                "Multiple feedback types accessibility service starts up");
     }
 
     /**
@@ -195,23 +143,37 @@
             }
         };
         manager.addAccessibilityStateChangeListener(listener);
-        try {
-            long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
-            while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-                synchronized (waitLockForA11yOff) {
-                    if (!accessibilityEnabled.get()) {
-                        return;
-                    }
-                    try {
-                        waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
-                    } catch (InterruptedException e) {
-                        // Ignored; loop again
-                    }
-                }
+        waitOn(waitLockForA11yOff, () -> !accessibilityEnabled.get(), TIMEOUT_FOR_SERVICE_ENABLE,
+                "Accessibility turns off");
+    }
+
+    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.removeAccessibilityStateChangeListener(listener);
+            manager.removeAccessibilityServicesStateChangeListener(listener);
         }
-        assertThat("Unable to turn accessibility off", manager.isEnabled(), is(equalTo(false)));
     }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
index 219a2ff..19daf4b 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
@@ -18,12 +18,16 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Stub accessibility service that reports itself as providing spoken feedback.
  */
 public class SpeakingAccessibilityService extends AccessibilityService {
+    public static final ComponentName COMPONENT_NAME = new ComponentName(
+            "android.view.accessibility.cts",
+            "android.view.accessibility.cts.SpeakingAccessibilityService");
     public static Object sWaitObjectForConnecting = new Object();
 
     public static SpeakingAccessibilityService sConnectedInstance;
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 5214410..029dc51 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -30,38 +30,38 @@
         <activity
             android:label="@string/accessibility_end_to_end_test_activity"
             android:name=".activities.AccessibilityEndToEndActivity"
-            android:screenOrientation="portrait"/>
+            android:screenOrientation="locked"/>
 
         <activity
             android:label="@string/accessibility_query_window_test_activity"
             android:name=".activities.AccessibilityWindowQueryActivity"
             android:supportsPictureInPicture="true"
-            android:screenOrientation="portrait"/>
+            android:screenOrientation="locked"/>
 
         <activity
             android:label="@string/accessibility_view_tree_reporting_test_activity"
             android:name=".activities.AccessibilityViewTreeReportingActivity"
-            android:screenOrientation="portrait"/>
+            android:screenOrientation="locked"/>
 
         <activity
             android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
             android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity"
-            android:screenOrientation="portrait"/>
+            android:screenOrientation="locked"/>
 
         <activity
             android:label="@string/accessibility_text_traversal_test_activity"
             android:name=".activities.AccessibilityTextTraversalActivity"
-            android:screenOrientation="portrait"/>
+            android:screenOrientation="locked"/>
 
         <activity android:label="Activity for testing window accessibility reporting"
              android:name=".activities.AccessibilityWindowReportingActivity"
              android:supportsPictureInPicture="true"
-             android:screenOrientation="portrait"/>
+             android:screenOrientation="locked"/>
 
         <activity
             android:label="Full screen activity for gesture dispatch testing"
             android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"
-            android:screenOrientation="portrait" />
+            android:screenOrientation="locked" />
 
         <activity
             android:label="@string/accessibility_soft_keyboard_modes_activity"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index 20a8bff..eb75538 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS accessibility service 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="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <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" />
diff --git a/tests/accessibilityservice/res/layout/end_to_end_test.xml b/tests/accessibilityservice/res/layout/end_to_end_test.xml
index a6c738c..475ccff 100644
--- a/tests/accessibilityservice/res/layout/end_to_end_test.xml
+++ b/tests/accessibilityservice/res/layout/end_to_end_test.xml
@@ -34,7 +34,8 @@
               android:accessibilityTraversalAfter="@+id/buttonWithTooltip">
     </EditText>
 
-    <LinearLayout android:layout_width="fill_parent"
+    <LinearLayout android:id="@+id/buttonLayout"
+              android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:gravity="center">
         <Button android:id="@+id/button"
@@ -42,6 +43,7 @@
                 android:accessibilityPaneTitle="@string/paneTitle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_marginRight="60dp"
                 android:bufferType="normal">
         </Button>
         <Button android:id="@id/buttonWithTooltip"
@@ -54,4 +56,18 @@
         </Button>
     </LinearLayout>
 
+    <LinearLayout android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:gravity="center">
+        <TextView android:id="@+id/delegateText"
+                  android:text="@string/text_input_blah"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        </TextView>
+        <Button android:id="@+id/buttonDelegated"
+                android:text="@string/button_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+        </Button>
+    </LinearLayout>
 </LinearLayout>
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java
deleted file mode 100644
index 282e624..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityActivityTestCase.java
+++ /dev/null
@@ -1,120 +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.accessibilityservice.cts;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.app.Activity;
-import android.app.UiAutomation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Base text case for testing accessibility APIs by instrumenting an Activity.
- */
-public abstract class AccessibilityActivityTestCase<T extends Activity>
-        extends ActivityInstrumentationTestCase2<T> {
-    /**
-     * Timeout required for pending Binder calls or event processing to
-     * complete.
-     */
-    public static final long TIMEOUT_ASYNC_PROCESSING = 5000;
-
-    /**
-     * The timeout since the last accessibility event to consider the device idle.
-     */
-    public static final long TIMEOUT_ACCESSIBILITY_STATE_IDLE = 500;
-
-    /**
-     * @param activityClass
-     */
-    public AccessibilityActivityTestCase(Class<T> activityClass) {
-        super(activityClass);
-    }
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
-        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
-        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-        getInstrumentation().getUiAutomation().setServiceInfo(info);
-
-        startActivityAndWaitForFirstEvent();
-
-        waitForIdle();
-    }
-
-    /**
-     * Waits for the UI do be idle.
-     *
-     * @throws TimeoutException if idle cannot be detected.
-     */
-    public void waitForIdle() throws TimeoutException {
-        getInstrumentation().getUiAutomation().waitForIdle(
-                TIMEOUT_ACCESSIBILITY_STATE_IDLE,
-                TIMEOUT_ASYNC_PROCESSING);
-    }
-
-    /**
-     * @return The string for a given <code>resId</code>.
-     */
-    public String getString(int resId) {
-        return getInstrumentation().getContext().getString(resId);
-    }
-
-    /**
-     * Starts the activity under tests and waits for the first accessibility
-     * event from that activity.
-     */
-    private void startActivityAndWaitForFirstEvent() throws Exception {
-        AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
-                new Runnable() {
-            @Override
-            public void run() {
-                getActivity();
-                getInstrumentation().waitForIdleSync();
-            }
-        },
-                new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                List<AccessibilityWindowInfo> windows = getInstrumentation().getUiAutomation()
-                        .getWindows();
-                // Wait for a window state changed event with our window showing
-                for (int i = 0; i < windows.size(); i++) {
-                    AccessibilityNodeInfo root = windows.get(i).getRoot();
-                    if ((root != null) &&
-                            root.getPackageName().equals(getActivity().getPackageName())) {
-                        return (event.getEventType()
-                                == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-                    }
-                }
-                return false;
-            }
-        },
-        TIMEOUT_ASYNC_PROCESSING);
-        assertNotNull(awaitedEvent);
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index d535fab..c86cbdf 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,21 +16,36 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils
-        .filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.accessibilityservice.cts.utils.RunOnMainUtils.getOnMain;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
-        .ACTION_HIDE_TOOLTIP;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
-        .ACTION_SHOW_TOOLTIP;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP;
 
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNull.nullValue;
-import static org.hamcrest.core.IsNull.notNullValue;
 import static org.hamcrest.Matchers.in;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertEquals;
+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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -49,31 +64,54 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Process;
+import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
 import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ListView;
 
+import com.android.compatibility.common.util.CtsMouseUtil;
+
+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.Iterator;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This class performs end-to-end testing of the accessibility feature by
  * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
  * are generated and their correct dispatch verified.
  */
-public class AccessibilityEndToEndTest extends
-        AccessibilityActivityTestCase<AccessibilityEndToEndActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEndToEndTest {
 
     private static final String LOG_TAG = "AccessibilityEndToEndTest";
 
@@ -85,22 +123,42 @@
 
     private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz";
 
-    /**
-     * Creates a new instance for testing {@link AccessibilityEndToEndActivity}.
-     */
-    public AccessibilityEndToEndTest() {
-        super(AccessibilityEndToEndActivity.class);
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private AccessibilityEndToEndActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeViewSelectedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
         expected.setClassName(ListView.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
-        expected.getText().add(getActivity().getString(R.string.second_list_item));
+        expected.setPackageName(mActivity.getPackageName());
+        expected.getText().add(mActivity.getString(R.string.second_list_item));
         expected.setItemCount(2);
         expected.setCurrentItemIndex(1);
         expected.setEnabled(true);
@@ -108,15 +166,15 @@
         expected.setFromIndex(0);
         expected.setToIndex(1);
 
-        final ListView listView = (ListView) getActivity().findViewById(R.id.listview);
+        final ListView listView = (ListView) mActivity.findViewById(R.id.listview);
 
         AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         listView.setSelection(1);
@@ -130,30 +188,31 @@
                     return equalsAccessiblityEvent(event, expected);
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeViewClickedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
         expected.setClassName(Button.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
-        expected.getText().add(getActivity().getString(R.string.button_title));
+        expected.setPackageName(mActivity.getPackageName());
+        expected.getText().add(mActivity.getString(R.string.button_title));
         expected.setEnabled(true);
 
-        final Button button = (Button) getActivity().findViewById(R.id.button);
+        final Button button = (Button) mActivity.findViewById(R.id.button);
 
         AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         button.performClick();
@@ -167,30 +226,31 @@
                     return equalsAccessiblityEvent(event, expected);
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeViewLongClickedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
         expected.setClassName(Button.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
-        expected.getText().add(getActivity().getString(R.string.button_title));
+        expected.setPackageName(mActivity.getPackageName());
+        expected.getText().add(mActivity.getString(R.string.button_title));
         expected.setEnabled(true);
 
-        final Button button = (Button) getActivity().findViewById(R.id.button);
+        final Button button = (Button) mActivity.findViewById(R.id.button);
 
         AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         button.performLongClick();
@@ -204,46 +264,48 @@
                     return equalsAccessiblityEvent(event, expected);
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeViewFocusedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         expected.setClassName(Button.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
-        expected.getText().add(getActivity().getString(R.string.button_title));
-        expected.setItemCount(4);
+        expected.setPackageName(mActivity.getPackageName());
+        expected.getText().add(mActivity.getString(R.string.button_title));
+        expected.setItemCount(5);
         expected.setCurrentItemIndex(3);
         expected.setEnabled(true);
 
-        final Button button = (Button) getActivity().findViewById(R.id.buttonWithTooltip);
+        final Button button = (Button) mActivity.findViewById(R.id.buttonWithTooltip);
 
         AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
-                    () -> getActivity().runOnUiThread(() -> button.requestFocus()),
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> mActivity.runOnUiThread(() -> button.requestFocus()),
                     (event) -> equalsAccessiblityEvent(event, expected),
-                    TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeViewTextChangedAccessibilityEvent() throws Throwable {
         // focus the edit text
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edittext);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edittext);
 
         AccessibilityEvent awaitedFocusEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         editText.requestFocus();
@@ -257,18 +319,18 @@
                     return event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED;
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected focuss event.", awaitedFocusEvent);
 
-        final String beforeText = getActivity().getString(R.string.text_input_blah);
-        final String newText = getActivity().getString(R.string.text_input_blah_blah);
+        final String beforeText = mActivity.getString(R.string.text_input_blah);
+        final String newText = mActivity.getString(R.string.text_input_blah_blah);
         final String afterText = beforeText.substring(0, 3) + newText;
 
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
         expected.setClassName(EditText.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
+        expected.setPackageName(mActivity.getPackageName());
         expected.getText().add(afterText);
         expected.setBeforeText(beforeText);
         expected.setFromIndex(3);
@@ -277,12 +339,12 @@
         expected.setEnabled(true);
 
         AccessibilityEvent awaitedTextChangeEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         editText.getEditableText().replace(3, 4, newText);
@@ -296,32 +358,33 @@
                     return equalsAccessiblityEvent(event, expected);
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedTextChangeEvent);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTypeWindowStateChangedAccessibilityEvent() throws Throwable {
         // create and populate the expected event
         final AccessibilityEvent expected = AccessibilityEvent.obtain();
         expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         expected.setClassName(AlertDialog.class.getName());
-        expected.setPackageName(getActivity().getPackageName());
-        expected.getText().add(getActivity().getString(R.string.alert_title));
-        expected.getText().add(getActivity().getString(R.string.alert_message));
+        expected.setPackageName(mActivity.getPackageName());
+        expected.getText().add(mActivity.getString(R.string.alert_title));
+        expected.getText().add(mActivity.getString(R.string.alert_message));
         expected.setEnabled(true);
 
         AccessibilityEvent awaitedEvent =
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+            sUiAutomation.executeAndWaitForEvent(
                 new Runnable() {
             @Override
             public void run() {
                 // trigger the event
-                getActivity().runOnUiThread(new Runnable() {
+                mActivity.runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        (new AlertDialog.Builder(getActivity()).setTitle(R.string.alert_title)
+                        (new AlertDialog.Builder(mActivity).setTitle(R.string.alert_title)
                                 .setMessage(R.string.alert_message)).create().show();
                     }
                 });
@@ -333,7 +396,7 @@
                     return equalsAccessiblityEvent(event, expected);
                 }
             },
-            TIMEOUT_ASYNC_PROCESSING);
+                    DEFAULT_TIMEOUT_MS);
         assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
     }
 
@@ -341,25 +404,26 @@
     @AppModeFull
     @SuppressWarnings("deprecation")
     @Presubmit
+    @Test
     public void testTypeNotificationStateChangedAccessibilityEvent() throws Throwable {
         // No notification UI on televisions.
-        if ((getActivity().getResources().getConfiguration().uiMode
+        if ((mActivity.getResources().getConfiguration().uiMode
                 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
             Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" +
                     " - No notification UI on televisions.");
             return;
         }
-        PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
+        PackageManager pm = sInstrumentation.getTargetContext().getPackageManager();
         if (pm.hasSystemFeature(pm.FEATURE_WATCH)) {
             Log.i(LOG_TAG, "Skipping: testTypeNotificationStateChangedAccessibilityEvent" +
                     " - Watches have different notification system.");
             return;
         }
 
-        String message = getActivity().getString(R.string.notification_message);
+        String message = mActivity.getString(R.string.notification_message);
 
         final NotificationManager notificationManager =
-                (NotificationManager) getActivity().getSystemService(Service.NOTIFICATION_SERVICE);
+                (NotificationManager) mActivity.getSystemService(Service.NOTIFICATION_SERVICE);
         final NotificationChannel channel =
                 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
         try {
@@ -372,9 +436,9 @@
                     notificationManager.getNotificationChannel(channel.getId());
             final int notificationId = 1;
             final Notification notification =
-                    new Notification.Builder(getActivity(), channel.getId())
+                    new Notification.Builder(mActivity, channel.getId())
                             .setSmallIcon(android.R.drawable.stat_notify_call_mute)
-                            .setContentIntent(PendingIntent.getActivity(getActivity(), 0,
+                            .setContentIntent(PendingIntent.getActivity(mActivity, 0,
                                     new Intent(),
                                     PendingIntent.FLAG_CANCEL_CURRENT))
                             .setTicker(message)
@@ -390,23 +454,23 @@
             final AccessibilityEvent expected = AccessibilityEvent.obtain();
             expected.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
             expected.setClassName(Notification.class.getName());
-            expected.setPackageName(getActivity().getPackageName());
+            expected.setPackageName(mActivity.getPackageName());
             expected.getText().add(message);
             expected.setParcelableData(notification);
 
             AccessibilityEvent awaitedEvent =
-                    getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                    sUiAutomation.executeAndWaitForEvent(
                             new Runnable() {
                                 @Override
                                 public void run() {
                                     // trigger the event
-                                    getActivity().runOnUiThread(new Runnable() {
+                                    mActivity.runOnUiThread(new Runnable() {
                                         @Override
                                         public void run() {
                                             // trigger the event
                                             notificationManager
                                                     .notify(notificationId, notification);
-                                            getActivity().finish();
+                                            mActivity.finish();
                                         }
                                     });
                                 }
@@ -418,7 +482,7 @@
                                     return equalsAccessiblityEvent(event, expected);
                                 }
                             },
-                            TIMEOUT_ASYNC_PROCESSING);
+                            DEFAULT_TIMEOUT_MS);
             assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
         } finally {
             notificationManager.deleteNotificationChannel(channel.getId());
@@ -426,16 +490,17 @@
     }
 
     @MediumTest
+    @Test
     public void testInterrupt_notifiesService() {
-        getInstrumentation()
+        sInstrumentation
                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                getInstrumentation(), InstrumentedAccessibilityService.class);
+                sInstrumentation, InstrumentedAccessibilityService.class);
         try {
             assertFalse(service.wasOnInterruptCalled());
 
-            getActivity().runOnUiThread(() -> {
-                AccessibilityManager accessibilityManager = (AccessibilityManager) getActivity()
+            mActivity.runOnUiThread(() -> {
+                AccessibilityManager accessibilityManager = (AccessibilityManager) mActivity
                         .getSystemService(Service.ACCESSIBILITY_SERVICE);
                 accessibilityManager.interrupt();
             });
@@ -444,7 +509,7 @@
             synchronized (waitObject) {
                 if (!service.wasOnInterruptCalled()) {
                     try {
-                        waitObject.wait(TIMEOUT_ASYNC_PROCESSING);
+                        waitObject.wait(DEFAULT_TIMEOUT_MS);
                     } catch (InterruptedException e) {
                         // Do nothing
                     }
@@ -457,26 +522,27 @@
     }
 
     @MediumTest
+    @Test
     public void testPackageNameCannotBeFaked() throws Exception {
-        getActivity().runOnUiThread(() -> {
+        mActivity.runOnUiThread(() -> {
             // Set the activity to report fake package for events and nodes
-            getActivity().setReportedPackageName("foo.bar.baz");
+            mActivity.setReportedPackageName("foo.bar.baz");
 
             // Make sure node package cannot be faked
-            AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+            AccessibilityNodeInfo root = sUiAutomation
                     .getRootInActiveWindow();
-            assertPackageName(root, getActivity().getPackageName());
+            assertPackageName(root, mActivity.getPackageName());
         });
 
         // Make sure event package cannot be faked
         try {
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
-                getInstrumentation().runOnMainSync(() ->
-                    getActivity().findViewById(R.id.button).requestFocus())
+            sUiAutomation.executeAndWaitForEvent(() ->
+                sInstrumentation.runOnMainSync(() ->
+                    mActivity.findViewById(R.id.button).requestFocus())
                 , (AccessibilityEvent event) ->
                     event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
-                            && event.getPackageName().equals(getActivity().getPackageName())
-                , TIMEOUT_ASYNC_PROCESSING);
+                            && event.getPackageName().equals(mActivity.getPackageName())
+                , DEFAULT_TIMEOUT_MS);
         } catch (TimeoutException e) {
             fail("Events from fake package should be fixed to use the correct package");
         }
@@ -485,36 +551,37 @@
     @AppModeFull
     @MediumTest
     @Presubmit
+    @Test
     public void testPackageNameCannotBeFakedAppWidget() throws Exception {
         if (!hasAppWidgets()) {
             return;
         }
 
-        getInstrumentation().runOnMainSync(() -> {
+        sInstrumentation.runOnMainSync(() -> {
             // Set the activity to report fake package for events and nodes
-            getActivity().setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE);
+            mActivity.setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE);
 
             // Make sure we cannot report nodes as if from the widget package
-            AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+            AccessibilityNodeInfo root = sUiAutomation
                     .getRootInActiveWindow();
-            assertPackageName(root, getActivity().getPackageName());
+            assertPackageName(root, mActivity.getPackageName());
         });
 
         // Make sure we cannot send events as if from the widget package
         try {
-            getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
-                getInstrumentation().runOnMainSync(() ->
-                    getActivity().findViewById(R.id.button).requestFocus())
+            sUiAutomation.executeAndWaitForEvent(() ->
+                sInstrumentation.runOnMainSync(() ->
+                    mActivity.findViewById(R.id.button).requestFocus())
                 , (AccessibilityEvent event) ->
                     event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
-                            && event.getPackageName().equals(getActivity().getPackageName())
-                , TIMEOUT_ASYNC_PROCESSING);
+                            && event.getPackageName().equals(mActivity.getPackageName())
+                , DEFAULT_TIMEOUT_MS);
         } catch (TimeoutException e) {
             fail("Should not be able to send events from a widget package if no widget hosted");
         }
 
         // Create a host and start listening.
-        final AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
+        final AppWidgetHost host = new AppWidgetHost(sInstrumentation.getTargetContext(), 0);
         host.deleteHost();
         host.startListening();
 
@@ -534,22 +601,22 @@
             assertTrue(widgetBound);
 
             // Make sure the app can use the package of a widget it hosts
-            getInstrumentation().runOnMainSync(() -> {
+            sInstrumentation.runOnMainSync(() -> {
                 // Make sure we can report nodes as if from the widget package
-                AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+                AccessibilityNodeInfo root = sUiAutomation
                         .getRootInActiveWindow();
                 assertPackageName(root, APP_WIDGET_PROVIDER_PACKAGE);
             });
 
             // Make sure we can send events as if from the widget package
             try {
-                getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
-                    getInstrumentation().runOnMainSync(() ->
-                        getActivity().findViewById(R.id.button).performClick())
+                sUiAutomation.executeAndWaitForEvent(() ->
+                    sInstrumentation.runOnMainSync(() ->
+                        mActivity.findViewById(R.id.button).performClick())
                     , (AccessibilityEvent event) ->
                             event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED
                                     && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE)
-                    , TIMEOUT_ASYNC_PROCESSING);
+                    , DEFAULT_TIMEOUT_MS);
             } catch (TimeoutException e) {
                 fail("Should be able to send events from a widget package if widget hosted");
             }
@@ -563,29 +630,28 @@
 
     @MediumTest
     @Presubmit
+    @Test
     public void testViewHeadingReportedToAccessibility() throws Exception {
-        final Instrumentation instrumentation = getInstrumentation();
-        final EditText editText = (EditText) getOnMain(instrumentation, () -> {
-            return getActivity().findViewById(R.id.edittext);
+        final EditText editText = (EditText) getOnMain(sInstrumentation, () -> {
+            return mActivity.findViewById(R.id.edittext);
         });
         // Make sure the edittext was populated properly from xml
-        final boolean editTextIsHeading = getOnMain(instrumentation, () -> {
+        final boolean editTextIsHeading = getOnMain(sInstrumentation, () -> {
             return editText.isAccessibilityHeading();
         });
         assertTrue("isAccessibilityHeading not populated properly from xml", editTextIsHeading);
 
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo editTextNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo editTextNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/edittext")
                 .get(0);
         assertTrue("isAccessibilityHeading not reported to accessibility",
                 editTextNode.isHeading());
 
-        uiAutomation.executeAndWaitForEvent(() -> instrumentation.runOnMainSync(() ->
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() ->
                         editText.setAccessibilityHeading(false)),
                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
         editTextNode.refresh();
         assertFalse("isAccessibilityHeading not reported to accessibility after update",
                 editTextNode.isHeading());
@@ -593,33 +659,31 @@
 
     @MediumTest
     @Presubmit
+    @Test
     public void testTooltipTextReportedToAccessibility() {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/buttonWithTooltip")
                 .get(0);
         assertEquals("Tooltip text not reported to accessibility",
-                instrumentation.getContext().getString(R.string.button_tooltip),
+                sInstrumentation.getContext().getString(R.string.button_tooltip),
                 buttonNode.getTooltipText());
     }
 
     @MediumTest
+    @Test
     public void testTooltipTextActionsReportedToAccessibility() throws Exception {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/buttonWithTooltip")
                 .get(0);
         assertFalse(hasTooltipShowing(R.id.buttonWithTooltip));
         assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList()));
         assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList())));
-        uiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction(
+        sUiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction(
                 ACTION_SHOW_TOOLTIP.getId()),
                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
 
         // The button should now be showing the tooltip, so it should have the option to hide it.
         buttonNode.refresh();
@@ -629,10 +693,9 @@
     }
 
     @MediumTest
+    @Test
     public void testTraversalBeforeReportedToAccessibility() throws Exception {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/buttonWithTooltip")
                 .get(0);
@@ -641,21 +704,20 @@
         assertThat(beforeNode.getViewIdResourceName(),
                 equalTo("android.accessibilityservice.cts:id/edittext"));
 
-        uiAutomation.executeAndWaitForEvent(() -> instrumentation.runOnMainSync(
-                () -> getActivity().findViewById(R.id.buttonWithTooltip)
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.findViewById(R.id.buttonWithTooltip)
                         .setAccessibilityTraversalBefore(View.NO_ID)),
                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
 
         buttonNode.refresh();
         assertThat(buttonNode.getTraversalBefore(), nullValue());
     }
 
     @MediumTest
+    @Test
     public void testTraversalAfterReportedToAccessibility() throws Exception {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo editNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/edittext")
                 .get(0);
@@ -664,28 +726,27 @@
         assertThat(afterNode.getViewIdResourceName(),
                 equalTo("android.accessibilityservice.cts:id/buttonWithTooltip"));
 
-        uiAutomation.executeAndWaitForEvent(() -> instrumentation.runOnMainSync(
-                () -> getActivity().findViewById(R.id.edittext)
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.findViewById(R.id.edittext)
                         .setAccessibilityTraversalAfter(View.NO_ID)),
                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
 
         editNode.refresh();
         assertThat(editNode.getTraversalAfter(), nullValue());
     }
 
     @MediumTest
+    @Test
     public void testLabelForReportedToAccessibility() throws Exception {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        uiAutomation.executeAndWaitForEvent(() -> instrumentation.runOnMainSync(() -> getActivity()
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(() -> mActivity
                 .findViewById(R.id.edittext).setLabelFor(R.id.buttonWithTooltip)),
                 filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
         // TODO: b/78022650: This code should move above the executeAndWait event. It's here because
         // the a11y cache doesn't get notified when labelFor changes, so the node with the
         // labledBy isn't updated.
-        final AccessibilityNodeInfo editNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo editNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/edittext")
                 .get(0);
@@ -696,6 +757,203 @@
         assertThat(labelForNode.getLabeledBy(), equalTo(editNode));
     }
 
+    @MediumTest
+    @Test
+    public void testA11yActionTriggerMotionEventActionOutside() throws Exception {
+        final View.OnTouchListener listener = mock(View.OnTouchListener.class);
+        final AccessibilityNodeInfo button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button")
+                .get(0);
+        final String title = sInstrumentation.getContext().getString(R.string.alert_title);
+
+        // Add a dialog that is watching outside touch
+        sUiAutomation.executeAndWaitForEvent(
+                () -> sInstrumentation.runOnMainSync(() -> {
+                            final AlertDialog dialog = new AlertDialog.Builder(mActivity)
+                                    .setTitle(R.string.alert_title)
+                                    .setMessage(R.string.alert_message)
+                                    .create();
+                            final Window window = dialog.getWindow();
+                            window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+                            window.getDecorView().setOnTouchListener(listener);
+                            window.setTitle(title);
+                            dialog.show();
+                    }),
+                (event) -> {
+                    // Ensure the dialog is shown over the activity
+                    final AccessibilityWindowInfo dialog = findWindowByTitle(
+                            sUiAutomation, title);
+                    final AccessibilityWindowInfo activity = findWindowByTitle(
+                            sUiAutomation, getActivityTitle(sInstrumentation, mActivity));
+                    return (dialog != null && activity != null)
+                            && (dialog.getLayer() > activity.getLayer());
+                }, DEFAULT_TIMEOUT_MS);
+
+        // Perform an action and wait for an event
+        sUiAutomation.executeAndWaitForEvent(
+                () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK),
+                filterForEventType(AccessibilityEvent.TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+
+        // Make sure the MotionEvent.ACTION_OUTSIDE is received.
+        verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class),
+                argThat(event -> event.getActionMasked() == MotionEvent.ACTION_OUTSIDE));
+    }
+
+    @MediumTest
+    @Test
+    public void testTouchDelegateInfoReportedToAccessibility() {
+        final Button button = getOnMain(sInstrumentation, () -> mActivity.findViewById(
+                R.id.button));
+        final View parent = (View) button.getParent();
+        final Rect rect = new Rect();
+        button.getHitRect(rect);
+        parent.setTouchDelegate(new TouchDelegate(rect, button));
+
+        final AccessibilityNodeInfo nodeInfo = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/buttonLayout")
+                .get(0);
+        AccessibilityNodeInfo.TouchDelegateInfo targetMapInfo =
+                nodeInfo.getTouchDelegateInfo();
+        assertNotNull("Did not receive TouchDelegate target map", targetMapInfo);
+        assertEquals("Incorrect target map size", 1, targetMapInfo.getRegionCount());
+        assertEquals("Incorrect target map region", new Region(rect),
+                targetMapInfo.getRegionAt(0));
+        final AccessibilityNodeInfo node = targetMapInfo.getTargetForRegion(
+                targetMapInfo.getRegionAt(0));
+        assertEquals("Incorrect target map view",
+                "android.accessibilityservice.cts:id/button",
+                node.getViewIdResourceName());
+        node.recycle();
+    }
+
+    @MediumTest
+    @Test
+    public void testTouchDelegateWithEbtBetweenView_ReHoverDelegate_FocusTargetAgain()
+            throws Throwable {
+        mActivity.waitForEnterAnimationComplete();
+
+        final Resources resources = sInstrumentation.getTargetContext().getResources();
+        final String buttonResourceName = resources.getResourceName(R.id.button);
+        final Button button = mActivity.findViewById(R.id.button);
+        final int[] buttonLocation = new int[2];
+        button.getLocationOnScreen(buttonLocation);
+        final int buttonX = button.getWidth() / 2;
+        final int buttonY = button.getHeight() / 2;
+        final int hoverY = buttonLocation[1] + buttonY;
+        final Button buttonWithTooltip = mActivity.findViewById(R.id.buttonWithTooltip);
+        final int touchableSize = 48;
+        final int hoverRight = buttonWithTooltip.getLeft() + touchableSize / 2;
+        final int hoverLeft = button.getRight() + touchableSize / 2;
+        final int hoverMiddle = (hoverLeft + hoverRight) / 2;
+        final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(button, false);
+        enableTouchExploration(sInstrumentation, true);
+
+        try {
+            // common downTime for touch explorer injected events
+            final long downTime = SystemClock.uptimeMillis();
+            // hover through delegate, parent, 2nd view, parent and delegate again
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> injectHoverEvent(downTime, false, hoverLeft, hoverY),
+                    filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                            buttonResourceName), DEFAULT_TIMEOUT_MS);
+            assertTrue(button.isHovered());
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        injectHoverEvent(downTime, true, hoverMiddle, hoverY);
+                        injectHoverEvent(downTime, true, hoverRight, hoverY);
+                        injectHoverEvent(downTime, true, hoverMiddle, hoverY);
+                        injectHoverEvent(downTime, true, hoverLeft, hoverY);
+                    },
+                    filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                            buttonResourceName), DEFAULT_TIMEOUT_MS);
+            // delegate target has a11y focus again
+            assertTrue(button.isHovered());
+
+            CtsMouseUtil.clearHoverListener(button);
+            View.OnHoverListener verifier = inOrder(listener).verify(listener);
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY));
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY));
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, hoverMiddle, buttonY));
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_EXIT, buttonX, buttonY));
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_ENTER, buttonX, buttonY));
+            verifier.onHover(eq(button),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, buttonX, buttonY));
+        } catch (TimeoutException e) {
+            fail("Accessibility events should be received as expected " + e.getMessage());
+        } finally {
+            enableTouchExploration(sInstrumentation, false);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testTouchDelegateCoverParentWithEbt_HoverChildAndBack_FocusTargetAgain()
+            throws Throwable {
+        mActivity.waitForEnterAnimationComplete();
+
+        final int touchableSize = 48;
+        final Resources resources = sInstrumentation.getTargetContext().getResources();
+        final String targetResourceName = resources.getResourceName(R.id.buttonDelegated);
+        final View textView = mActivity.findViewById(R.id.delegateText);
+        final Button target = mActivity.findViewById(R.id.buttonDelegated);
+        int[] location = new int[2];
+        textView.getLocationOnScreen(location);
+        final int textX = location[0] + touchableSize/2;
+        final int textY = location[1] + textView.getHeight() / 2;
+        final int delegateX = location[0] - touchableSize/2;
+        final int targetX = target.getWidth() / 2;
+        final int targetY = target.getHeight() / 2;
+        final View.OnHoverListener listener = CtsMouseUtil.installHoverListener(target, false);
+        enableTouchExploration(sInstrumentation, true);
+
+        try {
+            final long downTime = SystemClock.uptimeMillis();
+            // Like switch bar, it has a text view, a button and a delegate covers parent layout.
+            // hover the delegate, text and delegate again.
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> injectHoverEvent(downTime, false, delegateX, textY),
+                    filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                           targetResourceName), DEFAULT_TIMEOUT_MS);
+            assertTrue(target.isHovered());
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> injectHoverEvent(downTime, true, textX, textY),
+                    filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
+                           targetResourceName), DEFAULT_TIMEOUT_MS);
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> injectHoverEvent(downTime, true, delegateX, textY),
+                    filterForEventTypeWithResource(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+                           targetResourceName), DEFAULT_TIMEOUT_MS);
+            assertTrue(target.isHovered());
+
+            CtsMouseUtil.clearHoverListener(target);
+            View.OnHoverListener verifier = inOrder(listener).verify(listener);
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY));
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY));
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, textX, textY));
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_EXIT, targetX, targetY));
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_ENTER, targetX, targetY));
+            verifier.onHover(eq(target),
+                    matchHover(MotionEvent.ACTION_HOVER_MOVE, targetX, targetY));
+        } catch (TimeoutException e) {
+            fail("Accessibility events should be received as expected " + e.getMessage());
+        } finally {
+            enableTouchExploration(sInstrumentation, false);
+        }
+    }
+
     private static void assertPackageName(AccessibilityNodeInfo node, String packageName) {
         if (node == null) {
             return;
@@ -710,6 +968,66 @@
         }
     }
 
+    private static void enableTouchExploration(Instrumentation instrumentation, boolean enabled)
+            throws InterruptedException {
+        final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
+        final Object waitObject = new Object();
+        final AtomicBoolean atomicBoolean = new AtomicBoolean(!enabled);
+        AccessibilityManager.TouchExplorationStateChangeListener serviceListener = (boolean b) -> {
+            synchronized (waitObject) {
+                atomicBoolean.set(b);
+                waitObject.notifyAll();
+            }
+        };
+        final AccessibilityManager manager =
+                (AccessibilityManager) instrumentation.getContext().getSystemService(
+                        Service.ACCESSIBILITY_SERVICE);
+        manager.addTouchExplorationStateChangeListener(serviceListener);
+
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        final AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+        assert info != null;
+        if (enabled) {
+            info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        } else {
+            info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        }
+        uiAutomation.setServiceInfo(info);
+
+        final long timeoutTime = System.currentTimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
+        synchronized (waitObject) {
+            while ((enabled != atomicBoolean.get()) && (System.currentTimeMillis() < timeoutTime)) {
+                waitObject.wait(timeoutTime - System.currentTimeMillis());
+            }
+        }
+        if (enabled) {
+            assertTrue("Touch exploration state listener not called when services enabled",
+                    atomicBoolean.get());
+            assertTrue("Timed out enabling accessibility",
+                    manager.isEnabled() && manager.isTouchExplorationEnabled());
+        } else {
+            assertFalse("Touch exploration state listener not called when services disabled",
+                    atomicBoolean.get());
+            assertFalse("Timed out disabling accessibility",
+                    manager.isEnabled() && manager.isTouchExplorationEnabled());
+        }
+        manager.removeTouchExplorationStateChangeListener(serviceListener);
+    }
+
+    private static MotionEvent matchHover(int action, int x, int y) {
+        return argThat(new CtsMouseUtil.PositionMatcher(action, x, y));
+    }
+
+    private static void injectHoverEvent(long downTime, boolean isFirstHoverEvent,
+            int xOnScreen, int yOnScreen) {
+        final long eventTime = isFirstHoverEvent ? SystemClock.uptimeMillis() : downTime;
+        MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_HOVER_MOVE,
+                xOnScreen, yOnScreen, 0);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        sUiAutomation.injectInputEvent(event, true);
+        event.recycle();
+    }
+
     private AppWidgetProviderInfo getAppWidgetProviderInfo() {
         final ComponentName componentName = new ComponentName(
                 "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider");
@@ -726,22 +1044,22 @@
     }
 
     private void grantBindAppWidgetPermission() throws Exception {
-        ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+        ShellCommandBuilder.execShellCommand(sUiAutomation,
                 GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
     }
 
     private void revokeBindAppWidgetPermission() throws Exception {
-        ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+        ShellCommandBuilder.execShellCommand(sUiAutomation,
                 REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
     }
 
     private AppWidgetManager getAppWidgetManager() {
-        return (AppWidgetManager) getInstrumentation().getTargetContext()
+        return (AppWidgetManager) sInstrumentation.getTargetContext()
                 .getSystemService(Context.APPWIDGET_SERVICE);
     }
 
     private boolean hasAppWidgets() {
-        return getInstrumentation().getTargetContext().getPackageManager()
+        return sInstrumentation.getTargetContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
     }
 
@@ -807,8 +1125,8 @@
     }
 
     private boolean hasTooltipShowing(int id) {
-        return getOnMain(getInstrumentation(), () -> {
-            final View viewWithTooltip = getActivity().findViewById(id);
+        return getOnMain(sInstrumentation, () -> {
+            final View viewWithTooltip = mActivity.findViewById(id);
             if (viewWithTooltip == null) {
                 return false;
             }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 2043c33..06da001 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -14,14 +14,27 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+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.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.R;
 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -31,48 +44,74 @@
 import java.util.Queue;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+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;
+
 /**
  * Test cases for testing the accessibility focus APIs exposed to accessibility
  * services. These APIs allow moving accessibility focus in the view tree from
  * an AccessiiblityService. Specifically, this activity is for verifying the the
  * sync between accessibility and input focus.
  */
-public class AccessibilityFocusAndInputFocusSyncTest
-        extends AccessibilityActivityTestCase<AccessibilityFocusAndInputFocusSyncActivity>{
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityFocusAndInputFocusSyncTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
 
-    public AccessibilityFocusAndInputFocusSyncTest() {
-        super(AccessibilityFocusAndInputFocusSyncActivity.class);
+    private AccessibilityFocusAndInputFocusSyncActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+        sUiAutomation.setServiceInfo(info);
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
     }
 
     @MediumTest
     @Presubmit
+    @Test
     public void testFindAccessibilityFocus() throws Exception {
-        getInstrumentation().runOnMainSync(() -> {
-            getActivity().findViewById(R.id.firstEditText).requestFocus();
+        sInstrumentation.runOnMainSync(() -> {
+            mActivity.findViewById(R.id.firstEditText).requestFocus();
         });
         // Get the view that has input and accessibility focus.
-        final AccessibilityNodeInfo expected = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo expected = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.firstEditText)).get(0);
+                        sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
         assertNotNull(expected);
         assertFalse(expected.isAccessibilityFocused());
         assertTrue(expected.isFocused());
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a focus action and check for success.
-                assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)),
+                (event) ->
+                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                DEFAULT_TIMEOUT_MS);
 
         // Get the second expected node info.
-        AccessibilityNodeInfo received = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo received = sUiAutomation
                 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
         assertNotNull(received);
         assertTrue(received.isAccessibilityFocused());
@@ -83,34 +122,29 @@
 
     @MediumTest
     @Presubmit
+    @Test
     public void testInitialStateNoAccessibilityFocus() throws Exception {
         // Get the root which is only accessibility focused.
-        AccessibilityNodeInfo focused = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo focused = sUiAutomation
                 .getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
         assertNull(focused);
     }
 
     @MediumTest
+    @Test
     public void testActionAccessibilityFocus() throws Exception {
         // Get the root linear layout info.
-        final AccessibilityNodeInfo rootLinearLayout = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.rootLinearLayout)).get(0);
+                        sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
         assertFalse(rootLinearLayout.isAccessibilityFocused());
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a focus action and check for success.
-                assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
+                (event) ->
+                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
         rootLinearLayout.refresh();
@@ -121,25 +155,19 @@
 
     @MediumTest
     @Presubmit
+    @Test
     public void testActionClearAccessibilityFocus() throws Exception {
         // Get the root linear layout info.
-        final AccessibilityNodeInfo rootLinearLayout = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.rootLinearLayout)).get(0);
+                        sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a focus action and check for success.
-                assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
+                (event) ->
+                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
         rootLinearLayout.refresh();
@@ -147,19 +175,11 @@
         // Check if the node info is focused.
         assertTrue(rootLinearLayout.isAccessibilityFocused());
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a clear focus action and check for success.
-                assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType()
-                        == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
+                (event) -> event.getEventType()
+                        == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
         rootLinearLayout.refresh();
@@ -170,56 +190,42 @@
 
     @MediumTest
     @Presubmit
+    @Test
     public void testOnlyOneNodeHasAccessibilityFocus() throws Exception {
         // Get the first not focused edit text.
-        final AccessibilityNodeInfo firstEditText = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo firstEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.firstEditText)).get(0);
+                        sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
         assertNotNull(firstEditText);
         assertTrue(firstEditText.isFocusable());
         assertFalse(firstEditText.isAccessibilityFocused());
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a set focus action and check for success.
-                assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS));
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
+                (event) ->
+                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                DEFAULT_TIMEOUT_MS);
 
         // Get the second not focused edit text.
-        final AccessibilityNodeInfo secondEditText = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo secondEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.secondEditText)).get(0);
+                        sInstrumentation.getContext().getString(R.string.secondEditText)).get(0);
         assertNotNull(secondEditText);
         assertTrue(secondEditText.isFocusable());
         assertFalse(secondEditText.isFocused());
         assertFalse(secondEditText.isAccessibilityFocused());
 
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
-            @Override
-            public void run() {
-                // Perform a set focus action and check for success.
-                assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS));
-
-            }
-        }, new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                return event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-            }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
+                (event) ->
+                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
         secondEditText.refresh();
 
         // Make sure no other node has accessibility focus.
-        AccessibilityNodeInfo root = getInstrumentation().getUiAutomation().getRootInActiveWindow();
+        AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
         Queue<AccessibilityNodeInfo> workQueue = new LinkedList<AccessibilityNodeInfo>();
         workQueue.add(root);
         while (!workQueue.isEmpty()) {
@@ -238,18 +244,18 @@
     }
 
     @Presubmit
+    @Test
     public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
-        final Instrumentation instrumentation = getInstrumentation();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final AccessibilityNodeInfo secondButton = uiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.secondButton)).get(0);
+        final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(
+                        sInstrumentation.getContext().getString(R.string.secondButton)).get(0);
         assertTrue("Screen reader focusability not propagated from xml to accessibility",
                 secondButton.isScreenReaderFocusable());
 
         // Verify the setter and getter work
         final AtomicBoolean isScreenReaderFocusableAtomic = new AtomicBoolean(false);
-        instrumentation.runOnMainSync(() -> {
-            View secondButtonView = getActivity().findViewById(R.id.secondButton);
+        sInstrumentation.runOnMainSync(() -> {
+            View secondButtonView = mActivity.findViewById(R.id.secondButton);
             secondButtonView.setScreenReaderFocusable(false);
             isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable());
         });
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 01d841e..27ec20b 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -14,6 +14,11 @@
 
 package android.accessibilityservice.cts;
 
+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 org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.timeout;
@@ -27,11 +32,13 @@
 import android.content.pm.PackageManager;
 import android.graphics.Path;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,6 +60,8 @@
     static final long STROKE_MS = 400;
     static final long GESTURE_DISPATCH_TIMEOUT_MS = 3000;
     static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
+    static final long EVENT_DISPATCH_TIMEOUT_MS = 3000;
+    static final long EVENT_RECOGNIZE_TIMEOUT_MS = 3000;
 
     // Member variables
     StubService mService;  // Test AccessibilityService that collects gestures.
@@ -61,6 +70,7 @@
     int mStrokeLenPxX;  // Gesture stroke size, in pixels
     int mStrokeLenPxY;
     Point mCenter;  // Center of screen. Gestures all start from this point.
+    PointF mTapLocation;
     @Mock AccessibilityService.GestureResultCallback mGestureDispatchCallback;
 
     @Before
@@ -83,6 +93,7 @@
         final DisplayMetrics metrics = new DisplayMetrics();
         windowManager.getDefaultDisplay().getRealMetrics(metrics);
         mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2);
+        mTapLocation = new PointF(mCenter);
         mStrokeLenPxX = (int)(GESTURE_LENGTH_INCHES * metrics.xdpi);
         mStrokeLenPxY = (int)(GESTURE_LENGTH_INCHES * metrics.ydpi);
         mScreenBigEnough = (metrics.widthPixels / (2 * metrics.xdpi) > GESTURE_LENGTH_INCHES)
@@ -185,10 +196,86 @@
         return path;
     }
 
+    @Test
+    @AppModeFull
+    public void testVerifyGestureTouchEvent() {
+        if (!mHasTouchScreen || !mScreenBigEnough) {
+            return;
+        }
+
+        assertEventAfterGesture(swipe(),
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+        assertEventAfterGesture(tap(),
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+        assertEventAfterGesture(doubleTap(),
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+        assertEventAfterGesture(doubleTapAndHold(),
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /** Test touch for accessibility events */
+    private void assertEventAfterGesture(GestureDescription gesture, int... events) {
+        mService.clearEvents();
+        mService.runOnServiceSync(() ->
+                mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
+        verify(mGestureDispatchCallback,
+                timeout(EVENT_DISPATCH_TIMEOUT_MS).atLeastOnce())
+                .onCompleted(any());
+
+        mService.waitUntilEvent(events.length);
+        assertEquals(events.length, mService.getEventsSize());
+        for (int i = 0 ; i < events.length ; i++) {
+            assertEquals(events[i], mService.getEvent(i));
+        }
+    }
+
+    private GestureDescription swipe() {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription swipe = new StrokeDescription(
+                linePath(mCenter, p(0, mStrokeLenPxY), null), 0, STROKE_MS, false);
+        builder.addStroke(swipe);
+        return builder.build();
+    }
+
+    private GestureDescription tap() {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription tap = click(mTapLocation);
+        builder.addStroke(tap);
+        return builder.build();
+    }
+
+    private GestureDescription doubleTap() {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription tap1 = click(mTapLocation);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation));
+        builder.addStroke(tap1);
+        builder.addStroke(tap2);
+        return builder.build();
+    }
+
+    private GestureDescription doubleTapAndHold() {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription tap1 = click(mTapLocation);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, longClick(mTapLocation));
+        builder.addStroke(tap1);
+        builder.addStroke(tap2);
+        return builder.build();
+    }
+
     /** Acessibility service stub, which will collect recognized gestures. */
     public static class StubService extends InstrumentedAccessibilityService {
-
+        private final Object mLock = new Object();
         private ArrayList<Integer> mCollectedGestures = new ArrayList();
+        private ArrayList<Integer> mCollectedEvents = new ArrayList();
 
         public static StubService enableSelf(Instrumentation instrumentation) {
             return InstrumentedAccessibilityService.enableService(
@@ -235,6 +322,55 @@
                 }
             }
         }
+
+        @Override
+        public void onAccessibilityEvent(AccessibilityEvent event) {
+            synchronized (mLock) {
+                switch (event.getEventType()) {
+                    case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
+                        mCollectedEvents.add(event.getEventType());
+                        mLock.notifyAll();
+                        break;
+                    case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
+                    case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
+                    case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
+                        mCollectedEvents.add(event.getEventType());
+                }
+            }
+            super.onAccessibilityEvent(event);
+        }
+
+        public void clearEvents() {
+            synchronized (mLock) {
+                mCollectedEvents.clear();
+            }
+        }
+
+        public int getEventsSize() {
+            synchronized (mLock) {
+                return mCollectedEvents.size();
+            }
+        }
+
+        public int getEvent(int index) {
+            synchronized (mLock) {
+                return mCollectedEvents.get(index);
+            }
+        }
+
+        /** Wait for onAccessibilityEvent() to collect next gesture. */
+        public void waitUntilEvent(int count) {
+            synchronized (mLock) {
+                if (mCollectedEvents.size() >= count) {
+                    return;
+                }
+                try {
+                    mLock.wait(EVENT_RECOGNIZE_TIMEOUT_MS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
     }
 
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index cdd109c..3814125 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -112,6 +112,8 @@
             return;
         }
 
+        getActivity().waitForEnterAnimationComplete();
+
         mHasMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
index 1eedee1..167c7d1 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
@@ -16,10 +16,10 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.AccessibilityActivityTestCase.TIMEOUT_ASYNC_PROCESSING;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.AccessibilityEventTypeMatcher;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.ContentChangesMatcher;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE;
@@ -92,7 +92,7 @@
             mPaneView.setAccessibilityPaneTitle(newTitle);
             assertEquals(newTitle, mPaneView.getAccessibilityPaneTitle());
         }), (new ContentChangesMatcher(CONTENT_CHANGE_TYPE_PANE_TITLE))::matches,
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
 
         AccessibilityNodeInfo windowLikeNode = getPaneNode();
         assertEquals(newTitle, windowLikeNode.getPaneTitle());
@@ -107,16 +107,16 @@
                 both(new AccessibilityEventTypeMatcher(TYPE_WINDOW_STATE_CHANGED)).and(
                         new ContentChangesMatcher(CONTENT_CHANGE_TYPE_PANE_DISAPPEARED))::matches;
         sUiAutomation.executeAndWaitForEvent(setPaneViewVisibility(View.GONE),
-                paneDisappearsFilter, TIMEOUT_ASYNC_PROCESSING);
+                paneDisappearsFilter, DEFAULT_TIMEOUT_MS);
 
         sUiAutomation.executeAndWaitForEvent(setPaneViewVisibility(View.VISIBLE),
-                paneAppearsFilter, TIMEOUT_ASYNC_PROCESSING);
+                paneAppearsFilter, DEFAULT_TIMEOUT_MS);
 
         sUiAutomation.executeAndWaitForEvent(setPaneViewParentVisibility(View.GONE),
-                paneDisappearsFilter, TIMEOUT_ASYNC_PROCESSING);
+                paneDisappearsFilter, DEFAULT_TIMEOUT_MS);
 
         sUiAutomation.executeAndWaitForEvent(setPaneViewParentVisibility(View.VISIBLE),
-                paneAppearsFilter, TIMEOUT_ASYNC_PROCESSING);
+                paneAppearsFilter, DEFAULT_TIMEOUT_MS);
     }
 
     private AccessibilityNodeInfo getPaneNode() {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index b27ceaf..67626fe 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -100,6 +100,17 @@
                 AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS));
         assertEquals("FLAG_REQUEST_TOUCH_EXPLORATION_MODE", AccessibilityServiceInfo.flagToString(
                 AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE));
+        assertEquals("FLAG_RETRIEVE_INTERACTIVE_WINDOWS", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS));
+        assertEquals("FLAG_ENABLE_ACCESSIBILITY_VOLUME", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME));
+        assertEquals("FLAG_REQUEST_ACCESSIBILITY_BUTTON", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON));
+        assertEquals("FLAG_REQUEST_FINGERPRINT_GESTURES", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES));
+        assertEquals("FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK));
+
     }
 
     /**
@@ -115,6 +126,8 @@
         sentInfo.packageNames = new String[] {
             "foo.bar.baz"
         };
+        sentInfo.setInteractiveUiTimeoutMillis(2000);
+        sentInfo.setNonInteractiveUiTimeoutMillis(4000);
     }
 
     /**
@@ -135,5 +148,11 @@
                 receivedInfo.packageNames.length);
         assertEquals("packageNames not marshalled properly", sentInfo.packageNames[0],
                 receivedInfo.packageNames[0]);
+        assertEquals("interactiveUiTimeout not marshalled properly",
+                sentInfo.getInteractiveUiTimeoutMillis(),
+                receivedInfo.getInteractiveUiTimeoutMillis());
+        assertEquals("nonInteractiveUiTimeout not marshalled properly",
+                sentInfo.getNonInteractiveUiTimeoutMillis(),
+                receivedInfo.getNonInteractiveUiTimeoutMillis());
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index caae032..c83d108 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -13,146 +13,121 @@
  */
 
 package android.accessibilityservice.cts;
+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 org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.R;
+import android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener;
 import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
-import android.app.Activity;
+import android.accessibilityservice.cts.utils.AsyncUtils;
 import android.app.Instrumentation;
-import android.app.UiAutomation;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InputMethodManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Test cases for testing the accessibility APIs for interacting with the soft keyboard show mode.
  */
+@RunWith(AndroidJUnit4.class)
 @AppModeFull
-public class AccessibilitySoftKeyboardModesTest extends ActivityInstrumentationTestCase2
-        <AccessibilitySoftKeyboardModesTest.SoftKeyboardModesActivity> {
-
-    private static final long TIMEOUT_PROPAGATE_SETTING = 5000;
-
-    /**
-     * Timeout required for pending Binder calls or event processing to
-     * complete.
-     */
-    private static final long TIMEOUT_ASYNC_PROCESSING = 5000;
-
-    /**
-     * The timeout since the last accessibility event to consider the device idle.
-     */
-    private static final long TIMEOUT_ACCESSIBILITY_STATE_IDLE = 500;
-
-    /**
-     * The timeout since {@link InputMethodManager#showSoftInput(View, int, ResultReceiver)}
-     * is called to {@link ResultReceiver#onReceiveResult(int, Bundle)} is called back.
-     */
-    private static final int TIMEOUT_SHOW_SOFTINPUT_RESULT = 2000;
-
-    private static final int SHOW_MODE_AUTO = 0;
-    private static final int SHOW_MODE_HIDDEN = 1;
-
+public class AccessibilitySoftKeyboardModesTest {
     private int mLastCallbackValue;
 
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private InstrumentedAccessibilityService mService;
-    private SoftKeyboardController mKeyboardController;
-    private UiAutomation mUiAutomation;
-    private Activity mActivity;
-    private View mKeyboardTargetView;
+    private final Object mLock = new Object();
+    private final OnShowModeChangedListener mListener = (c, showMode) -> {
+        synchronized (mLock) {
+            mLastCallbackValue = showMode;
+            mLock.notifyAll();
+        }
+    };
 
-    private Object mLock = new Object();
 
-    public AccessibilitySoftKeyboardModesTest() {
-        super(SoftKeyboardModesActivity.class);
-    }
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        // If we don't call getActivity(), we get an empty list when requesting the number of
-        // windows on screen.
-        mActivity = getActivity();
-
-        mService = InstrumentedAccessibilityService.enableService(
-                getInstrumentation(), InstrumentedAccessibilityService.class);
-        mKeyboardController = mService.getSoftKeyboardController();
-        mUiAutomation = getInstrumentation()
-                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-        AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
-        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        mUiAutomation.setServiceInfo(info);
-        getInstrumentation().runOnMainSync(
-                () -> mKeyboardTargetView = mActivity.findViewById(R.id.edit_text));
+        mService = InstrumentedAccessibilityService.enableService(mInstrumentation,
+                InstrumentedAccessibilityService.class);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
-        mKeyboardController.setShowMode(SHOW_MODE_AUTO);
-        mService.runOnServiceSync(() -> mService.disableSelf());
-        Activity activity = getActivity();
-        View currentFocus = activity.getCurrentFocus();
-        if (currentFocus != null) {
-            activity.getSystemService(InputMethodManager.class)
-                    .hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
+        if (mService != null) {
+            mService.runOnServiceSync(() -> mService.disableSelf());
         }
     }
 
+    @Test
     public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
-        mLastCallbackValue = -1;
+        final SoftKeyboardController controller = mService.getSoftKeyboardController();
 
-        final SoftKeyboardController.OnShowModeChangedListener listener =
-                new SoftKeyboardController.OnShowModeChangedListener() {
-                    @Override
-                    public void onShowModeChanged(SoftKeyboardController controller, int showMode) {
-                        synchronized (mLock) {
-                            mLastCallbackValue = showMode;
-                            mLock.notifyAll();
-                        }
-                    }
-                };
-        mKeyboardController.addOnShowModeChangedListener(listener);
+        // Confirm that we start in the default state
+        assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
 
-        // The soft keyboard should be in its default mode.
-        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
-
-        // Set the show mode to SHOW_MODE_HIDDEN.
-        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
-
-        // Make sure the mode was changed.
-        assertEquals(SHOW_MODE_HIDDEN, mKeyboardController.getShowMode());
-
-        // Make sure we're getting the callback with the proper value.
-        waitForCallbackValueWithLock(SHOW_MODE_HIDDEN);
-
-        // Make sure we can set the value back to the default.
-        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_AUTO));
-        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
-        waitForCallbackValueWithLock(SHOW_MODE_AUTO);
+        controller.addOnShowModeChangedListener(mListener);
+        assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_HIDDEN, mService);
+        assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_IGNORE_HARD_KEYBOARD, mService);
+        assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_AUTO, mService);
 
         // Make sure we can remove our listener.
-        assertTrue(mKeyboardController.removeOnShowModeChangedListener(listener));
+        assertTrue(controller.removeOnShowModeChangedListener(mListener));
+    }
+
+    @Test
+    public void secondServiceChangingTheShowMode_updatesModeAndNotifiesFirstService()
+            throws Exception {
+
+        final SoftKeyboardController controller = mService.getSoftKeyboardController();
+        // Confirm that we start in the default state
+        assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
+
+        final InstrumentedAccessibilityService secondService =
+                StubAccessibilityButtonService.enableSelf(mInstrumentation);
+        try {
+            // Listen on the first service
+            controller.addOnShowModeChangedListener(mListener);
+            assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_HIDDEN, mService);
+
+            // Change the mode on the second service
+            assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_IGNORE_HARD_KEYBOARD,
+                    secondService);
+        } finally {
+            secondService.runOnServiceSync(() -> secondService.disableSelf());
+        }
+
+        // Shutting down the second service, which was controlling the mode, should put us back
+        // to the default
+        waitForCallbackValueWithLock(SHOW_MODE_AUTO);
+        final int showMode = mService.getOnService(() -> controller.getShowMode());
+        assertEquals(SHOW_MODE_AUTO, showMode);
+    }
+
+    private void assertCanSetAndGetShowModeAndCallbackHappens(
+            int mode, InstrumentedAccessibilityService service)
+            throws Exception  {
+        final SoftKeyboardController controller = service.getSoftKeyboardController();
+        mLastCallbackValue = -1;
+        final boolean setShowModeReturns =
+                service.getOnService(() -> controller.setShowMode(mode));
+        assertTrue(setShowModeReturns);
+        waitForCallbackValueWithLock(mode);
+        assertEquals(mode, controller.getShowMode());
     }
 
     private void waitForCallbackValueWithLock(int expectedValue) throws Exception {
-        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_PROPAGATE_SETTING;
+        long timeoutTimeMillis = SystemClock.uptimeMillis() + AsyncUtils.DEFAULT_TIMEOUT_MS;
 
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
             synchronized(mLock) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index cd84fb9..164a196 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -14,13 +14,17 @@
 
 package android.accessibilityservice.cts;
 
-import static android.view.accessibility.AccessibilityNodeInfo
-        .EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
-import static android.view.accessibility.AccessibilityNodeInfo
-        .EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
-import static android.view.accessibility.AccessibilityNodeInfo
-        .EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 
+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.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -28,11 +32,15 @@
 
 import android.accessibilityservice.cts.R;
 import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Message;
 import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -51,36 +59,55 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
+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;
+
 /**
  * Test cases for actions taken on text views.
  */
-public class AccessibilityTextActionTest extends
-        AccessibilityActivityTestCase<AccessibilityTextTraversalActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityTextActionTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
     final Object mClickableSpanCallbackLock = new Object();
     final AtomicBoolean mClickableSpanCalled = new AtomicBoolean(false);
-    UiAutomation mUiAutomation;
+    
 
-    public AccessibilityTextActionTest() {
-        super(AccessibilityTextTraversalActivity.class);
+    private AccessibilityTextTraversalActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
     }
 
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        mUiAutomation = getInstrumentation().getUiAutomation();
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
         mClickableSpanCalled.set(false);
     }
 
-    public void tearDown() throws Exception {
-        mUiAutomation.destroy();
-        super.tearDown();
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
     }
 
+    @Test
     public void testNotEditableTextView_shouldNotExposeOrRespondToSetTextAction() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
-        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, mActivity.getString(R.string.a_b));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.a_b)).get(0);
 
         assertFalse("Standard text view should not support SET_TEXT", text.getActionList()
                 .contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT));
@@ -88,27 +115,28 @@
                 text.getActions() & AccessibilityNodeInfo.ACTION_SET_TEXT);
         Bundle args = new Bundle();
         args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
-                getString(R.string.text_input_blah));
+                mActivity.getString(R.string.text_input_blah));
         assertFalse(text.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args));
 
-        getInstrumentation().waitForIdleSync();
+        sInstrumentation.waitForIdleSync();
         assertTrue("Text view should not update on failed set text",
-                TextUtils.equals(getString(R.string.a_b), textView.getText()));
+                TextUtils.equals(mActivity.getString(R.string.a_b), textView.getText()));
     }
 
+    @Test
     public void testEditableTextView_shouldExposeAndRespondToSetTextAction() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 textView.setVisibility(View.VISIBLE);
-                textView.setText(getString(R.string.a_b), TextView.BufferType.EDITABLE);
+                textView.setText(mActivity.getString(R.string.a_b), TextView.BufferType.EDITABLE);
             }
         });
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.a_b)).get(0);
 
         assertTrue("Editable text view should support SET_TEXT", text.getActionList()
                 .contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT));
@@ -117,23 +145,24 @@
                 text.getActions() & AccessibilityNodeInfo.ACTION_SET_TEXT);
 
         Bundle args = new Bundle();
-        String textToSet = getString(R.string.text_input_blah);
+        String textToSet = mActivity.getString(R.string.text_input_blah);
         args.putCharSequence(
                 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, textToSet);
 
         assertTrue(text.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args));
 
-        getInstrumentation().waitForIdleSync();
+        sInstrumentation.waitForIdleSync();
         assertTrue("Editable text should update on set text",
                 TextUtils.equals(textToSet, textView.getText()));
     }
 
+    @Test
     public void testEditText_shouldExposeAndRespondToSetTextAction() {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
-        makeTextViewVisibleAndSetText(editText, getString(R.string.a_b));
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
+        makeTextViewVisibleAndSetText(editText, mActivity.getString(R.string.a_b));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.a_b)).get(0);
 
         assertTrue("EditText should support SET_TEXT", text.getActionList()
                 .contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT));
@@ -142,19 +171,20 @@
                 text.getActions() & AccessibilityNodeInfo.ACTION_SET_TEXT);
 
         Bundle args = new Bundle();
-        String textToSet = getString(R.string.text_input_blah);
+        String textToSet = mActivity.getString(R.string.text_input_blah);
         args.putCharSequence(
                 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, textToSet);
 
         assertTrue(text.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args));
 
-        getInstrumentation().waitForIdleSync();
+        sInstrumentation.waitForIdleSync();
         assertTrue("EditText should update on set text",
                 TextUtils.equals(textToSet, editText.getText()));
     }
 
+    @Test
     public void testClickableSpan_shouldWorkFromAccessibilityService() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
         final ClickableSpan clickableSpan = new ClickableSpan() {
             @Override
             public void onClick(View widget) {
@@ -162,7 +192,8 @@
                 onClickCallback();
             }
         };
-        final SpannableString textWithClickableSpan = new SpannableString(getString(R.string.a_b));
+        final SpannableString textWithClickableSpan =
+                new SpannableString(mActivity.getString(R.string.a_b));
         textWithClickableSpan.setSpan(clickableSpan, 0, 1, 0);
         makeTextViewVisibleAndSetText(textView, textWithClickableSpan);
 
@@ -172,8 +203,9 @@
         assertOnClickCalled();
     }
 
+    @Test
     public void testUrlSpan_shouldWorkFromAccessibilityService() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
         final String url = "com.android.some.random.url";
         final URLSpan urlSpan = new URLSpan(url) {
             @Override
@@ -182,7 +214,8 @@
                 onClickCallback();
             }
         };
-        final SpannableString textWithClickableSpan = new SpannableString(getString(R.string.a_b));
+        final SpannableString textWithClickableSpan =
+                new SpannableString(mActivity.getString(R.string.a_b));
         textWithClickableSpan.setSpan(urlSpan, 0, 1, 0);
         makeTextViewVisibleAndSetText(textView, textWithClickableSpan);
 
@@ -193,16 +226,16 @@
         assertOnClickCalled();
     }
 
-
+    @Test
     public void testTextLocations_textViewShouldProvideWhenRequested() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        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.
         // That replacement requires us to properly handle the length of the string changing.
-        String stringToSet = getString(R.string.german_text_with_strong_s);
+        String stringToSet = mActivity.getString(R.string.german_text_with_strong_s);
         makeTextViewVisibleAndSetText(textView, stringToSet);
-        getInstrumentation().runOnMainSync(() -> textView.setAllCaps(true));
+        sInstrumentation.runOnMainSync(() -> textView.setAllCaps(true));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByText(stringToSet).get(0);
         List<String> textAvailableExtraData = text.getAvailableExtraData();
         assertTrue("Text view should offer text location to accessibility",
@@ -215,12 +248,13 @@
         assertNodeContainsTextLocationInfoOnOneLineLTR(text);
     }
 
+    @Test
     public void testTextLocations_textOutsideOfViewBounds_locationsShouldBeNull() {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
-        makeTextViewVisibleAndSetText(editText, getString(R.string.android_wiki));
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
+        makeTextViewVisibleAndSetText(editText, mActivity.getString(R.string.android_wiki));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.android_wiki)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.android_wiki)).get(0);
         List<String> textAvailableExtraData = text.getAvailableExtraData();
         assertTrue("Text view should offer text location to accessibility",
                 textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
@@ -249,7 +283,7 @@
         }
 
         // Scroll down one line
-        getInstrumentation().runOnMainSync(() -> {
+        sInstrumentation.runOnMainSync(() -> {
             int[] viewPosition = new int[2];
             editText.getLocationOnScreen(viewPosition);
             final int oneLineDownY = (int) locationsBeforeScroll[0].bottom - viewPosition[1];
@@ -268,12 +302,13 @@
         assertNotNull(locationsAfterScroll[firstNullRectIndex]);
     }
 
+    @Test
     public void testTextLocations_withRequestPreparer_shouldHoldOffUntilReady() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
-        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, mActivity.getString(R.string.a_b));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.a_b)).get(0);
         final List<String> textAvailableExtraData = text.getAvailableExtraData();
         final Bundle getTextArgs = getTextLocationArguments(text);
 
@@ -284,7 +319,7 @@
         Runnable mockRunnableForPrepare = mock(Runnable.class);
 
         AccessibilityManager a11yManager =
-                getActivity().getSystemService(AccessibilityManager.class);
+                mActivity.getSystemService(AccessibilityManager.class);
         AccessibilityRequestPreparer requestPreparer = new AccessibilityRequestPreparer(
                 textView, AccessibilityRequestPreparer.REQUEST_TYPE_EXTRA_DATA) {
             @Override
@@ -304,35 +339,33 @@
 
         // Make the extra data request in another thread
         Runnable mockRunnableForData = mock(Runnable.class);
-        new Thread() {
-            @Override
-            public void run() {
+        new Thread(()-> {
                 assertTrue("Refresh failed", text.refreshWithExtraData(
                         EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
                 mockRunnableForData.run();
-            }
-        }.start();
+        }).start();
 
         // The extra data request should trigger the request preparer
-        verify(mockRunnableForPrepare, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        verify(mockRunnableForPrepare, timeout(DEFAULT_TIMEOUT_MS)).run();
         // Verify that the request for extra data didn't return. This is a bit racy, as we may still
         // not catch it if it does return prematurely, but it does provide some protection.
-        getInstrumentation().waitForIdleSync();
+        sInstrumentation.waitForIdleSync();
         verify(mockRunnableForData, times(0)).run();
 
         // Declare preparation for the request complete, and verify that it runs to completion
         messageRefForPrepare.get().sendToTarget();
-        verify(mockRunnableForData, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        verify(mockRunnableForData, timeout(DEFAULT_TIMEOUT_MS)).run();
         assertNodeContainsTextLocationInfoOnOneLineLTR(text);
         a11yManager.removeAccessibilityRequestPreparer(requestPreparer);
     }
 
+    @Test
     public void testTextLocations_withUnresponsiveRequestPreparer_shouldTimeout() {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
-        makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
+        makeTextViewVisibleAndSetText(textView, mActivity.getString(R.string.a_b));
 
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.a_b)).get(0);
         final List<String> textAvailableExtraData = text.getAvailableExtraData();
         final Bundle getTextArgs = getTextLocationArguments(text);
 
@@ -340,7 +373,7 @@
         Runnable mockRunnableForPrepare = mock(Runnable.class);
 
         AccessibilityManager a11yManager =
-                getActivity().getSystemService(AccessibilityManager.class);
+                mActivity.getSystemService(AccessibilityManager.class);
         AccessibilityRequestPreparer requestPreparer = new AccessibilityRequestPreparer(
                 textView, AccessibilityRequestPreparer.REQUEST_TYPE_EXTRA_DATA) {
             @Override
@@ -354,23 +387,20 @@
 
         // Make the extra data request in another thread
         Runnable mockRunnableForData = mock(Runnable.class);
-        new Thread() {
-            @Override
-            public void run() {
-                /*
-                 * Don't worry about the return value, as we're timing out. We're just making
-                 * sure that we don't hang the system.
-                 */
-                text.refreshWithExtraData(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs);
-                mockRunnableForData.run();
-            }
-        }.start();
+        new Thread(() -> {
+            /*
+             * Don't worry about the return value, as we're timing out. We're just making
+             * sure that we don't hang the system.
+             */
+            text.refreshWithExtraData(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs);
+            mockRunnableForData.run();
+        }).start();
 
         // The extra data request should trigger the request preparer
-        verify(mockRunnableForPrepare, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        verify(mockRunnableForPrepare, timeout(DEFAULT_TIMEOUT_MS)).run();
 
         // Declare preparation for the request complete, and verify that it runs to completion
-        verify(mockRunnableForData, timeout(TIMEOUT_ASYNC_PROCESSING)).run();
+        verify(mockRunnableForData, timeout(DEFAULT_TIMEOUT_MS)).run();
         a11yManager.removeAccessibilityRequestPreparer(requestPreparer);
     }
 
@@ -388,8 +418,8 @@
         assertEquals(info.getText().length(), locations.length);
         // The text should all be on one line, running left to right
         for (int i = 0; i < locations.length; i++) {
-            assertEquals(locations[0].top, locations[i].top);
-            assertEquals(locations[0].bottom, locations[i].bottom);
+            assertEquals(locations[0].top, locations[i].top, 0.01);
+            assertEquals(locations[0].bottom, locations[i].bottom, 0.01);
             assertTrue(locations[i].right > locations[i].left);
             if (i > 0) {
                 assertTrue(locations[i].left > locations[i-1].left);
@@ -406,7 +436,7 @@
 
     private void assertOnClickCalled() {
         synchronized (mClickableSpanCallbackLock) {
-            long endTime = System.currentTimeMillis() + TIMEOUT_ASYNC_PROCESSING;
+            long endTime = System.currentTimeMillis() + DEFAULT_TIMEOUT_MS;
             while (!mClickableSpanCalled.get() && (System.currentTimeMillis() < endTime)) {
                 try {
                     mClickableSpanCallbackLock.wait(endTime - System.currentTimeMillis());
@@ -417,8 +447,8 @@
     }
 
     private <T> T findSingleSpanInViewWithText(int stringId, Class<T> type) {
-        final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(stringId)).get(0);
+        final AccessibilityNodeInfo text = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(stringId)).get(0);
         CharSequence accessibilityTextWithSpan = text.getText();
         // The span should work even with the node recycled
         text.recycle();
@@ -431,7 +461,7 @@
     }
 
     private void makeTextViewVisibleAndSetText(final TextView textView, final CharSequence text) {
-        getInstrumentation().runOnMainSync(() -> {
+        sInstrumentation.runOnMainSync(() -> {
             textView.setVisibility(View.VISIBLE);
             textView.setText(text);
         });
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 5888273..f01f53c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -14,11 +14,24 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+
+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 android.accessibilityservice.cts.R;
 import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.Selection;
 import android.text.TextUtils;
@@ -28,26 +41,56 @@
 import android.widget.EditText;
 import android.widget.TextView;
 
+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;
+
 /**
  * Test cases for testing the accessibility APIs for traversing the text content of
  * a View at several granularities.
  */
-public class AccessibilityTextTraversalTest
-        extends AccessibilityActivityTestCase<AccessibilityTextTraversalActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityTextTraversalTest {
     // The number of characters per page may vary with font, so this number is slightly uncertain.
     // We need some threshold, however, to make sure moving by a page isn't just moving by a line.
     private static final int[] CHARACTER_INDICES_OF_PAGE_START = {0, 53, 122, 178};
 
-    public AccessibilityTextTraversalTest() {
-        super(AccessibilityTextTraversalActivity.class);
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private AccessibilityTextTraversalActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityCharacterOverContentDescription()
             throws Exception {
-        final View view = getActivity().findViewById(R.id.view);
+        final View view = mActivity.findViewById(R.id.view);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 view.setVisibility(View.VISIBLE);
@@ -55,7 +98,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                         getString(R.string.a_b)).get(0);
 
@@ -69,7 +112,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -84,7 +127,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -93,13 +136,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -114,7 +157,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -123,13 +166,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -144,7 +187,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -153,7 +196,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -163,7 +206,7 @@
                 arguments));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -178,7 +221,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -187,13 +230,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -208,7 +251,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -217,13 +260,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -238,7 +281,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.a_b))
@@ -247,7 +290,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -258,11 +301,12 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityWordOverContentDescription()
             throws Exception {
-        final View view = getActivity().findViewById(R.id.view);
+        final View view = mActivity.findViewById(R.id.view);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 view.setVisibility(View.VISIBLE);
@@ -270,7 +314,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                         getString(R.string.foo_bar_baz)).get(0);
 
@@ -284,7 +328,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -299,7 +343,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -308,13 +352,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -329,7 +373,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -338,13 +382,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -359,7 +403,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -368,7 +412,7 @@
                         && event.getMovementGranularity() ==
                                AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -378,7 +422,7 @@
                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -393,7 +437,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -402,13 +446,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -423,7 +467,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -432,13 +476,13 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -453,7 +497,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(View.class.getName())
                         && event.getContentDescription().toString().equals(
                                 getString(R.string.foo_bar_baz))
@@ -462,7 +506,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -473,11 +517,12 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityCharacterOverText()
             throws Exception {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 textView.setVisibility(View.VISIBLE);
@@ -485,7 +530,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                        getString(R.string.a_b)).get(0);
 
@@ -501,7 +546,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -516,7 +561,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -525,7 +570,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -535,7 +580,7 @@
         assertEquals(1, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -550,7 +595,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -559,7 +604,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -569,7 +614,7 @@
         assertEquals(2, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -584,7 +629,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -593,7 +638,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -611,7 +656,7 @@
         assertEquals(3, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -626,7 +671,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -635,7 +680,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -645,7 +690,7 @@
         assertEquals(2, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -660,7 +705,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -669,7 +714,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -679,7 +724,7 @@
         assertEquals(1, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent seventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent seventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -694,7 +739,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -703,7 +748,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(seventhExpected);
@@ -722,11 +767,12 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityCharacterOverTextExtend()
             throws Exception {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -735,7 +781,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                        getString(R.string.a_b)).get(0);
 
@@ -752,7 +798,7 @@
         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -767,7 +813,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -776,7 +822,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -786,7 +832,7 @@
         assertEquals(1, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -801,7 +847,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -810,7 +856,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -820,7 +866,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -835,7 +881,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -844,7 +890,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -862,7 +908,7 @@
         assertEquals(3, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -877,7 +923,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -886,7 +932,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -896,7 +942,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -911,7 +957,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -920,7 +966,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -930,7 +976,7 @@
         assertEquals(1, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -945,7 +991,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -954,7 +1000,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -972,7 +1018,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Focus the view so we can change selection.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setFocusable(true);
@@ -992,7 +1038,7 @@
         assertEquals(3, Selection.getSelectionEnd(editText.getText()));
 
         // Unfocus the view so we can get rid of the soft-keyboard.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.clearFocus();
@@ -1001,7 +1047,7 @@
         });
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent seventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent seventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1016,7 +1062,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1025,7 +1071,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(seventhExpected);
@@ -1035,7 +1081,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent eightExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eightExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1050,7 +1096,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1059,7 +1105,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eightExpected);
@@ -1069,7 +1115,7 @@
         assertEquals(1, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent ninethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent ninethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1084,7 +1130,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1093,7 +1139,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(ninethExpected);
@@ -1111,7 +1157,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent tenthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent tenthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1126,7 +1172,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1135,7 +1181,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(tenthExpected);
@@ -1145,7 +1191,7 @@
         assertEquals(1, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent eleventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eleventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1160,7 +1206,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1169,7 +1215,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eleventhExpected);
@@ -1179,7 +1225,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent twelvethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent twelvethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1194,7 +1240,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.a_b))
@@ -1203,7 +1249,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(twelvethExpected);
@@ -1222,10 +1268,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityWordOverText() throws Exception {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 textView.setVisibility(View.VISIBLE);
@@ -1233,7 +1280,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.foo_bar_baz)).get(0);
 
@@ -1249,7 +1296,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1264,7 +1311,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1273,7 +1320,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -1283,7 +1330,7 @@
         assertEquals(3, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1298,7 +1345,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1307,7 +1354,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -1317,7 +1364,7 @@
         assertEquals(7, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1332,7 +1379,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1341,7 +1388,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -1359,7 +1406,7 @@
         assertEquals(11, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1374,7 +1421,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1383,7 +1430,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -1393,7 +1440,7 @@
         assertEquals(8, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1408,7 +1455,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1417,7 +1464,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -1427,7 +1474,7 @@
         assertEquals(4, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1442,7 +1489,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1451,7 +1498,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -1470,11 +1517,12 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityWordOverEditTextWithContentDescription()
             throws Exception {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -1483,7 +1531,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.foo_bar_baz)).get(0);
 
@@ -1499,7 +1547,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1514,7 +1562,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1524,7 +1572,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -1534,7 +1582,7 @@
         assertEquals(3, editText.getSelectionEnd());
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1549,7 +1597,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1559,7 +1607,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -1569,7 +1617,7 @@
         assertEquals(7, editText.getSelectionEnd());
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1584,7 +1632,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1594,7 +1642,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -1612,7 +1660,7 @@
         assertEquals(11, editText.getSelectionEnd());
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1627,7 +1675,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1637,7 +1685,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -1647,7 +1695,7 @@
         assertEquals(8, editText.getSelectionEnd());
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1662,7 +1710,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1672,7 +1720,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -1682,7 +1730,7 @@
         assertEquals(4, editText.getSelectionEnd());
 
         // Move to the next character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1697,7 +1745,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1707,7 +1755,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -1726,10 +1774,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityWordOverTextExtend() throws Exception {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -1738,7 +1787,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.foo_bar_baz)).get(0);
 
@@ -1755,7 +1804,7 @@
         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1770,7 +1819,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1779,7 +1828,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -1789,7 +1838,7 @@
         assertEquals(3, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1804,7 +1853,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1813,7 +1862,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -1823,7 +1872,7 @@
         assertEquals(7, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1838,7 +1887,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1847,7 +1896,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -1861,7 +1910,7 @@
                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments));
 
         // Move to the previous word and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1876,7 +1925,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1885,7 +1934,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -1895,7 +1944,7 @@
         assertEquals(8, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous word and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1910,7 +1959,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1919,7 +1968,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -1929,7 +1978,7 @@
         assertEquals(4, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -1944,7 +1993,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -1953,7 +2002,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -1971,7 +2020,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Focus the view so we can change selection.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setFocusable(true);
@@ -1991,7 +2040,7 @@
         assertEquals(11, Selection.getSelectionEnd(editText.getText()));
 
         // Unfocus the view so we can get rid of the soft-keyboard.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.clearFocus();
@@ -2000,7 +2049,7 @@
         });
 
         // Move to the previous word and wait for an event.
-        AccessibilityEvent seventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent seventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2015,7 +2064,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2024,7 +2073,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(seventhExpected);
@@ -2034,7 +2083,7 @@
         assertEquals(8, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous word and wait for an event.
-        AccessibilityEvent eightExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eightExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2049,7 +2098,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2058,7 +2107,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eightExpected);
@@ -2068,7 +2117,7 @@
         assertEquals(4, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous character and wait for an event.
-        AccessibilityEvent ninethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent ninethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2083,7 +2132,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2092,7 +2141,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(ninethExpected);
@@ -2110,7 +2159,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent tenthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent tenthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2125,7 +2174,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2134,7 +2183,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(tenthExpected);
@@ -2144,7 +2193,7 @@
         assertEquals(3, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent eleventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eleventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2159,7 +2208,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2168,7 +2217,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eleventhExpected);
@@ -2178,7 +2227,7 @@
         assertEquals(7, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next word and wait for an event.
-        AccessibilityEvent twelvthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent twelvthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2193,7 +2242,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(R.string.foo_bar_baz))
@@ -2202,7 +2251,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(twelvthExpected);
@@ -2221,10 +2270,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityLineOverText() throws Exception {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 textView.setVisibility(View.VISIBLE);
@@ -2232,7 +2282,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.android_wiki_short)).get(0);
 
@@ -2248,7 +2298,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2263,7 +2313,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2273,7 +2323,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -2283,7 +2333,7 @@
         assertEquals(13, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2298,7 +2348,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2308,7 +2358,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -2318,7 +2368,7 @@
         assertEquals(25, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2333,7 +2383,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2343,7 +2393,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -2361,7 +2411,7 @@
         assertEquals(34, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2376,7 +2426,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2386,7 +2436,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -2396,7 +2446,7 @@
         assertEquals(25, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2411,7 +2461,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2421,7 +2471,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -2431,7 +2481,7 @@
         assertEquals(13, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2446,7 +2496,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(TextView.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2456,7 +2506,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -2475,10 +2525,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityLineOverTextExtend() throws Exception {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -2487,7 +2538,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.android_wiki_short)).get(0);
 
@@ -2504,7 +2555,7 @@
         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2519,7 +2570,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2529,7 +2580,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -2539,7 +2590,7 @@
         assertEquals(13, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2554,7 +2605,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2564,7 +2615,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -2574,7 +2625,7 @@
         assertEquals(25, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2589,7 +2640,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2599,7 +2650,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -2609,7 +2660,7 @@
         assertEquals(34, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2624,7 +2675,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2634,7 +2685,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -2644,7 +2695,7 @@
         assertEquals(25, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2659,7 +2710,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2669,7 +2720,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -2679,7 +2730,7 @@
         assertEquals(13, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2694,7 +2745,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2704,7 +2755,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -2722,7 +2773,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Focus the view so we can change selection.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setFocusable(true);
@@ -2742,7 +2793,7 @@
         assertEquals(34, Selection.getSelectionEnd(editText.getText()));
 
         // Unocus the view so we can hide the keyboard.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.clearFocus();
@@ -2751,7 +2802,7 @@
         });
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent seventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent seventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2766,7 +2817,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2776,7 +2827,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(seventhExpected);
@@ -2786,7 +2837,7 @@
         assertEquals(25, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent eightExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eightExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2801,7 +2852,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2811,7 +2862,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eightExpected);
@@ -2821,7 +2872,7 @@
         assertEquals(13, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous line and wait for an event.
-        AccessibilityEvent ninethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent ninethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2836,7 +2887,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2846,7 +2897,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(ninethExpected);
@@ -2864,7 +2915,7 @@
         assertEquals(0, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent tenthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent tenthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2879,7 +2930,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2889,7 +2940,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(tenthExpected);
@@ -2899,7 +2950,7 @@
         assertEquals(13, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent eleventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eleventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2914,7 +2965,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2924,7 +2975,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eleventhExpected);
@@ -2934,7 +2985,7 @@
         assertEquals(25, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next line and wait for an event.
-        AccessibilityEvent twelvethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent twelvethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -2949,7 +3000,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -2959,7 +3010,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(twelvethExpected);
@@ -2970,10 +3021,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityParagraphOverText() throws Exception {
-        final TextView textView = (TextView) getActivity().findViewById(R.id.edit);
+        final TextView textView = (TextView) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 textView.setVisibility(View.VISIBLE);
@@ -2981,7 +3033,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.android_wiki_paragraphs)).get(0);
 
@@ -2997,7 +3049,7 @@
                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3012,7 +3064,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3022,7 +3074,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -3032,7 +3084,7 @@
         assertEquals(14, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3047,7 +3099,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                  AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3057,7 +3109,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -3067,7 +3119,7 @@
         assertEquals(32, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3082,7 +3134,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3092,7 +3144,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -3110,7 +3162,7 @@
         assertEquals(47, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3125,7 +3177,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3135,7 +3187,7 @@
                         && event.getMovementGranularity() ==
                                  AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -3145,7 +3197,7 @@
         assertEquals(33, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3160,7 +3212,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3170,7 +3222,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -3180,7 +3232,7 @@
         assertEquals(16, Selection.getSelectionEnd(textView.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3195,7 +3247,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3205,7 +3257,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -3224,10 +3276,11 @@
     }
 
     @MediumTest
+    @Test
     public void testActionNextAndPreviousAtGranularityParagraphOverTextExtend() throws Exception {
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -3236,7 +3289,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(getString(
                        R.string.android_wiki_paragraphs)).get(0);
 
@@ -3253,7 +3306,7 @@
         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent firstExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent firstExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3268,7 +3321,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3278,7 +3331,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(firstExpected);
@@ -3288,7 +3341,7 @@
         assertEquals(14, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent secondExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent secondExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3303,7 +3356,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3313,7 +3366,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(secondExpected);
@@ -3323,7 +3376,7 @@
         assertEquals(32, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent thirdExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent thirdExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3338,7 +3391,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3348,7 +3401,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(thirdExpected);
@@ -3366,7 +3419,7 @@
         assertEquals(47, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent fourthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fourthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3381,7 +3434,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3391,7 +3444,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fourthExpected);
@@ -3401,7 +3454,7 @@
         assertEquals(33, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent fifthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent fifthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3416,7 +3469,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3426,7 +3479,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(fifthExpected);
@@ -3436,7 +3489,7 @@
         assertEquals(16, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent sixthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent sixthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3451,7 +3504,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3461,7 +3514,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(sixthExpected);
@@ -3479,7 +3532,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Focus the view so we can change selection.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setFocusable(true);
@@ -3499,7 +3552,7 @@
         assertEquals(47, Selection.getSelectionEnd(editText.getText()));
 
         // Unfocus the view so we can get rid of the soft-keyboard.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.clearFocus();
@@ -3508,7 +3561,7 @@
         });
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent seventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent seventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3523,7 +3576,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3533,7 +3586,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(seventhExpected);
@@ -3543,7 +3596,7 @@
         assertEquals(33, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent eightExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eightExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3558,7 +3611,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3568,7 +3621,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eightExpected);
@@ -3578,7 +3631,7 @@
         assertEquals(16, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the previous paragraph and wait for an event.
-        AccessibilityEvent ninethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent ninethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3593,7 +3646,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3603,7 +3656,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(ninethExpected);
@@ -3621,7 +3674,7 @@
         assertEquals(2, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent tenthExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent tenthExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3636,7 +3689,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3646,7 +3699,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(tenthExpected);
@@ -3656,7 +3709,7 @@
         assertEquals(14, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent eleventhExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent eleventhExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3671,7 +3724,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3681,7 +3734,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(eleventhExpected);
@@ -3691,7 +3744,7 @@
         assertEquals(32, Selection.getSelectionEnd(editText.getText()));
 
         // Move to the next paragraph and wait for an event.
-        AccessibilityEvent twlevethExpected = getInstrumentation().getUiAutomation()
+        AccessibilityEvent twlevethExpected = sUiAutomation
                 .executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
@@ -3706,7 +3759,7 @@
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                         && event.getAction() ==
                                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
-                        && event.getPackageName().equals(getActivity().getPackageName())
+                        && event.getPackageName().equals(mActivity.getPackageName())
                         && event.getClassName().equals(EditText.class.getName())
                         && event.getText().size() > 0
                         && event.getText().get(0).toString().equals(getString(
@@ -3716,7 +3769,7 @@
                         && event.getMovementGranularity() ==
                                 AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Make sure we got the expected event.
         assertNotNull(twlevethExpected);
@@ -3735,16 +3788,17 @@
     }
 
     @MediumTest
+    @Test
     public void testTextEditingActions() throws Exception {
-        if (!getActivity().getPackageManager().hasSystemFeature(
+        if (!mActivity.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INPUT_METHODS)) {
             return;
         }
 
-        final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+        final EditText editText = (EditText) mActivity.findViewById(R.id.edit);
         final String textContent = getString(R.string.foo_bar_baz);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 editText.setVisibility(View.VISIBLE);
@@ -3755,7 +3809,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                        getString(R.string.foo_bar_baz)).get(0);
 
@@ -3802,10 +3856,11 @@
         assertTrue(TextUtils.isEmpty(editText.getText()));
     }
 
+    @Test
     public void testSetSelectionInContentDescription() throws Exception {
-        final View view = getActivity().findViewById(R.id.view);
+        final View view = mActivity.findViewById(R.id.view);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 view.setVisibility(View.VISIBLE);
@@ -3813,7 +3868,7 @@
             }
         });
 
-        AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                        getString(R.string.foo_bar_baz)).get(0);
 
@@ -3831,10 +3886,11 @@
                 AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
     }
 
+    @Test
     public void testSelectionPositionForNonEditableView() throws Exception {
-        final View view = getActivity().findViewById(R.id.view);
+        final View view = mActivity.findViewById(R.id.view);
 
-        getInstrumentation().runOnMainSync(new Runnable() {
+        sInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 view.setVisibility(View.VISIBLE);
@@ -3842,7 +3898,7 @@
             }
         });
 
-        final AccessibilityNodeInfo text = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo text = sUiAutomation
                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                        getString(R.string.foo_bar_baz)).get(0);
 
@@ -3852,7 +3908,7 @@
         assertSame(text.getTextSelectionEnd(), -1);
 
         // Set the cursor position.
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(new Runnable() {
+        sUiAutomation.executeAndWaitForEvent(new Runnable() {
             @Override
             public void run() {
                 Bundle arguments = new Bundle();
@@ -3867,10 +3923,10 @@
                 return (event.getEventType()
                         == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
             }
-        }, TIMEOUT_ASYNC_PROCESSING);
+        }, DEFAULT_TIMEOUT_MS);
 
         // Refresh the node.
-        AccessibilityNodeInfo refreshedText = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo refreshedText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                         getString(R.string.foo_bar_baz)).get(0);
 
@@ -3887,7 +3943,7 @@
                 AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments));
 
         // Refresh the node.
-        refreshedText = getInstrumentation().getUiAutomation()
+        refreshedText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
                         getString(R.string.foo_bar_baz)).get(0);
 
@@ -3920,12 +3976,16 @@
                         == AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
                 boolean actionMatches = event.getAction() == action;
                 boolean packageMatches =
-                        event.getPackageName().equals(getActivity().getPackageName());
+                        event.getPackageName().equals(mActivity.getPackageName());
                 boolean granularityMatches = event.getMovementGranularity() == granularity;
                 return isMovementEvent && actionMatches && packageMatches && granularityMatches;
             }
         };
-        return getInstrumentation().getUiAutomation()
-                .executeAndWaitForEvent(performActionRunnable, filter, TIMEOUT_ASYNC_PROCESSING);
+        return sUiAutomation
+                .executeAndWaitForEvent(performActionRunnable, filter, DEFAULT_TIMEOUT_MS);
+    }
+
+    private String getString(int id) {
+        return mActivity.getString(id);
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
index 9983699..6fe8b4c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -38,6 +38,8 @@
     AudioManager mAudioManager;
     // If a platform collects all volumes into one, these tests aren't relevant
     boolean mSingleVolume;
+    // If a11y volume is stuck at a single value, don't run the tests
+    boolean mFixedA11yVolume;
 
     @Before
     public void setUp() {
@@ -49,16 +51,21 @@
         mSingleVolume = (pm != null) && (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                 || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION))
                 || mAudioManager.isVolumeFixed();
+        final int MIN = mAudioManager.getStreamMinVolume(AudioManager.STREAM_ACCESSIBILITY);
+        final int MAX = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ACCESSIBILITY);
+        mFixedA11yVolume = (MIN == MAX);
     }
 
     @Test
     @Presubmit
     public void testChangeAccessibilityVolume_outsideValidAccessibilityService_shouldFail() {
-        if (mSingleVolume) {
+        if (mSingleVolume || mFixedA11yVolume) {
             return;
         }
-        int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
-        int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
+        final int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
+        final int MIN = mAudioManager.getStreamMinVolume(AudioManager.STREAM_ACCESSIBILITY);
+        final int MAX = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ACCESSIBILITY);
+        final int otherVolume = (startingVolume == MIN) ? MAX : MIN;
         mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume, 0);
         assertEquals("Non accessibility service should not be able to change accessibility volume",
                 startingVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
@@ -67,11 +74,13 @@
     @Test
     @AppModeFull
     public void testChangeAccessibilityVolume_inAccessibilityService_shouldWork() {
-        if (mSingleVolume) {
+        if (mSingleVolume || mFixedA11yVolume) {
             return;
         }
         final int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
-        final int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
+        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 {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index ea98078..f1d99b6 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -18,6 +18,8 @@
 
 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.AsyncUtils.DEFAULT_TIMEOUT_MS;
 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_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
@@ -34,13 +36,24 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
 
+import static junit.framework.TestCase.assertNull;
+
 import static org.hamcrest.Matchers.lessThan;
+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.assertSame;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
 import android.app.ActivityManager;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
 import android.content.Context;
@@ -48,7 +61,11 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
@@ -72,49 +89,78 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
+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;
+
 /**
  * Test cases for testing the accessibility APIs for querying of the screen content.
  * These APIs allow exploring the screen and requesting an action to be performed
  * on a given view from an AccessibilityService.
  */
 @AppModeFull
-public class AccessibilityWindowQueryTest
-        extends AccessibilityActivityTestCase<AccessibilityWindowQueryActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityWindowQueryTest {
+    private static final String LOG_TAG = "AccessibilityWindowQueryTest";
     private static String CONTENT_VIEW_RES_NAME =
             "android.accessibilityservice.cts:id/added_content";
     private static final long TIMEOUT_WINDOW_STATE_IDLE = 500;
-    private final AccessibilityEventFilter mDividerPresentFilter =
-            new AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
-                            isDividerWindowPresent(getInstrumentation().getUiAutomation())    );
-                }
-            };
-    private final AccessibilityEventFilter mDividerAbsentFilter =
-            new AccessibilityEventFilter() {
-                @Override
-                public boolean accept(AccessibilityEvent event) {
-                    return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
-                            !isDividerWindowPresent(getInstrumentation().getUiAutomation())   );
-                }
-            };
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
 
-    public AccessibilityWindowQueryTest() {
-        super(AccessibilityWindowQueryActivity.class);
+    private AccessibilityWindowQueryActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
+
+    @Rule
+    public ActivityTestRule<AccessibilityEndToEndActivity> mEndToEndActivityRule =
+            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        sUiAutomation.setServiceInfo(info);
     }
 
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+    }
+
+    private final AccessibilityEventFilter mDividerPresentFilter = (event) ->
+            (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED)
+                    && isDividerWindowPresent();
+
+    private final AccessibilityEventFilter mDividerAbsentFilter = (event) ->
+            (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED)
+                    && !isDividerWindowPresent();
+
     @MediumTest
+    @Test
     public void testFindByText() throws Throwable {
         // First, make the root view of the activity an accessibility node. This allows us to
         // later exclude views that are part of the activity's DecorView.
-        runTestOnUiThread(() -> getActivity().findViewById(R.id.added_content)
+        sInstrumentation.runOnMainSync(() -> mActivity.findViewById(R.id.added_content)
                     .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
 
         // Start looking from the added content instead of from the root accessibility node so
         // that nodes that we don't expect (i.e. window control buttons) are not included in the
         // list of accessibility nodes returned by findAccessibilityNodeInfosByText.
-        final AccessibilityNodeInfo addedContent = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo addedContent = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByViewId(CONTENT_VIEW_RES_NAME)
                         .get(0);
 
@@ -125,51 +171,52 @@
     }
 
     @MediumTest
+    @Test
     public void testFindByContentDescription() throws Exception {
         // find a view by text
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
-                .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.contentDescription)).get(0);
+        AccessibilityNodeInfo button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.contentDescription))
+                .get(0);
         assertNotNull(button);
     }
 
     @MediumTest
+    @Test
     public void testTraverseWindow() throws Exception {
-        verifyNodesInAppWindow(getInstrumentation().getUiAutomation().getRootInActiveWindow());
+        verifyNodesInAppWindow(sUiAutomation.getRootInActiveWindow());
     }
 
     @MediumTest
+    @Test
     public void testNoWindowsAccessIfFlagNotSet() throws Exception {
         // Clear window access flag
-        AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        getInstrumentation().getUiAutomation().setServiceInfo(info);
+        sUiAutomation.setServiceInfo(info);
 
         // Make sure the windows cannot be accessed.
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        assertTrue(uiAutomation.getWindows().isEmpty());
+        assertTrue(sUiAutomation.getWindows().isEmpty());
 
         // Find a button to click on.
-        final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/button1").get(0);
 
         // Click the button to generate an event
-        AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+        AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
                 () -> button1.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
 
         // Make sure the source window cannot be accessed.
         assertNull(event.getSource().getWindow());
     }
 
     @MediumTest
+    @Test
     public void testTraverseAllWindows() throws Exception {
         setAccessInteractiveWindowsFlag();
         try {
-            UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-
-            List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+            List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
             Rect boundsInScreen = new Rect();
 
             final int windowCount = windows.size();
@@ -198,20 +245,19 @@
     }
 
     @MediumTest
+    @Test
     public void testTraverseWindowFromEvent() throws Exception {
         setAccessInteractiveWindowsFlag();
         try {
-            UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-
             // Find a button to click on.
-            final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+            final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
                     .findAccessibilityNodeInfosByViewId(
                             "android.accessibilityservice.cts:id/button1").get(0);
 
             // Click the button.
-            AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
                     () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
 
             // Get the source window.
             AccessibilityWindowInfo window = event.getSource().getWindow();
@@ -219,7 +265,7 @@
             // Verify the application window.
             Rect boundsInScreen = new Rect();
             window.getBoundsInScreen(boundsInScreen);
-            assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check.
+            assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check
             assertSame(window.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
             assertTrue(window.isFocused());
             assertTrue(window.isActive());
@@ -235,20 +281,19 @@
     }
 
     @MediumTest
+    @Test
     public void testInteractWithAppWindow() throws Exception {
         setAccessInteractiveWindowsFlag();
         try {
-            UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-
             // Find a button to click on.
-            final AccessibilityNodeInfo button1 = uiAutomation.getRootInActiveWindow()
+            final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
                     .findAccessibilityNodeInfosByViewId(
                             "android.accessibilityservice.cts:id/button1").get(0);
 
             // Click the button.
-            AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
                     () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
 
             // Get the source window.
             AccessibilityWindowInfo window = event.getSource().getWindow();
@@ -259,14 +304,15 @@
                             "android.accessibilityservice.cts:id/button2").get(0);
 
             // Click the second button.
-            uiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+            sUiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
+                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
         } finally {
             clearAccessInteractiveWindowsFlag();
         }
     }
 
     @MediumTest
+    @Test
     public void testSingleAccessibilityFocusAcrossWindows() throws Exception {
         try {
             // Add two more windows.
@@ -290,9 +336,9 @@
                 assertSingleAccessibilityFocus();
             } finally {
                 // Clean up panel windows
-                getInstrumentation().runOnMainSync(() -> {
+                sInstrumentation.runOnMainSync(() -> {
                     WindowManager wm =
-                            getInstrumentation().getContext().getSystemService(WindowManager.class);
+                            sInstrumentation.getContext().getSystemService(WindowManager.class);
                     for (View view : views) {
                         wm.removeView(view);
                     }
@@ -305,61 +351,64 @@
     }
 
     @MediumTest
+    @Test
     public void testPerformActionSetAndClearFocus() throws Exception {
         // find a view and make sure it is not focused
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isFocused());
 
         // focus the view
         assertTrue(button.performAction(ACTION_FOCUS));
 
         // find the view again and make sure it is focused
-        button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
+        button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.button5)).get(0);
         assertTrue(button.isFocused());
 
         // unfocus the view
         assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
 
         // find the view again and make sure it is not focused
-        button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
+        button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isFocused());
     }
 
     @MediumTest
+    @Test
     public void testPerformActionSelect() throws Exception {
         // find a view and make sure it is not selected
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
         // select the view
         assertTrue(button.performAction(ACTION_SELECT));
 
         // find the view again and make sure it is selected
-        button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
+        button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.button5)).get(0);
         assertTrue(button.isSelected());
     }
 
     @MediumTest
+    @Test
     public void testPerformActionClearSelection() throws Exception {
         // find a view and make sure it is not selected
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
         // select the view
         assertTrue(button.performAction(ACTION_SELECT));
 
         // find the view again and make sure it is selected
-        button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
+        button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.button5)).get(0);
 
         assertTrue(button.isSelected());
 
@@ -367,40 +416,42 @@
         assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
 
         // find the view again and make sure it is not selected
-        button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
+        button = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
     }
 
     @MediumTest
+    @Test
     public void testPerformActionClick() throws Exception {
         // find a view and make sure it is not selected
-        final AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event
-        AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
                 () -> button.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
     }
 
     @MediumTest
+    @Test
     public void testPerformActionLongClick() throws Exception {
         // find a view and make sure it is not selected
-        final AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event.
-        AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
                 () -> button.performAction(ACTION_LONG_CLICK),
-                filterForEventType(TYPE_VIEW_LONG_CLICKED), TIMEOUT_ASYNC_PROCESSING);
+                filterForEventType(TYPE_VIEW_LONG_CLICKED), DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -408,11 +459,12 @@
 
 
     @MediumTest
+    @Test
     public void testPerformCustomAction() throws Exception {
         // find a view and make sure it is not selected
-        AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
 
         // find the custom action and perform it
         List<AccessibilityAction> actions = button.getActionList();
@@ -429,17 +481,18 @@
     }
 
     @MediumTest
+    @Test
     public void testGetEventSource() throws Exception {
         // find a view and make sure it is not focused
-        final AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+        final AccessibilityNodeInfo button = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        getString(R.string.button5)).get(0);
+                        mActivity.getString(R.string.button5)).get(0);
         assertFalse(button.isSelected());
 
         // focus and wait for the event
-        AccessibilityEvent awaitedEvent = getInstrumentation().getUiAutomation()
+        AccessibilityEvent awaitedEvent = sUiAutomation
                 .executeAndWaitForEvent(() -> button.performAction(ACTION_FOCUS),
-                        filterForEventType(TYPE_VIEW_FOCUSED), TIMEOUT_ASYNC_PROCESSING);
+                        filterForEventType(TYPE_VIEW_FOCUSED), DEFAULT_TIMEOUT_MS);
 
         assertNotNull(awaitedEvent);
 
@@ -477,16 +530,17 @@
     }
 
     @MediumTest
+    @Test
     public void testObjectContract() throws Exception {
         try {
-            AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+            AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
             info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            getInstrumentation().getUiAutomation().setServiceInfo(info);
+            sUiAutomation.setServiceInfo(info);
 
             // find a view and make sure it is not focused
-            AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
+            AccessibilityNodeInfo button = sUiAutomation
                     .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                            getString(R.string.button5)).get(0);
+                            mActivity.getString(R.string.button5)).get(0);
             AccessibilityNodeInfo parent = button.getParent();
             final int childCount = parent.getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -500,15 +554,16 @@
             }
             fail("Parent's children do not have the info whose parent is the parent.");
         } finally {
-            AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+            AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
             info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            getInstrumentation().getUiAutomation().setServiceInfo(info);
+            sUiAutomation.setServiceInfo(info);
         }
     }
 
     @MediumTest
+    @Test
     public void testWindowDockAndUndock_dividerWindowAppearsAndDisappears() throws Exception {
-        if (getInstrumentation().getContext().getPackageManager()
+        if (sInstrumentation.getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
             // Android TV doesn't support the divider window
             return;
@@ -516,7 +571,7 @@
 
         // Get com.android.internal.R.bool.config_supportsSplitScreenMultiWindow
         try {
-            if (!getInstrumentation().getContext().getResources().getBoolean(
+            if (!sInstrumentation.getContext().getResources().getBoolean(
                     Resources.getSystem().getIdentifier(
                             "config_supportsSplitScreenMultiWindow", "bool", "android"))) {
                 // Check if split screen multi window is not supported.
@@ -526,51 +581,35 @@
             // Do nothing, assume split screen multi window is supported.
         }
 
-        /* In P as ActivityManager.supportsMultiWindow() becomes a @TestAPI
-           i.e. a compatibility requirement, make the test fail in the next API */
-        try {
-            final Method method = ActivityManager.class
-                    .getMethod("supportsMultiWindow", Context.class);
-            if (!(Boolean)method.invoke(getInstrumentation().getContext().getSystemService(
-                    ActivityManager.class), getInstrumentation().getContext())) {
-                // Check if multiWindow is supported.
-                return;
-            }
-        } catch (NoSuchMethodException e) {
-        } catch (IllegalAccessException e) {
-        } catch (InvocationTargetException e) {
-            return;
-        }
-
         setAccessInteractiveWindowsFlag();
-        final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        assertFalse(isDividerWindowPresent(uiAutomation));
-        Runnable toggleSplitScreenRunnable = () -> assertTrue(uiAutomation.performGlobalAction(
+        assertFalse(isDividerWindowPresent());
+
+        Runnable toggleSplitScreenRunnable = () -> assertTrue(sUiAutomation.performGlobalAction(
                         AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN));
 
-        uiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerPresentFilter,
-                TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerPresentFilter,
+                DEFAULT_TIMEOUT_MS);
 
-        uiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerAbsentFilter,
-                TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerAbsentFilter,
+                DEFAULT_TIMEOUT_MS);
     }
 
+    @Test
     public void testFindPictureInPictureWindow() throws Exception {
-        if (!getInstrumentation().getContext().getPackageManager()
+        if (!sInstrumentation.getContext().getPackageManager()
                 .hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             return;
         }
-        final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        uiAutomation.executeAndWaitForEvent(() -> {
-            getInstrumentation().runOnMainSync(() -> {
-                getActivity().enterPictureInPictureMode();
+        sUiAutomation.executeAndWaitForEvent(() -> {
+            sInstrumentation.runOnMainSync(() -> {
+                mActivity.enterPictureInPictureMode();
             });
-        }, filterForEventType(TYPE_WINDOWS_CHANGED), TIMEOUT_ASYNC_PROCESSING);
-        waitForIdle();
+        }, filterForEventType(TYPE_WINDOWS_CHANGED), DEFAULT_TIMEOUT_MS);
+        sInstrumentation.waitForIdleSync();
 
         // We should be able to find a picture-in-picture window now
         int numPictureInPictureWindows = 0;
-        final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+        final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
             final AccessibilityWindowInfo window = windows.get(i);
@@ -581,9 +620,10 @@
         assertTrue(numPictureInPictureWindows >= 1);
     }
 
+    @Test
     public void testGetWindows_resultIsSortedByLayerDescending() throws TimeoutException {
         addTwoAppPanelWindows();
-        List<AccessibilityWindowInfo> windows = getInstrumentation().getUiAutomation().getWindows();
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
 
         AccessibilityWindowInfo windowAddedFirst = findWindow(windows, R.string.button1);
         AccessibilityWindowInfo windowAddedSecond = findWindow(windows, R.string.button2);
@@ -596,14 +636,14 @@
             int btnTextRes) {
         return windows.stream()
                 .filter(w -> w.getRoot()
-                        .findAccessibilityNodeInfosByText(getString(btnTextRes))
+                        .findAccessibilityNodeInfosByText(mActivity.getString(btnTextRes))
                         .size() == 1)
                 .findFirst()
                 .get();
     }
 
-    private boolean isDividerWindowPresent(UiAutomation uiAutomation) {
-        List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+    private boolean isDividerWindowPresent() {
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
             AccessibilityWindowInfo window = windows.get(i);
@@ -615,8 +655,7 @@
     }
 
     private void assertSingleAccessibilityFocus() {
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         AccessibilityWindowInfo focused = null;
 
         final int windowCount = windows.size();
@@ -628,7 +667,7 @@
                     focused = window;
 
                     AccessibilityNodeInfo root = window.getRoot();
-                    assertEquals(uiAutomation.findFocus(
+                    assertEquals(sUiAutomation.findFocus(
                             AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), root);
                     assertEquals(root.findFocus(
                             AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), root);
@@ -646,8 +685,7 @@
     }
 
     private void ensureAppWindowFocusedOrFail(int appWindowIndex) throws TimeoutException {
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         AccessibilityWindowInfo focusTarget = null;
 
         int visitedAppWindows = -1;
@@ -672,12 +710,12 @@
         }
 
         final AccessibilityWindowInfo finalFocusTarget = focusTarget;
-        uiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
+        sUiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
                 .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
                 filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
-                TIMEOUT_ASYNC_PROCESSING);
+                DEFAULT_TIMEOUT_MS);
 
-        windows = uiAutomation.getWindows();
+        windows = sUiAutomation.getWindows();
         for (int i = 0; i < windowCount; i++) {
             AccessibilityWindowInfo window = windows.get(i);
             if (window.getId() == focusTarget.getId()) {
@@ -689,13 +727,13 @@
 
     private View[] addTwoAppPanelWindows() throws TimeoutException {
         setAccessInteractiveWindowsFlag();
-        getInstrumentation().getUiAutomation()
-                .waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation
+                .waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, DEFAULT_TIMEOUT_MS);
 
         return new View[] {
                 addWindow(R.string.button1, params -> {
                     params.gravity = Gravity.TOP;
-                    params.y = getStatusBarHeight(getActivity());
+                    params.y = getStatusBarHeight(mActivity);
                 }),
                 addWindow(R.string.button2, params -> {
                     params.gravity = Gravity.BOTTOM;
@@ -706,8 +744,8 @@
     private Button addWindow(int btnTextRes, Consumer<WindowManager.LayoutParams> configure)
             throws TimeoutException {
         AtomicReference<Button> result = new AtomicReference<>();
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(() -> {
-            getInstrumentation().runOnMainSync(() -> {
+        sUiAutomation.executeAndWaitForEvent(() -> {
+            sInstrumentation.runOnMainSync(() -> {
                 final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
                 params.width = WindowManager.LayoutParams.MATCH_PARENT;
                 params.height = WindowManager.LayoutParams.WRAP_CONTENT;
@@ -716,37 +754,34 @@
                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
                 params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-                params.token = getActivity().getWindow().getDecorView().getWindowToken();
+                params.token = mActivity.getWindow().getDecorView().getWindowToken();
                 configure.accept(params);
 
-                final Button button = new Button(getActivity());
+                final Button button = new Button(mActivity);
                 button.setText(btnTextRes);
                 result.set(button);
-                getActivity().getWindowManager().addView(button, params);
+                mActivity.getWindowManager().addView(button, params);
             });
-        }, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), TIMEOUT_ASYNC_PROCESSING);
+        }, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), DEFAULT_TIMEOUT_MS);
         return result.get();
     }
 
     private void setAccessInteractiveWindowsFlag () {
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        uiAutomation.setServiceInfo(info);
+        sUiAutomation.setServiceInfo(info);
     }
 
     private void clearAccessInteractiveWindowsFlag () {
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        uiAutomation.setServiceInfo(info);
+        sUiAutomation.setServiceInfo(info);
     }
 
     private void ensureAccessibilityFocusCleared() {
         try {
-            final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-            uiAutomation.executeAndWaitForEvent(() -> {
-                List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+            sUiAutomation.executeAndWaitForEvent(() -> {
+                List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
                 final int windowCount = windows.size();
                 for (int i = 0; i < windowCount; i++) {
                     AccessibilityWindowInfo window = windows.get(i);
@@ -758,7 +793,7 @@
                         }
                     }
                 }
-            }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), TIMEOUT_ASYNC_PROCESSING);
+            }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), DEFAULT_TIMEOUT_MS);
         } catch (TimeoutException te) {
             /* ignore */
         }
@@ -766,9 +801,9 @@
 
     private void verifyNodesInAppWindow(AccessibilityNodeInfo root) throws Exception {
         try {
-            AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+            AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
             info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            getInstrumentation().getUiAutomation().setServiceInfo(info);
+            sUiAutomation.setServiceInfo(info);
 
             root.refresh();
 
@@ -819,17 +854,12 @@
                 }
             }
         } finally {
-            AccessibilityServiceInfo info = getInstrumentation().getUiAutomation().getServiceInfo();
+            AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
             info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            getInstrumentation().getUiAutomation().setServiceInfo(info);
+            sUiAutomation.setServiceInfo(info);
         }
     }
 
-    @Override
-    protected void scrubClass(Class<?> testCaseClass) {
-        /* intentionally do not scrub */
-    }
-
     private static class IsSortedBy<T> extends TypeSafeMatcher<List<T>> {
 
         private final Function<T, ? extends Comparable> mProperty;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index fe595a4..0ff067f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -31,8 +31,10 @@
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -99,6 +101,22 @@
         assertTrue("Timed out waiting for runOnServiceSync()", sr.waitForComplete());
     }
 
+    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);
+            }
+        });
+        if (throwable.get() != null) {
+            throw new RuntimeException(throwable.get());
+        }
+        return returnValue.get();
+    }
+
     public boolean wasOnInterruptCalled() {
         synchronized (mInterruptWaitObject) {
             return mOnInterruptCalled;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
index cd9055d..0e16b79 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
@@ -17,17 +17,22 @@
 package android.accessibilityservice.cts.activities;
 
 import android.accessibilityservice.cts.R;
-
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.BaseAdapter;
+import android.widget.Button;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 /**
  * This class is an {@link android.app.Activity} used to perform end-to-end
  * testing of the accessibility feature by interaction with the
@@ -68,6 +73,24 @@
 
         ListView listView = (ListView) findViewById(R.id.listview);
         listView.setAdapter(listAdapter);
+
+        final int touchableSize = 48;
+        Button button = findViewById(R.id.button);
+        Function<View, Rect> withTouchableAtRight = (v) -> new Rect(
+                v.getLeft(), 0, v.getRight() + touchableSize, v.getHeight());
+        button.getViewTreeObserver().addOnGlobalLayoutListener(setTouchDelegate(button,
+                () -> withTouchableAtRight.apply(button))::run);
+
+        Button delegated = findViewById(R.id.buttonDelegated);
+        Function<View, Rect> withTouchableAsParent = (v) -> new Rect(
+                0, 0, v.getWidth(), v.getHeight());
+        delegated.getViewTreeObserver().addOnGlobalLayoutListener(setTouchDelegate(delegated,
+                () -> withTouchableAsParent.apply((View) delegated.getParent()))::run);
+    }
+
+    private static Runnable setTouchDelegate(View target, Supplier<Rect> rectSupplier) {
+        return () -> ((View) target.getParent()).setTouchDelegate(
+                new TouchDelegate(rectSupplier.get(), target));
     }
 
     public void setReportedPackageName(String packageName) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
index 49be337..a078e2c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
@@ -19,6 +19,12 @@
 import android.view.WindowManager;
 
 public abstract class AccessibilityTestActivity extends Activity {
+    boolean mEnterAnimationComplete = false;
+
+    public void onStop() {
+        super.onStop();
+        mEnterAnimationComplete = false;
+    }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -28,4 +34,22 @@
                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
     }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        synchronized (this) {
+            mEnterAnimationComplete = true;
+            notifyAll();
+        }
+    }
+
+    public void waitForEnterAnimationComplete() {
+        synchronized(this) {
+            if (mEnterAnimationComplete == false) {
+                try {
+                    wait(5000);
+                } catch (InterruptedException e) {}
+            }
+        }
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
index 846acd9..3da457a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -15,15 +15,14 @@
 package android.accessibilityservice.cts.utils;
 
 import static org.hamcrest.CoreMatchers.both;
-
 import android.app.UiAutomation.AccessibilityEventFilter;
 import android.view.accessibility.AccessibilityEvent;
 
 import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeMatcher;
 
+import java.util.function.BiPredicate;
+
 /**
  * Utility class for creating AccessibilityEventFilters
  */
@@ -36,6 +35,16 @@
         return (both(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED))
                         .and(new WindowChangesMatcher(changes)))::matches;
     }
+
+    public static AccessibilityEventFilter filterForEventTypeWithResource(int eventType,
+            String ResourceName) {
+        TypeSafeMatcher<AccessibilityEvent> matchResourceName = new PropertyMatcher<>(
+                ResourceName, "Resource name",
+                (event, expect) -> event.getSource() != null
+                        && event.getSource().getViewIdResourceName().equals(expect));
+        return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchResourceName))::matches;
+    }
+
     public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> {
         private int mType;
 
@@ -89,7 +98,31 @@
 
         @Override
         public void describeTo(Description description) {
-            description.appendText("With window change type " + mContentChanges);
+            description.appendText("With content change type " + mContentChanges);
+        }
+    }
+
+    public static class PropertyMatcher<T> extends TypeSafeMatcher<AccessibilityEvent> {
+        private T mProperty;
+        private String mDescription;
+        private BiPredicate<AccessibilityEvent, T> mComparator;
+
+        public PropertyMatcher(T property, String description,
+                BiPredicate<AccessibilityEvent, T> comparator) {
+            super();
+            mProperty = property;
+            mDescription = description;
+            mComparator = comparator;
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessibilityEvent event) {
+            return mComparator.test(event, mProperty);
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to " + mDescription + " " + mProperty.toString());
         }
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 822b2a6..5e7745a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -14,33 +14,51 @@
 
 package android.accessibilityservice.cts.utils;
 
-import static android.accessibilityservice.cts.AccessibilityActivityTestCase
-        .TIMEOUT_ASYNC_PROCESSING;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
+import android.os.PowerManager;
+import android.os.SystemClock;
 import android.support.test.rule.ActivityTestRule;
 import android.text.TextUtils;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 
+import com.android.compatibility.common.util.TestUtils;
+
 import java.util.List;
+import java.util.function.BooleanSupplier;
 
 /**
  * Utilities useful when launching an activity to make sure it's all the way on the screen
  * before we start testing it.
  */
 public class ActivityLaunchUtils {
+    private static final String LOG_TAG = "ActivityLaunchUtils";
+
     // Using a static variable so it can be used in lambdas. Not preserving state in it.
     private static Activity mTempActivity;
 
-    public static Activity launchActivityAndWaitForItToBeOnscreen(Instrumentation instrumentation,
-            UiAutomation uiAutomation, ActivityTestRule<? extends Activity> rule) throws Exception {
+    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();
@@ -49,6 +67,7 @@
         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);
@@ -71,9 +90,9 @@
                     }
                     return (!bounds.isEmpty())
                             && (bounds.left == location[0]) && (bounds.top == location[1]);
-                }, TIMEOUT_ASYNC_PROCESSING);
+                }, DEFAULT_TIMEOUT_MS);
         assertNotNull(awaitedEvent);
-        return mTempActivity;
+        return (T) mTempActivity;
     }
 
     public static CharSequence getActivityTitle(
@@ -97,4 +116,110 @@
         }
         return returnValue;
     }
+
+    public static void homeScreenOrBust(Context context, UiAutomation uiAutomation) {
+        wakeUpOrBust(context, uiAutomation);
+        if (context.getPackageManager().isInstantApp()) return;
+        if (isHomeScreenShowing(context, uiAutomation)) return;
+        try {
+            executeAndWaitOn(
+                    uiAutomation,
+                    () -> uiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME),
+                    () -> isHomeScreenShowing(context, uiAutomation),
+                    DEFAULT_TIMEOUT_MS,
+                    "home screen");
+        } catch (AssertionError error) {
+            Log.e(LOG_TAG, "Timed out looking for home screen. Dumping window list");
+            final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+            if (windows == null) {
+                Log.e(LOG_TAG, "Window list is null");
+            } else if (windows.isEmpty()) {
+                Log.e(LOG_TAG, "Window list is empty");
+            } else {
+                for (AccessibilityWindowInfo window : windows) {
+                    Log.e(LOG_TAG, window.toString());
+                }
+            }
+
+            fail("Unable to reach home screen");
+        }
+    }
+
+    private static boolean isHomeScreenShowing(Context context, UiAutomation uiAutomation) {
+        final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+        final PackageManager packageManager = context.getPackageManager();
+        final List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(
+                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+                PackageManager.MATCH_DEFAULT_ONLY);
+
+        // Look for a window with a package name that matches the default home screen
+        for (AccessibilityWindowInfo window : windows) {
+            final AccessibilityNodeInfo root = window.getRoot();
+            if (root != null) {
+                final CharSequence packageName = root.getPackageName();
+                if (packageName != null) {
+                    for (ResolveInfo resolveInfo : resolveInfos) {
+                        if ((resolveInfo.activityInfo != null)
+                                && packageName.equals(resolveInfo.activityInfo.packageName)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private static void wakeUpOrBust(Context context, UiAutomation uiAutomation) {
+        final long deadlineUptimeMillis = SystemClock.uptimeMillis() + DEFAULT_TIMEOUT_MS;
+        final PowerManager powerManager = context.getSystemService(PowerManager.class);
+        do {
+            if (powerManager.isInteractive()) {
+                Log.d(LOG_TAG, "Device is interactive");
+                return;
+            }
+
+            Log.d(LOG_TAG, "Sending wakeup keycode");
+            final long eventTime = SystemClock.uptimeMillis();
+            uiAutomation.injectInputEvent(
+                    new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
+                            KeyEvent.KEYCODE_WAKEUP, 0 /* repeat */, 0 /* metastate */,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 0 /* flags */,
+                            InputDevice.SOURCE_KEYBOARD), true /* sync */);
+            uiAutomation.injectInputEvent(
+                    new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
+                            KeyEvent.KEYCODE_WAKEUP, 0 /* repeat */, 0 /* metastate */,
+                            KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 0 /* flags */,
+                            InputDevice.SOURCE_KEYBOARD), true /* sync */);
+            try {
+                Thread.sleep(50);
+            } catch (InterruptedException e) {}
+        } while (SystemClock.uptimeMillis() < deadlineUptimeMillis);
+        fail("Unable to wake up screen");
+    }
+
+    /**
+     * Executes a command and waits for a specified condition up to a given wait timeout. It checks
+     * condition result each time when events delivered, and throws exception if the condition
+     * result is not {@code true} within the given timeout.
+     */
+    private static void executeAndWaitOn(UiAutomation uiAutomation, Runnable command,
+            BooleanSupplier condition, long timeoutMillis, String conditionName) {
+        final Object waitObject = new Object();
+        final long executionStartTimeMillis = SystemClock.uptimeMillis();
+        try {
+            uiAutomation.setOnAccessibilityEventListener((event) -> {
+                if (event.getEventTime() < executionStartTimeMillis) {
+                    return;
+                }
+                synchronized (waitObject) {
+                    waitObject.notifyAll();
+                }
+            });
+            command.run();
+            TestUtils.waitOn(waitObject, condition, timeoutMillis, conditionName);
+        } finally {
+            uiAutomation.setOnAccessibilityEventListener(null);
+        }
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
index 12bb737..09c6cc7 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
@@ -20,7 +20,7 @@
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-import android.os.SystemClock;
+import com.android.compatibility.common.util.TestUtils;
 
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Future;
@@ -70,27 +70,6 @@
     }
 
     public static void waitOn(Object notifyLock, BooleanSupplier condition) {
-        waitOn(notifyLock, condition, DEFAULT_TIMEOUT_MS);
-    }
-
-    public static void waitOn(Object notifyLock, BooleanSupplier condition, long timeoutMs) {
-        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 condition");
-                }
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
+        TestUtils.waitOn(notifyLock, condition, DEFAULT_TIMEOUT_MS, null);
     }
 }
diff --git a/tests/admin/Android.mk b/tests/admin/Android.mk
index bd5346d..40ce285 100644
--- a/tests/admin/Android.mk
+++ b/tests/admin/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner mockito-target-minus-junit4
+    ctstestrunner mockito-target-minus-junit4 truth-prebuilt testng
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index 7f1a7ad..c0b10fe 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -16,6 +16,12 @@
 <configuration description="Config for the CTS device admin tests">
     <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" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
diff --git a/tests/admin/app/AndroidManifest.xml b/tests/admin/app/AndroidManifest.xml
index 0307487..baff9ab 100644
--- a/tests/admin/app/AndroidManifest.xml
+++ b/tests/admin/app/AndroidManifest.xml
@@ -18,6 +18,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.admin.app">
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+
     <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
@@ -67,6 +69,24 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="android.admin.app.CtsDeviceAdminReceiverVisible"
+                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin_visible" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="android.admin.app.CtsDeviceAdminReceiverInvisible"
+                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin_invisible" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+
         <!-- Device Admin that needs to be in the deactivated state in order
              for tests to pass. -->
         <receiver android:name="android.admin.app.CtsDeviceAdminDeactivatedReceiver"
diff --git a/tests/admin/app/res/xml/device_admin_invisible.xml b/tests/admin/app/res/xml/device_admin_invisible.xml
new file mode 100644
index 0000000..6af5a60
--- /dev/null
+++ b/tests/admin/app/res/xml/device_admin_invisible.xml
@@ -0,0 +1,24 @@
+<!--
+ * 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.
+ -->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <support-transfer-ownership />
+    <uses-policies>
+        <limit-password />
+        <reset-password />
+        <wipe-data />
+    </uses-policies>
+</device-admin>
diff --git a/tests/admin/app/res/xml/device_admin_visible.xml b/tests/admin/app/res/xml/device_admin_visible.xml
new file mode 100644
index 0000000..5d37a95
--- /dev/null
+++ b/tests/admin/app/res/xml/device_admin_visible.xml
@@ -0,0 +1,24 @@
+<!--
+ * 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.
+ -->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="true">
+    <support-transfer-ownership />
+    <uses-policies>
+        <limit-password />
+        <reset-password />
+        <wipe-data />
+    </uses-policies>
+</device-admin>
diff --git a/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverInvisible.java b/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverInvisible.java
new file mode 100644
index 0000000..05f9426
--- /dev/null
+++ b/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverInvisible.java
@@ -0,0 +1,22 @@
+/*
+ * 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.admin.app;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminReceiverInvisible extends DeviceAdminReceiver {
+}
diff --git a/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverVisible.java b/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverVisible.java
new file mode 100644
index 0000000..9bf440a
--- /dev/null
+++ b/tests/admin/app/src/android/admin/app/CtsDeviceAdminReceiverVisible.java
@@ -0,0 +1,22 @@
+/*
+ * 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.admin.app;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminReceiverVisible extends DeviceAdminReceiver {
+}
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminInfoTest.java b/tests/admin/src/android/admin/cts/DeviceAdminInfoTest.java
index 1790503..a0533cd 100644
--- a/tests/admin/src/android/admin/cts/DeviceAdminInfoTest.java
+++ b/tests/admin/src/android/admin/cts/DeviceAdminInfoTest.java
@@ -16,8 +16,11 @@
 
 package android.admin.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.admin.DeviceAdminInfo;
 import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.test.AndroidTestCase;
@@ -60,6 +63,16 @@
         return new ComponentName("android.admin.app", "android.admin.app.CtsDeviceAdminProfileOwner");
     }
 
+    static ComponentName getVisibleComponent() {
+        return new ComponentName(
+                "android.admin.app", "android.admin.app.CtsDeviceAdminReceiverVisible");
+    }
+
+    static ComponentName getInvisibleComponent() {
+        return new ComponentName(
+                "android.admin.app", "android.admin.app.CtsDeviceAdminReceiverInvisible");
+    }
+
     public void testDeviceAdminInfo() throws Exception {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testDeviceAdminInfo");
@@ -70,27 +83,27 @@
                 PackageManager.GET_META_DATA);
 
         DeviceAdminInfo info = new DeviceAdminInfo(mContext, resolveInfo);
-        assertEquals(mComponent, info.getComponent());
-        assertEquals(mComponent.getPackageName(), info.getPackageName());
-        assertEquals(mComponent.getClassName(), info.getReceiverName());
+        assertThat(mComponent).isEqualTo(info.getComponent());
+        assertThat(mComponent.getPackageName()).isEqualTo(info.getPackageName());
+        assertThat(mComponent.getClassName()).isEqualTo(info.getReceiverName());
 
-        assertFalse(info.supportsTransferOwnership());
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
+        assertThat(info.supportsTransferOwnership()).isFalse();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA)).isTrue();
 
-        assertEquals("force-lock",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
-        assertEquals("limit-password",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
-        assertEquals("reset-password",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
-        assertEquals("watch-login",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
-        assertEquals("wipe-data",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
+        assertThat("force-lock")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
+        assertThat("limit-password")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+        assertThat("reset-password")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
+        assertThat("watch-login")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
+        assertThat("wipe-data")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
     }
 
     public void testDeviceAdminInfo2() throws Exception {
@@ -103,27 +116,27 @@
                 PackageManager.GET_META_DATA);
 
         DeviceAdminInfo info = new DeviceAdminInfo(mContext, resolveInfo);
-        assertEquals(mSecondComponent, info.getComponent());
-        assertEquals(mSecondComponent.getPackageName(), info.getPackageName());
-        assertEquals(mSecondComponent.getClassName(), info.getReceiverName());
+        assertThat(mSecondComponent).isEqualTo(info.getComponent());
+        assertThat(mSecondComponent.getPackageName()).isEqualTo(info.getPackageName());
+        assertThat(mSecondComponent.getClassName()).isEqualTo(info.getReceiverName());
 
-        assertFalse(info.supportsTransferOwnership());
-        assertFalse(info.usesPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
-        assertFalse(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
-        assertTrue(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
+        assertThat(info.supportsTransferOwnership()).isFalse();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK)).isFalse();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD)).isTrue();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN)).isFalse();
+        assertThat(info.usesPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA)).isTrue();
 
-        assertEquals("force-lock",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
-        assertEquals("limit-password",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
-        assertEquals("reset-password",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
-        assertEquals("watch-login",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
-        assertEquals("wipe-data",
-                info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
+        assertThat("force-lock")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_FORCE_LOCK));
+        assertThat("limit-password")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+        assertThat("reset-password")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_RESET_PASSWORD));
+        assertThat("watch-login")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WATCH_LOGIN));
+        assertThat("wipe-data")
+                .isEqualTo(info.getTagForPolicy(DeviceAdminInfo.USES_POLICY_WIPE_DATA));
     }
 
     public void testDeviceAdminInfo3() throws Exception {
@@ -136,6 +149,66 @@
                 PackageManager.GET_META_DATA);
 
         DeviceAdminInfo info = new DeviceAdminInfo(mContext, resolveInfo);
-        assertTrue(info.supportsTransferOwnership());
+        assertThat(info.supportsTransferOwnership()).isTrue();
+    }
+
+    public void testDescribeContents_returnsAtLeastZero() throws Exception {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testDescribeContents_returnsAtLeastZero");
+            return;
+        }
+
+        assertThat(buildDeviceAdminInfo(buildActivityInfo()).describeContents()).isAtLeast(0);
+    }
+
+    public void testGetActivityInfo_returnsActivityInfo() throws Exception {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testGetActivityInfo_returnsActivityInfo");
+            return;
+        }
+        ActivityInfo activityInfo = buildActivityInfo();
+        DeviceAdminInfo deviceAdminInfo = buildDeviceAdminInfo(activityInfo);
+
+        assertThat(deviceAdminInfo.getActivityInfo()).isEqualTo(activityInfo);
+    }
+
+    public void testIsVisible_visibleComponent_returnsTrue() throws Exception {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testIsVisible_visibleComponent_returnsTrue");
+            return;
+        }
+        ActivityInfo activityInfo = buildActivityInfo(getVisibleComponent());
+        DeviceAdminInfo deviceAdminInfo = buildDeviceAdminInfo(activityInfo);
+
+        assertThat(deviceAdminInfo.isVisible()).isTrue();
+    }
+
+    public void testIsVisible_invisibleComponent_returnsFalse() throws Exception {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testIsVisible_invisibleComponent_returnsFalse");
+            return;
+        }
+        ActivityInfo activityInfo = buildActivityInfo(getInvisibleComponent());
+        DeviceAdminInfo deviceAdminInfo = buildDeviceAdminInfo(activityInfo);
+
+        assertThat(deviceAdminInfo.isVisible()).isFalse();
+    }
+
+    private DeviceAdminInfo buildDeviceAdminInfo(ActivityInfo activityInfo) throws Exception {
+        return new DeviceAdminInfo(mContext, buildResolveInfo(activityInfo));
+    }
+
+    private ResolveInfo buildResolveInfo(ActivityInfo activityInfo) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = activityInfo;
+        return resolveInfo;
+    }
+
+    private ActivityInfo buildActivityInfo() throws Exception {
+        return buildActivityInfo(mThirdComponent);
+    }
+
+    private ActivityInfo buildActivityInfo(ComponentName componentName) throws Exception {
+        return mPackageManager.getReceiverInfo(componentName, PackageManager.GET_META_DATA);
     }
 }
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminReceiverTest.java b/tests/admin/src/android/admin/cts/DeviceAdminReceiverTest.java
index 4888476..0407811 100644
--- a/tests/admin/src/android/admin/cts/DeviceAdminReceiverTest.java
+++ b/tests/admin/src/android/admin/cts/DeviceAdminReceiverTest.java
@@ -16,10 +16,12 @@
 
 package android.admin.cts;
 
+import static android.app.admin.DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING;
 import static android.app.admin.DeviceAdminReceiver.ACTION_PASSWORD_CHANGED;
 import static android.app.admin.DeviceAdminReceiver.ACTION_PASSWORD_FAILED;
 import static android.app.admin.DeviceAdminReceiver.ACTION_PASSWORD_SUCCEEDED;
 import static android.app.admin.DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.argThat;
@@ -209,6 +211,34 @@
                 eq(NETWORK_LOGS_TOKEN), eq(NETWORK_LOGS_COUNT));
     }
 
+    @Presubmit
+    public void testOnReceive_enterLockTaskWithoutPackage_callsCallback() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testOnReceive_enterLockTask_callsCallback");
+            return;
+        }
+        final Intent intent = new Intent(ACTION_LOCK_TASK_ENTERING);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mReceiver).onLockTaskModeEntering(mContext, intent, null);
+    }
+
+    @Presubmit
+    public void testOnReceive_enterLockTaskWithPackage_callsCallback() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testOnReceive_enterLockTask_callsCallback");
+            return;
+        }
+        final Intent intent = new Intent(ACTION_LOCK_TASK_ENTERING);
+        final String pkg = "pkg";
+        intent.putExtra(EXTRA_LOCK_TASK_PACKAGE, pkg);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mReceiver).onLockTaskModeEntering(mContext, intent, pkg);
+    }
+
     private Intent actionEq(final String expected) {
         return argThat(x -> expected.equals(x.getAction()));
     }
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index c6240d7c..9298ac2 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -17,6 +17,7 @@
 package android.admin.cts;
 
 import static org.junit.Assert.assertNotEquals;
+import static org.testng.Assert.assertThrows;
 
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
@@ -45,6 +46,7 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -65,6 +67,7 @@
     private boolean mManagedProfiles;
     private PackageManager mPackageManager;
     private NotificationManager mNotificationManager;
+    private boolean mHasSecureLockScreen;
 
     private static final String TEST_CA_STRING1 =
             "-----BEGIN CERTIFICATE-----\n" +
@@ -97,6 +100,8 @@
         mDeviceAdmin = mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
         mManagedProfiles = mDeviceAdmin
                 && mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
+        mHasSecureLockScreen =
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
     }
 
     public void testGetActiveAdmins() {
@@ -117,8 +122,9 @@
         }
         int originalValue = mDevicePolicyManager.getKeyguardDisabledFeatures(mComponent);
         try {
+            // Test all possible combinations which mathematically ends at 2 * LAST - 1
             for (int which = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
-                    which < 2 * DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; ++which) {
+                    which < 2 * DevicePolicyManager.KEYGUARD_DISABLE_IRIS; ++which) {
                 mDevicePolicyManager.setKeyguardDisabledFeatures(mComponent, which);
                 assertEquals(which, mDevicePolicyManager.getKeyguardDisabledFeatures(mComponent));
             }
@@ -838,7 +844,7 @@
             mDevicePolicyManager.setBackupServiceEnabled(mComponent, false);
             fail("did not throw expected SecurityException");
         } catch (SecurityException e) {
-            assertDeviceOwnerMessage(e.getMessage());
+            assertProfileOwnerMessage(e.getMessage());
         }
     }
 
@@ -851,7 +857,7 @@
             mDevicePolicyManager.isBackupServiceEnabled(mComponent);
             fail("did not throw expected SecurityException");
         } catch (SecurityException e) {
-            assertDeviceOwnerMessage(e.getMessage());
+            assertProfileOwnerMessage(e.getMessage());
         }
     }
 
@@ -868,7 +874,7 @@
     }
 
     public void testSetResetPasswordToken_failIfNotDeviceOrProfileOwner() {
-        if (!mDeviceAdmin) {
+        if (!mDeviceAdmin || !mHasSecureLockScreen) {
             Log.w(TAG, "Skipping testSetResetPasswordToken_failIfNotDeviceOwner");
             return;
         }
@@ -881,7 +887,7 @@
     }
 
     public void testClearResetPasswordToken_failIfNotDeviceOrProfileOwner() {
-        if (!mDeviceAdmin) {
+        if (!mDeviceAdmin || !mHasSecureLockScreen) {
             Log.w(TAG, "Skipping testClearResetPasswordToken_failIfNotDeviceOwner");
             return;
         }
@@ -894,7 +900,7 @@
     }
 
     public void testIsResetPasswordTokenActive_failIfNotDeviceOrProfileOwner() {
-        if (!mDeviceAdmin) {
+        if (!mDeviceAdmin || !mHasSecureLockScreen) {
             Log.w(TAG, "Skipping testIsResetPasswordTokenActive_failIfNotDeviceOwner");
             return;
         }
@@ -907,7 +913,7 @@
     }
 
     public void testResetPasswordWithToken_failIfNotDeviceOrProfileOwner() {
-        if (!mDeviceAdmin) {
+        if (!mDeviceAdmin || !mHasSecureLockScreen) {
             Log.w(TAG, "Skipping testResetPasswordWithToken_failIfNotDeviceOwner");
             return;
         }
@@ -920,7 +926,7 @@
     }
 
     public void testIsUsingUnifiedPassword_failIfNotProfileOwner() {
-        if (!mDeviceAdmin) {
+        if (!mDeviceAdmin || !mHasSecureLockScreen) {
             Log.w(TAG, "Skipping testIsUsingUnifiedPassword_failIfNotProfileOwner");
             return;
         }
@@ -1036,4 +1042,37 @@
             assertProfileOwnerMessage(tolerated.getMessage());
         }
     }
+
+    public void testSetStorageEncryption_noAdmin() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testSetStorageEncryption_noAdmin");
+            return;
+        }
+        final ComponentName notAdmin = new ComponentName("com.test.foo", ".bar");
+        assertThrows(SecurityException.class,
+            () -> mDevicePolicyManager.setStorageEncryption(notAdmin, true));
+        assertThrows(SecurityException.class,
+            () -> mDevicePolicyManager.setStorageEncryption(notAdmin, false));
+    }
+
+    public void testCrossProfileCalendar_failIfNotProfileOwner() {
+        final String TEST_PACKAGE_NAME = "test.package.name";
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testCrossProfileCalendar_failIfNotProfileOwner");
+            return;
+        }
+        try {
+            mDevicePolicyManager.setCrossProfileCalendarPackages(mComponent,
+                    Collections.singleton(TEST_PACKAGE_NAME));
+            fail("setCrossProfileCalendarPackages did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+        try {
+            mDevicePolicyManager.getCrossProfileCalendarPackages(mComponent);
+            fail("getCrossProfileCalendarPackages did not throw expected SecurityException");
+        } catch (SecurityException e) {
+            assertProfileOwnerMessage(e.getMessage());
+        }
+    }
 }
diff --git a/tests/app/Android.mk b/tests/app/Android.mk
index e38ad2f..6e30b28 100644
--- a/tests/app/Android.mk
+++ b/tests/app/Android.mk
@@ -34,7 +34,8 @@
     android-support-test \
     platform-test-annotations \
     cts-amwm-util \
-    android-support-test
+    android-support-test \
+    platformprotosnano
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src)
@@ -47,6 +48,7 @@
 LOCAL_INSTRUMENTATION_FOR := CtsAppTestStubs
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 11
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 96977fb..326719e 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 69505c3..dc9c0b9 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -21,10 +21,13 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSimpleApp.apk" />
         <option name="test-file-name" value="CtsAppTestStubs.apk" />
-        <option name="test-file-name" value="CtsAppTestStubsDifferentUid.apk" />
+        <option name="test-file-name" value="CtsAppTestStubsApp1.apk" />
+        <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="CtsCantSaveState1.apk" />
         <option name="test-file-name" value="CtsCantSaveState2.apk" />
+        <option name="test-file-name" value="NotificationDelegator.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/tests/app/NotificationDelegator/Android.mk b/tests/app/NotificationDelegator/Android.mk
new file mode 100644
index 0000000..dde1787
--- /dev/null
+++ b/tests/app/NotificationDelegator/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := NotificationDelegator
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
new file mode 100644
index 0000000..54d1f05
--- /dev/null
+++ b/tests/app/NotificationDelegator/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.notificationdelegator">
+    <application android:label="Notification Delegator">
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator">
+            <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>
+
+        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker">
+            <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/res/layout/activity.xml b/tests/app/NotificationDelegator/res/layout/activity.xml
new file mode 100644
index 0000000..f443d36
--- /dev/null
+++ b/tests/app/NotificationDelegator/res/layout/activity.xml
@@ -0,0 +1,31 @@
+<?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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="This app #1 can't save its state"
+    />
+
+</LinearLayout>
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
new file mode 100644
index 0000000..15cf42d
--- /dev/null
+++ b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.test.notificationdelegator;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegator extends Activity {
+    private static final String TAG = "Delegator";
+    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());
+        finish();
+    }
+}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
new file mode 100644
index 0000000..d51d41f
--- /dev/null
+++ b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.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.test.notificationdelegator;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationRevoker extends Activity {
+    private static final String TAG = "Revoker";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        nm.revokeNotificationDelegate();
+        Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
+        finish();
+    }
+}
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index fe340c9..7f66a12 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -44,6 +44,7 @@
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <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" />
 
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
@@ -51,7 +52,8 @@
                 android:multiArch="true"
                 android:name="android.app.stubs.MockApplication"
                 android:supportsRtl="true"
-                android:networkSecurityConfig="@xml/network_security_config">
+                android:networkSecurityConfig="@xml/network_security_config"
+                android:zygotePreloadName=".ZygotePreload">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
 
@@ -59,6 +61,8 @@
 
         <activity android:name="android.app.stubs.ActionBarActivity" />
 
+        <activity android:name="android.app.stubs.ActivityCallbacksTestActivity" />
+
         <activity android:name="android.app.stubs.MockActivity" android:label="MockActivity">
             <meta-data android:name="android.app.alias"
                 android:resource="@xml/alias" />
@@ -130,6 +134,16 @@
         </service>
 
         <service android:name="android.app.stubs.LocalForegroundService">
+            <intent-filter>
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE" />
+            </intent-filter>
+        </service>
+
+        <service android:name="android.app.stubs.LocalForegroundServiceLocation"
+                android:foregroundServiceType="location">
+            <intent-filter>
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE_LOCATION" />
+            </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalGrantedService"
@@ -146,6 +160,9 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.app.stubs.IsolatedService" android:isolatedProcess="true" android:useAppZygote="true">
+        </service>
+
         <activity android:name="android.app.stubs.TestedScreen"
                 android:process=":remoteScreen">
         </activity>
@@ -301,7 +318,8 @@
                   android:label="PipActivity"
                   android:resizeableActivity="true"
                   android:supportsPictureInPicture="true"
-                  android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout">
+                  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" />
@@ -345,14 +363,27 @@
             </meta-data>
         </service>
 
-        <service android:name="android.app.stubs.MockNotificationListener"
+        <service android:name="android.app.stubs.TestNotificationListener"
                  android:exported="true"
-                 android:label="MockNotificationListener"
+                 android:label="TestNotificationListener"
                  android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.notification.NotificationListenerService" />
             </intent-filter>
         </service>
+
+        <activity android:name="android.app.stubs.AutomaticZenRuleActivity">
+            <intent-filter>
+                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
+            </intent-filter>
+            <meta-data android:name="android.app.automatic.ruleType"
+                       android:value="@string/automatic_zen_rule_name" />
+            <meta-data android:name="android.app.zen.automatic.ruleInstanceLimit"
+                       android:value="1" />
+        </activity>
+
+        <receiver android:name="android.app.stubs.CommandReceiver"
+                  android:exported="true" />
     </application>
 
 </manifest>
diff --git a/tests/app/app/res/values/strings.xml b/tests/app/app/res/values/strings.xml
index 839e398..1915bef 100644
--- a/tests/app/app/res/values/strings.xml
+++ b/tests/app/app/res/values/strings.xml
@@ -183,4 +183,6 @@
     <string name="wallpaper_title">Title</string>
     <string name="wallpaper_context">Context</string>
     <string name="wallpaper_context_uri">http://android.com</string>
+    <string name="wallpaper_slice_uri">content://com.android.wallpaper/slice</string>
+    <string name="automatic_zen_rule_name">Rule!</string>
 </resources>
diff --git a/tests/app/app/res/xml/wallpaper.xml b/tests/app/app/res/xml/wallpaper.xml
index f70b20f..e86c9b9 100644
--- a/tests/app/app/res/xml/wallpaper.xml
+++ b/tests/app/app/res/xml/wallpaper.xml
@@ -20,6 +20,8 @@
     android:author="@string/wallpaper_collection"
     android:description="@string/wallpaper_description"
     android:showMetadataInPreview="true"
+    android:supportsMultipleDisplays="true"
     android:contextDescription="@string/wallpaper_context"
     android:contextUri="@string/wallpaper_context_uri"
-/>
\ No newline at end of file
+    android:settingsSliceUri="@string/wallpaper_slice_uri"
+/>
diff --git a/tests/app/app/src/android/app/stubs/ActivityCallbacksTestActivity.java b/tests/app/app/src/android/app/stubs/ActivityCallbacksTestActivity.java
new file mode 100644
index 0000000..38f0b52
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/ActivityCallbacksTestActivity.java
@@ -0,0 +1,215 @@
+/*
+ * 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.app.stubs;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import android.util.Pair;
+
+import java.util.ArrayList;
+
+public class ActivityCallbacksTestActivity extends Activity {
+
+    public enum Event {
+        ON_PRE_CREATE,
+        ON_CREATE,
+        ON_POST_CREATE,
+
+        ON_PRE_START,
+        ON_START,
+        ON_POST_START,
+
+        ON_PRE_RESUME,
+        ON_RESUME,
+        ON_POST_RESUME,
+
+        ON_PRE_PAUSE,
+        ON_PAUSE,
+        ON_POST_PAUSE,
+
+        ON_PRE_STOP,
+        ON_STOP,
+        ON_POST_STOP,
+
+        ON_PRE_DESTROY,
+        ON_DESTROY,
+        ON_POST_DESTROY,
+    }
+
+    public enum Source {
+        ACTIVITY,
+        ACTIVITY_CALLBACK,
+        APPLICATION_ACTIVITY_CALLBACK
+    }
+
+    private final Application.ActivityLifecycleCallbacks mActivityCallbacks;
+
+    private ArrayList<Pair<Source, Event>> mCollectedEvents = new ArrayList<>();
+
+    public ActivityCallbacksTestActivity() {
+        mActivityCallbacks = new Application.ActivityLifecycleCallbacks() {
+
+            @Override
+            public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_CREATE);
+            }
+
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_CREATE);
+            }
+
+            @Override
+            public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_CREATE);
+            }
+
+            @Override
+            public void onActivityPreStarted(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_START);
+            }
+
+            @Override
+            public void onActivityStarted(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_START);
+            }
+
+            @Override
+            public void onActivityPostStarted(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_START);
+            }
+
+            @Override
+            public void onActivityPreResumed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_RESUME);
+            }
+
+            @Override
+            public void onActivityResumed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_RESUME);
+            }
+
+            @Override
+            public void onActivityPostResumed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_RESUME);
+            }
+
+            @Override
+            public void onActivityPrePaused(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_PAUSE);
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PAUSE);
+            }
+
+            @Override
+            public void onActivityPostPaused(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_PAUSE);
+            }
+
+            @Override
+            public void onActivityPreStopped(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_STOP);
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_STOP);
+            }
+
+            @Override
+            public void onActivityPostStopped(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_STOP);
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+            }
+
+            @Override
+            public void onActivityPreDestroyed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_PRE_DESTROY);
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_DESTROY);
+            }
+
+            @Override
+            public void onActivityPostDestroyed(Activity activity) {
+                collectEvent(Source.ACTIVITY_CALLBACK, Event.ON_POST_DESTROY);
+            }
+        };
+        registerActivityLifecycleCallbacks(mActivityCallbacks);
+    }
+
+    public void collectEvent(Source source, Event event) {
+        mCollectedEvents.add(new Pair<>(source, event));
+    }
+
+    public ArrayList<Pair<Source, Event>> getCollectedEvents() {
+        return mCollectedEvents;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_CREATE);
+        super.onCreate(savedInstanceState);
+        collectEvent(Source.ACTIVITY, Event.ON_POST_CREATE);
+    }
+
+    @Override
+    protected void onStart() {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_START);
+        super.onStart();
+        collectEvent(Source.ACTIVITY, Event.ON_POST_START);
+    }
+
+    @Override
+    protected void onResume() {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_RESUME);
+        super.onResume();
+        collectEvent(Source.ACTIVITY, Event.ON_POST_RESUME);
+    }
+
+    @Override
+    protected void onPause() {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_PAUSE);
+        super.onPause();
+        collectEvent(Source.ACTIVITY, Event.ON_POST_PAUSE);
+    }
+
+    @Override
+    protected void onStop() {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_STOP);
+        super.onStop();
+        collectEvent(Source.ACTIVITY, Event.ON_POST_STOP);
+    }
+
+    @Override
+    protected void onDestroy() {
+        collectEvent(Source.ACTIVITY, Event.ON_PRE_DESTROY);
+        super.onDestroy();
+        collectEvent(Source.ACTIVITY, Event.ON_POST_DESTROY);
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/ActivityTestsBase.java b/tests/app/app/src/android/app/stubs/ActivityTestsBase.java
index 12463b4..abdd91a 100644
--- a/tests/app/app/src/android/app/stubs/ActivityTestsBase.java
+++ b/tests/app/app/src/android/app/stubs/ActivityTestsBase.java
@@ -38,6 +38,7 @@
     private boolean mFinished;
     private int mResultCode = 0;
     private Intent mData;
+    private Activity mActivity;
     private RuntimeException mResultStack = null;
 
     @Override
@@ -84,8 +85,12 @@
         }
     }
 
+    public void activityRunning(Activity activity) {
+        finishWithActivity(activity);
+    }
+
     public void activityFinished(int resultCode, Intent data, RuntimeException where) {
-        finishWithResult(resultCode, data, where);
+        finishWithResult(resultCode, data, null, where);
     }
 
     public Intent editIntent() {
@@ -110,16 +115,25 @@
         finishWithResult(Activity.RESULT_CANCELED, new Intent().setAction(error));
     }
 
+    public void finishWithActivity(Activity activity) {
+        final RuntimeException where = new RuntimeException("Original error was here");
+        where.fillInStackTrace();
+        finishWithResult(Activity.RESULT_OK, null, activity, where);
+
+    }
+
     public void finishWithResult(int resultCode, Intent data) {
         final RuntimeException where = new RuntimeException("Original error was here");
         where.fillInStackTrace();
-        finishWithResult(resultCode, data, where);
+        finishWithResult(resultCode, data, null, where);
     }
 
-    public void finishWithResult(int resultCode, Intent data, RuntimeException where) {
+    public void finishWithResult(int resultCode, Intent data, Activity activity,
+            RuntimeException where) {
         synchronized (this) {
             mResultCode = resultCode;
             mData = data;
+            mActivity = activity;
             mResultStack = where;
             mFinished = true;
             notifyAll();
@@ -204,6 +218,10 @@
         return mResultStack;
     }
 
+    public Activity getRunningActivity() {
+        return mActivity;
+    }
+
     public void onTimeout() {
         final String msg = mExpecting == null ? "Timeout" : "Timeout while expecting " + mExpecting;
         finishWithResult(Activity.RESULT_CANCELED, new Intent().setAction(msg));
diff --git a/tests/app/app/src/android/app/stubs/AutomaticZenRuleActivity.java b/tests/app/app/src/android/app/stubs/AutomaticZenRuleActivity.java
new file mode 100644
index 0000000..421c1e8
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/AutomaticZenRuleActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.app.stubs;
+
+import android.app.Activity;
+
+public class AutomaticZenRuleActivity extends Activity {
+
+}
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
new file mode 100644
index 0000000..75ed0a8
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -0,0 +1,159 @@
+/*
+ * 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.app.stubs;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.util.Log;
+
+public class CommandReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "CommandReceiver";
+
+    // Requires flags and targetPackage
+    public static final int COMMAND_BIND_SERVICE = 1;
+    // Requires targetPackage
+    public static final int COMMAND_UNBIND_SERVICE = 2;
+    public static final int COMMAND_START_FOREGROUND_SERVICE = 3;
+    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 String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
+    public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
+    public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS";
+
+    public static final String SERVICE_NAME = "android.app.stubs.LocalService";
+
+    private static ArrayMap<String,ServiceConnection> sServiceMap = new ArrayMap<>();
+
+    /**
+     * Handle the different types of binding/unbinding requests.
+     * @param context The Context in which the receiver is running.
+     * @param intent The Intent being received.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        int command = intent.getIntExtra(EXTRA_COMMAND, -1);
+        Log.d(TAG + "_" + context.getPackageName(), "Got command " + command + ", intent="
+                + intent);
+        switch (command) {
+            case COMMAND_BIND_SERVICE:
+                doBindService(context, intent);
+                break;
+            case COMMAND_UNBIND_SERVICE:
+                doUnbindService(context, intent);
+                break;
+            case COMMAND_START_FOREGROUND_SERVICE:
+                doStartForegroundService(context, LocalForegroundService.class);
+                break;
+            case COMMAND_STOP_FOREGROUND_SERVICE:
+                doStopForegroundService(context, LocalForegroundService.class);
+                break;
+            case COMMAND_START_FOREGROUND_SERVICE_LOCATION:
+                doStartForegroundService(context, LocalForegroundServiceLocation.class);
+                break;
+            case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
+                doStopForegroundService(context, LocalForegroundServiceLocation.class);
+                break;
+        }
+    }
+
+    private void doBindService(Context context, Intent commandIntent) {
+        context = context.getApplicationContext();
+        if (LocalService.sServiceContext != null) {
+            context = LocalService.sServiceContext;
+        }
+
+        String targetPackage = getTargetPackage(commandIntent);
+        int flags = getFlags(commandIntent);
+
+        Intent bindIntent = new Intent();
+        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);
+        context.unbindService(sServiceMap.remove(targetPackage));
+    }
+
+    private void doStartForegroundService(Context context, Class cls) {
+        Intent fgsIntent = new Intent(context, cls);
+        int command = LocalForegroundService.COMMAND_START_FOREGROUND;
+        fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
+        context.startForegroundService(fgsIntent);
+    }
+
+    private void doStopForegroundService(Context context, Class cls) {
+        Intent fgsIntent = new Intent(context, cls);
+        context.stopService(fgsIntent);
+    }
+
+    private String getTargetPackage(Intent intent) {
+        return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
+    }
+
+    private int getFlags(Intent intent) {
+        return intent.getIntExtra(EXTRA_FLAGS, 0);
+    }
+
+    public static void sendCommand(Context context, int command, String sourcePackage,
+            String targetPackage, int flags, Bundle extras) {
+        Intent intent = new Intent();
+        if (command == COMMAND_BIND_SERVICE || command == COMMAND_START_FOREGROUND_SERVICE) {
+            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        intent.setComponent(new ComponentName(sourcePackage, "android.app.stubs.CommandReceiver"));
+        intent.putExtra(EXTRA_COMMAND, command);
+        intent.putExtra(EXTRA_FLAGS, flags);
+        intent.putExtra(EXTRA_TARGET_PACKAGE, targetPackage);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        sendCommand(context, intent);
+    }
+
+    private static void sendCommand(Context context, Intent intent) {
+        Log.d(TAG, "Sending broadcast " + intent);
+        context.sendBroadcast(intent);
+    }
+
+    private ServiceConnection addServiceConnection(final String packageName) {
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+            }
+        };
+        sServiceMap.put(packageName, connection);
+        return connection;
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/IsolatedService.java b/tests/app/app/src/android/app/stubs/IsolatedService.java
new file mode 100644
index 0000000..75f4eff
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/IsolatedService.java
@@ -0,0 +1,20 @@
+/*
+ * 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.app.stubs;
+
+public class IsolatedService extends LocalService {
+}
diff --git a/tests/app/app/src/android/app/stubs/LaunchpadActivity.java b/tests/app/app/src/android/app/stubs/LaunchpadActivity.java
index 16b1363..33650e9 100644
--- a/tests/app/app/src/android/app/stubs/LaunchpadActivity.java
+++ b/tests/app/app/src/android/app/stubs/LaunchpadActivity.java
@@ -67,15 +67,17 @@
 
 public class LaunchpadActivity extends Activity {
     public interface CallingTest extends PerformanceTestCase.Intermediates {
-        public void startTiming(boolean realTime);
+        void startTiming(boolean realTime);
 
-        public void addIntermediate(String name);
+        void addIntermediate(String name);
 
-        public void addIntermediate(String name, long timeInNS);
+        void addIntermediate(String name, long timeInNS);
 
-        public void finishTiming(boolean realTime);
+        void finishTiming(boolean realTime);
 
-        public void activityFinished(int resultCode, Intent data, RuntimeException where);
+        void activityRunning(Activity activity);
+
+        void activityFinished(int resultCode, Intent data, RuntimeException where);
     }
 
     // Also used as the Binder interface descriptor string in these tests
@@ -93,6 +95,8 @@
     public static final String LIFECYCLE_SCREEN = "android.app.cts.activity.LIFECYCLE_SCREEN";
     public static final String LIFECYCLE_DIALOG = "android.app.cts.activity.LIFECYCLE_DIALOG";
 
+    public static final String ACTIVITY_PREPARE = "android.app.cts.activity.LIFECYCLE_DIALOG";
+
     public static final String BROADCAST_REGISTERED = "android.app.cts.activity.BROADCAST_REGISTERED";
     public static final String BROADCAST_LOCAL = "android.app.cts.activity.BROADCAST_LOCAL";
     public static final String BROADCAST_REMOTE = "android.app.cts.activity.BROADCAST_REMOTE";
@@ -283,7 +287,8 @@
                 intent.setFlags(0);
                 intent.setComponent((ComponentName) intent.getParcelableExtra("component"));
                 startActivityForResult(intent, LAUNCHED_RESULT);
-
+            } else if (ACTIVITY_PREPARE.equals(action)) {
+                sCallingTest.activityRunning(this);
             } else if (FORWARD_RESULT.equals(action)) {
                 final Intent intent = getIntent();
                 intent.setFlags(0);
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index d60a1df..e7d01f8 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -50,11 +50,17 @@
         Log.d(TAG, "service created: " + this + " in " + android.os.Process.myPid());
     }
 
+    /** Returns the channel id for this service */
+    protected String getNotificationChannelId() {
+        return NOTIFICATION_CHANNEL_ID;
+    }
+
     @Override
     public void onStart(Intent intent, int startId) {
+        String notificationChannelId = getNotificationChannelId();
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         notificationManager.createNotificationChannel(new NotificationChannel(
-                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                notificationChannelId, notificationChannelId,
                 NotificationManager.IMPORTANCE_DEFAULT));
 
         Context context = getApplicationContext();
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
new file mode 100644
index 0000000..82aaf96
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.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 android.app.stubs;
+
+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.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.app.stubs.R;
+
+import com.android.compatibility.common.util.IBinderParcelable;
+
+/**
+ * Foreground Service with location type.
+ */
+public class LocalForegroundServiceLocation extends LocalForegroundService {
+
+    private static final String TAG = "LocalForegroundServiceLocation";
+    private static final String EXTRA_COMMAND = "LocalForegroundServiceLocation.command";
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+
+    /** Returns the channel id for this service */
+    @Override
+    protected String getNotificationChannelId() {
+        return NOTIFICATION_CHANNEL_ID;
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/LocalService.java b/tests/app/app/src/android/app/stubs/LocalService.java
index f0624b2..f914cdf 100644
--- a/tests/app/app/src/android/app/stubs/LocalService.java
+++ b/tests/app/app/src/android/app/stubs/LocalService.java
@@ -17,10 +17,12 @@
 package android.app.stubs;
 
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 
 import com.android.compatibility.common.util.IBinderParcelable;
@@ -40,25 +42,58 @@
     public static final int SET_REPORTER_CODE = 3;
     public static final int UNBIND_CODE = 4;
     public static final int REBIND_CODE = 5;
+    public static final int GET_VALUE_CODE = 6;
+    public static final int SET_VALUE_CODE = 7;
+    public static final int GET_PID_CODE = 8;
+    public static final int GET_UID_CODE = 9;
+    public static final int GET_PPID_CODE = 10;
+    public static final int GET_ZYGOTE_PRELOAD_CALLED = 11;
+
+    public static Context sServiceContext = null;
 
     private IBinder mReportObject;
     private int mStartCount = 1;
+    private int mValue = 0;
 
     private final IBinder mBinder = new Binder() {
         @Override
         protected boolean onTransact(int code, Parcel data, Parcel reply,
                 int flags) throws RemoteException {
-            if (code == SET_REPORTER_CODE) {
-                data.enforceInterface(SERVICE_LOCAL);
-                mReportObject = data.readStrongBinder();
-                return true;
-            } else {
-                return super.onTransact(code, data, reply, flags);
+            switch (code) {
+                case SET_REPORTER_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    mReportObject = data.readStrongBinder();
+                    return true;
+                case GET_VALUE_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    reply.writeInt(mValue);
+                    return true;
+                case SET_VALUE_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    mValue = data.readInt();
+                    return true;
+                case GET_PID_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    reply.writeInt(Process.myPid());
+                    return true;
+                case GET_UID_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    reply.writeInt(Process.myUid());
+                    return true;
+                case GET_PPID_CODE:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    reply.writeInt(Process.myPpid());
+                    return true;
+                case GET_ZYGOTE_PRELOAD_CALLED:
+                    data.enforceInterface(SERVICE_LOCAL);
+                    reply.writeBoolean(ZygotePreload.preloadCalled());
+                    return true;
+                default:
+                    return super.onTransact(code, data, reply, flags);
             }
         }
     };
 
-
     public LocalService() {
     }
 
@@ -72,6 +107,9 @@
                 bindAction(STARTED_CODE);
             }
         }
+        if (sServiceContext == null) {
+            sServiceContext = this;
+        }
     }
 
     @Override
@@ -83,6 +121,9 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        if (sServiceContext == null) {
+            sServiceContext = this;
+        }
         return mBinder;
     }
 
diff --git a/tests/app/app/src/android/app/stubs/MockNotificationListener.java b/tests/app/app/src/android/app/stubs/MockNotificationListener.java
deleted file mode 100644
index d47b2b1..0000000
--- a/tests/app/app/src/android/app/stubs/MockNotificationListener.java
+++ /dev/null
@@ -1,91 +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.app.stubs;
-
-import android.content.ComponentName;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-public class MockNotificationListener extends NotificationListenerService {
-    public static final String TAG = "TestNotificationListener";
-    public static final String PKG = "android.app.stubs";
-
-    private ArrayList<String> mTestPackages = new ArrayList<>();
-
-    public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
-    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
-    public RankingMap mRankingMap;
-
-    private static MockNotificationListener sNotificationListenerInstance = null;
-    boolean isConnected;
-
-    public static String getId() {
-        return String.format("%s/%s", MockNotificationListener.class.getPackage().getName(),
-                MockNotificationListener.class.getName());
-    }
-
-    public static ComponentName getComponentName() {
-        return new ComponentName(MockNotificationListener.class.getPackage().getName(),
-                MockNotificationListener.class.getName());
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mTestPackages.add(PKG);
-    }
-
-    @Override
-    public void onListenerConnected() {
-        super.onListenerConnected();
-        sNotificationListenerInstance = this;
-        isConnected = true;
-    }
-
-    @Override
-    public void onListenerDisconnected() {
-        isConnected = false;
-    }
-
-    public static MockNotificationListener getInstance() {
-        return sNotificationListenerInstance;
-    }
-
-    public void resetData() {
-        mPosted.clear();
-        mRemoved.clear();
-    }
-
-    @Override
-    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
-        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
-        mPosted.add(sbn);
-    }
-
-    @Override
-    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
-        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
-        mRemoved.add(sbn);
-    }
-
-    @Override
-    public void onNotificationRankingUpdate(RankingMap rankingMap) {
-        mRankingMap = rankingMap;
-    }
-}
diff --git a/tests/app/app/src/android/app/stubs/PipActivity.java b/tests/app/app/src/android/app/stubs/PipActivity.java
index 533f054..6f53e9f 100644
--- a/tests/app/app/src/android/app/stubs/PipActivity.java
+++ b/tests/app/app/src/android/app/stubs/PipActivity.java
@@ -17,7 +17,43 @@
 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/TestDialog.java b/tests/app/app/src/android/app/stubs/TestDialog.java
index 18ef3be..e43e1d8 100644
--- a/tests/app/app/src/android/app/stubs/TestDialog.java
+++ b/tests/app/app/src/android/app/stubs/TestDialog.java
@@ -74,7 +74,6 @@
     public boolean dispatchTouchEventResult;
     public boolean dispatchKeyEventResult;
     public int keyDownCode = -1;
-    public Window window;
     public Bundle saveInstanceState;
     public Bundle savedInstanceState;
     public KeyEvent keyEvent;
diff --git a/tests/app/app/src/android/app/stubs/TestNotificationListener.java b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
new file mode 100644
index 0000000..c9d08d2
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
@@ -0,0 +1,93 @@
+/*
+ * 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.app.stubs;
+
+import android.content.ComponentName;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class TestNotificationListener extends NotificationListenerService {
+    public static final String TAG = "TestNotificationListener";
+    public static final String PKG = "android.app.stubs";
+
+    private ArrayList<String> mTestPackages = new ArrayList<>();
+
+    public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
+    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public RankingMap mRankingMap;
+
+    private static TestNotificationListener sNotificationListenerInstance = null;
+    boolean isConnected;
+
+    public static String getId() {
+        return String.format("%s/%s", TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mTestPackages.add(PKG);
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationListenerInstance = this;
+        isConnected = true;
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        isConnected = false;
+    }
+
+    public static TestNotificationListener getInstance() {
+        return sNotificationListenerInstance;
+    }
+
+    public void resetData() {
+        mPosted.clear();
+        mRemoved.clear();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mRankingMap = rankingMap;
+        mPosted.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mRankingMap = rankingMap;
+        mRemoved.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(RankingMap rankingMap) {
+        mRankingMap = rankingMap;
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/ZygotePreload.java b/tests/app/app/src/android/app/stubs/ZygotePreload.java
new file mode 100644
index 0000000..5601dd0
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/ZygotePreload.java
@@ -0,0 +1,34 @@
+/*
+ * 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.app.stubs;
+
+import android.content.pm.ApplicationInfo;
+
+public class ZygotePreload implements android.app.ZygotePreload {
+    static final String TAG = "ZygotePreload";
+
+    static boolean sPreloadCalled = false;
+
+    static synchronized public boolean preloadCalled() {
+        return sPreloadCalled;
+    }
+
+    @Override
+    synchronized public void doPreload(ApplicationInfo appInfo) {
+        sPreloadCalled = true;
+    }
+}
diff --git a/tests/app/app1/Android.mk b/tests/app/app1/Android.mk
new file mode 100644
index 0000000..6a008ea
--- /dev/null
+++ b/tests/app/app1/Android.mk
@@ -0,0 +1,56 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)/../app
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    telephony-common \
+    voip-common \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+    ctstestserver \
+    mockito-target-minus-junit4 \
+    androidx.legacy_legacy-support-v4
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+              src/android/app/stubs/ISecondary.aidl
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsAppTestStubsApp1
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.app1
+
+# Disable AAPT2 manifest checks to fix:
+# cts/tests/app/app/AndroidManifest.xml:25: error: unexpected element <meta-data> found in <manifest><permission>.
+# TODO(b/79755007): Remove when AAPT2 recognizes the manifest elements.
+LOCAL_AAPT_FLAGS += --warn-manifest-validation
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/app2/Android.mk b/tests/app/app2/Android.mk
index 304f79e..541806e 100644
--- a/tests/app/app2/Android.mk
+++ b/tests/app/app2/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2017 The Android Open Source Project
+# 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.
@@ -12,25 +12,45 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH:= $(call my-dir)/../app
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := tests
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    telephony-common \
+    voip-common \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
+    ctstestrunner \
+    ctstestserver \
+    mockito-target-minus-junit4 \
+    androidx.legacy_legacy-support-v4
 
-LOCAL_SRC_FILES := \
-    ../app/src/android/app/stubs/LocalService.java
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_PACKAGE_NAME := CtsAppTestStubsDifferentUid
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+              src/android/app/stubs/ISecondary.aidl
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_DEX_PREOPT := false
+LOCAL_PACKAGE_NAME := CtsAppTestStubsApp2
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.app2
+
+# Disable AAPT2 manifest checks to fix:
+# cts/tests/app/app/AndroidManifest.xml:25: error: unexpected element <meta-data> found in <manifest><permission>.
+# TODO(b/79755007): Remove when AAPT2 recognizes the manifest elements.
+LOCAL_AAPT_FLAGS += --warn-manifest-validation
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/app2/AndroidManifest.xml b/tests/app/app2/AndroidManifest.xml
deleted file mode 100644
index 0926251..0000000
--- a/tests/app/app2/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +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
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.app2">
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-
-        <service android:name="android.app.stubs.LocalService"
-                 android:exported="true"/>
-        <service android:name=".AlertWindowService"
-                 android:exported="true"/>
-    </application>
-</manifest>
diff --git a/tests/app/app3/Android.mk b/tests/app/app3/Android.mk
new file mode 100644
index 0000000..212c525
--- /dev/null
+++ b/tests/app/app3/Android.mk
@@ -0,0 +1,56 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)/../app
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    telephony-common \
+    voip-common \
+    org.apache.http.legacy \
+    android.test.base.stubs \
+
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+    ctstestserver \
+    mockito-target-minus-junit4 \
+    androidx.legacy_legacy-support-v4
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+              src/android/app/stubs/ISecondary.aidl
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsAppTestStubsApp3
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.app3
+
+# Disable AAPT2 manifest checks to fix:
+# cts/tests/app/app/AndroidManifest.xml:25: error: unexpected element <meta-data> found in <manifest><permission>.
+# TODO(b/79755007): Remove when AAPT2 recognizes the manifest elements.
+LOCAL_AAPT_FLAGS += --warn-manifest-validation
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/src/android/app/cts/ActivityCallbacksTest.java b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
new file mode 100644
index 0000000..ff9c401
--- /dev/null
+++ b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.app.cts;
+
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_CREATE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_DESTROY;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PAUSE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_CREATE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_DESTROY;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_PAUSE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_RESUME;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_START;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_POST_STOP;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_CREATE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_DESTROY;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_PAUSE;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_RESUME;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_START;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_PRE_STOP;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_RESUME;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_START;
+import static android.app.stubs.ActivityCallbacksTestActivity.Event.ON_STOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.stubs.ActivityCallbacksTestActivity;
+import android.app.stubs.ActivityCallbacksTestActivity.Event;
+import android.app.stubs.ActivityCallbacksTestActivity.Source;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityCallbacksTest {
+
+    private static final long TIMEOUT_SECS = 2;
+
+    @Rule
+    public ActivityTestRule<ActivityCallbacksTestActivity> mActivityRule =
+            new ActivityTestRule<>(ActivityCallbacksTestActivity.class, false, false);
+
+    private Application.ActivityLifecycleCallbacks mActivityCallbacks;
+
+    @After
+    public void tearDown() {
+        if (mActivityCallbacks != null) {
+            Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+            Application application = (Application) targetContext.getApplicationContext();
+            application.unregisterActivityLifecycleCallbacks(mActivityCallbacks);
+        }
+    }
+
+    @Test
+    public void testActivityCallbackOrder() throws InterruptedException {
+        Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        Application application = (Application) targetContext.getApplicationContext();
+        ArrayList<Pair<Source, Event>> actualEvents = new ArrayList<>();
+        CountDownLatch latch = new CountDownLatch(1);
+        mActivityCallbacks = new Application.ActivityLifecycleCallbacks() {
+
+            @Override
+            public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_CREATE);
+            }
+
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_CREATE);
+            }
+
+            @Override
+            public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_CREATE);
+            }
+
+            @Override
+            public void onActivityPreStarted(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_START);
+            }
+
+            @Override
+            public void onActivityStarted(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_START);
+            }
+
+            @Override
+            public void onActivityPostStarted(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_START);
+            }
+
+            @Override
+            public void onActivityPreResumed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_RESUME);
+            }
+
+            @Override
+            public void onActivityResumed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_RESUME);
+            }
+
+            @Override
+            public void onActivityPostResumed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_RESUME);
+                a.finish();
+            }
+
+            @Override
+            public void onActivityPrePaused(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_PAUSE);
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PAUSE);
+            }
+
+            @Override
+            public void onActivityPostPaused(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_PAUSE);
+            }
+
+            @Override
+            public void onActivityPreStopped(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_STOP);
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_STOP);
+            }
+
+            @Override
+            public void onActivityPostStopped(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_STOP);
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+            }
+
+            @Override
+            public void onActivityPreDestroyed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_DESTROY);
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_DESTROY);
+            }
+
+            @Override
+            public void onActivityPostDestroyed(Activity activity) {
+                ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
+                a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_DESTROY);
+                actualEvents.addAll(a.getCollectedEvents());
+                latch.countDown();
+            }
+        };
+
+        application.registerActivityLifecycleCallbacks(mActivityCallbacks);
+
+        mActivityRule.launchActivity(null);
+        assertTrue("Failed to await for an activity to finish ",
+                latch.await(TIMEOUT_SECS, TimeUnit.SECONDS));
+
+        ArrayList<Pair<Source, Event>> expectedEvents = new ArrayList<>();
+
+        addNestedEvents(expectedEvents, ON_PRE_CREATE, ON_CREATE, ON_POST_CREATE);
+        addNestedEvents(expectedEvents, ON_PRE_START, ON_START, ON_POST_START);
+        addNestedEvents(expectedEvents, ON_PRE_RESUME, ON_RESUME, ON_POST_RESUME);
+
+        addNestedEvents(expectedEvents, ON_PRE_PAUSE, ON_PAUSE, ON_POST_PAUSE);
+        addNestedEvents(expectedEvents, ON_PRE_STOP, ON_STOP, ON_POST_STOP);
+        addNestedEvents(expectedEvents, ON_PRE_DESTROY, ON_DESTROY, ON_POST_DESTROY);
+
+        assertEquals(expectedEvents, actualEvents);
+    }
+
+    private void addNestedEvents(ArrayList<Pair<Source, Event>> expectedEvents,
+            Event preEvent, Event event, Event postEvent) {
+        expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, preEvent));
+        expectedEvents.add(new Pair<>(Source.ACTIVITY_CALLBACK, preEvent));
+        expectedEvents.add(new Pair<>(Source.ACTIVITY, preEvent));
+        if (event == ON_CREATE || event == ON_START || event == ON_RESUME) {
+            // Application goes first on upward lifecycle events
+            expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, event));
+            expectedEvents.add(new Pair<>(Source.ACTIVITY_CALLBACK, event));
+        } else {
+            // Application goes last on downward lifecycle events
+            expectedEvents.add(new Pair<>(Source.ACTIVITY_CALLBACK, event));
+            expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, event));
+        }
+        expectedEvents.add(new Pair<>(Source.ACTIVITY, postEvent));
+        expectedEvents.add(new Pair<>(Source.ACTIVITY_CALLBACK, postEvent));
+        expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, postEvent));
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerBaseTaskInfoTest.java b/tests/app/src/android/app/cts/ActivityManagerBaseTaskInfoTest.java
new file mode 100644
index 0000000..de6d264
--- /dev/null
+++ b/tests/app/src/android/app/cts/ActivityManagerBaseTaskInfoTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.app.cts;
+
+import static android.content.Intent.ACTION_MAIN;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ActivityManagerBaseTaskInfoTest {
+
+    protected void fillTaskInfo(TaskInfo taskInfo) {
+        taskInfo.taskId = Integer.MAX_VALUE;
+        taskInfo.isRunning = true;
+        taskInfo.baseIntent = new Intent(ACTION_MAIN);
+        ComponentName cn = new ComponentName("android.app.cts",
+                ActivityManagerBaseTaskInfoTest.class.getSimpleName());
+        taskInfo.baseActivity = cn.clone();
+        taskInfo.topActivity = cn.clone();
+        taskInfo.origActivity = cn.clone();
+        taskInfo.numActivities = Integer.MAX_VALUE;
+        taskInfo.taskDescription = new ActivityManager.TaskDescription("Test");
+    }
+
+    protected void verifyTaskInfo(TaskInfo taskInfo, TaskInfo sourceTaskInfo) {
+        assertEquals(sourceTaskInfo.taskId, taskInfo.taskId);
+
+        assertEquals(sourceTaskInfo.isRunning, taskInfo.isRunning);
+        assertTrue(sourceTaskInfo.baseIntent.filterEquals(taskInfo.baseIntent));
+        assertEquals(sourceTaskInfo.baseActivity, taskInfo.baseActivity);
+        assertEquals(sourceTaskInfo.topActivity, taskInfo.topActivity);
+        assertEquals(sourceTaskInfo.origActivity, taskInfo.origActivity);
+        assertEquals(sourceTaskInfo.numActivities, taskInfo.numActivities);
+        assertEquals(sourceTaskInfo.taskDescription.getLabel(),
+                taskInfo.taskDescription.getLabel());
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryInfoTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryInfoTest.java
index 8ac5afb..90c1e6d 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryInfoTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryInfoTest.java
@@ -16,6 +16,7 @@
 package android.app.cts;
 
 import android.app.ActivityManager;
+import android.os.Debug;
 import android.os.Parcel;
 import android.os.Process;
 import android.test.AndroidTestCase;
@@ -84,4 +85,23 @@
         }
     }
 
+    public void testGetProcessMemoryInfo() {
+        // PID == 1 is the init process.
+        Debug.MemoryInfo[] result = getContext().getSystemService(ActivityManager.class)
+                .getProcessMemoryInfo(new int[]{1, Process.myPid(), 1});
+        assertEquals(3, result.length);
+        isEmpty(result[0]);
+        isEmpty(result[2]);
+        isNotEmpty(result[1]);
+    }
+
+    private static void isEmpty(Debug.MemoryInfo mi) {
+        assertEquals(0, mi.dalvikPss);
+        assertEquals(0, mi.nativePss);
+    }
+
+    private static void isNotEmpty(Debug.MemoryInfo mi) {
+        assertTrue(mi.dalvikPss > 0);
+        assertTrue(mi.nativePss > 0);
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index ae58e43..3cac46d 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -28,12 +28,15 @@
 import android.app.cts.android.app.cts.tools.UidImportanceListener;
 import android.app.cts.android.app.cts.tools.WaitForBroadcast;
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.app.stubs.CommandReceiver;
+import android.app.stubs.ScreenOnActivity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.PowerManager;
@@ -54,7 +57,12 @@
     private static final String TAG = ActivityManagerProcessStateTest.class.getName();
 
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
+    private static final String PACKAGE_NAME_APP1 = "com.android.app1";
+    private static final String PACKAGE_NAME_APP2 = "com.android.app2";
+    private static final String PACKAGE_NAME_APP3 = "com.android.app3";
+
     private static final int WAIT_TIME = 2000;
+    private static final int WAITFOR_MSEC = 5000;
     // A secondary test activity from another APK.
     static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
     static final String SIMPLE_SERVICE = ".SimpleService";
@@ -103,9 +111,15 @@
         mAllProcesses[1] = mService2Intent;
         mContext.stopService(mServiceIntent);
         mContext.stopService(mService2Intent);
+        turnScreenOn();
         removeTestAppFromWhitelists();
     }
 
+    private void turnScreenOn() throws Exception {
+        executeShellCmd("input keyevent KEYCODE_WAKEUP");
+        executeShellCmd("wm dismiss-keyguard");
+    }
+
     private void removeTestAppFromWhitelists() throws Exception {
         executeShellCmd("cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
         executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
@@ -1149,11 +1163,13 @@
             // Now go to home, leaving the app.  It should be put in the heavy weight state.
             mContext.startActivity(homeIntent);
 
+            final int expectedImportance =
+                    (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O)
+                            ? ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
+                            : ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE_PRE_26;
             // Wait for process to go down to background heavy-weight.
-            uidBackgroundListener.waitForValue(
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
-                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE);
-            assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
+            uidBackgroundListener.waitForValue(expectedImportance, expectedImportance);
+            assertEquals(expectedImportance,
                     am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
 
             uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
@@ -1356,4 +1372,375 @@
             uid1Watcher.finish();
         }
     }
+
+    /**
+     * Test a service binding cycle between two apps, with one of them also running a
+     * foreground service. The other app should also get an FGS proc state. On stopping the
+     * foreground service, app should go back to cached state.
+     * @throws Exception
+     */
+    public void testCycleFgs() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP3, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            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);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+
+            // Create a cycle
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            try {
+                uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App3 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            // Stop the foreground service
+            CommandReceiver.sendCommand(mContext, CommandReceiver
+                            .COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            // Check that the app's proc state has fallen
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a service binding cycle between three apps, with one of them also running a
+     * foreground service. The other apps should also get an FGS proc state. On stopping the
+     * foreground service, app should go back to cached state.
+     * @throws Exception
+     */
+    public void testCycleFgsTriangle() throws Exception {
+        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);
+        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 {
+            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);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+
+            // Bind from 2 to 3
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+
+            // Create a cycle
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            try {
+                uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App3 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            try {
+                uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App2 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            try {
+                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App1 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a service binding cycle between three apps, with one of them also running a
+     * foreground service. The other apps should also get an FGS proc state. On stopping the
+     * foreground service, app should go back to cached state.
+     * @throws Exception
+     */
+    public void testCycleFgsTriangleBiDi() throws Exception {
+        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);
+        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 {
+            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);
+
+            // Bind from 1 to 2, 1 to 3
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+
+            // Bind from 2 to 3, 3 to 2, 3 to 1
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            try {
+                uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App3 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            // Stop the foreground service
+            CommandReceiver.sendCommand(mContext, CommandReceiver
+                            .COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 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 {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * 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.
+     * @throws Exception
+     */
+    public void testFgsLocation() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            // First start a foreground service
+            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);
+
+            // Try to elevate to foreground service location
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE_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);
+
+            try {
+                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+                fail("App1 should not be demoted to cached");
+            } catch (IllegalStateException ise) {
+            }
+
+            // Remove foreground service as well
+            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);
+
+        } finally {
+            uid1Watcher.finish();
+        }
+    }
+
+    private final <T extends Activity> Activity startSubActivity(Class<T> activityClass) {
+        final Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(
+                0, new Intent());
+        final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                activityClass.getName(), result, false);
+        mInstrumentation.addMonitor(monitor);
+        launchActivity(STUB_PACKAGE_NAME, activityClass, null);
+        return monitor.waitForActivity();
+    }
+
+    public void testCycleTop() throws Exception {
+        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 uid1Listener = new UidImportanceListener(mContext,
+                app1Info.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE,
+                WAITFOR_MSEC);
+        uid1Listener.register();
+
+        UidImportanceListener uid1ServiceListener = new UidImportanceListener(mContext,
+                app1Info.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                WAITFOR_MSEC);
+        uid1ServiceListener.register();
+
+        UidImportanceListener uid2Listener = new UidImportanceListener(mContext,
+                app2Info.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE,
+                WAITFOR_MSEC);
+        uid2Listener.register();
+
+        UidImportanceListener uid2ServiceListener = new UidImportanceListener(mContext,
+                app2Info.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                WAITFOR_MSEC);
+        uid2ServiceListener.register();
+
+        UidImportanceListener uid3Listener = new UidImportanceListener(mContext,
+                app3Info.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE,
+                WAITFOR_MSEC);
+        uid3Listener.register();
+
+        Activity activity = null;
+
+        try {
+            // Start an activity
+            activity = startSubActivity(ScreenOnActivity.class);
+
+            // Start a FGS in app2
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE, PACKAGE_NAME_APP2,
+                    PACKAGE_NAME_APP2, 0, null);
+
+            uid2Listener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+
+            // Bind from TOP to the service in app1
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+            uid1Listener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+
+            // Bind from app1 to a service in app2
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+
+            // Bind from app2 to a service in app3
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+
+            uid3Listener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+
+            // Create a cycle
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            try {
+                uid3Listener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                        ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+                fail("App3 should not be demoted to cached, expecting FGS");
+            } catch (IllegalStateException e) {
+                // Didn't go to cached in spite of cycle. Good!
+            }
+
+            // Unbind from the TOP app
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+            // Check that the apps' proc state is FOREGROUND_SERVICE
+            uid2Listener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+
+            // Stop the foreground service
+            CommandReceiver.sendCommand(mContext, CommandReceiver
+                            .COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+            // Check that the apps fall down to cached state
+            uid1ServiceListener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+
+            uid2ServiceListener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+            uid3Listener.waitForValue(
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+        } finally {
+            uid1Listener.unregister();
+            uid1ServiceListener.unregister();
+            uid2Listener.unregister();
+            uid2ServiceListener.unregister();
+            uid3Listener.unregister();
+            if (activity != null) {
+                activity.finish();
+            }
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerRecentTaskInfoTest.java b/tests/app/src/android/app/cts/ActivityManagerRecentTaskInfoTest.java
index 652dd40..58a3687 100644
--- a/tests/app/src/android/app/cts/ActivityManagerRecentTaskInfoTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerRecentTaskInfoTest.java
@@ -16,93 +16,65 @@
 package android.app.cts;
 
 import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Intent;
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 
-public class ActivityManagerRecentTaskInfoTest extends AndroidTestCase {
-    protected ActivityManager.RecentTaskInfo mRecentTaskInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+import static org.junit.Assert.assertEquals;
+
+/**
+ * atest CtsAppTestCases:ActivityManagerRecentTaskInfoTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerRecentTaskInfoTest extends ActivityManagerBaseTaskInfoTest {
+
+    private ActivityManager.RecentTaskInfo mRecentTaskInfo;
+
+    @Before
+    public void setUp() throws Exception {
         mRecentTaskInfo = new ActivityManager.RecentTaskInfo();
+        mRecentTaskInfo.id = 1;
+        mRecentTaskInfo.persistentId = 1;
     }
 
+    @Test
     public void testConstructor() {
         new ActivityManager.RecentTaskInfo();
     }
 
+    @Test
     public void testDescribeContents() {
         assertEquals(0, mRecentTaskInfo.describeContents());
     }
 
-    public void testWriteToParcel() throws Exception {
-        int id = 1;
-        Intent baseIntent = null;
-        ComponentName origActivity = null;
-        mRecentTaskInfo.id = id;
-        mRecentTaskInfo.baseIntent = baseIntent;
-        mRecentTaskInfo.origActivity = origActivity;
-
+    @Test
+    public void testWriteToParcel() {
+        fillTaskInfo(mRecentTaskInfo);
         Parcel parcel = Parcel.obtain();
         mRecentTaskInfo.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        ActivityManager.RecentTaskInfo values = ActivityManager.RecentTaskInfo.CREATOR
+
+        ActivityManager.RecentTaskInfo info = ActivityManager.RecentTaskInfo.CREATOR
                 .createFromParcel(parcel);
-        assertEquals(id, values.id);
-        assertEquals(null, values.baseIntent);
-        assertEquals(null, values.origActivity);
-        // test baseIntent,origActivity is not null, and id is -1(not running)
-        baseIntent = new Intent();
-        baseIntent.setAction(Intent.ACTION_CALL);
-        origActivity = new ComponentName(mContext, this.getClass());
-        mRecentTaskInfo.id = -1;
-        mRecentTaskInfo.baseIntent = baseIntent;
-        mRecentTaskInfo.origActivity = origActivity;
-        parcel = Parcel.obtain();
-        mRecentTaskInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        values = ActivityManager.RecentTaskInfo.CREATOR
-                .createFromParcel(parcel);
-        assertEquals(-1, values.id);
-        assertNotNull(values.baseIntent);
-        assertEquals(Intent.ACTION_CALL, values.baseIntent.getAction());
-        assertEquals(origActivity, values.origActivity);
+        verifyTaskInfo(info, mRecentTaskInfo);
+        assertEquals(1, info.id);
+        assertEquals(1, info.persistentId);
     }
 
-    public void testReadFromParcel() throws Exception {
-        int id = 1;
-        Intent baseIntent = null;
-        ComponentName origActivity = null;
-        mRecentTaskInfo.id = id;
-        mRecentTaskInfo.baseIntent = baseIntent;
-        mRecentTaskInfo.origActivity = origActivity;
-
+    @Test
+    public void testReadFromParcel() {
+        fillTaskInfo(mRecentTaskInfo);
         Parcel parcel = Parcel.obtain();
         mRecentTaskInfo.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        ActivityManager.RecentTaskInfo values = new ActivityManager.RecentTaskInfo();
-        values.readFromParcel(parcel);
-        assertEquals(id, values.id);
-        assertEquals(null, values.baseIntent);
-        assertEquals(null, values.origActivity);
-        // test baseIntent,origActivity is not null, and id is -1(not running)
-        baseIntent = new Intent();
-        baseIntent.setAction(Intent.ACTION_CALL);
-        origActivity = new ComponentName(mContext, this.getClass());
-        mRecentTaskInfo.id = -1;
-        mRecentTaskInfo.baseIntent = baseIntent;
-        mRecentTaskInfo.origActivity = origActivity;
-        parcel = Parcel.obtain();
-        mRecentTaskInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        values.readFromParcel(parcel);
-        assertEquals(-1, values.id);
-        assertNotNull(values.baseIntent);
-        assertEquals(Intent.ACTION_CALL, values.baseIntent.getAction());
-        assertEquals(origActivity, values.origActivity);
-    }
 
+        ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+        info.readFromParcel(parcel);
+        verifyTaskInfo(info, mRecentTaskInfo);
+        assertEquals(1, info.id);
+        assertEquals(1, info.persistentId);
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerRunningTaskInfoTest.java b/tests/app/src/android/app/cts/ActivityManagerRunningTaskInfoTest.java
index 4857c03..9889a4b 100644
--- a/tests/app/src/android/app/cts/ActivityManagerRunningTaskInfoTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerRunningTaskInfoTest.java
@@ -16,87 +16,63 @@
 package android.app.cts;
 
 import android.app.ActivityManager;
-import android.graphics.Bitmap;
+import android.os.Debug;
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 
-public class ActivityManagerRunningTaskInfoTest extends AndroidTestCase {
-    protected ActivityManager.RunningTaskInfo mRunningTaskInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+import static org.junit.Assert.assertEquals;
+
+/**
+ * atest CtsAppTestCases:ActivityManagerRunningTaskInfoTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerRunningTaskInfoTest extends ActivityManagerBaseTaskInfoTest {
+
+    private ActivityManager.RunningTaskInfo mRunningTaskInfo;
+
+    @Before
+    public void setUp() throws Exception {
         mRunningTaskInfo = new ActivityManager.RunningTaskInfo();
-
         mRunningTaskInfo.id = 1;
-        mRunningTaskInfo.baseActivity = null;
-        mRunningTaskInfo.topActivity = null;
-        mRunningTaskInfo.thumbnail = null;
-        mRunningTaskInfo.numActivities = 1;
-        mRunningTaskInfo.numRunning = 2;
-        mRunningTaskInfo.description = null;
     }
 
+    @Test
     public void testConstructor() {
         new ActivityManager.RunningTaskInfo();
     }
 
+    @Test
     public void testDescribeContents() {
         assertEquals(0, mRunningTaskInfo.describeContents());
     }
 
-    public void testWriteToParcel() throws Exception {
-
+    @Test
+    public void testWriteToParcel() {
+        fillTaskInfo(mRunningTaskInfo);
         Parcel parcel = Parcel.obtain();
         mRunningTaskInfo.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        ActivityManager.RunningTaskInfo values = ActivityManager.RunningTaskInfo.CREATOR
-                .createFromParcel(parcel);
-        assertEquals(1, values.id);
-        assertNull(values.baseActivity);
-        assertNull(values.topActivity);
-        assertNull(values.thumbnail);
-        assertEquals(1, values.numActivities);
-        assertEquals(2, values.numRunning);
-        // test thumbnail is not null
-        mRunningTaskInfo.thumbnail = Bitmap.createBitmap(480, 320,
-                Bitmap.Config.RGB_565);
-        parcel = Parcel.obtain();
-        mRunningTaskInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        values = ActivityManager.RunningTaskInfo.CREATOR
-                .createFromParcel(parcel);
-        assertNotNull(values.thumbnail);
-        assertEquals(320, values.thumbnail.getHeight());
-        assertEquals(480, values.thumbnail.getWidth());
-        assertEquals(Bitmap.Config.RGB_565, values.thumbnail.getConfig());
 
+        ActivityManager.RunningTaskInfo info = ActivityManager.RunningTaskInfo.CREATOR
+                .createFromParcel(parcel);
+        verifyTaskInfo(info, mRunningTaskInfo);
+        assertEquals(1, info.id);
     }
 
-    public void testReadFromParcel() throws Exception {
-
+    @Test
+    public void testReadFromParcel() {
+        fillTaskInfo(mRunningTaskInfo);
         Parcel parcel = Parcel.obtain();
         mRunningTaskInfo.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        ActivityManager.RunningTaskInfo values = new ActivityManager.RunningTaskInfo();
-        values.readFromParcel(parcel);
-        assertEquals(1, values.id);
-        assertNull(values.baseActivity);
-        assertNull(values.topActivity);
-        assertNull(values.thumbnail);
-        assertEquals(1, values.numActivities);
-        assertEquals(2, values.numRunning);
-        // test thumbnail is not null
-        mRunningTaskInfo.thumbnail = Bitmap.createBitmap(480, 320,
-                Bitmap.Config.RGB_565);
-        parcel = Parcel.obtain();
-        mRunningTaskInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        values.readFromParcel(parcel);
-        assertNotNull(values.thumbnail);
-        assertEquals(320, values.thumbnail.getHeight());
-        assertEquals(480, values.thumbnail.getWidth());
-        assertEquals(Bitmap.Config.RGB_565, values.thumbnail.getConfig());
-    }
 
+        ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+        info.readFromParcel(parcel);
+        verifyTaskInfo(info, mRunningTaskInfo);
+        assertEquals(1, info.id);
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 747c52e..8ac3a66 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -42,9 +42,12 @@
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 
 public class ActivityManagerTest extends InstrumentationTestCase {
     private static final String TAG = ActivityManagerTest.class.getSimpleName();
@@ -622,4 +625,64 @@
         timeReceiver.close();
         assertTrue(timeReceiver.mTimeUsed != 0);
     }
+
+    /**
+     * Verify that after force-stopping a package which has a foreground task contains multiple
+     * activities, the process of the package should not be alive (restarted).
+     */
+    public void testForceStopPackageWontRestartProcess() throws Exception {
+        ActivityReceiverFilter appStartedReceiver = new ActivityReceiverFilter(
+                ACTIVITY_LAUNCHED_ACTION);
+        // Start an activity of another APK.
+        Intent intent = new Intent();
+        intent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        assertEquals(RESULT_PASS, appStartedReceiver.waitForActivity());
+
+        // Start a new activity in the same task. Here adds an action to make a different to intent
+        // filter comparison so another same activity will be created.
+        intent.setAction(Intent.ACTION_MAIN);
+        mContext.startActivity(intent);
+        assertEquals(RESULT_PASS, appStartedReceiver.waitForActivity());
+        appStartedReceiver.close();
+
+        // Wait for the first activity to stop so its ActivityRecord.haveState will be true. The
+        // condition is required to keep the activity record when its process is died.
+        Thread.sleep(WAIT_TIME);
+
+        // The package name is also the default name for the activity process.
+        final String testProcess = SIMPLE_PACKAGE_NAME;
+        Predicate<RunningAppProcessInfo> processNamePredicate =
+                runningApp -> testProcess.equals(runningApp.processName);
+
+        List<RunningAppProcessInfo> runningApps = SystemUtil.callWithShellPermissionIdentity(
+                () -> mActivityManager.getRunningAppProcesses());
+        assertTrue("Process " + testProcess + " should be found in running process list",
+                runningApps.stream().anyMatch(processNamePredicate));
+
+        runningApps = SystemUtil.callWithShellPermissionIdentity(() -> {
+            mActivityManager.forceStopPackage(SIMPLE_PACKAGE_NAME);
+            // Wait awhile (process starting may be asynchronous) to verify if the process is
+            // started again unexpectedly.
+            Thread.sleep(WAIT_TIME);
+            return mActivityManager.getRunningAppProcesses();
+        });
+
+        assertFalse("Process " + testProcess + " should not be alive after force-stop",
+                runningApps.stream().anyMatch(processNamePredicate));
+    }
+
+    /**
+     * This test is to verify that devices are patched with the fix in b/119327603 for b/115384617.
+     */
+    public void testIsAppForegroundRemoved() throws ClassNotFoundException {
+       try {
+           Class.forName("android.app.IActivityManager").getDeclaredMethod(
+                   "isAppForeground", int.class);
+           fail("IActivityManager.isAppForeground() API should not be available.");
+       } catch (NoSuchMethodException e) {
+           // Patched devices should throw this exception since isAppForeground is removed.
+       }
+    }
 }
diff --git a/tests/app/src/android/app/cts/AppTaskTests.java b/tests/app/src/android/app/cts/AppTaskTests.java
new file mode 100644
index 0000000..16cb03b
--- /dev/null
+++ b/tests/app/src/android/app/cts/AppTaskTests.java
@@ -0,0 +1,261 @@
+/*
+ * 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.app.cts;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+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 org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.stubs.MockActivity;
+import android.app.stubs.MockListActivity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BooleanSupplier;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * atest CtsAppTestCases:AppTaskTests
+ */
+@MediumTest
+@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
+@RunWith(AndroidJUnit4.class)
+public class AppTaskTests {
+
+    private static final long TIME_SLICE_MS = 100;
+    private static final long MAX_WAIT_MS = 1500;
+
+    private Instrumentation mInstrumentation;
+    private ActivityLifecycleMonitor mLifecycleMonitor;
+    private Context mTargetContext;
+
+    @Rule
+    public ActivityTestRule<MockActivity> mActivityRule =
+            new ActivityTestRule<MockActivity>(MockActivity.class) {
+        @Override
+        public Intent getActivityIntent() {
+            Intent intent = new Intent();
+            intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT
+                    | FLAG_ACTIVITY_MULTIPLE_TASK);
+            return intent;
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance();
+        mTargetContext = mInstrumentation.getTargetContext();
+        removeAllAppTasks();
+    }
+
+    /**
+     * Launch an activity and ensure it is in the app task list.
+     */
+    @Test
+    public void testSingleAppTask() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        final List<ActivityManager.AppTask> appTasks = getAppTasks();
+        assertTrue(appTasks.size() == 1);
+        assertTrue(appTasks.get(0).getTaskInfo().topActivity.equals(a1.getComponentName()));
+    }
+
+    /**
+     * Launch a couple tasks and ensure they are in the app tasks list.
+     */
+    @Test
+    public void testMultipleAppTasks() throws Exception {
+        final ArrayList<Activity> activities = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            activities.add(mActivityRule.launchActivity(null));
+        }
+        final List<ActivityManager.AppTask> appTasks = getAppTasks();
+        assertTrue(appTasks.size() == activities.size());
+        for (int i = 0; i < appTasks.size(); i++) {
+            assertTrue(appTasks.get(i).getTaskInfo().topActivity.equals(
+                    activities.get(i).getComponentName()));
+        }
+    }
+
+    /**
+     * Remove an app task and ensure that it is actually removed.
+     */
+    @Test
+    public void testFinishAndRemoveTask() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        waitAndAssertCondition(() -> getAppTasks().size() == 1, "Expected 1 running task");
+        getAppTask(a1).finishAndRemoveTask();
+        waitAndAssertCondition(() -> getAppTasks().isEmpty(), "Expected no running tasks");
+    }
+
+    /**
+     * Ensure that moveToFront will bring the first activity forward.
+     */
+    @Test
+    public void testMoveToFront() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        final Activity a2 = mActivityRule.launchActivity(null);
+        final BooleanValue targetResumed = new BooleanValue();
+        mLifecycleMonitor.addLifecycleCallback(new ActivityLifecycleCallback() {
+            public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+                if (activity == a1 && stage == Stage.RESUMED) {
+                    targetResumed.value = true;
+                }
+            }
+        });
+
+        getAppTask(a1).moveToFront();
+        waitAndAssertCondition(() -> targetResumed.value,
+                "Expected activity brought to front and resumed");
+    }
+
+    /**
+     * Ensure that starting a new activity in the same task results in two activities in the task.
+     */
+    @Test
+    public void testStartActivityInTask_NoNewTask() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        final ActivityManager.AppTask task = getAppTask(a1);
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(mTargetContext, MockListActivity.class));
+        task.startActivity(mTargetContext, intent, null);
+        waitAndAssertCondition(
+                () -> getAppTask(a1) != null && getAppTask(a1).getTaskInfo().numActivities == 2,
+                "Waiting for activity launch");
+
+        final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
+        assertTrue(taskInfo.numActivities == 2);
+        assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
+        assertTrue(taskInfo.topActivity.equals(intent.getComponent()));
+    }
+
+    /**
+     * Ensure that an activity with FLAG_ACTIVITY_NEW_TASK causes the task to be brought forward
+     * and the new activity not being started.
+     */
+    @Test
+    public void testStartActivityInTask_NewTask() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        final ActivityManager.AppTask task = getAppTask(a1);
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(mTargetContext, MockActivity.class));
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        task.startActivity(mTargetContext, intent, null);
+
+        final ActivityManager.RecentTaskInfo taskInfo = task.getTaskInfo();
+        assertTrue(taskInfo.numActivities == 1);
+        assertTrue(taskInfo.baseActivity.equals(a1.getComponentName()));
+    }
+
+    /**
+     * Ensure that the activity that is excluded from recents is reflected in the recent task info.
+     */
+    @Test
+    public void testSetExcludeFromRecents() throws Exception {
+        final Activity a1 = mActivityRule.launchActivity(null);
+        final List<ActivityManager.AppTask> appTasks = getAppTasks();
+        final ActivityManager.AppTask t1 = appTasks.get(0);
+        t1.setExcludeFromRecents(true);
+        assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                != 0);
+        t1.setExcludeFromRecents(false);
+        assertTrue((t1.getTaskInfo().baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                == 0);
+    }
+
+    /**
+     * @return all the {@param ActivityManager.AppTask}s for the current app.
+     */
+    private List<ActivityManager.AppTask> getAppTasks() {
+        ActivityManager am = (ActivityManager) mTargetContext.getSystemService(ACTIVITY_SERVICE);
+        return am.getAppTasks();
+    }
+
+    /**
+     * @return the {@param ActivityManager.AppTask} for the associated activity.
+     */
+    private ActivityManager.AppTask getAppTask(Activity activity) {
+        waitAndAssertCondition(() -> getAppTask(getAppTasks(), activity) != null,
+                "Waiting for app task");
+        return getAppTask(getAppTasks(), activity);
+    }
+
+    private ActivityManager.AppTask getAppTask(List<ActivityManager.AppTask> appTasks,
+            Activity activity) {
+        for (ActivityManager.AppTask task : appTasks) {
+            if (task.getTaskInfo().taskId == activity.getTaskId()) {
+                return task;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes all the app tasks the test app.
+     */
+    private void removeAllAppTasks() {
+        final List<ActivityManager.AppTask> appTasks = getAppTasks();
+        for (ActivityManager.AppTask task : appTasks) {
+            task.finishAndRemoveTask();
+        }
+        waitAndAssertCondition(() -> getAppTasks().isEmpty(),
+                "Expected no app tasks after all removed");
+    }
+
+    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);
+            }
+        }
+    }
+
+    private class BooleanValue {
+        boolean value;
+    }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/AutomaticZenRuleTest.java b/tests/app/src/android/app/cts/AutomaticZenRuleTest.java
index 37972b6..c1ddd0d 100644
--- a/tests/app/src/android/app/cts/AutomaticZenRuleTest.java
+++ b/tests/app/src/android/app/cts/AutomaticZenRuleTest.java
@@ -21,12 +21,15 @@
 import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Parcel;
+import android.service.notification.ZenPolicy;
 import android.test.AndroidTestCase;
 
 public class AutomaticZenRuleTest extends AndroidTestCase {
 
     private final String mName = "name";
     private final ComponentName mOwner = new ComponentName("pkg", "cls");
+    private final ComponentName mConfigActivity = new ComponentName("pkg", "act");
+    private final ZenPolicy mPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
     private final Uri mConditionId = new Uri.Builder().scheme("scheme")
             .authority("authority")
             .appendPath("path")
@@ -48,8 +51,8 @@
     }
 
     public void testWriteToParcel() {
-        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
-                mInterruptionFilter, mEnabled);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
         Parcel parcel = Parcel.obtain();
         rule.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -59,6 +62,8 @@
         assertEquals(mConditionId, rule1.getConditionId());
         assertEquals(mInterruptionFilter, rule1.getInterruptionFilter());
         assertEquals(mEnabled, rule1.isEnabled());
+        assertEquals(mPolicy, rule1.getZenPolicy());
+        assertEquals(mConfigActivity, rule1.getConfigurationActivity());
 
         rule.setName(null);
         parcel = Parcel.obtain();
@@ -74,22 +79,22 @@
                 .appendPath("3path")
                 .appendPath("test4")
                 .build();
-        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
-                mInterruptionFilter, mEnabled);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
         rule.setConditionId(newConditionId);
         assertEquals(newConditionId, rule.getConditionId());
     }
 
     public void testSetEnabled() {
-        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
-                mInterruptionFilter, mEnabled);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
         rule.setEnabled(!mEnabled);
         assertEquals(!mEnabled, rule.isEnabled());
     }
 
     public void testSetInterruptionFilter() {
-        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
-                mInterruptionFilter, mEnabled);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
         for (int i = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
              i <= NotificationManager.INTERRUPTION_FILTER_ALARMS; i++) {
             rule.setInterruptionFilter(i);
@@ -98,9 +103,27 @@
     }
 
     public void testSetName() {
-        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConditionId,
-                mInterruptionFilter, mEnabled);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
         rule.setName(mName + "new");
         assertEquals(mName + "new", rule.getName());
     }
+
+    public void testSetConfigurationActivity() {
+        ComponentName newConfigActivity = new ComponentName("pkg", "new!");
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                mPolicy, mInterruptionFilter, mEnabled);
+        rule.setConfigurationActivity(newConfigActivity);
+        assertEquals(newConfigActivity, rule.getConfigurationActivity());
+    }
+
+    public void testCreateRuleWithZenPolicy() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.build();
+        builder.allowAlarms(true);
+        AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigActivity, mConditionId,
+                policy, mInterruptionFilter, mEnabled);
+        assertEquals(mInterruptionFilter, rule.getInterruptionFilter());
+        assertEquals(rule.getZenPolicy(), policy);
+    }
 }
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index c9a9a38..a3edb10 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -47,6 +47,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
@@ -473,10 +474,13 @@
     public void testTouchEvent() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
-        final Rect containingRect = new Rect();
-        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(containingRect);
-        final int x = containingRect.left + 1;
-        final int y = containingRect.top + 1;
+
+        int dialogLocation[] = new int[2];
+        d.getWindow().getDecorView().getRootView().getLocationOnScreen(dialogLocation);
+
+        final int touchSlop = ViewConfiguration.get(mActivity).getScaledWindowTouchSlop();
+        final int x = dialogLocation[0] - (touchSlop + 1);
+        final int y = dialogLocation[1] - (touchSlop + 1);
 
         assertNull(d.onTouchEvent);
         assertNull(d.touchEvent);
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 873647f..76cd58c 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -15,20 +15,37 @@
  */
 package android.app.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static org.junit.Assert.assertArrayEquals;
+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.app.DownloadManager;
 import android.app.DownloadManager.Query;
 import android.app.DownloadManager.Request;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
 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.PackageManager;
+import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.provider.MediaStore;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
@@ -36,11 +53,28 @@
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PollingCheck;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
 import java.util.Arrays;
 import java.util.HashSet;
 
-public class DownloadManagerTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DownloadManagerTest {
     private static final String TAG = "DownloadManagerTest";
 
     /**
@@ -52,24 +86,25 @@
     private static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long LONG_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
 
+    private Context mContext;
     private DownloadManager mDownloadManager;
 
     private CtsTestServer mWebServer;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mWebServer = new CtsTestServer(mContext);
         clearDownloads();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         mWebServer.shutdown();
     }
 
+    @Test
     public void testDownloadManager() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
@@ -97,6 +132,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttp() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
@@ -119,6 +155,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttpWithExternalWebServer() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testDownloadManagerSupportsHttpWithExternalWebServer() ignored on device without Internet");
@@ -150,6 +187,7 @@
         }
     }
 
+    @Test
     public void testDownloadManagerSupportsHttpsWithExternalWebServer() throws Exception {
         if (!hasInternetConnection()) {
             Log.i(TAG, "testDownloadManagerSupportsHttpsWithExternalWebServer() ignored on device without Internet");
@@ -184,6 +222,7 @@
     }
 
     @CddTest(requirement="7.6.1")
+    @Test
     public void testMinimumDownload() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
@@ -223,6 +262,7 @@
      * Checks three different methods of setting location: directly via setDestinationUri, and
      * indirectly through setDestinationInExternalFilesDir and setDestinationinExternalPublicDir.
      */
+    @Test
     public void testDownloadManagerDestination() throws Exception {
         File uriLocation = new File(mContext.getExternalFilesDir(null), "uriFile.bin");
         if (uriLocation.exists()) {
@@ -279,6 +319,7 @@
     /**
      * Set the download location and verify that the extension of the file name is left unchanged.
      */
+    @Test
     public void testDownloadManagerDestinationExtension() throws Exception {
         String noExt = "noiseandchirps";
         File noExtLocation = new File(mContext.getExternalFilesDir(null), noExt);
@@ -320,6 +361,288 @@
         }
     }
 
+    private String cannonicalizeProcessName(ApplicationInfo ai) {
+        return cannonicalizeProcessName(ai.processName, ai);
+    }
+
+    private String cannonicalizeProcessName(String process, ApplicationInfo ai) {
+        if (process == null) {
+            return null;
+        }
+        // Handle private scoped process names.
+        if (process.startsWith(":")) {
+            return ai.packageName + process;
+        }
+        return process;
+    }
+
+    @Test
+    public void testProviderAcceptsCleartext() throws Exception {
+        // Check that all the applications that share an android:process with the DownloadProvider
+        // accept cleartext traffic. Otherwise process loading races can lead to inconsistent flags.
+        final PackageManager pm = mContext.getPackageManager();
+        ProviderInfo downloadInfo = pm.resolveContentProvider("downloads", 0);
+        assertNotNull(downloadInfo);
+        String downloadProcess
+                = cannonicalizeProcessName(downloadInfo.processName, downloadInfo.applicationInfo);
+
+        for (PackageInfo pi : mContext.getPackageManager().getInstalledPackages(0)) {
+            if (downloadProcess.equals(cannonicalizeProcessName(pi.applicationInfo))) {
+                assertTrue("package: " + pi.applicationInfo.packageName
+                        + " must set android:usesCleartextTraffic=true"
+                        ,(pi.applicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC)
+                        != 0);
+            }
+        }
+    }
+
+    /**
+     * Test that a download marked as not visible in Downloads ui can be successfully downloaded.
+     */
+    @Test
+    public void testDownloadNotVisibleInUi() throws Exception {
+        File uriLocation = new File(mContext.getExternalFilesDir(null), "uriFile.bin");
+        if (uriLocation.exists()) {
+            assertTrue(uriLocation.delete());
+        }
+
+        final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
+        try {
+            IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+            mContext.registerReceiver(receiver, intentFilter);
+
+            final Request request = new Request(getGoodUrl());
+            request.setDestinationUri(Uri.fromFile(uriLocation))
+                    .setVisibleInDownloadsUi(false);
+            long uriId = mDownloadManager.enqueue(request);
+
+            int allDownloads = getTotalNumberDownloads();
+            assertEquals(1, allDownloads);
+
+            receiver.waitForDownloadComplete(SHORT_TIMEOUT, uriId);
+
+            assertSuccessfulDownload(uriId, uriLocation);
+
+            assertRemoveDownload(uriId, 0);
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    @Test
+    public void testAddCompletedDownload() throws Exception {
+        final String fileContents = "RED;GREEN;BLUE";
+        final File file = createFile(Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOCUMENTS), "colors.txt");
+        writeToFile(file, fileContents);
+
+        final long id = mDownloadManager.addCompletedDownload("Test title", "Test desc", true,
+                "text/plain", file.getPath(), fileContents.getBytes().length, true);
+        final String actualContents = readFromFile(mDownloadManager.openDownloadedFile(id));
+        assertEquals(fileContents, actualContents);
+
+        final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id);
+        mContext.grantUriPermission("com.android.shell", downloadUri,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        final String rawFilePath = getRawFilePath(downloadUri);
+        final String rawFileContents = readFromRawFile(rawFilePath);
+        assertEquals(fileContents, rawFileContents);
+        assertRemoveDownload(id, 0);
+    }
+
+    /**
+     * Download a file using DownloadManager and verify that the file has been added
+     * to MediaStore as well.
+     */
+    @Test
+    public void testDownloadManager_mediaStoreEntry() throws Exception {
+        final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
+        try {
+            mContext.registerReceiver(receiver,
+                    new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+
+            final String fileName = "noiseandchirps.mp3";
+            final Request request = new Request(getAssetUrl(fileName));
+            final File downloadLocation = new File(mContext.getExternalFilesDir(null),
+                    fileName);
+            request.setDestinationUri(Uri.fromFile(downloadLocation));
+            final long downloadId = mDownloadManager.enqueue(request);
+            receiver.waitForDownloadComplete(SHORT_TIMEOUT, downloadId);
+            assertSuccessfulDownload(downloadId, downloadLocation);
+            final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(downloadId);
+            final Uri mediaStoreUri = getMediaStoreUri(downloadUri);
+            final ContentResolver contentResolver = mContext.getContentResolver();
+            assertArrayEquals(hash(contentResolver.openInputStream(downloadUri)),
+                    hash(contentResolver.openInputStream(mediaStoreUri)));
+
+            assertRemoveDownload(downloadId, 0);
+        } finally {
+            mContext.unregisterReceiver(receiver);
+        }
+    }
+
+    /**
+     * Add a file to DownloadProvider using DownloadManager.addCompletedDownload and verify
+     * updates to this entry in DownlaodProvider are reflected in MediaProvider as well.
+     */
+    @Test
+    public void testDownloadManagerUpdates() throws Exception {
+        final File dataDir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
+        dataDir.mkdir();
+        final File downloadFile = new File(dataDir, "colors.txt");
+        downloadFile.createNewFile();
+        final String fileContents = "RED;GREEN;BLUE";
+        try (final PrintWriter pw = new PrintWriter(downloadFile)) {
+            pw.print(fileContents);
+        }
+
+        // Insert into DownloadProvider and verify it's added to MediaProvider as well
+        final String testTitle = "Test title";
+        final long downloadId = mDownloadManager.addCompletedDownload(testTitle, "Test desc", true,
+                "text/plain", downloadFile.getPath(), fileContents.getBytes().length, true);
+        assertTrue(downloadId >= 0);
+        final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(downloadId);
+        final Uri mediaStoreUri = getMediaStoreUri(downloadUri);
+        assertArrayEquals(hash(new FileInputStream(downloadFile)),
+                hash(mContext.getContentResolver().openInputStream(mediaStoreUri)));
+        try (Cursor cursor = mContext.getContentResolver().query(mediaStoreUri,
+                new String[] { MediaStore.DownloadColumns.DISPLAY_NAME }, null, null)) {
+            cursor.moveToNext();
+            assertEquals(testTitle, cursor.getString(0));
+        }
+
+        // Update title in DownloadProvider and verify the change took effect in MediaProvider
+        // as well.
+        final String newTitle = "New title";
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(DownloadManager.COLUMN_TITLE, newTitle);
+        assertEquals(1, mContext.getContentResolver().update(
+                downloadUri, updateValues, null, null));
+        try (Cursor cursor = mContext.getContentResolver().query(mediaStoreUri,
+                new String[] { MediaStore.DownloadColumns.DISPLAY_NAME }, null, null)) {
+            cursor.moveToNext();
+            assertEquals(newTitle, cursor.getString(0));
+        }
+
+        // Delete entry in DownloadProvider and verify it's deleted from MediaProvider as well.
+        assertRemoveDownload(downloadId, 0);
+        try (Cursor cursor = mContext.getContentResolver().query(
+                mediaStoreUri, null, null, null)) {
+            assertEquals(0, cursor.getCount());
+        }
+    }
+
+    @Test
+    public void testDownloadManager_mediaStoreUpdates() throws Exception {
+        final File dataDir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
+        dataDir.mkdir();
+        final File downloadFile = new File(dataDir, "colors.txt");
+        downloadFile.createNewFile();
+        final String fileContents = "RED;GREEN;BLUE";
+        try (final PrintWriter pw = new PrintWriter(downloadFile)) {
+            pw.print(fileContents);
+        }
+
+        // Insert into DownloadProvider and verify it's added to MediaProvider as well
+        final String testTitle = "Test_title";
+        final long downloadId = mDownloadManager.addCompletedDownload(testTitle, "Test desc", true,
+                "text/plain", downloadFile.getPath(), fileContents.getBytes().length, true);
+        assertTrue(downloadId >= 0);
+        final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(downloadId);
+        try (Cursor cursor = mContext.getContentResolver().query(downloadUri,
+                new String[] { DownloadManager.COLUMN_TITLE }, null, null)) {
+            cursor.moveToNext();
+            assertEquals(testTitle, cursor.getString(0));
+        }
+
+        final Uri mediaStoreUri = getMediaStoreUri(downloadUri);
+        final String newTitle = "New_title";
+        updateUri(mediaStoreUri, "_display_name", newTitle);
+        try (Cursor cursor = mContext.getContentResolver().query(downloadUri,
+                new String[] { DownloadManager.COLUMN_TITLE }, null, null)) {
+            cursor.moveToNext();
+            assertEquals(newTitle, cursor.getString(0));
+        }
+
+        assertRemoveDownload(downloadId, 0);
+    }
+
+    private void updateUri(Uri uri, String column, String value) throws Exception {
+        final String cmd = String.format("content update --uri %s --bind %s:s:%s",
+                uri, column, value);
+        final String res = runShellCommand(cmd).trim();
+        assertTrue(res, TextUtils.isEmpty(res));
+    }
+
+    private static byte[] hash(InputStream in) throws Exception {
+        try (DigestInputStream digestIn = new DigestInputStream(in,
+                MessageDigest.getInstance("SHA-1"));
+                OutputStream out = new FileOutputStream(new File("/dev/null"))) {
+            FileUtils.copy(digestIn, out);
+            return digestIn.getMessageDigest().digest();
+        } finally {
+            FileUtils.closeQuietly(in);
+        }
+    }
+
+    private Uri getMediaStoreUri(Uri downloadUri) throws Exception {
+        final String res = runShellCommand(
+                "content query --uri " + downloadUri + " --projection mediastore_uri").trim();
+        final String str = "mediastore_uri=";
+        final int i = res.indexOf(str);
+        if (i >= 0) {
+            return Uri.parse(res.substring(i + str.length()));
+        } else {
+            throw new FileNotFoundException("Failed to find mediastore_uri for "
+                    + downloadUri + "; found " + res);
+        }
+    }
+
+    private static String getRawFilePath(Uri uri) throws Exception {
+        final String res = runShellCommand("content query --uri " + uri + " --projection _data");
+        final int i = res.indexOf("_data=");
+        if (i >= 0) {
+            return res.substring(i + 6);
+        } else {
+            throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
+        }
+    }
+
+    private static String readFromRawFile(String filePath) throws Exception {
+        Log.d(TAG, "Reading form file: " + filePath);
+        return runShellCommand("cat " + filePath);
+    }
+
+    private static String readFromFile(ParcelFileDescriptor pfd) throws Exception {
+        BufferedReader br = null;
+        try (final InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
+            br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+            String str;
+            StringBuilder out = new StringBuilder();
+            while ((str = br.readLine()) != null) {
+                out.append(str);
+            }
+            return out.toString();
+        } finally {
+            if (br != null) {
+                br.close();
+            }
+        }
+    }
+
+    private static File createFile(File baseDir, String fileName) {
+        if (!baseDir.exists()) {
+            baseDir.mkdirs();
+        }
+        return new File(baseDir, fileName);
+    }
+
+    private static void writeToFile(File file, String contents) throws Exception {
+        try (final PrintWriter out = new PrintWriter(file)) {
+            out.print(contents);
+        }
+    }
+
     private class DownloadCompleteReceiver extends BroadcastReceiver {
         private HashSet<Long> mCompleteIds = new HashSet<>();
 
@@ -477,7 +800,7 @@
     }
 
     private boolean hasInternetConnection() {
-        final PackageManager pm = getContext().getPackageManager();
+        final PackageManager pm = mContext.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
                 || pm.hasSystemFeature(PackageManager.FEATURE_WIFI)
                 || pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET);
diff --git a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
index 7931a1f..850f6ba 100644
--- a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -40,6 +40,7 @@
         assertFalse(group.isBlocked());
         assertNull(group.getDescription());
         assertEquals(0, group.getChannels().size());
+        assertEquals(0, group.getUserLockedFields());
     }
 
     public void testIsBlocked() {
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 70ec9cf..f2b5cd4 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -17,10 +17,10 @@
 package android.app.cts;
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.graphics.Color;
 import android.media.AudioAttributes;
 import android.net.Uri;
@@ -44,7 +44,7 @@
 
     public void testConstructor() {
         NotificationChannel channel =
-                new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+                new NotificationChannel("1", "one", IMPORTANCE_HIGH);
         assertEquals("1", channel.getId());
         assertEquals("one", channel.getName());
         assertEquals(null, channel.getDescription());
@@ -52,12 +52,14 @@
         assertEquals(false, channel.shouldShowLights());
         assertEquals(false, channel.shouldVibrate());
         assertEquals(null, channel.getVibrationPattern());
-        assertEquals(IMPORTANCE_DEFAULT, channel.getImportance());
+        assertEquals(IMPORTANCE_HIGH, channel.getImportance());
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, channel.getSound());
         assertTrue(channel.canShowBadge());
         assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes());
         assertEquals(null, channel.getGroup());
         assertTrue(channel.getLightColor() == 0);
+        assertTrue(channel.canBubble());
+        assertFalse(channel.isImportanceLockedByOEM());
     }
 
     public void testWriteToParcel() {
@@ -152,4 +154,26 @@
         channel.setGroup("banana");
         assertEquals("banana", channel.getGroup());
     }
+
+    public void testBubble() {
+        NotificationChannel channel =
+                new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+        channel.setAllowBubbles(true);
+        assertEquals("Only HIGH channels can have bubbles", false, channel.canBubble());
+
+        channel = new NotificationChannel("1", "one", IMPORTANCE_HIGH);
+        channel.setAllowBubbles(false);
+        assertEquals(false, channel.canBubble());
+
+        channel = new NotificationChannel("1", "one", IMPORTANCE_HIGH);
+        channel.setAllowBubbles(true);
+        assertEquals(true, channel.canBubble());
+    }
+
+    public void testIsImportanceLockedByOEM() {
+        NotificationChannel channel =
+                new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+        channel.setImportanceLockedByOEM(true);
+        assertTrue(channel.isImportanceLockedByOEM());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 3f61605..f639d56 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -16,6 +16,7 @@
 
 package android.app.cts;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 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,34 +28,50 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
 import android.app.ActivityManager;
+import android.app.AutomaticZenRule;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.Person;
 import android.app.UiAutomation;
-import android.app.stubs.MockNotificationListener;
+import android.app.stubs.AutomaticZenRuleActivity;
 import android.app.stubs.R;
+import android.app.stubs.TestNotificationListener;
 import android.content.ComponentName;
+import android.content.ContentProviderOperation;
 import android.content.Context;
 import android.content.Intent;
+import android.content.OperationApplicationException;
 import android.content.pm.PackageManager;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.Settings;
 import android.provider.Telephony.Threads;
+import android.service.notification.Condition;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import junit.framework.Assert;
 
 import java.io.FileInputStream;
@@ -65,6 +82,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 
 public class NotificationManagerTest extends AndroidTestCase {
@@ -72,11 +90,16 @@
     final boolean DEBUG = false;
     final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
 
+    private static final String DELEGATOR = "com.android.test.notificationdelegator";
+    private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
+    private static final int WAIT_TIME = 2000;
+
     private PackageManager mPackageManager;
     private NotificationManager mNotificationManager;
     private ActivityManager mActivityManager;
     private String mId;
-    private MockNotificationListener mListener;
+    private TestNotificationListener mListener;
+    private List<String> mRuleIds;
 
     @Override
     protected void setUp() throws Exception {
@@ -91,6 +114,8 @@
                 NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
+        mRuleIds = new ArrayList<>();
+
         // delay between tests so notifications aren't dropped by the rate limiter
         try {
             Thread.sleep(500);
@@ -101,6 +126,13 @@
     protected void tearDown() throws Exception {
         super.tearDown();
         mNotificationManager.cancelAll();
+
+        for (String id : mRuleIds) {
+            mNotificationManager.removeAutomaticZenRule(id);
+        }
+
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
         List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
         // Delete all channels.
         for (NotificationChannel nc : channels) {
@@ -110,7 +142,9 @@
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
 
-        toggleListenerAccess(MockNotificationListener.getId(),
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
                 InstrumentationRegistry.getInstrumentation(), false);
 
         List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
@@ -120,6 +154,348 @@
         }
     }
 
+    private void insertSingleContact(String name, String phone, String email, boolean starred) {
+        final ArrayList<ContentProviderOperation> operationList =
+                new ArrayList<ContentProviderOperation>();
+        ContentProviderOperation.Builder builder =
+                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
+        builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
+        operationList.add(builder.build());
+
+        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+        builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        builder.withValue(StructuredName.DISPLAY_NAME, name);
+        operationList.add(builder.build());
+
+        if (phone != null) {
+            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+            builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+            builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+            builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+            builder.withValue(Phone.NUMBER, phone);
+            builder.withValue(Data.IS_PRIMARY, 1);
+            operationList.add(builder.build());
+        }
+        if (email != null) {
+            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+            builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+            builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+            builder.withValue(Email.TYPE, Email.TYPE_HOME);
+            builder.withValue(Email.DATA, email);
+            operationList.add(builder.build());
+        }
+
+        try {
+            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+        } catch (RemoteException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        } catch (OperationApplicationException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        }
+    }
+
+    private Uri lookupContact(String phone) {
+        Cursor c = null;
+        try {
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(phone));
+            String[] projection = new String[] { ContactsContract.Contacts._ID,
+                    ContactsContract.Contacts.LOOKUP_KEY };
+            c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
+            if (c != null && c.getCount() > 0) {
+                c.moveToFirst();
+                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
+                String lookupKey = c.getString(lookupIdx);
+                long contactId = c.getLong(idIdx);
+                return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+            }
+        } catch (Throwable t) {
+            Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return null;
+    }
+
+
+    private StatusBarNotification findPostedNotification(int id) {
+        // 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();
+            for (StatusBarNotification sbn : sbns) {
+                Log.d(TAG, "Found " + sbn.getKey());
+                if (sbn.getId() == id) {
+                    n = sbn;
+                    break;
+                }
+            }
+            if (n != null) break;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return n;
+    }
+
+    private PendingIntent getPendingIntent() {
+        return PendingIntent.getActivity(
+                getContext(), 0, new Intent(getContext(), this.getClass()), 0);
+    }
+
+    private boolean isGroupSummary(Notification n) {
+        return n.getGroup() != null && (n.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+    }
+
+    private void assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds) {
+        String expectedGroupKey = null;
+        try {
+            // Posting can take ~100 ms
+            Thread.sleep(150);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+        for (StatusBarNotification sbn : sbns) {
+            if (isGroupSummary(sbn.getNotification())
+                    || autoGroupedIds.contains(sbn.getId())) {
+                assertTrue(sbn.getKey() + " is unexpectedly not autogrouped",
+                        sbn.getOverrideGroupKey() != null);
+                if (expectedGroupKey == null) {
+                    expectedGroupKey = sbn.getGroupKey();
+                }
+                assertEquals(expectedGroupKey, sbn.getGroupKey());
+            } else {
+                assertTrue(sbn.isGroup());
+                assertTrue(sbn.getKey() + " is unexpectedly autogrouped,",
+                        sbn.getOverrideGroupKey() == null);
+                assertTrue(sbn.getKey() + " has an unusual group key",
+                        sbn.getGroupKey() != expectedGroupKey);
+            }
+        }
+    }
+
+    private void assertAllPostedNotificationsAutogrouped() {
+        String expectedGroupKey = null;
+        try {
+            // Posting can take ~100 ms
+            Thread.sleep(150);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+        for (StatusBarNotification sbn : sbns) {
+            // all notis should be in a group determined by autogrouping
+            assertTrue(sbn.getOverrideGroupKey() != null);
+            if (expectedGroupKey == null) {
+                expectedGroupKey = sbn.getGroupKey();
+            }
+            // all notis should be in the same group
+            assertEquals(expectedGroupKey, sbn.getGroupKey());
+        }
+    }
+
+    private void cancelAndPoll(int id) {
+        mNotificationManager.cancel(id);
+
+        if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
+            fail("canceled notification was still alive, id=" + 1);
+        }
+    }
+
+    private void sendNotification(final int id, final int icon) throws Exception {
+        sendNotification(id, null, icon);
+    }
+
+    private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
+
+        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(mContext, 0, intent, 0);
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .setContentIntent(pendingIntent)
+                        .setGroup(groupKey)
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
+            fail("couldn't find posted notification id=" + id);
+        }
+    }
+
+    private boolean checkNotificationExistence(int id, boolean shouldExist) {
+        // 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
+        boolean found = false;
+        for (int tries = 3; tries--> 0;) {
+            // Need reset flag.
+            found = false;
+            final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+            for (StatusBarNotification sbn : sbns) {
+                Log.d(TAG, "Found " + sbn.getKey());
+                if (sbn.getId() == id) {
+                    found = true;
+                    break;
+                }
+            }
+            if (found == shouldExist) break;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return found == shouldExist;
+    }
+
+    private void assertNotificationCount(int expectedCount) {
+        // notification is a bit asynchronous so it may take a few ms to appear in
+        // getActiveNotifications()
+        // we will check for it for up to 400ms before giving up
+        int lastCount = 0;
+        for (int tries = 4; tries-- > 0;) {
+            final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+            lastCount = sbns.length;
+            if (expectedCount == lastCount) return;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        fail("Expected " + expectedCount + " posted notifications, were " +  lastCount);
+    }
+
+    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
+        if (actual == null) {
+            fail("actual channel is null");
+            return;
+        }
+        if (expected == null) {
+            fail("expected channel is null");
+            return;
+        }
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(expected.getImportance(), actual.getImportance());
+        if (expected.getSound() == null) {
+            assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actual.getSound());
+            assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, actual.getAudioAttributes());
+        } else {
+            assertEquals(expected.getSound(), actual.getSound());
+            assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
+        }
+        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(expected.getGroup(), actual.getGroup());
+    }
+
+    private void toggleNotificationPolicyAccess(String packageName,
+            Instrumentation instrumentation, boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+
+        runCommand(command, instrumentation);
+
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        Assert.assertEquals("Notification Policy Access Grant is " +
+                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+                nm.isNotificationPolicyAccessGranted());
+    }
+
+    private void suspendPackage(String packageName,
+            Instrumentation instrumentation, boolean suspend) throws IOException {
+        String command = " cmd package " + (suspend ? "suspend " : "unsuspend ") + packageName;
+
+        runCommand(command, instrumentation);
+    }
+
+    private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
+            boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
+                + componentName;
+
+        runCommand(command, instrumentation);
+
+        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        final ComponentName listenerComponent = TestNotificationListener.getComponentName();
+        assertTrue(listenerComponent + " has not been granted access",
+                nm.isNotificationListenerAccessGranted(listenerComponent) == on);
+    }
+
+    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+        UiAutomation uiAutomation = instrumentation.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();
+        }
+    }
+
+    private boolean areRulesSame(AutomaticZenRule a, AutomaticZenRule b) {
+        return a.isEnabled() == b.isEnabled()
+                && Objects.equals(a.getName(), b.getName())
+                && a.getInterruptionFilter() == b.getInterruptionFilter()
+                && Objects.equals(a.getConditionId(), b.getConditionId())
+                && Objects.equals(a.getOwner(), b.getOwner())
+                && Objects.equals(a.getZenPolicy(), b.getZenPolicy())
+                && Objects.equals(a.getConfigurationActivity(), b.getConfigurationActivity());
+    }
+
+    private AutomaticZenRule createRule(String name) {
+        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);
+    }
+
+    private void assertExpectedDndState(int expectedState) {
+        int tries = 3;
+        for (int i = tries; i >=0; i--) {
+            if (expectedState ==
+                    mNotificationManager.getCurrentInterruptionFilter()) {
+                break;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        assertEquals(expectedState, mNotificationManager.getCurrentInterruptionFilter());
+    }
+
     public void testPostPCanToggleAlarmsMediaSystemTest() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             return;
@@ -153,9 +529,6 @@
             assertTrue((policy.priorityCategories &
                     NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0);
         }
-
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
     }
 
     public void testCreateChannelGroup() throws Exception {
@@ -201,6 +574,37 @@
         assertEquals(channel.getId(), actual.getChannels().get(0).getId());
     }
 
+    public void testGetChannelGroups() throws Exception {
+        final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
+        ncg.setDescription("bananas");
+        final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
+        final NotificationChannel channel =
+                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setGroup(ncg2.getId());
+
+        mNotificationManager.createNotificationChannelGroup(ncg);
+        mNotificationManager.createNotificationChannelGroup(ncg2);
+        mNotificationManager.createNotificationChannel(channel);
+
+        List<NotificationChannelGroup> actual =
+                mNotificationManager.getNotificationChannelGroups();
+        assertEquals(2, actual.size());
+        for (NotificationChannelGroup group : actual) {
+            if (group.getId().equals(ncg.getId())) {
+                assertEquals(group.getName(), ncg.getName());
+                assertEquals(group.getDescription(), ncg.getDescription());
+                assertEquals(0, group.getChannels().size());
+            } else if (group.getId().equals(ncg2.getId())) {
+                assertEquals(group.getName(), ncg2.getName());
+                assertEquals(group.getDescription(), ncg2.getDescription());
+                assertEquals(1, group.getChannels().size());
+                assertEquals(channel.getId(), group.getChannels().get(0).getId());
+            } else {
+                fail("Extra group found " + group.getId());
+            }
+        }
+    }
+
     public void testDeleteChannelGroup() throws Exception {
         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
         final NotificationChannel channel =
@@ -474,11 +878,11 @@
             return;
         }
 
-        toggleListenerAccess(MockNotificationListener.getId(),
+        toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), true);
         Thread.sleep(500); // wait for listener to be allowed
 
-        mListener = MockNotificationListener.getInstance();
+        mListener = TestNotificationListener.getInstance();
         assertNotNull(mListener);
 
         sendNotification(1, R.drawable.black);
@@ -515,6 +919,52 @@
         mListener.resetData();
     }
 
+    public void testSuspendedPackageSendsNotification() 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);
+
+        // suspend package, post notification while package is suspended, see notification
+        // in ranking map with suspended = true
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                true);
+        sendNotification(1, R.drawable.black);
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size()); // apps targeting P receive notification
+        NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
+        NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
+        for (String key : rankingMap.getOrderedKeys()) {
+            if (key.contains(mListener.getPackageName())) {
+                rankingMap.getRanking(key, outRanking);
+                Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
+                assertTrue(outRanking.isSuspended());
+            }
+        }
+
+        // unsuspend package, ranking should be updated with suspended = false
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                false);
+        Thread.sleep(500); // wait for notification listener to get response
+        assertEquals(1, mListener.mPosted.size()); // should see previously posted notification
+        rankingMap = mListener.mRankingMap;
+        for (String key : rankingMap.getOrderedKeys()) {
+            if (key.contains(mListener.getPackageName())) {
+                rankingMap.getRanking(key, outRanking);
+                Log.d(TAG, "key=" + key + " suspended=" + outRanking.isSuspended());
+                assertFalse(outRanking.isSuspended());
+            }
+        }
+
+        mListener.resetData();
+    }
+
     public void testNotify_blockedChannel() throws Exception {
         mNotificationManager.cancelAll();
 
@@ -827,6 +1277,151 @@
         assertOnlySomeNotificationsAutogrouped(postedIds);
     }
 
+    public void testAddAutomaticZenRule_configActivity() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+
+        assertNotNull(id);
+        mRuleIds.add(id);
+        assertTrue(areRulesSame(ruleToCreate, mNotificationManager.getAutomaticZenRule(id)));
+    }
+
+    public void testUpdateAutomaticZenRule_configActivity() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+        ruleToCreate.setEnabled(false);
+        mNotificationManager.updateAutomaticZenRule(id, ruleToCreate);
+
+        assertNotNull(id);
+        mRuleIds.add(id);
+        assertTrue(areRulesSame(ruleToCreate, mNotificationManager.getAutomaticZenRule(id)));
+    }
+
+    public void testRemoveAutomaticZenRule_configActivity() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+
+        assertNotNull(id);
+        mRuleIds.add(id);
+        mNotificationManager.removeAutomaticZenRule(id);
+
+        assertNull(mNotificationManager.getAutomaticZenRule(id));
+        assertEquals(0, mNotificationManager.getAutomaticZenRules().size());
+    }
+
+    public void testSetAutomaticZenRuleState() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+        mRuleIds.add(id);
+
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+        // enable DND
+        Condition condition =
+                new Condition(ruleToCreate.getConditionId(), "summary", Condition.STATE_TRUE);
+        mNotificationManager.setAutomaticZenRuleState(id, condition);
+
+        assertExpectedDndState(ruleToCreate.getInterruptionFilter());
+    }
+
+    public void testSetAutomaticZenRuleState_turnOff() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+        mRuleIds.add(id);
+
+        // make sure DND is off
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+        // enable DND
+        Condition condition =
+                new Condition(ruleToCreate.getConditionId(), "on", Condition.STATE_TRUE);
+        mNotificationManager.setAutomaticZenRuleState(id, condition);
+
+        assertExpectedDndState(ruleToCreate.getInterruptionFilter());
+
+        // disable DND
+        condition = new Condition(ruleToCreate.getConditionId(), "off", Condition.STATE_FALSE);
+
+        mNotificationManager.setAutomaticZenRuleState(id, condition);
+
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+    }
+
+    public void testSetAutomaticZenRuleState_deletedRule() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+        mRuleIds.add(id);
+
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+        // enable DND
+        Condition condition =
+                new Condition(ruleToCreate.getConditionId(), "summary", Condition.STATE_TRUE);
+        mNotificationManager.setAutomaticZenRuleState(id, condition);
+
+        assertExpectedDndState(ruleToCreate.getInterruptionFilter());
+
+        mNotificationManager.removeAutomaticZenRule(id);
+
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+    }
+
+    public void testSetAutomaticZenRuleState_multipleRules() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        AutomaticZenRule ruleToCreate = createRule("Rule");
+        String id = mNotificationManager.addAutomaticZenRule(ruleToCreate);
+        mRuleIds.add(id);
+
+        AutomaticZenRule secondRuleToCreate = createRule("Rule 2");
+        secondRuleToCreate.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+        String secondId = mNotificationManager.addAutomaticZenRule(secondRuleToCreate);
+        mRuleIds.add(secondId);
+
+        // make sure DND is off
+        assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+        // enable DND
+        Condition condition =
+                new Condition(ruleToCreate.getConditionId(), "summary", Condition.STATE_TRUE);
+        mNotificationManager.setAutomaticZenRuleState(id, condition);
+        Condition secondCondition =
+                new Condition(secondRuleToCreate.getConditionId(), "summary", Condition.STATE_TRUE);
+        mNotificationManager.setAutomaticZenRuleState(secondId, secondCondition);
+
+        // the second rule has a 'more silent' DND filter, so the system wide DND should be
+        // using its filter
+        assertExpectedDndState(secondRuleToCreate.getInterruptionFilter());
+
+        // remove intense rule, system should fallback to other rule
+        mNotificationManager.removeAutomaticZenRule(secondId);
+        assertExpectedDndState(ruleToCreate.getInterruptionFilter());
+    }
+
     public void testSetNotificationPolicy_P_setOldFields() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             return;
@@ -845,8 +1440,6 @@
             assertEquals(expected,
                     mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
         }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
     }
 
     public void testSetNotificationPolicy_P_setNewFields() throws Exception {
@@ -867,8 +1460,6 @@
             assertEquals(expected,
                     mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
         }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
     }
 
     public void testSetNotificationPolicy_P_setOldNewFields() throws Exception {
@@ -897,210 +1488,179 @@
             assertEquals(expected,
                     mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
         }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
     }
 
-    private PendingIntent getPendingIntent() {
-        return PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()), 0);
-    }
+    public void testPostFullScreenIntent_permission() {
+        int id = 6000;
 
-    private boolean isGroupSummary(Notification n) {
-        return n.getGroup() != null && (n.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
-    }
-
-    private void assertOnlySomeNotificationsAutogrouped(List<Integer> autoGroupedIds) {
-        String expectedGroupKey = null;
-        StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
-        for (StatusBarNotification sbn : sbns) {
-            if (isGroupSummary(sbn.getNotification())
-                    || autoGroupedIds.contains(sbn.getId())) {
-                assertTrue(sbn.getKey() + " is unexpectedly not autogrouped",
-                        sbn.getOverrideGroupKey() != null);
-                if (expectedGroupKey == null) {
-                    expectedGroupKey = sbn.getGroupKey();
-                }
-                assertEquals(expectedGroupKey, sbn.getGroupKey());
-            } else {
-                assertTrue(sbn.isGroup());
-                assertTrue(sbn.getKey() + " is unexpectedly autogrouped,",
-                        sbn.getOverrideGroupKey() == null);
-                assertTrue(sbn.getKey() + " has an unusual group key",
-                        sbn.getGroupKey() != expectedGroupKey);
-            }
-        }
-    }
-
-    private void assertAllPostedNotificationsAutogrouped() {
-        String expectedGroupKey = null;
-        StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
-        for (StatusBarNotification sbn : sbns) {
-            // all notis should be in a group determined by autogrouping
-            assertTrue(sbn.getOverrideGroupKey() != null);
-            if (expectedGroupKey == null) {
-                expectedGroupKey = sbn.getGroupKey();
-            }
-            // all notis should be in the same group
-            assertEquals(expectedGroupKey, sbn.getGroupKey());
-        }
-    }
-
-    private void cancelAndPoll(int id) {
-        mNotificationManager.cancel(id);
-
-        if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
-            fail("canceled notification was still alive, id=" + 1);
-        }
-    }
-
-    private void sendNotification(final int id, final int icon) throws Exception {
-        sendNotification(id, null, icon);
-    }
-
-    private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
-        final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
-
-        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(mContext, 0, intent, 0);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                        .setSmallIcon(icon)
+                        .setSmallIcon(R.drawable.black)
                         .setWhen(System.currentTimeMillis())
-                        .setContentTitle("notify#" + id)
-                        .setContentText("This is #" + id + "notification  ")
-                        .setContentIntent(pendingIntent)
-                        .setGroup(groupKey)
+                        .setFullScreenIntent(getPendingIntent(), true)
+                        .setContentText("This is #FSI notification")
+                        .setContentIntent(getPendingIntent())
                         .build();
         mNotificationManager.notify(id, notification);
 
-        if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
-            fail("couldn't find posted notification id=" + id);
+        StatusBarNotification n = findPostedNotification(id);
+        assertNotNull(n);
+        assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
+    }
+
+    public void testNotificationPolicyVisualEffectsEqual() {
+        NotificationManager.Policy policy = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_SCREEN_ON);
+        NotificationManager.Policy policy2 = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_PEEK);
+        assertTrue(policy.equals(policy2));
+        assertTrue(policy2.equals(policy));
+
+        policy = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_SCREEN_ON);
+        policy2 = new NotificationManager.Policy(0,0 ,0 ,
+                0);
+        assertFalse(policy.equals(policy2));
+        assertFalse(policy2.equals(policy));
+
+        policy = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_SCREEN_OFF);
+        policy2 = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_FULL_SCREEN_INTENT | SUPPRESSED_EFFECT_AMBIENT
+                        | SUPPRESSED_EFFECT_LIGHTS);
+        assertTrue(policy.equals(policy2));
+        assertTrue(policy2.equals(policy));
+
+        policy = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_SCREEN_OFF);
+        policy2 = new NotificationManager.Policy(0,0 ,0 ,
+                SUPPRESSED_EFFECT_LIGHTS);
+        assertFalse(policy.equals(policy2));
+        assertFalse(policy2.equals(policy));
+    }
+
+    public void testNotificationDelegate_grantAndPost() 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, "tag", 0, n);
+
+        findPostedNotification(0);
+    }
+
+    public void testNotificationDelegate_grantAndRevoke() 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);
+
+        mContext.startActivity(activityIntent);
+        Thread.sleep(1000);
+
+        assertTrue(mNotificationManager.canNotifyAsPackage(DELEGATOR));
+
+        final Intent revokeIntent = new Intent();
+        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(revokeIntent);
+        Thread.sleep(1000);
+
+        try {
+            // send notification
+            Notification n = new Notification.Builder(mContext, "channel")
+                    .setSmallIcon(android.R.id.icon)
+                    .build();
+            mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
+            fail("Should not be able to post as a delegate when permission revoked");
+        } catch (SecurityException e) {
+            // yay
         }
     }
 
-    private boolean checkNotificationExistence(int id, boolean shouldExist) {
-        // 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
-        boolean found = false;
-        for (int tries = 3; tries--> 0;) {
-            // Need reset flag.
-            found = false;
-            final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
-            for (StatusBarNotification sbn : sbns) {
-                Log.d(TAG, "Found " + sbn.getKey());
-                if (sbn.getId() == id) {
-                    found = true;
-                    break;
-                }
-            }
-            if (found == shouldExist) break;
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ex) {
-                // pass
-            }
-        }
-        return found == shouldExist;
+    public void testAreBubblesAllowed() {
+        assertTrue(mNotificationManager.areBubblesAllowed());
     }
 
-    private void assertNotificationCount(int expectedCount) {
-        // notification is a bit asynchronous so it may take a few ms to appear in
-        // getActiveNotifications()
-        // we will check for it for up to 400ms before giving up
-        int lastCount = 0;
-        for (int tries = 4; tries-- > 0;) {
-            final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
-            lastCount = sbns.length;
-            if (expectedCount == lastCount) return;
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ex) {
-                // pass
-            }
-        }
-        fail("Expected " + expectedCount + " posted notifications, were " +  lastCount);
+    public void testNotificationIcon() {
+        int id = 6000;
+
+        Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.id.icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setFullScreenIntent(getPendingIntent(), true)
+                        .setContentText("This notification has a resource icon")
+                        .setContentIntent(getPendingIntent())
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(Icon.createWithResource(mContext, android.R.id.icon))
+                        .setWhen(System.currentTimeMillis())
+                        .setFullScreenIntent(getPendingIntent(), true)
+                        .setContentText("This notification has an Icon icon")
+                        .setContentIntent(getPendingIntent())
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        StatusBarNotification n = findPostedNotification(id);
+        assertNotNull(n);
     }
 
-    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
-        if (actual == null) {
-            fail("actual channel is null");
-            return;
+    public void testShouldHideSilentStatusIcons() throws Exception {
+        try {
+            mNotificationManager.shouldHideSilentStatusBarIcons();
+            fail("Non-privileged apps should not get this information");
+        } catch (SecurityException e) {
+            // pass
         }
-        if (expected == null) {
-            fail("expected channel is null");
-            return;
-        }
-        assertEquals(expected.getId(), actual.getId());
-        assertEquals(expected.getName(), actual.getName());
-        assertEquals(expected.getDescription(), actual.getDescription());
-        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
-        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
-        assertEquals(expected.getImportance(), actual.getImportance());
-        if (expected.getSound() == null) {
-            assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actual.getSound());
-            assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, actual.getAudioAttributes());
-        } else {
-            assertEquals(expected.getSound(), actual.getSound());
-            assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
-        }
-        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
-        assertEquals(expected.getGroup(), actual.getGroup());
+
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        // no exception this time
+        mNotificationManager.shouldHideSilentStatusBarIcons();
     }
 
-    private void toggleNotificationPolicyAccess(String packageName,
-            Instrumentation instrumentation, boolean on) throws IOException {
+    public void testMatchesCallFilter() throws Exception {
+        // allow all callers
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        NotificationManager.Policy currPolicy = mNotificationManager.getNotificationPolicy();
+        NotificationManager.Policy newPolicy = new NotificationManager.Policy(
+                NotificationManager.Policy.PRIORITY_CATEGORY_CALLS
+                        | NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS,
+                NotificationManager.Policy.PRIORITY_SENDERS_ANY,
+                currPolicy.priorityMessageSenders,
+                currPolicy.suppressedVisualEffects);
+        mNotificationManager.setNotificationPolicy(newPolicy);
 
-        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+        // add a contact
+        String ALICE = "Alice";
+        String ALICE_PHONE = "+16175551212";
+        String ALICE_EMAIL = "alice@_foo._bar";
 
-        runCommand(command, instrumentation);
+        insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, false);
 
-        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        Assert.assertEquals("Notification Policy Access Grant is " +
-                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
-                nm.isNotificationPolicyAccessGranted());
-    }
-
-    private void suspendPackage(String packageName,
-            Instrumentation instrumentation, boolean suspend) throws IOException {
-        String command = " cmd notification " + (suspend ? "suspend_package "
-                : "unsuspend_package ") + packageName;
-
-        runCommand(command, instrumentation);
-    }
-
-    private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
-            boolean on) throws IOException {
-
-        String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
-                + componentName;
-
-        runCommand(command, instrumentation);
-
-        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        final ComponentName listenerComponent = MockNotificationListener.getComponentName();
-        assertTrue(listenerComponent + " has not been granted access",
-                nm.isNotificationListenerAccessGranted(listenerComponent) == on);
-    }
-
-    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
-        UiAutomation uiAutomation = instrumentation.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();
-        }
+        final Bundle peopleExtras = new Bundle();
+        ArrayList<Person> personList = new ArrayList<>();
+        personList.add(new Person.Builder().setUri(lookupContact(ALICE_PHONE).toString()).build());
+        peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                assertTrue(mNotificationManager.matchesCallFilter(peopleExtras)));
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 3549145..18a8776 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -25,7 +25,6 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.app.RemoteInput;
-import android.app.stubs.R;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
@@ -56,12 +55,15 @@
     private static final String CONTENT_TEXT = "contentText";
     private static final String URI_STRING = "uriString";
     private static final String ACTION_TITLE = "actionTitle";
+    private static final String BUBBLE_TITLE = "bubbleTitle";
+    private static final int BUBBLE_HEIGHT = 300;
     private static final int TOLERANCE = 200;
     private static final long TIMEOUT = 4000;
     private static final NotificationChannel CHANNEL = new NotificationChannel("id", "name",
             NotificationManager.IMPORTANCE_HIGH);
     private static final String SHORTCUT_ID = "shortcutId";
     private static final String SETTING_TEXT = "work chats";
+    private static final boolean ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS = false;
 
     @Override
     protected void setUp() throws Exception {
@@ -120,13 +122,15 @@
     }
 
     public void testWriteToParcel() {
-
+        Notification.BubbleMetadata bubble = makeBubbleMetadata();
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setBadgeIconType(Notification.BADGE_ICON_SMALL)
                 .setShortcutId(SHORTCUT_ID)
                 .setTimeoutAfter(TIMEOUT)
                 .setSettingsText(SETTING_TEXT)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+                .setBubbleMetadata(bubble)
+                .setAllowSystemGeneratedContextualActions(ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS)
                 .build();
         mNotification.icon = 0;
         mNotification.number = 1;
@@ -183,6 +187,9 @@
         assertEquals(mNotification.getChannelId(), result.getChannelId());
         assertEquals(mNotification.getSettingsText(), result.getSettingsText());
         assertEquals(mNotification.getGroupAlertBehavior(), result.getGroupAlertBehavior());
+        assertNotNull(result.getBubbleMetadata());
+        assertEquals(mNotification.getAllowSystemGeneratedContextualActions(),
+                result.getAllowSystemGeneratedContextualActions());
 
         mNotification.contentIntent = null;
         parcel = Parcel.obtain();
@@ -233,6 +240,7 @@
     public void testBuilder() {
         final Intent intent = new Intent();
         final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        Notification.BubbleMetadata bubble = makeBubbleMetadata();
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
                 .setContentTitle(CONTENT_TITLE)
@@ -243,6 +251,8 @@
                 .setTimeoutAfter(TIMEOUT)
                 .setSettingsText(SETTING_TEXT)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
+                .setBubbleMetadata(bubble)
+                .setAllowSystemGeneratedContextualActions(ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS)
                 .build();
         assertEquals(CONTENT_TEXT, mNotification.extras.getString(Notification.EXTRA_TEXT));
         assertEquals(CONTENT_TITLE, mNotification.extras.getString(Notification.EXTRA_TITLE));
@@ -254,6 +264,9 @@
         assertEquals(TIMEOUT, mNotification.getTimeoutAfter());
         assertEquals(SETTING_TEXT, mNotification.getSettingsText());
         assertEquals(Notification.GROUP_ALERT_SUMMARY, mNotification.getGroupAlertBehavior());
+        assertEquals(bubble, mNotification.getBubbleMetadata());
+        assertEquals(ALLOW_SYS_GEN_CONTEXTUAL_ACTIONS,
+                mNotification.getAllowSystemGeneratedContextualActions());
     }
 
     public void testBuilder_getStyle() {
@@ -483,6 +496,32 @@
         assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
     }
 
+    public void testAction_builder_contextualAction_nullIcon() {
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Notification.Action.Builder builder =
+                new Notification.Action.Builder(null /* icon */, "title", pendingIntent)
+                .setContextual(true);
+        try {
+            builder.build();
+            fail("Creating a semantic Action with a null icon should cause a NullPointerException");
+        } catch (NullPointerException e) {
+            // Expected
+        }
+    }
+
+    public void testAction_builder_contextualAction_nullIntent() {
+        Notification.Action.Builder builder =
+                new Notification.Action.Builder(0 /* icon */, "title", null /* intent */)
+                .setContextual(true);
+        try {
+            builder.build();
+            fail("Creating a semantic Action with a null PendingIntent should cause a "
+                    + "NullPointerException");
+        } catch (NullPointerException e) {
+            // Expected
+        }
+    }
+
     public void testAction_parcel() {
         Notification.Action action = writeAndReadParcelable(
                 makeNotificationAction(builder -> {
@@ -522,6 +561,106 @@
         }
     }
 
+    public void testGetAllowSystemGeneratedContextualActions_trueByDefault() {
+        Notification notification = new Notification.Builder(mContext, CHANNEL.getId()).build();
+        assertTrue(notification.getAllowSystemGeneratedContextualActions());
+    }
+
+    public void testGetAllowSystemGeneratedContextualActions() {
+        Notification notification = new Notification.Builder(mContext, CHANNEL.getId())
+                .setAllowSystemGeneratedContextualActions(false)
+                .build();
+        assertFalse(notification.getAllowSystemGeneratedContextualActions());
+    }
+
+    public void testBubbleMetadataBuilder() {
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, 1);
+        Notification.BubbleMetadata.Builder metadataBuilder =
+                new Notification.BubbleMetadata.Builder()
+                .setDesiredHeight(BUBBLE_HEIGHT)
+                .setTitle(BUBBLE_TITLE)
+                .setIcon(icon)
+                .setIntent(bubbleIntent);
+
+        Notification.BubbleMetadata data = metadataBuilder.build();
+        assertEquals(BUBBLE_HEIGHT, data.getDesiredHeight());
+        assertEquals(BUBBLE_TITLE, data.getTitle());
+        assertEquals(icon, data.getIcon());
+        assertEquals(bubbleIntent, data.getIntent());
+        assertFalse(data.getSuppressInitialNotification());
+        assertFalse(data.getAutoExpandBubble());
+    }
+
+    public void testBubbleMetadata_parcel() {
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, 1);
+        Notification.BubbleMetadata metadata =
+                new Notification.BubbleMetadata.Builder()
+                        .setDesiredHeight(BUBBLE_HEIGHT)
+                        .setTitle(BUBBLE_TITLE)
+                        .setAutoExpandBubble(true)
+                        .setSuppressInitialNotification(true)
+                        .setIcon(icon)
+                        .setIntent(bubbleIntent)
+                        .build();
+
+        writeAndReadParcelable(metadata);
+        assertEquals(BUBBLE_TITLE, metadata.getTitle());
+        assertEquals(BUBBLE_HEIGHT, metadata.getDesiredHeight());
+        assertEquals(icon, metadata.getIcon());
+        assertEquals(bubbleIntent, metadata.getIntent());
+        assertTrue(metadata.getAutoExpandBubble());
+        assertTrue(metadata.getSuppressInitialNotification());
+
+    }
+
+    public void testBubbleMetadataBuilder_throwForNoIntent() {
+        Icon icon = Icon.createWithResource(mContext, 1);
+        Notification.BubbleMetadata.Builder metadataBuilder =
+                new Notification.BubbleMetadata.Builder()
+                .setDesiredHeight(BUBBLE_HEIGHT)
+                .setTitle(BUBBLE_TITLE)
+                .setIcon(icon);
+        try {
+            metadataBuilder.build();
+            fail("Should have thrown IllegalStateException, no pending intent");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    public void testBubbleMetadataBuilder_throwForNoTitle() {
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, 1);
+        Notification.BubbleMetadata.Builder metadataBuilder =
+                new Notification.BubbleMetadata.Builder()
+                .setDesiredHeight(BUBBLE_HEIGHT)
+                .setIcon(icon)
+                .setIntent(bubbleIntent);
+        try {
+            metadataBuilder.build();
+            fail("Should have thrown IllegalStateException, no title");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    public void testBubbleMetadataBuilder_throwForNoIcon() {
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Notification.BubbleMetadata.Builder metadataBuilder =
+                new Notification.BubbleMetadata.Builder()
+                .setDesiredHeight(BUBBLE_HEIGHT)
+                .setTitle(BUBBLE_TITLE)
+                .setIntent(bubbleIntent);
+        try {
+            metadataBuilder.build();
+            fail("Should have thrown IllegalStateException, no icon");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
@@ -573,4 +712,14 @@
         }
         return actionBuilder.build();
     }
+
+    private Notification.BubbleMetadata makeBubbleMetadata() {
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        return new Notification.BubbleMetadata.Builder()
+                        .setIntent(bubbleIntent)
+                        .setTitle(BUBBLE_TITLE)
+                        .setIcon(Icon.createWithResource(mContext, 1))
+                        .setDesiredHeight(BUBBLE_HEIGHT)
+                        .build();
+    }
 }
diff --git a/tests/app/src/android/app/cts/PersonTest.java b/tests/app/src/android/app/cts/PersonTest.java
index 0607e99..7988b8d 100644
--- a/tests/app/src/android/app/cts/PersonTest.java
+++ b/tests/app/src/android/app/cts/PersonTest.java
@@ -102,6 +102,52 @@
         assertEquals(person.getUri(), result.getUri());
     }
 
+    public void testEquals() {
+        Icon testIcon = createIcon();
+        Person.Builder builder = new Person.Builder()
+                .setName(TEST_NAME)
+                .setIcon(testIcon)
+                .setUri(TEST_URI)
+                .setKey(TEST_KEY)
+                .setBot(true)
+                .setImportant(true);
+
+        Person personA = builder.build();
+        Person personB = builder.build();
+
+        assertEquals(personA, personB);
+    }
+
+    public void testEquals_noIcon() {
+        Person.Builder builder = new Person.Builder()
+                .setName(TEST_NAME)
+                .setUri(TEST_URI)
+                .setKey(TEST_KEY)
+                .setBot(true)
+                .setImportant(true);
+
+        Person personA = builder.build();
+        Person personB = builder.build();
+
+        assertEquals(personA, personB);
+    }
+
+    public void testEquals_different() {
+        Icon testIcon = createIcon();
+        Person.Builder builder = new Person.Builder()
+                .setName(TEST_NAME)
+                .setIcon(testIcon)
+                .setUri(TEST_URI)
+                .setKey(TEST_KEY)
+                .setBot(true)
+                .setImportant(true);
+
+        Person personA = builder.build();
+        Person personB = builder.setKey("different_key").build();
+
+        assertFalse(personA.equals(personB));
+    }
+
     public void testDescribeContents() {
         Person person = new Person.Builder().build();
 
diff --git a/tests/app/src/android/app/cts/PipActivityTest.java b/tests/app/src/android/app/cts/PipActivityTest.java
index 214de59..c3afc8a 100644
--- a/tests/app/src/android/app/cts/PipActivityTest.java
+++ b/tests/app/src/android/app/cts/PipActivityTest.java
@@ -16,15 +16,30 @@
 
 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.support.test.filters.FlakyTest;
 import android.test.ActivityInstrumentationTestCase2;
+import java.util.function.BooleanSupplier;
 
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
+/**
+ * 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;
 
@@ -40,38 +55,70 @@
     }
 
     public void testLaunchPipActivity() throws Throwable {
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                final boolean supportsPip =
-                        mActivity.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-                if (supportsPip) {
-                    mActivity.enterPictureInPictureMode();
+        final boolean supportsPip =
+                mActivity.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
 
-                    // Entering PIP mode is not synchronous, so waiting for completion of all work
-                    // on UI thread.
-                    mInstrumentation.waitForIdle(new Runnable() {
-                        @Override
-                        public void run() {
-                            assertTrue(mActivity.isInMultiWindowMode());
-                            assertTrue(mActivity.isInPictureInPictureMode());
-                        }
-                    });
-                } else {
-                    assertTrue(!mActivity.enterPictureInPictureMode(
-                            new PictureInPictureParams.Builder().build()));
+        if (supportsPip) {
+            mActivity.enterPictureInPictureMode();
 
-                    // Entering PIP mode is not synchronous, so waiting for completion of all work
-                    // on UI thread.
-                    mInstrumentation.waitForIdle(new Runnable() {
-                        @Override
-                        public void run() {
-                            assertFalse(mActivity.isInMultiWindowMode());
-                            assertFalse(mActivity.isInPictureInPictureMode());
-                        }
-                    });
-                }
-            }
-        });
+            // 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/RemoteInputTest.java b/tests/app/src/android/app/cts/RemoteInputTest.java
index b8e0fce..80d5bc3 100644
--- a/tests/app/src/android/app/cts/RemoteInputTest.java
+++ b/tests/app/src/android/app/cts/RemoteInputTest.java
@@ -35,6 +35,8 @@
         assertTrue(input.isDataOnly());
         assertFalse(input.getAllowFreeFormInput());
         assertTrue(input.getChoices() == null || input.getChoices().length == 0);
+        assertEquals(RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO,
+                input.getEditChoicesBeforeSending());
         assertEquals(1, input.getAllowedDataTypes().size());
         assertTrue(input.getAllowedDataTypes().contains(MIME_TYPE));
     }
@@ -45,6 +47,8 @@
         assertFalse(input.isDataOnly());
         assertTrue(input.getAllowFreeFormInput());
         assertTrue(input.getChoices() == null || input.getChoices().length == 0);
+        assertEquals(RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO,
+                input.getEditChoicesBeforeSending());
         assertTrue(input.getAllowedDataTypes() == null || input.getAllowedDataTypes().isEmpty());
     }
 
@@ -54,6 +58,8 @@
         assertFalse(input.isDataOnly());
         assertFalse(input.getAllowFreeFormInput());
         assertTrue(input.getChoices() != null && input.getChoices().length > 0);
+        assertEquals(RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO,
+                input.getEditChoicesBeforeSending());
         assertTrue(input.getAllowedDataTypes() == null || input.getAllowedDataTypes().isEmpty());
     }
 
@@ -70,10 +76,38 @@
         assertFalse(input.isDataOnly());
         assertTrue(input.getAllowFreeFormInput());
         assertTrue(input.getChoices() != null && input.getChoices().length > 0);
+        assertEquals(RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO,
+                input.getEditChoicesBeforeSending());
         assertEquals(1, input.getAllowedDataTypes().size());
         assertTrue(input.getAllowedDataTypes().contains(MIME_TYPE));
     }
 
+    public void testRemoteInputBuilder_setEditChoicesBeforeSending() throws Throwable {
+        RemoteInput input =
+                new RemoteInput.Builder(RESULT_KEY)
+                        .setChoices(new CharSequence[]{"first", "second"})
+                        .setEditChoicesBeforeSending(
+                                RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED)
+                        .build();
+        assertEquals(RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED,
+                input.getEditChoicesBeforeSending());
+    }
+
+    public void testRemoteInputBuilder_setEditChoicesBeforeSendingRequiresFreeInput()
+            throws Throwable {
+        RemoteInput.Builder builder =
+                new RemoteInput.Builder(RESULT_KEY)
+                        .setEditChoicesBeforeSending(
+                                RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED)
+                        .setAllowFreeFormInput(false);
+        try {
+            builder.build();
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
     public void testRemoteInputBuilder_addAndGetDataResultsFromIntent() throws Throwable {
         Uri uri = Uri.parse("Some Uri");
         RemoteInput input = newDataOnlyRemoteInput();
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index d660040..720ec4a 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,12 +16,15 @@
 
 package android.app.cts;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.stubs.ActivityTestsBase;
+import android.app.stubs.IsolatedService;
+import android.app.stubs.LaunchpadActivity;
 import android.app.stubs.LocalDeniedService;
 import android.app.stubs.LocalForegroundService;
 import android.app.stubs.LocalGrantedService;
@@ -31,21 +34,32 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.ParcelFileDescriptor;
 import android.support.test.InstrumentationRegistry;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.app.stubs.R;
+import android.util.SparseArray;
 
 import com.android.compatibility.common.util.IBinderParcelable;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.server.am.nano.ActivityManagerServiceDumpProcessesProto;
+import com.android.server.am.nano.ProcessRecordProto;
 
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 public class ServiceTest extends ActivityTestsBase {
@@ -65,6 +79,7 @@
     private static final String EXTERNAL_SERVICE_PACKAGE = "com.android.app2";
     private static final String EXTERNAL_SERVICE_COMPONENT =
             EXTERNAL_SERVICE_PACKAGE + "/android.app.stubs.LocalService";
+    private static final String APP_ZYGOTE_PROCESS_NAME = "android.app.stubs_zygote";
     private int mExpectedServiceState;
     private Context mContext;
     private Intent mLocalService;
@@ -73,6 +88,7 @@
     private Intent mLocalGrantedService;
     private Intent mLocalService_ApplicationHasPermission;
     private Intent mLocalService_ApplicationDoesNotHavePermission;
+    private Intent mIsolatedService;
     private Intent mExternalService;
 
     private IBinder mStateReceiver;
@@ -190,6 +206,187 @@
         }
     }
 
+    final class IsolatedConnection implements ServiceConnection {
+        private IBinder mService;
+        private int mUid;
+        private int mPid;
+        private int mPpid;
+
+        public IsolatedConnection() {
+            mUid = mPid = -1;
+        }
+
+        public void waitForService(int timeoutMs) {
+            final long endTime = System.currentTimeMillis() + timeoutMs;
+
+            boolean timeout = false;
+            synchronized (this) {
+                while (mService == null) {
+                    final long delay = endTime - System.currentTimeMillis();
+                    if (delay < 0) {
+                        timeout = true;
+                        break;
+                    }
+
+                    try {
+                        wait(delay);
+                    } catch (final java.lang.InterruptedException e) {
+                        // do nothing
+                    }
+                }
+            }
+
+            if (timeout) {
+                throw new RuntimeException("Timed out waiting for connection");
+            }
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getPid() {
+            return mPid;
+        }
+
+        public int getPpid() {
+            return mPpid;
+        }
+
+        public boolean zygotePreloadCalled() {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            try {
+                mService.transact(LocalService.GET_ZYGOTE_PRELOAD_CALLED, data, reply, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            boolean value = reply.readBoolean();
+            reply.recycle();
+            data.recycle();
+            return value;
+        }
+
+        public void setValue(int value) {
+            Parcel data = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            data.writeInt(value);
+            try {
+                mService.transact(LocalService.SET_VALUE_CODE, data, null, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            data.recycle();
+        }
+
+        public int getValue() {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            try {
+                mService.transact(LocalService.GET_VALUE_CODE, data, reply, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            int value = reply.readInt();
+            reply.recycle();
+            data.recycle();
+            return value;
+        }
+
+        public int getPidIpc() {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            try {
+                mService.transact(LocalService.GET_PID_CODE, data, reply, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            int value = reply.readInt();
+            reply.recycle();
+            data.recycle();
+            return value;
+        }
+
+        public int getPpidIpc() {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            try {
+                mService.transact(LocalService.GET_PPID_CODE, data, reply, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            int value = reply.readInt();
+            reply.recycle();
+            data.recycle();
+            return value;
+        }
+
+        public int getUidIpc() {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(LocalService.SERVICE_LOCAL);
+            try {
+                mService.transact(LocalService.GET_UID_CODE, data, reply, 0);
+            } catch (RemoteException e) {
+                finishBad("DeadObjectException when sending reporting object");
+            }
+            int value = reply.readInt();
+            reply.recycle();
+            data.recycle();
+            return value;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (this) {
+                mService = service;
+                mUid = getUidIpc();
+                mPid = getPidIpc();
+                mPpid = getPpidIpc();
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (this) {
+                mService = null;
+            }
+        }
+    }
+
+    private byte[] executeShellCommand(String cmd) {
+        try {
+            ParcelFileDescriptor pfd =
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                            .executeShellCommand(cmd);
+            byte[] buf = new byte[512];
+            int bytesRead;
+            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+            while ((bytesRead = fis.read(buf)) != -1) {
+                stdout.write(buf, 0, bytesRead);
+            }
+            fis.close();
+            return stdout.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public ActivityManagerServiceDumpProcessesProto getActivityManagerProcesses() {
+        byte[] dump = executeShellCommand("dumpsys activity --proto processes");
+        try {
+            return ActivityManagerServiceDumpProcessesProto.parseFrom(dump);
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new RuntimeException("Failed parsing proto", e);
+        }
+    }
+
     private void startExpectResult(Intent service) {
         startExpectResult(service, new Bundle());
     }
@@ -441,6 +638,7 @@
                 LocalService.SERVICE_LOCAL_GRANTED, null /*uri*/, mContext, LocalService.class);
         mLocalService_ApplicationDoesNotHavePermission = new Intent(
                 LocalService.SERVICE_LOCAL_DENIED, null /*uri*/, mContext, LocalService.class);
+        mIsolatedService = new Intent(mContext, IsolatedService.class);
         mStateReceiver = new MockBinder();
         getNotificationManager().createNotificationChannel(new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
@@ -892,4 +1090,529 @@
             mContext.unbindService(conn2);
         }
     }
+
+    /**
+     * Verify that we can't use bindIsolatedService() on a non-isolated service.
+     */
+    @MediumTest
+    public void testFailBindNonIsolatedService() throws Exception {
+        EmptyConnection conn = new EmptyConnection();
+        try {
+            mContext.bindIsolatedService(mLocalService, conn, 0, "isolated");
+            mContext.unbindService(conn);
+            fail("Didn't get IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // This is expected.
+        }
+    }
+
+    /**
+     * Verify that bindIsolatedService() correctly makes different instances when given
+     * different instance names.
+     */
+    @MediumTest
+    public void testBindIsolatedServiceInstances() throws Exception {
+        IsolatedConnection conn1a = null;
+        IsolatedConnection conn1b = null;
+        IsolatedConnection conn2 = null;
+        try {
+            conn1a = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn1a, Context.BIND_AUTO_CREATE, "1");
+            conn1b = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn1b, Context.BIND_AUTO_CREATE, "1");
+            conn2 = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn2, Context.BIND_AUTO_CREATE, "2");
+
+            conn1a.waitForService(DELAY);
+            conn1b.waitForService(DELAY);
+            conn2.waitForService(DELAY);
+
+            if (conn1a.getPid() != conn1b.getPid()) {
+                fail("Connections to same service name in different pids");
+            }
+            if (conn1a.getPid() == conn2.getPid()) {
+                fail("Connections to different service names in same pids");
+            }
+
+            conn1a.setValue(1);
+            assertEquals(1, conn1a.getValue());
+            assertEquals(1, conn1b.getValue());
+
+            conn2.setValue(2);
+            assertEquals(1, conn1a.getValue());
+            assertEquals(1, conn1b.getValue());
+            assertEquals(2, conn2.getValue());
+
+            conn1b.setValue(3);
+            assertEquals(3, conn1a.getValue());
+            assertEquals(3, conn1b.getValue());
+            assertEquals(2, conn2.getValue());
+        } finally {
+            if (conn2 != null) {
+                mContext.unbindService(conn2);
+            }
+            if (conn1b != null) {
+                mContext.unbindService(conn1b);
+            }
+            if (conn1a != null) {
+                mContext.unbindService(conn1a);
+            }
+        }
+    }
+
+    static final int BINDING_WEAK = 0;
+    static final int BINDING_STRONG = 1;
+    static final int BINDING_ANY = -1;
+
+    final class IsolatedConnectionInfo {
+        final int mStrong;
+        final String mInstanceName;
+        final String mLabel;
+        int mGroup;
+        int mImportance;
+        IsolatedConnection mConnection;
+
+        IsolatedConnectionInfo(int group, int importance, int strong) {
+            mGroup = group;
+            mImportance = importance;
+            mStrong = strong;
+            mInstanceName = group + "_" + importance;
+            StringBuilder b = new StringBuilder(mInstanceName);
+            b.append('_');
+            if (strong == BINDING_WEAK) {
+                b.append('W');
+            } else if (strong == BINDING_STRONG) {
+                b.append('S');
+            } else {
+                b.append(strong);
+            }
+            mLabel = b.toString();
+        }
+
+        void setGroup(int group) {
+            mGroup = group;
+        }
+
+        void setImportance(int importance) {
+            mImportance = importance;
+        }
+
+        boolean match(int group, int strong) {
+            return (group < 0 || mGroup == group)
+                    && (strong == BINDING_ANY || mStrong == strong);
+        }
+
+        boolean bind(Context context) {
+            if (mConnection != null) {
+                return true;
+            }
+            Log.i("XXXXXXX", "Binding " + mLabel + ": conn=" + mConnection
+                    + " context=" + context);
+            mConnection = new IsolatedConnection();
+            boolean result = context.bindIsolatedService(
+                    mIsolatedService, mConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_DEBUG_UNBIND
+                            | (mStrong == BINDING_STRONG ? 0 : Context.BIND_ALLOW_OOM_MANAGEMENT),
+                    mInstanceName);
+            if (!result) {
+                mConnection = null;
+            }
+            return result;
+        }
+
+        IsolatedConnection getConnection() {
+            return mConnection;
+        }
+
+        void unbind(Context context) {
+            if (mConnection != null) {
+                Log.i("XXXXXXX", "Unbinding " + mLabel + ": conn=" + mConnection
+                        + " context=" + context);
+                context.unbindService(mConnection);
+                mConnection = null;
+            }
+        }
+    }
+
+    final class LruOrderItem {
+        static final int FLAG_SKIP_UNKNOWN = 1<<0;
+
+        final IsolatedConnectionInfo mInfo;
+        final int mUid;
+        final int mFlags;
+
+        LruOrderItem(IsolatedConnectionInfo info, int flags) {
+            mInfo = info;
+            mUid = -1;
+            mFlags = flags;
+        }
+
+        LruOrderItem(int uid, int flags) {
+            mInfo = null;
+            mUid = uid;
+            mFlags = flags;
+        }
+
+        IsolatedConnectionInfo getInfo() {
+            return mInfo;
+        }
+
+        int getUid() {
+            return mInfo != null ? mInfo.getConnection().getUid() : mUid;
+        }
+
+        int getFlags() {
+            return mFlags;
+        }
+    }
+
+    private void doBind(Context context, IsolatedConnectionInfo[] connections, int group,
+            int strong) {
+        for (IsolatedConnectionInfo ci : connections) {
+            if (ci.match(group, strong)) {
+                ci.bind(context);
+            }
+        }
+    }
+
+    private void doBind(Context context, IsolatedConnectionInfo[] connections, int[] selected) {
+        for (int i : selected) {
+            boolean result = connections[i].bind(context);
+            if (!result) {
+                fail("Unable to bind connection " + connections[i].mLabel);
+            }
+        }
+    }
+
+    private void doWaitForService(IsolatedConnectionInfo[] connections, int group,
+            int strong) {
+        for (IsolatedConnectionInfo ci : connections) {
+            if (ci.match(group, strong)) {
+                ci.mConnection.waitForService(DELAY);
+            }
+        }
+    }
+
+    private void doUpdateServiceGroup(Context context, IsolatedConnectionInfo[] connections,
+            int group, int strong) {
+        for (IsolatedConnectionInfo ci : connections) {
+            if (ci.match(group, strong)) {
+                context.updateServiceGroup(ci.mConnection, ci.mGroup, ci.mImportance);
+            }
+        }
+    }
+
+    private void doUnbind(Context context, IsolatedConnectionInfo[] connections, int group,
+            int strong) {
+        for (IsolatedConnectionInfo ci : connections) {
+            if (ci.match(group, strong)) {
+                ci.unbind(context);
+            }
+        }
+    }
+
+    private void doUnbind(Context context, IsolatedConnectionInfo[] connections, int[] selected) {
+        for (int i : selected) {
+            connections[i].unbind(context);
+        }
+    }
+
+    List<ProcessRecordProto> getLruProcesses() {
+        ActivityManagerServiceDumpProcessesProto dump = getActivityManagerProcesses();
+        SparseArray<ProcessRecordProto> procs = new SparseArray<>();
+        ProcessRecordProto[] procsList = dump.procs;
+        for (ProcessRecordProto proc : procsList) {
+            procs.put(proc.lruIndex, proc);
+        }
+        ArrayList<ProcessRecordProto> lruProcs = new ArrayList<>();
+        for (int i = 0; i < procs.size(); i++) {
+            lruProcs.add(procs.valueAt(i));
+        }
+        return lruProcs;
+    }
+
+    String printProc(int i, ProcessRecordProto proc) {
+        return "#" + i + ": " + proc.processName
+                + " pid=" + proc.pid + " uid=" + proc.uid
+                + (proc.isolatedAppId != 0 ? " isolated=" + proc.isolatedAppId : "");
+    }
+
+    private void logProc(int i, ProcessRecordProto proc) {
+        Log.i("XXXXXXXX", printProc(i, proc));
+    }
+
+    private void verifyLruOrder(LruOrderItem[] orderItems) {
+        List<ProcessRecordProto> procs = getLruProcesses();
+        Log.i("XXXXXXXX", "Processes:");
+        int orderI = 0;
+        for (int i = procs.size() - 1; i >= 0; i--) {
+            ProcessRecordProto proc = procs.get(i);
+            logProc(i, proc);
+            final LruOrderItem lru = orderItems[orderI];
+            Log.i("XXXXXXXX", "Expecting uid: " + lru.getUid());
+            int procUid = proc.isolatedAppId != 0 ? proc.isolatedAppId : proc.uid;
+            if (procUid != lru.getUid()) {
+                if ((lru.getFlags() & LruOrderItem.FLAG_SKIP_UNKNOWN) != 0) {
+                    while (i > 0) {
+                        i--;
+                        proc = procs.get(i);
+                        logProc(i, proc);
+                        procUid = proc.isolatedAppId != 0 ? proc.isolatedAppId : proc.uid;
+                        if (procUid == lru.getUid()) {
+                            break;
+                        }
+                    }
+                }
+                if (procUid != lru.getUid()) {
+                    if ((lru.getFlags() & LruOrderItem.FLAG_SKIP_UNKNOWN) != 0) {
+                        fail("Didn't find expected LRU proc uid=" + lru.getUid());
+                    }
+                    fail("Expected proc uid=" + lru.getUid() + " at found proc "
+                            + printProc(i, proc));
+                }
+            }
+            orderI++;
+            if (orderI >= orderItems.length) {
+                return;
+            }
+        }
+    }
+
+    @MediumTest
+    public void testAppZygotePreload() throws Exception {
+        IsolatedConnection conn = new IsolatedConnection();
+        try {
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn, Context.BIND_AUTO_CREATE, "1");
+
+            conn.waitForService(DELAY);
+
+            // Verify application preload was done
+            assertTrue(conn.zygotePreloadCalled());
+        } finally {
+            if (conn != null) {
+                mContext.unbindService(conn);
+            }
+        }
+    }
+
+    @MediumTest
+    public void testAppZygoteServices() throws Exception {
+        IsolatedConnection conn1a = null;
+        IsolatedConnection conn1b = null;
+        IsolatedConnection conn2 = null;
+        int appZygotePid;
+        try {
+            conn1a = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn1a, Context.BIND_AUTO_CREATE, "1");
+            conn1b = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn1b, Context.BIND_AUTO_CREATE, "1");
+            conn2 = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn2, Context.BIND_AUTO_CREATE, "2");
+
+            conn1a.waitForService(DELAY);
+            conn1b.waitForService(DELAY);
+            conn2.waitForService(DELAY);
+
+            // Get PPID of each service, and verify they're identical
+            int ppid1a = conn1a.getPpid();
+            int ppid1b = conn1b.getPpid();
+            int ppid2 = conn2.getPpid();
+
+            assertEquals(ppid1a, ppid1b);
+            assertEquals(ppid1b, ppid2);
+            // Find the app zygote process hosting these
+            String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "ps -p " + Integer.toString(ppid1a) + " -o NAME=");
+            result = result.replaceAll("\\s+", "");
+            assertEquals(result, APP_ZYGOTE_PROCESS_NAME);
+            appZygotePid = ppid1a;
+        } finally {
+            if (conn2 != null) {
+                mContext.unbindService(conn2);
+            }
+            if (conn1b != null) {
+                mContext.unbindService(conn1b);
+            }
+            if (conn1a != null) {
+                mContext.unbindService(conn1a);
+            }
+        }
+        // Sleep for 2 seconds and bind a service again, see it uses the same Zygote
+        try {
+            conn1a = new IsolatedConnection();
+            mContext.bindIsolatedService(
+                    mIsolatedService, conn1a, Context.BIND_AUTO_CREATE, "1");
+
+            conn1a.waitForService(DELAY);
+
+            int ppid1a = conn1a.getPpid();
+            assertEquals(appZygotePid, ppid1a);
+        } finally {
+            if (conn1a != null) {
+                mContext.unbindService(conn1a);
+            }
+        }
+        // Sleep for 10 seconds, verify the app_zygote is gone
+        Thread.sleep(10000);
+        String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+            "ps -p " + Integer.toString(appZygotePid) + " -o NAME=");
+        result = result.replaceAll("\\s+", "");
+        assertEquals("", result);
+    }
+
+    /**
+     * Test that the system properly orders processes bound by an activity within the
+     * LRU list.
+     */
+    @MediumTest
+    public void testActivityServiceBindingLru() throws Exception {
+        // Bring up the activity we will hang services off of.
+        runLaunchpad(LaunchpadActivity.ACTIVITY_PREPARE);
+
+        final Activity a = getRunningActivity();
+
+        final int CONN_1_1_W = 0;
+        final int CONN_1_1_S = 1;
+        final int CONN_1_2_W = 2;
+        final int CONN_1_2_S = 3;
+        final int CONN_2_1_W = 4;
+        final int CONN_2_1_S = 5;
+        final int CONN_2_2_W = 6;
+        final int CONN_2_2_S = 7;
+        final int CONN_2_3_W = 8;
+        final int CONN_2_3_S = 9;
+
+        // We are going to have both weak and strong references to services, so we can allow
+        // some to go down in the LRU list.
+        final IsolatedConnectionInfo[] connections = new IsolatedConnectionInfo[] {
+                new IsolatedConnectionInfo(1, 1, BINDING_WEAK),
+                new IsolatedConnectionInfo(1, 1, BINDING_STRONG),
+                new IsolatedConnectionInfo(1, 2, BINDING_WEAK),
+                new IsolatedConnectionInfo(1, 2, BINDING_STRONG),
+                new IsolatedConnectionInfo(2, 1, BINDING_WEAK),
+                new IsolatedConnectionInfo(2, 1, BINDING_STRONG),
+                new IsolatedConnectionInfo(2, 2, BINDING_WEAK),
+                new IsolatedConnectionInfo(2, 2, BINDING_STRONG),
+                new IsolatedConnectionInfo(2, 3, BINDING_WEAK),
+                new IsolatedConnectionInfo(2, 3, BINDING_STRONG),
+        };
+
+        final int[] REV_GROUP_1_STRONG = new int[] {
+                CONN_1_2_S, CONN_1_1_S
+        };
+
+        final int[] REV_GROUP_2_STRONG = new int[] {
+                CONN_2_3_S, CONN_2_2_S, CONN_2_1_S
+        };
+
+        final int[] MIXED_GROUP_3_STRONG = new int[] {
+                CONN_2_3_S, CONN_1_1_S, CONN_2_1_S, CONN_2_2_S
+        };
+
+        boolean passed = false;
+
+        try {
+            // Start the group 1 processes as weak.
+            doBind(a, connections, 1, BINDING_WEAK);
+            doUpdateServiceGroup(a, connections, 1, BINDING_WEAK);
+
+            // Wait for them to come up.
+            doWaitForService(connections, 1, BINDING_WEAK);
+
+            // Now fully bind to the services.
+            doBind(a, connections, 1, BINDING_STRONG);
+            doWaitForService(connections, 1, BINDING_STRONG);
+
+            verifyLruOrder(new LruOrderItem[] {
+                    new LruOrderItem(Process.myUid(), 0),
+                    new LruOrderItem(connections[CONN_1_1_W], 0),
+                    new LruOrderItem(connections[CONN_1_2_W], 0),
+            });
+
+            // Now remove the full binding, leaving only the weak.
+            doUnbind(a, connections, 1, BINDING_STRONG);
+
+            // Start the group 2 processes as weak.
+            doBind(a, connections, 2, BINDING_WEAK);
+
+            // Wait for them to come up.
+            doWaitForService(connections, 2, BINDING_WEAK);
+
+            // Set the group and index.  In this case we do it after we know the process
+            // is started, to make sure setting it directly works.
+            doUpdateServiceGroup(a, connections, 2, BINDING_WEAK);
+
+            // Now fully bind to group 2
+            doBind(a, connections, REV_GROUP_2_STRONG);
+
+            verifyLruOrder(new LruOrderItem[] {
+                    new LruOrderItem(Process.myUid(), 0),
+                    new LruOrderItem(connections[CONN_2_1_W], 0),
+                    new LruOrderItem(connections[CONN_2_2_W], 0),
+                    new LruOrderItem(connections[CONN_2_3_W], 0),
+                    new LruOrderItem(connections[CONN_1_1_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
+                    new LruOrderItem(connections[CONN_1_2_W], 0),
+            });
+
+            // Bring group 1 back to the foreground, but in the opposite order.
+            doBind(a, connections, REV_GROUP_1_STRONG);
+
+            verifyLruOrder(new LruOrderItem[] {
+                    new LruOrderItem(Process.myUid(), 0),
+                    new LruOrderItem(connections[CONN_1_1_W], 0),
+                    new LruOrderItem(connections[CONN_1_2_W], 0),
+                    new LruOrderItem(connections[CONN_2_1_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
+                    new LruOrderItem(connections[CONN_2_2_W], 0),
+                    new LruOrderItem(connections[CONN_2_3_W], 0),
+            });
+
+            // Now remove all full bindings, keeping only weak.
+            doUnbind(a, connections, 1, BINDING_STRONG);
+            doUnbind(a, connections, 2, BINDING_STRONG);
+
+            // Change the grouping and importance to make sure that gets reflected.
+            connections[CONN_1_1_W].setGroup(3);
+            connections[CONN_1_1_W].setImportance(1);
+            connections[CONN_2_1_W].setGroup(3);
+            connections[CONN_2_1_W].setImportance(2);
+            connections[CONN_2_2_W].setGroup(3);
+            connections[CONN_2_2_W].setImportance(3);
+            connections[CONN_2_3_W].setGroup(3);
+            connections[CONN_2_3_W].setImportance(4);
+
+            doUpdateServiceGroup(a, connections, 3, BINDING_WEAK);
+
+            // Now bind them back up in an interesting order.
+            doBind(a, connections, MIXED_GROUP_3_STRONG);
+
+            verifyLruOrder(new LruOrderItem[] {
+                    new LruOrderItem(Process.myUid(), 0),
+                    new LruOrderItem(connections[CONN_1_1_W], 0),
+                    new LruOrderItem(connections[CONN_2_1_W], 0),
+                    new LruOrderItem(connections[CONN_2_2_W], 0),
+                    new LruOrderItem(connections[CONN_2_3_W], 0),
+                    new LruOrderItem(connections[CONN_1_2_W], LruOrderItem.FLAG_SKIP_UNKNOWN),
+            });
+
+            passed = true;
+
+        } finally {
+            if (!passed) {
+                List<ProcessRecordProto> procs = getLruProcesses();
+                Log.i("XXXXXXXX", "Processes:");
+                for (int i = procs.size() - 1; i >= 0; i--) {
+                    ProcessRecordProto proc = procs.get(i);
+                    logProc(i, proc);
+                }
+            }
+            doUnbind(a, connections, -1, BINDING_ANY);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 270fc38..3109a0e 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -44,6 +44,7 @@
 import android.test.InstrumentationTestCase;
 
 import com.android.compatibility.common.util.PropertyUtil;
+import com.android.compatibility.common.util.SystemUtil;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -539,10 +540,12 @@
         boolean enabled = mWifiManager.isWifiEnabled();
         try {
             // assert wifimanager can toggle wifi from current sate
-            assertTrue(mWifiManager.setWifiEnabled(!enabled));
+            SystemUtil.runShellCommand("svc wifi " + (!enabled ? "enable" : "disable"));
+            Thread.sleep(5_000); // wait for the toggle to take effect.
+            assertEquals(!enabled, mWifiManager.isWifiEnabled());
 
         } finally {
-            mWifiManager.setWifiEnabled(enabled);
+            SystemUtil.runShellCommand("svc wifi " + (enabled ? "enable" : "disable"));
         }
     }
 
diff --git a/tests/app/src/android/app/cts/TaskDescriptionTest.java b/tests/app/src/android/app/cts/TaskDescriptionTest.java
index 408930e..fa35f16 100644
--- a/tests/app/src/android/app/cts/TaskDescriptionTest.java
+++ b/tests/app/src/android/app/cts/TaskDescriptionTest.java
@@ -27,8 +27,10 @@
 import android.app.ActivityManager.TaskDescription;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import java.util.List;
+import java.util.function.BooleanSupplier;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -42,8 +44,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 /**
- * Build: mmma -j32 cts/tests/app
- * Run: cts/tests/framework/base/activitymanager/util/run-test CtsAppTestCases android.app.cts.TaskDescriptionTest
+ * Build & Run: atest android.app.cts.TaskDescriptionTest
  */
 @RunWith(AndroidJUnit4.class)
 @Presubmit
@@ -52,6 +53,8 @@
     private static final int TEST_NO_DATA = 0;
     private static final int TEST_RES_DATA = 777;
     private static final int TEST_COLOR = Color.BLACK;
+    private static final int WAIT_TIMEOUT_MS = 1000;
+    private static final int WAIT_RETRIES = 5;
 
     @Rule
     public ActivityTestRule<MockActivity> mTaskDescriptionActivity =
@@ -83,12 +86,17 @@
                 final TaskDescription td = info.taskDescription;
                 assertNotNull(td);
                 if (resId == TEST_NO_DATA) {
-                    assertNotNull(td.getIcon());
-                    assertNotNull(td.getIconFilename());
+                    // TaskPersister at the worst case scenario waits 3 secs (PRE_TASK_DELAY_MS) to
+                    // write the image to disk if its write time has ended
+                    waitFor("TaskDescription's icon is null", () -> td.getIcon() != null);
+                    waitFor("TaskDescription's icon filename is null",
+                            () -> td.getIconFilename() != null);
                 } else {
-                    assertNull(td.getIconFilename());
-                    assertNull(td.getIcon());
+                    waitFor("TaskDescription's icon is not null", () -> td.getIcon() == null);
+                    waitFor("TaskDescription's icon filename is not null",
+                            () -> td.getIconFilename() == null);
                 }
+
                 assertEquals(resId, td.getIconResource());
                 assertEquals(label, td.getLabel());
                 return;
@@ -96,4 +104,14 @@
         }
         fail("Did not find activity (id=" + activity.getTaskId() + ") in recent tasks list");
     }
+
+    private void waitFor(String message, BooleanSupplier waitCondition) {
+        for (int retry = 0; retry < WAIT_RETRIES; retry++) {
+            if (waitCondition.getAsBoolean()) {
+                return;
+            }
+            SystemClock.sleep(WAIT_TIMEOUT_MS);
+        }
+        fail(message);
+    }
 }
diff --git a/tests/app/src/android/app/cts/TestContextTest.java b/tests/app/src/android/app/cts/TestContextTest.java
new file mode 100644
index 0000000..c2a6f50
--- /dev/null
+++ b/tests/app/src/android/app/cts/TestContextTest.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.cts;
+
+import android.app.Instrumentation;
+import android.provider.Settings.Global;
+import android.test.InstrumentationTestCase;
+
+public class TestContextTest extends InstrumentationTestCase {
+    public void testTestOpPackageName() {
+        Instrumentation instrumentation = getInstrumentation();
+        assertEquals("android.app.cts", instrumentation.getContext().getPackageName());
+
+        // getOpPackageName() should return the target app's package name.
+        assertEquals("android.app.stubs", instrumentation.getContext().getOpPackageName());
+
+        instrumentation.getContext().getContentResolver().query(
+                Global.CONTENT_URI, null, null, null, null);
+    }
+}
diff --git a/tests/app/src/android/app/cts/WallpaperInfoTest.java b/tests/app/src/android/app/cts/WallpaperInfoTest.java
index 1b30902..90a303c 100644
--- a/tests/app/src/android/app/cts/WallpaperInfoTest.java
+++ b/tests/app/src/android/app/cts/WallpaperInfoTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import android.app.WallpaperInfo;
+import android.app.stubs.R;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -46,12 +47,19 @@
         assertEquals(1, result.size());
         ResolveInfo info = result.get(0);
         WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info);
-        assertEquals("Title", wallpaperInfo.loadLabel(pm));
-        assertEquals("Description", wallpaperInfo.loadDescription(pm));
-        assertEquals("Collection", wallpaperInfo.loadAuthor(pm));
-        assertEquals("Context", wallpaperInfo.loadContextDescription(pm));
-        assertEquals("http://android.com", wallpaperInfo.loadContextUri(pm).toString());
+        assertEquals(context.getString(R.string.wallpaper_title), wallpaperInfo.loadLabel(pm));
+        assertEquals(context.getString(R.string.wallpaper_description),
+            wallpaperInfo.loadDescription(pm));
+        assertEquals(context.getString(R.string.wallpaper_collection),
+            wallpaperInfo.loadAuthor(pm));
+        assertEquals(context.getString(R.string.wallpaper_context),
+            wallpaperInfo.loadContextDescription(pm));
+        assertEquals(context.getString(R.string.wallpaper_context_uri),
+            wallpaperInfo.loadContextUri(pm).toString());
+        assertEquals(context.getString(R.string.wallpaper_slice_uri),
+            wallpaperInfo.getSettingsSliceUri().toString());
         assertEquals(true, wallpaperInfo.getShowMetadataInPreview());
+        assertEquals(true, wallpaperInfo.supportsMultipleDisplays());
         assertNotNull(wallpaperInfo.loadIcon(pm));
         assertNotNull(wallpaperInfo.loadThumbnail(pm));
     }
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 139e7c1..cc1d479 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -226,7 +226,8 @@
      * Suggesting desired dimensions is only a hint to the system that can be ignored.
      *
      * Test if the desired minimum width or height the WallpaperManager returns
-     * is greater than 0.
+     * is greater than 0. If so, then we check whether that the size is the dimension
+     * that was suggested.
      */
     @Test
     public void suggestDesiredDimensionsTest() {
@@ -246,10 +247,12 @@
         mWallpaperManager.suggestDesiredDimensions(suggestedSize.x, suggestedSize.y);
         Point actualSize = new Point(mWallpaperManager.getDesiredMinimumWidth(),
                 mWallpaperManager.getDesiredMinimumHeight());
-        if (actualSize.x <= 0 || actualSize.y <= 0) {
-            throw new AssertionError("Expected x: " + expectedSize.x + " y: "
-                    + expectedSize.y + ", got x: " + actualSize.x +
-                    " y: " + actualSize.y);
+        if (actualSize.x > 0 || actualSize.y > 0) {
+            if ((actualSize.x != expectedSize.x || actualSize.y != expectedSize.y)) {
+                throw new AssertionError("Expected x: " + expectedSize.x + " y: "
+                        + expectedSize.y + ", got x: " + actualSize.x +
+                        " y: " + actualSize.y);
+            }
         }
     }
 
diff --git a/tests/app/src/android/app/cts/ZenPolicyTest.java b/tests/app/src/android/app/cts/ZenPolicyTest.java
new file mode 100644
index 0000000..1b3ffa6
--- /dev/null
+++ b/tests/app/src/android/app/cts/ZenPolicyTest.java
@@ -0,0 +1,456 @@
+/*
+ * 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.app.cts;
+
+import android.app.NotificationManager;
+import android.os.Parcel;
+import android.service.notification.ZenPolicy;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class ZenPolicyTest extends Assert {
+
+    @Test
+    public void testWriteToParcel() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
+                .showInNotificationList(false);
+        ZenPolicy policy = builder.build();
+
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        ZenPolicy policyFromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
+        assertEquals(policy, policyFromParcel);
+    }
+
+    @Test
+    public void testAlarms() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowAlarms(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryAlarms());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowAlarms(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryAlarms());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testMedia() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowMedia(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMedia());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
+        assertAllVisualEffectsUnsetExcept(policy, -1);
+
+        policy = builder.allowMedia(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMedia());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testSystem() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowSystem(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategorySystem());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowSystem(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategorySystem());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testReminders() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowReminders(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryReminders());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowReminders(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryReminders());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testEvents() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowEvents(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryEvents());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS);
+        assertAllVisualEffectsUnset(policy);
+
+        builder.allowEvents(false);
+        policy = builder.build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryEvents());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testRepeatCallers() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowRepeatCallers(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryRepeatCallers());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowRepeatCallers(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryRepeatCallers());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testMessages() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowMessages(ZenPolicy.PEOPLE_TYPE_NONE).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMessages());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowMessages(ZenPolicy.PEOPLE_TYPE_ANYONE).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowMessages(ZenPolicy.PEOPLE_TYPE_CONTACTS).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testCalls() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryCalls());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_CALLS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowCalls(ZenPolicy.PEOPLE_TYPE_ANYONE).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_CALLS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_CALLS);
+        assertAllVisualEffectsUnset(policy);
+
+        policy = builder.allowCalls(ZenPolicy.PEOPLE_TYPE_STARRED).build();
+        assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
+        assertAllPriorityCategoriesUnsetExcept(policy,
+                NotificationManager.Policy.PRIORITY_CATEGORY_CALLS);
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testFullScreenIntent() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showFullScreenIntent(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectFullScreenIntent());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+
+        policy = builder.showFullScreenIntent(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectFullScreenIntent());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+    }
+
+    @Test
+    public void testLights() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showLights(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectLights());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS);
+
+        policy = builder.showLights(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectLights());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS);
+    }
+
+    @Test
+    public void testPeek() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showPeeking(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectPeek());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK);
+
+        policy = builder.showPeeking(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectPeek());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK);
+    }
+
+    @Test
+    public void testStatusBar() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showStatusBarIcons(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectStatusBar());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR);
+
+        policy = builder.showStatusBarIcons(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectStatusBar());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR);
+    }
+
+    @Test
+    public void testBadge() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showBadges(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectBadge());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE);
+
+        policy = builder.showBadges(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectBadge());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE);
+    }
+
+    @Test
+    public void testAmbient() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showInAmbientDisplay(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectAmbient());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT);
+
+        policy = builder.showInAmbientDisplay(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectAmbient());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT);
+    }
+
+    @Test
+    public void testNotificationList() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showInNotificationList(true).build();
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectNotificationList());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+
+        policy = builder.showInNotificationList(false).build();
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectNotificationList());
+        assertAllPriorityCategoriesUnset(policy);
+        assertAllVisualEffectsUnsetExcept(policy,
+                NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+    }
+
+    @Test
+    public void testDisallowAllSounds() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.disallowAllSounds().build();
+
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryAlarms());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMedia());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategorySystem());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryReminders());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryEvents());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMessages());
+        assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryCalls());
+        assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryRepeatCallers());
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testAllowAllSounds() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.allowAllSounds().build();
+
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryAlarms());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMedia());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategorySystem());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryReminders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryEvents());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages());
+        assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityMessageSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls());
+        assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryRepeatCallers());
+        assertAllVisualEffectsUnset(policy);
+    }
+
+    @Test
+    public void testAllowAllVisualEffects() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.showAllVisualEffects().build();
+
+        assertAllPriorityCategoriesUnset(policy);
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectFullScreenIntent());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectLights());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectPeek());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectStatusBar());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectBadge());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectAmbient());
+        assertEquals(ZenPolicy.STATE_ALLOW, policy.getVisualEffectNotificationList());
+    }
+
+    @Test
+    public void testDisallowAllVisualEffects() {
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.hideAllVisualEffects().build();
+
+        assertAllPriorityCategoriesUnset(policy);
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectFullScreenIntent());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectLights());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectPeek());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectStatusBar());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectBadge());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectAmbient());
+        assertEquals(ZenPolicy.STATE_DISALLOW, policy.getVisualEffectNotificationList());
+    }
+
+    private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) {
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryReminders());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryEvents());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryMessages());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryCalls());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryRepeatCallers());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryAlarms());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryMedia());
+        }
+
+        if (except != NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategorySystem());
+        }
+    }
+
+    private void assertAllVisualEffectsUnsetExcept(ZenPolicy policy, int except) {
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectFullScreenIntent());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectLights());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectPeek());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectStatusBar());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectBadge());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectAmbient());
+        }
+
+        if (except != NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) {
+            assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectNotificationList());
+        }
+    }
+
+    private void assertAllPriorityCategoriesUnset(ZenPolicy policy) {
+        assertAllPriorityCategoriesUnsetExcept(policy, -1);
+    }
+
+    private void assertAllVisualEffectsUnset(ZenPolicy policy) {
+        assertAllVisualEffectsUnsetExcept(policy, -1);
+    }
+}
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 4daf78b..09c28e7 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
@@ -52,6 +52,7 @@
     public static final String STATE_PERSISTENT_UI = "PERU";
     public static final String STATE_TOP = "TOP";
     public static final String STATE_BOUND_FG_SERVICE = "BFGS";
+    public static final String STATE_FG_SERVICE_LOCATION = "FGSL";
     public static final String STATE_FG_SERVICE = "FGS";
     public static final String STATE_TOP_SLEEPING = "TPSL";
     public static final String STATE_IMPORTANT_FG = "IMPF";
diff --git a/tests/apppredictionservice/Android.mk b/tests/apppredictionservice/Android.mk
new file mode 100644
index 0000000..c30a677
--- /dev/null
+++ b/tests/apppredictionservice/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    androidx.annotation_annotation \
+    compatibility-device-util \
+    ctstestrunner \
+    truth-prebuilt
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppPredictionServiceTestCases
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/apppredictionservice/AndroidManifest.xml b/tests/apppredictionservice/AndroidManifest.xml
new file mode 100644
index 0000000..7da57c06
--- /dev/null
+++ b/tests/apppredictionservice/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.apppredictionservice.cts"
+    android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <!-- TODO(b/111701043): Update with required permissions -->
+        <service
+            android:name=".PredictionService"
+            android:label="CtsAppPredictionService">
+            <intent-filter>
+                <!-- This constant must match AppPredictionService.SERVICE_INTERFACE -->
+                <action android:name="android.service.appprediction.AppPredictionService" />
+            </intent-filter>
+        </service>
+
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for the App Prediction Framework APIs."
+        android:targetPackage="android.apppredictionservice.cts" >
+    </instrumentation>
+
+</manifest>
diff --git a/tests/apppredictionservice/AndroidTest.xml b/tests/apppredictionservice/AndroidTest.xml
new file mode 100644
index 0000000..660955c
--- /dev/null
+++ b/tests/apppredictionservice/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 App Prediction CTS tests.">
+  <option name="test-suite-tag" value="cts" />
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsAppPredictionServiceTestCases.apk" />
+  </target_preparer>
+
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.apppredictionservice.cts" />
+    <!-- 20x default timeout of 600sec -->
+    <option name="shell-timeout" value="12000000"/>
+  </test>
+
+</configuration>
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
new file mode 100644
index 0000000..4cf9741
--- /dev/null
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.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 android.apppredictionservice.cts;
+
+import static android.apppredictionservice.cts.PredictionService.EXTRA_REPORTER;
+
+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.Assert.fail;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.apppredictionservice.cts.ServiceReporter.RequestVerifier;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * atest CtsAppPredictionServiceTestCases:AppPredictionServiceTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppPredictionServiceTest {
+
+    private static final String TAG = "AppPredictionServiceTest";
+
+    private static final String APP_PREDICTION_SERVICE = "app_prediction";
+
+    private static final String TEST_UI_SURFACE = "testSysUiSurface";
+    private static final int TEST_NUM_PREDICTIONS = 10;
+    private static final String TEST_LAUNCH_LOCATION = "testCollapsedLocation";
+    private static final int TEST_ACTION = 2;
+
+    private ServiceReporter mReporter;
+    private Bundle mPredictionContextExtras;
+
+    @Before
+    public void setUp() throws Exception {
+        // Enable the prediction service
+        setService(PredictionService.SERVICE_NAME);
+        mReporter = new ServiceReporter();
+        mPredictionContextExtras = new Bundle();
+        mPredictionContextExtras.putBinder(EXTRA_REPORTER, mReporter);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Reset the prediction service
+        setService(null);
+        mReporter = null;
+        SystemClock.sleep(1000);
+    }
+
+    @Test
+    public void testCreateDestroySession() {
+        AppPredictionContext context =  createTestPredictionContext();
+        AppPredictor client = createTestPredictor(context);
+
+        // Wait for the service to bind and the session to be created
+        mReporter.awaitOnCreatePredictionSession();
+        mReporter.assertActiveSession(client.getSessionId());
+        assertEquals(mReporter.getPredictionContext(client.getSessionId()), context);
+
+        // Create another session, and ensure that the session ids differ
+        AppPredictionContext context2 =  createTestPredictionContext();
+        AppPredictor client2 = createTestPredictor(context2);
+
+        mReporter.awaitOnCreatePredictionSession();
+        mReporter.assertActiveSession(client2.getSessionId());
+        assertEquals(mReporter.getPredictionContext(client2.getSessionId()), context2);
+        assertNotEquals(client.getSessionId(), client2.getSessionId());
+
+        // Destroy both sessions
+        client.destroy();
+        mReporter.awaitOnDestroyPredictionSession();
+        client2.destroy();
+        mReporter.awaitOnDestroyPredictionSession();
+
+        // Ensure that the session are no longer active
+        assertFails(() -> mReporter.assertActiveSession(client.getSessionId()));
+        assertFails(() -> mReporter.assertActiveSession(client2.getSessionId()));
+
+        // Ensure that future calls to the client fail
+        assertFails(() -> client.notifyAppTargetEvent(null));
+        assertFails(() -> client.notifyLocationShown(null, null));
+        assertFails(() -> client.registerPredictionUpdates(null, null));
+        assertFails(() -> client.unregisterPredictionUpdates(null));
+        assertFails(() -> client.requestPredictionUpdate());
+        assertFails(() -> client.sortTargets(null, null, null));
+        assertFails(() -> client.destroy());
+    }
+
+    @Test
+    public void testRegisterPredictionUpdatesLifecycle() {
+        AppPredictionContext context = createTestPredictionContext();
+        AppPredictor client = createTestPredictor(context);
+
+        RequestVerifier cb = new RequestVerifier(mReporter);
+        client.registerPredictionUpdates(Executors.newSingleThreadExecutor(), cb);
+
+        // Verify some updates
+        assertTrue(cb.requestAndWaitForTargets(createPredictions(),
+                () -> client.requestPredictionUpdate()));
+        assertTrue(cb.requestAndWaitForTargets(createPredictions(),
+                () -> client.requestPredictionUpdate()));
+        assertTrue(cb.requestAndWaitForTargets(createPredictions(),
+                () -> client.requestPredictionUpdate()));
+
+        client.unregisterPredictionUpdates(cb);
+
+        // Ensure we don't get updates after the listeners are unregistered
+        assertFalse(cb.requestAndWaitForTargets(createPredictions(),
+                () -> client.requestPredictionUpdate()));
+    }
+
+    @Test
+    public void testAppTargetEvent() {
+        AppPredictionContext context = createTestPredictionContext();
+        AppPredictor client = createTestPredictor(context);
+
+        List<AppTarget> targets = createPredictions();
+        List<AppTargetEvent> events = new ArrayList<>();
+        for (AppTarget target : targets) {
+            AppTargetEvent event = new AppTargetEvent.Builder(target, TEST_ACTION)
+                    .setLaunchLocation(TEST_LAUNCH_LOCATION)
+                    .build();
+            events.add(event);
+            client.notifyAppTargetEvent(event);
+            mReporter.awaitOnAppTargetEvent();
+        }
+        assertEquals(mReporter.mEvents, events);
+    }
+
+    @Test
+    public void testNotifyLocationShown() {
+        AppPredictionContext context = createTestPredictionContext();
+        AppPredictor client = createTestPredictor(context);
+
+        List<AppTarget> targets = createPredictions();
+        List<AppTargetId> targetIds = new ArrayList<>();
+        for (AppTarget target : targets) {
+            AppTargetId id = target.getId();
+            targetIds.add(id);
+        }
+        client.notifyLocationShown(TEST_LAUNCH_LOCATION, targetIds);
+        mReporter.awaitOnLocationShown();
+        assertEquals(mReporter.mLocationsShown, TEST_LAUNCH_LOCATION);
+        assertEquals(mReporter.mLocationsShownTargets, targetIds);
+    }
+
+    @Test
+    public void testSortTargets() {
+        AppPredictionContext context = createTestPredictionContext();
+        AppPredictor client = createTestPredictor(context);
+
+        List<AppTarget> sortedTargets = createPredictions();
+        List<AppTarget> shuffledTargets = new ArrayList<>(sortedTargets);
+        Collections.shuffle(shuffledTargets);
+
+        // We call sortTargets below with the shuffled targets, ensure that the service receives the
+        // shuffled targets, and return the sorted targets to the RequestVerifier below
+        mReporter.setSortedPredictionsProvider((targets) -> {
+            assertEquals(targets, shuffledTargets);
+            return sortedTargets;
+        });
+        RequestVerifier cb = new RequestVerifier(mReporter);
+        assertTrue(cb.requestAndWaitForTargets(sortedTargets,
+                () -> client.sortTargets(shuffledTargets,
+                        Executors.newSingleThreadExecutor(), cb)));
+    }
+
+    private void assertFails(Runnable r) {
+        try {
+            r.run();
+        } catch (Exception|Error e) {
+            // Expected failure
+            return;
+        }
+        fail("Expected failure");
+    }
+
+    /** Creates a random number of targets by increasing id */
+    private List<AppTarget> createPredictions() {
+        List<AppTarget> targets = new ArrayList<>();
+        int n = (int) (Math.random() * 20);
+        for (int i = 0; i < n; i++) {
+            targets.add(new AppTarget(new AppTargetId(String.valueOf(i)), "test.pkg",
+                    "test.class." + i, UserHandle.CURRENT));
+        }
+        return targets;
+    }
+
+    private AppPredictionContext createTestPredictionContext() {
+        return new AppPredictionContext.Builder(InstrumentationRegistry.getTargetContext())
+                .setExtras(mPredictionContextExtras)
+                .setUiSurface(TEST_UI_SURFACE)
+                .setPredictedTargetCount(TEST_NUM_PREDICTIONS)
+                .build();
+    }
+
+    private AppPredictor createTestPredictor(AppPredictionContext context) {
+        Context ctx = InstrumentationRegistry.getTargetContext();
+        AppPredictionManager mgr = (AppPredictionManager) ctx.getSystemService(
+                APP_PREDICTION_SERVICE);
+        return mgr.createAppPredictionSession(context);
+    }
+
+    private void setService(String service) {
+        Log.d(TAG, "Setting app prediction service to " + service);
+        if (service != null) {
+            runShellCommand("cmd app_prediction set temporary-service 0 " + service + " 12000");
+        } else {
+            runShellCommand("cmd app_prediction set temporary-service 0");
+        }
+    }
+
+    private void runShellCommand(String command) {
+        Log.d(TAG, "runShellCommand(): " + command);
+        try {
+            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+        } catch (Exception e) {
+            throw new RuntimeException("Command '" + command + "' failed: ", e);
+        }
+    }
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionService.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionService.java
new file mode 100644
index 0000000..f65910c
--- /dev/null
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionService.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 android.apppredictionservice.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.os.CancellationSignal;
+import android.app.prediction.AppPredictionContext;
+import android.service.appprediction.AppPredictionService;
+import android.app.prediction.AppTarget;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+public class PredictionService extends AppPredictionService {
+
+    private static final String TAG = PredictionService.class.getSimpleName();
+
+    public static final String MY_PACKAGE = "android.apppredictionservice.cts";
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + PredictionService.class.getSimpleName();
+
+    public static final String EXTRA_REPORTER = "extra_reporter";
+
+    private ServiceReporter mReporter;
+
+    @Override
+    public void onCreatePredictionSession(AppPredictionContext context,
+            AppPredictionSessionId sessionId) {
+        mReporter = (ServiceReporter) context.getExtras().getBinder(EXTRA_REPORTER);
+        mReporter.onCreatePredictionSession(context, sessionId);
+    }
+
+    @Override
+    public void onAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+        mReporter.onAppTargetEvent(sessionId, event);
+    }
+
+    @Override
+    public void onLocationShown(AppPredictionSessionId sessionId, String launchLocation,
+            List<AppTargetId> targetIds) {
+        mReporter.onLocationShown(sessionId, launchLocation, targetIds);
+    }
+
+    @Override
+    public void onSortAppTargets(AppPredictionSessionId sessionId, List<AppTarget> targets,
+            CancellationSignal cancellationSignal, Consumer<List<AppTarget>> callback) {
+        mReporter.onSortAppTargets(sessionId, targets, callback);
+        callback.accept(mReporter.getSortedPredictionsProvider().sortTargets(targets));
+    }
+
+    @Override
+    public void onRequestPredictionUpdate(AppPredictionSessionId sessionId) {
+        mReporter.onRequestPredictionUpdate(sessionId);
+        updatePredictions(sessionId, mReporter.getPredictionsProvider().getTargets(sessionId));
+    }
+
+    @Override
+    public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+        mReporter.onDestroyPredictionSession(sessionId);
+    }
+
+    @Override
+    public void onStartPredictionUpdates() {
+        mReporter.onStartPredictionUpdates();
+    }
+
+    @Override
+    public void onStopPredictionUpdates() {
+        mReporter.onStopPredictionUpdates();
+    }
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionsProvider.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionsProvider.java
new file mode 100644
index 0000000..811b52e
--- /dev/null
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/PredictionsProvider.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.apppredictionservice.cts;
+
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+
+import java.util.List;
+
+/**
+ * Implemented by the tests to provide predictions to be reported by the CTS prediction service to
+ * the prediction callbacks.
+ */
+public interface PredictionsProvider {
+    List<AppTarget> getTargets(AppPredictionSessionId sessionId);
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/ServiceReporter.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/ServiceReporter.java
new file mode 100644
index 0000000..0f1ff81
--- /dev/null
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/ServiceReporter.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.apppredictionservice.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 android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.os.Binder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Reports calls from the CTS prediction service back to the tests.
+ */
+public class ServiceReporter extends Binder {
+
+    public HashMap<AppPredictionSessionId, AppPredictionContext> mSessions = new HashMap<>();
+
+    public ArrayList<AppTargetEvent> mEvents = new ArrayList<>();
+    public String mLocationsShown;
+    public ArrayList<AppTargetId> mLocationsShownTargets = new ArrayList<>();
+    public int mNumRequestedUpdates = 0;
+    public boolean mPredictionUpdatesStarted = false;
+
+    private CountDownLatch mCreateSessionLatch = new CountDownLatch(1);
+    private CountDownLatch mEventLatch = new CountDownLatch(1);
+    private CountDownLatch mLocationShownLatch = new CountDownLatch(1);
+    private CountDownLatch mSortLatch = new CountDownLatch(1);
+    private CountDownLatch mStartPredictionUpdatesLatch = new CountDownLatch(1);
+    private CountDownLatch mStopPredictionUpdatesLatch = new CountDownLatch(1);
+    private CountDownLatch mPredictionUpdateLatch = new CountDownLatch(1);
+    private CountDownLatch mDestroyLatch = new CountDownLatch(1);
+
+    private PredictionsProvider mPredictionsProvider;
+    private SortedPredictionsProvider mSortedPredictionsProvider;
+
+    void setPredictionsProvider(PredictionsProvider cb) {
+        mPredictionsProvider = cb;
+    }
+
+    PredictionsProvider getPredictionsProvider() {
+        return mPredictionsProvider;
+    }
+
+    void setSortedPredictionsProvider(SortedPredictionsProvider cb) {
+        mSortedPredictionsProvider = cb;
+    }
+
+    SortedPredictionsProvider getSortedPredictionsProvider() {
+        return mSortedPredictionsProvider;
+    }
+
+    void assertActiveSession(AppPredictionSessionId sessionId) {
+        assertTrue(mSessions.containsKey(sessionId));
+    }
+
+    AppPredictionContext getPredictionContext(AppPredictionSessionId sessionId) {
+        assertTrue(mSessions.containsKey(sessionId));
+        return mSessions.get(sessionId);
+    }
+
+    void onCreatePredictionSession(AppPredictionContext context,
+            AppPredictionSessionId sessionId) {
+        assertNotNull(context);
+        assertNotNull(sessionId);
+        assertFalse(mSessions.containsKey(sessionId));
+        mSessions.put(sessionId, context);
+        mCreateSessionLatch.countDown();
+    }
+
+    boolean awaitOnCreatePredictionSession() {
+        try {
+            return await(mCreateSessionLatch);
+        } finally {
+            mCreateSessionLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+        assertTrue(mSessions.containsKey(sessionId));
+        mEvents.add(event);
+        mEventLatch.countDown();
+    }
+
+    boolean awaitOnAppTargetEvent() {
+        try {
+            return await(mEventLatch);
+        } finally {
+            mEventLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onLocationShown(AppPredictionSessionId sessionId, String launchLocation,
+            List<AppTargetId> targetIds) {
+        assertTrue(mSessions.containsKey(sessionId));
+        mLocationsShown = launchLocation;
+        mLocationsShownTargets.addAll(targetIds);
+        mLocationShownLatch.countDown();
+    }
+
+    boolean awaitOnLocationShown() {
+        try {
+            return await(mLocationShownLatch);
+        } finally {
+            mLocationShownLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onSortAppTargets(AppPredictionSessionId sessionId, List<AppTarget> targets,
+            Consumer<List<AppTarget>> callback) {
+        assertTrue(mSessions.containsKey(sessionId));
+        assertNotNull(targets);
+        assertNotNull(callback);
+        mSortLatch.countDown();
+    }
+
+    boolean awaitOnSortAppTargets() {
+        try {
+            return await(mSortLatch);
+        } finally {
+            mSortLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onStartPredictionUpdates() {
+        mPredictionUpdatesStarted = true;
+    }
+
+    boolean awaitOnStartPredictionUpdates() {
+        try {
+            return await(mStartPredictionUpdatesLatch);
+        } finally {
+            mStartPredictionUpdatesLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onStopPredictionUpdates() {
+        mPredictionUpdatesStarted = false;
+    }
+
+    boolean awaitOnStopPredictionUpdates() {
+        try {
+            return await(mStopPredictionUpdatesLatch);
+        } finally {
+            mStopPredictionUpdatesLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onRequestPredictionUpdate(AppPredictionSessionId sessionId) {
+        assertTrue(mSessions.containsKey(sessionId));
+        mNumRequestedUpdates++;
+        mPredictionUpdateLatch.countDown();
+    }
+
+    boolean awaitOnRequestPredictionUpdate() {
+        try {
+            return await(mPredictionUpdateLatch);
+        } finally {
+            mPredictionUpdateLatch = new CountDownLatch(1);
+        }
+    }
+
+    void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+        assertTrue(mSessions.containsKey(sessionId));
+        mSessions.remove(sessionId);
+        mDestroyLatch.countDown();
+    }
+
+    boolean awaitOnDestroyPredictionSession() {
+        try {
+            return await(mDestroyLatch);
+        } finally {
+            mDestroyLatch = new CountDownLatch(1);
+        }
+    }
+
+    public class Event {
+        final AppTarget target;
+        final int launchLocation;
+        final int eventType;
+
+        public Event(AppTarget target, int launchLocation, int eventType) {
+            this.target = target;
+            this.launchLocation = launchLocation;
+            this.eventType = eventType;
+        }
+    }
+
+    private boolean await(CountDownLatch latch) {
+        try {
+            latch.await(500, TimeUnit.MILLISECONDS);
+            return true;
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
+    public static class RequestVerifier implements AppPredictor.Callback, PredictionsProvider,
+            Consumer<List<AppTarget>> {
+
+        private ServiceReporter mReporter;
+        private CountDownLatch mReceivedLatch;
+        private List<AppTarget> mTargets;
+
+        public RequestVerifier(ServiceReporter reporter) {
+            mReporter = reporter;
+            mReceivedLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public List<AppTarget> getTargets(AppPredictionSessionId sessionId) {
+            return mTargets;
+        }
+
+        @Override
+        public void onTargetsAvailable(List<AppTarget> targets) {
+            if (mTargets != null) {
+                // Verify that the targets match
+                assertEquals(targets, mTargets);
+            } else {
+                // For the case where we didn't setup the request, save the targets so we can verify
+                // them in awaitTargets()
+                mTargets = targets;
+            }
+            mReceivedLatch.countDown();
+        }
+
+        @Override
+        public void accept(List<AppTarget> appTargets) {
+            onTargetsAvailable(appTargets);
+        }
+
+        /**
+         * @param requestUpdateCb Callback called when the request is setup
+         */
+        boolean requestAndWaitForTargets(List<AppTarget> targets, Runnable requestUpdateCb) {
+            mTargets = targets;
+            mReceivedLatch = new CountDownLatch(1);
+            mReporter.setPredictionsProvider(this);
+            requestUpdateCb.run();
+            try {
+                return awaitTargets(targets);
+            } finally {
+                mReporter.setPredictionsProvider(null);
+            }
+        }
+
+        boolean awaitTargets(List<AppTarget> targets) {
+            try {
+                boolean result = mReceivedLatch.await(500, TimeUnit.MILLISECONDS);
+                assertEquals(targets, mTargets);
+                return result;
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/SortedPredictionsProvider.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/SortedPredictionsProvider.java
new file mode 100644
index 0000000..939506f
--- /dev/null
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/SortedPredictionsProvider.java
@@ -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.
+ */
+package android.apppredictionservice.cts;
+
+import android.app.prediction.AppTarget;
+
+import java.util.List;
+
+/**
+ * Implemented by the tests to sort predictions to be reported by the CTS prediction service to
+ * the prediction callbacks.
+ */
+public interface SortedPredictionsProvider {
+    List<AppTarget> sortTargets(List<AppTarget> targets);
+}
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 805bd67..5955274 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -19,8 +19,6 @@
     android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"/>
-    <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING"/>
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
@@ -31,7 +29,7 @@
 
         <activity android:name=".LoginActivity" >
             <intent-filter>
-                <!-- This intent filter is not really needed by CTS, but it maks easier to launch
+                <!-- 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" />
@@ -54,7 +52,7 @@
         <activity android:name=".FatActivity" />
         <activity android:name=".VirtualContainerActivity">
             <intent-filter>
-                <!-- This intent filter is not really needed by CTS, but it maks easier to launch
+                <!-- 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" />
@@ -73,18 +71,19 @@
         <activity android:name=".SimpleSaveActivity"/>
         <activity android:name=".PreSimpleSaveActivity">
             <intent-filter>
-                <!-- This intent filter is not really needed by CTS, but it maks easier to launch
+                <!-- 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>
         <activity android:name=".WebViewActivity"/>
+        <activity android:name=".WebViewMultiScreenLoginActivity"/>
         <activity android:name=".TrampolineWelcomeActivity"/>
         <activity android:name=".AttachedContextActivity"/>
         <activity android:name=".DialogLauncherActivity" >
             <intent-filter>
-                <!-- This intent filter is not really needed by CTS, but it maks easier to launch
+                <!-- 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" />
@@ -96,6 +95,23 @@
             android:exported="true" />
 
         <activity android:name=".TrampolineForResultActivity" />
+        <activity android:name=".OnCreateServiceStatusVerifierActivity"/>
+        <activity android:name=".UsernameOnlyActivity" >
+            <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>
+        <activity android:name=".PasswordOnlyActivity" >
+            <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>
 
         <receiver android:name=".SelfDestructReceiver"
             android:exported="true"
@@ -140,6 +156,16 @@
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
+
+        <service
+            android:name=".augmented.CtsAugmentedAutofillService"
+            android:label="CtsAugmentedAutofillService"
+            android:permission="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE" >
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+        </service>
+
     </application>
 
     <instrumentation
diff --git a/tests/autofillservice/AndroidTest.xml b/tests/autofillservice/AndroidTest.xml
index a94da63..4fe406f 100644
--- a/tests/autofillservice/AndroidTest.xml
+++ b/tests/autofillservice/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for AutoFill Framework CTS tests.">
   <option name="test-suite-tag" value="cts" />
   <option name="config-descriptor:metadata" key="component" value="autofill" />
+  <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/autofillservice/assets/login.html b/tests/autofillservice/assets/login.html
index 60e7cf7..4d8effc 100644
--- a/tests/autofillservice/assets/login.html
+++ b/tests/autofillservice/assets/login.html
@@ -17,8 +17,8 @@
 <html>
 <body>
 <form action="login.html" name="FORM AM I">
-    Username: <input type="text" name="username" autocomplete="username" placeholder="There's no place like a holder"/><br/>
-    Password: <input type="password" name="password" autocomplete="current-password" placeholder="Holder it like it cannnot passer a word"/><br/>
+    Username: <input type="text" name="username" autocomplete="username" placeholder="There's no place like a holder" oninput="javascript:JsHandler.onUsernameChanged(this.value)"/><br/>
+    Password: <input type="password" name="password" autocomplete="current-password" placeholder="Holder it like it cannnot passer a word" oninput="javascript:JsHandler.onPasswordChanged(this.value)"/><br/>
     <br/>
     <input type="submit" value="Login"/>
 </form>
diff --git a/tests/autofillservice/assets/password.html b/tests/autofillservice/assets/password.html
new file mode 100644
index 0000000..303bfb4
--- /dev/null
+++ b/tests/autofillservice/assets/password.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ * 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.
+-->
+<html>
+<body>
+<form action="password.html" >
+    Password: <input type="password" name="password" oninput="javascript:JsHandler.onPasswordChanged(this.value)"/><br/>
+    <br/>
+    <input type="submit" value="Login"/>
+</form>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/autofillservice/assets/username.html b/tests/autofillservice/assets/username.html
new file mode 100644
index 0000000..adc87cb
--- /dev/null
+++ b/tests/autofillservice/assets/username.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ * 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.
+-->
+<html>
+<body>
+<form action="password.html" method="post">
+    Username: <input type="text" name="username" oninput="javascript:JsHandler.onUsernameChanged(this.value)"/><br/>
+    <br/>
+    <input type="submit" value="Next"/>
+</form>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/augmented_autofill_ui.xml b/tests/autofillservice/res/layout/augmented_autofill_ui.xml
new file mode 100644
index 0000000..e6f5a28
--- /dev/null
+++ b/tests/autofillservice/res/layout/augmented_autofill_ui.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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/augmentedAutofillUi"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"/>
diff --git a/tests/autofillservice/res/layout/custom_description_with_hidden_fields.xml b/tests/autofillservice/res/layout/custom_description_with_hidden_fields.xml
new file mode 100644
index 0000000..58862fe
--- /dev/null
+++ b/tests/autofillservice/res/layout/custom_description_with_hidden_fields.xml
@@ -0,0 +1,78 @@
+<?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="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/username_label"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="User:"/>
+
+    <TextView android:id="@+id/username_plain"
+        android:visibility="gone"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="IF YOU SEE ME, SOMETHING IS WRONG"/>
+
+    <TextView android:id="@+id/username_masked"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="****"/>
+
+    <TextView android:id="@+id/password_label"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="Pass:"/>
+
+    <TextView android:id="@+id/password_plain"
+        android:visibility="gone"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="IF YOU SEE ME, SOMETHING IS WRONG"/>
+
+    <TextView android:id="@+id/password_masked"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="...."/>
+
+    <Button android:id="@+id/show"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="SHOW"/>
+
+    <Button android:id="@+id/hide"
+        android:visibility="gone"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="HIDE"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/custom_description_with_username_and_password.xml b/tests/autofillservice/res/layout/custom_description_with_username_and_password.xml
new file mode 100644
index 0000000..31cb505
--- /dev/null
+++ b/tests/autofillservice/res/layout/custom_description_with_username_and_password.xml
@@ -0,0 +1,47 @@
+<?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="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/username_label"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="User:"/>
+
+    <TextView android:id="@+id/username"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="u"/>
+
+     <TextView android:id="@+id/password_label"
+        android:visibility="visible"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="Pass:"/>
+
+    <TextView android:id="@+id/password"
+        android:paddingEnd="16dp"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:text="p"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/fragment_container.xml b/tests/autofillservice/res/layout/fragment_container.xml
index 156efad..f5600e6 100644
--- a/tests/autofillservice/res/layout/fragment_container.xml
+++ b/tests/autofillservice/res/layout/fragment_container.xml
@@ -19,4 +19,6 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
     android:id="@+id/rootContainer" />
diff --git a/tests/autofillservice/res/layout/login_with_custom_highlight_activity.xml b/tests/autofillservice/res/layout/login_with_custom_highlight_activity.xml
deleted file mode 100644
index 73926a9..0000000
--- a/tests/autofillservice/res/layout/login_with_custom_highlight_activity.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?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"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true"
-    android:theme="@style/MyAutofilledHighlight"
-    android:focusableInTouchMode="true"
-    android:orientation="vertical" >
-
-    <LinearLayout
-        android:id="@+id/username_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal" >
-
-        <TextView
-            android:id="@+id/username_label"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@+string/username_string" />
-
-        <EditText
-            android:id="@+id/username"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal" >
-
-        <TextView
-            android:id="@+id/password_label"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@+string/password_string" />
-
-        <EditText
-            android:id="@+id/password"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="textPassword"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal" >
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal" >
-
-        <Button
-            android:id="@+id/clear"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Clear" />
-
-        <Button
-            android:id="@+id/save"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Save" />
-
-        <Button
-            android:id="@+id/login"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Login" />
-
-        <Button
-            android:id="@+id/cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="Cancel" />
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/output"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/login_with_strings_activity.xml b/tests/autofillservice/res/layout/login_with_strings_activity.xml
index 2f90761..972f53f 100644
--- a/tests/autofillservice/res/layout/login_with_strings_activity.xml
+++ b/tests/autofillservice/res/layout/login_with_strings_activity.xml
@@ -33,7 +33,7 @@
             android:id="@+id/username_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@+string/username_string" />
+            android:text="@string/username_string" />
 
         <EditText
             android:id="@+id/username"
@@ -50,7 +50,7 @@
             android:id="@+id/password_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="@+string/password_string" />
+            android:text="@string/password_string" />
 
         <EditText
             android:id="@+id/password"
diff --git a/tests/autofillservice/res/layout/password_only_activity.xml b/tests/autofillservice/res/layout/password_only_activity.xml
new file mode 100644
index 0000000..dff1d3e
--- /dev/null
+++ b/tests/autofillservice/res/layout/password_only_activity.xml
@@ -0,0 +1,60 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/welcome"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/password_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Password" />
+
+        <EditText
+            android:id="@+id/password"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/login"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Login" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/username_only_activity.xml b/tests/autofillservice/res/layout/username_only_activity.xml
new file mode 100644
index 0000000..aacbefd
--- /dev/null
+++ b/tests/autofillservice/res/layout/username_only_activity.xml
@@ -0,0 +1,55 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:id="@+id/username_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/username_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Username" />
+
+        <EditText
+            android:id="@+id/username"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/next"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Next" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/view_attribute_test_activity.xml b/tests/autofillservice/res/layout/view_attribute_test_activity.xml
index d03d65c..f2b7c3b 100644
--- a/tests/autofillservice/res/layout/view_attribute_test_activity.xml
+++ b/tests/autofillservice/res/layout/view_attribute_test_activity.xml
@@ -19,6 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
     android:id="@+id/rootContainer">
     <EditText android:id="@+id/editTextNoHint"
         android:layout_width="wrap_content"
diff --git a/tests/autofillservice/res/layout/webview_only_activity.xml b/tests/autofillservice/res/layout/webview_only_activity.xml
new file mode 100644
index 0000000..469028a
--- /dev/null
+++ b/tests/autofillservice/res/layout/webview_only_activity.xml
@@ -0,0 +1,33 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <android.autofillservice.cts.MyWebView
+        android:id="@+id/my_webview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" />
+
+</LinearLayout>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
index a8a8091..bc7e7c1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
@@ -22,10 +22,13 @@
 import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.PixelCopy;
 import android.view.View;
 import android.view.autofill.AutofillManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.compatibility.common.util.SynchronousPixelCopy;
 
 import java.util.concurrent.CountDownLatch;
@@ -34,8 +37,9 @@
 /**
   * Base class for all activities in this test suite
   */
-abstract class AbstractAutoFillActivity extends Activity {
+public abstract class AbstractAutoFillActivity extends Activity {
 
+    private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
     private MyAutofillCallback mCallback;
 
     /**
@@ -121,13 +125,46 @@
         unregisterNonNullCallback();
     }
 
+    /**
+     * Waits until {@link #onDestroy()} is called.
+     */
+    public void waintUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException {
+        if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(timeout, "activity %s not destroyed", this);
+        }
+    }
+
     private void unregisterNonNullCallback() {
         getAutofillManager().unregisterCallback(mCallback);
         mCallback = null;
     }
 
     @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        AutofillTestWatcher.registerActivity("onCreate()", this);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        // Activitiy is typically unregistered at finish(), but we need to unregister here too
+        // for the cases where it's destroyed due to a config change (like device rotation).
+        AutofillTestWatcher.unregisterActivity("onDestroy()", this);
+        mDestroyedLatch.countDown();
+    }
+
+    @Override
     public void finish() {
+        finishOnly();
+        AutofillTestWatcher.unregisterActivity("finish()", this);
+    }
+
+    /**
+     * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}.
+     */
+    void finishOnly() {
         if (mCallback != null) {
             unregisterNonNullCallback();
         }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
new file mode 100644
index 0000000..b9d631e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
@@ -0,0 +1,70 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for test cases using {@link GridActivity}.
+ */
+abstract class AbstractGridActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<GridActivity> {
+
+    protected GridActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<GridActivity> getActivityRule() {
+        return new AutofillActivityTestRule<GridActivity>(GridActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                postActivityLaunched();
+            }
+        };
+    }
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched() {
+    }
+
+    /**
+     * Focus to a cell and expect window event
+     */
+    protected void focusCell(int row, int column) throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column));
+    }
+
+    /**
+     * Focus to a cell and expect no window event.
+     */
+    protected void focusCellNoWindowChange(int row, int column) {
+        final AccessibilityEvent event;
+        try {
+            event = mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
+        } catch (TimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException(String.format("Expect no window event when focusing to"
+                + " column %d row %d, but event happened: %s", row, column, event));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
index 8ec9a3e..c0b2994 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
@@ -17,61 +17,63 @@
 package android.autofillservice.cts;
 
 import android.view.View;
-
-import org.junit.Before;
-import org.junit.Rule;
+import android.view.accessibility.AccessibilityEvent;
 
 import java.util.concurrent.TimeoutException;
 
 /**
  * Base class for test cases using {@link LoginActivity}.
  */
-abstract class AbstractLoginActivityTestCase extends AutoFillServiceTestCase {
-    @Rule
-    public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-            new AutofillActivityTestRule<LoginActivity>(LoginActivity.class);
-
-    @Rule
-    public final AutofillActivityTestRule<CheckoutActivity> mCheckoutActivityRule =
-            new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class, false);
+public abstract class AbstractLoginActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginActivity> {
 
     protected LoginActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginActivity>(
+                LoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     /**
      * Requests focus on username and expect Window event happens.
      */
     protected void requestFocusOnUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
     }
 
     /**
      * Requests focus on username and expect no Window event happens.
      */
     protected void requestFocusOnUsernameNoWindowChange() {
+        final AccessibilityEvent event;
         try {
-            // TODO: define a small value in Timeout
-            mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
-                    Timeouts.UI_TIMEOUT.ms());
+            event = mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
         } catch (TimeoutException ex) {
             // no window events! looking good
             return;
         }
         throw new IllegalStateException("Expect no window event when focusing to"
-                + " username, but event happened");
+                + " username, but event happened: " + event);
     }
 
     /**
      * Requests focus on password and expect Window event happens.
      */
     protected void requestFocusOnPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus));
     }
 
+    /**
+     * Clears focus from input fields by focusing on the parent layout.
+     */
+    protected void clearFocus() {
+        mActivity.clearFocus();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
new file mode 100644
index 0000000..edd270b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
+
+    static final String FAKE_DOMAIN = "y.u.no.real.server";
+
+    static final String HTML_NAME_USERNAME = "username";
+    static final String HTML_NAME_PASSWORD = "password";
+
+    protected MyWebView mWebView;
+
+    protected UiObject2 getInput(UiBot uiBot, UiObject2 label) throws Exception {
+        // Then the input is next.
+        final UiObject2 parent = label.getParent();
+        UiObject2 previous = null;
+        for (UiObject2 child : parent.getChildren()) {
+            if (label.equals(previous)) {
+                if (child.getClassName().equals(EditText.class.getName())) {
+                    return child;
+                }
+                uiBot.dumpScreen("getInput() for " + child + "failed");
+                throw new IllegalStateException("Invalid class for " + child);
+            }
+            previous = child;
+        }
+        uiBot.dumpScreen("getInput() for label " + label + "failed");
+        throw new IllegalStateException("could not find username (label=" + label + ")");
+    }
+
+    public void dispatchKeyPress(int keyCode) {
+        runOnUiThread(() -> {
+            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+        });
+        // wait webview to process the key event.
+        SystemClock.sleep(300);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
new file mode 100644
index 0000000..7e290be
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.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 android.autofillservice.cts;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
+    // set using accessibility.
+    protected static final boolean INJECT_EVENTS = true;
+
+    @BeforeClass
+    public static void setReplierMode() {
+        sReplier.setIdMode(IdMode.HTML_NAME);
+    }
+
+    @AfterClass
+    public static void resetReplierMode() {
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
index 50526dd..97ed220 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
@@ -18,24 +18,25 @@
 
 import android.autofillservice.cts.AttachedContextActivity.FillExpectation;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
  * Makes sure activity with attached context can be autofilled.
  */
-public class AttachedContextActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<AttachedContextActivity> mActivityRule =
-            new AutofillActivityTestRule<>(AttachedContextActivity.class);
+public class AttachedContextActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<AttachedContextActivity> {
 
     private AttachedContextActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<AttachedContextActivity> getActivityRule() {
+        return new AutofillActivityTestRule<AttachedContextActivity>(
+                AttachedContextActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
index 62072c3..9e2c8f9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
@@ -89,8 +89,7 @@
     /**
      * Creates an {@link IntentSender} with the given unique id for the given dataset.
      */
-    public static IntentSender createSender(Context context, int id,
-            CannedDataset dataset) {
+    public static IntentSender createSender(Context context, int id, CannedDataset dataset) {
         return createSender(context, id, dataset, null);
     }
 
@@ -137,7 +136,8 @@
     public static IntentSender createSender(Context context, int id) {
         Preconditions.checkArgument(id > 0, "id must be positive");
         return PendingIntent
-                .getActivity(context, id, new Intent(context, AuthenticationActivity.class), 0)
+                .getActivity(context, id, new Intent(context, AuthenticationActivity.class),
+                        PendingIntent.FLAG_CANCEL_CURRENT)
                 .getIntentSender();
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 69b749a..1e1e740 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -17,15 +17,15 @@
 package android.autofillservice.cts;
 
 import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Helper.getLoggingLevel;
-import static android.autofillservice.cts.Helper.hasAutofillFeature;
-import static android.autofillservice.cts.Helper.setLoggingLevel;
 import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
 import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+import static android.content.Context.CLIPBOARD_SERVICE;
 
 import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
 import android.autofillservice.cts.common.SettingsStateKeeperRule;
+import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
@@ -33,180 +33,319 @@
 import android.util.Log;
 import android.widget.RemoteViews;
 
+import androidx.annotation.NonNull;
+
 import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.SafeCleanerRule;
 
-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.rules.TestRule;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
 
 /**
- * Base class for all other tests.
+ * Placeholder for the base class for all integration tests:
+ *
+ * <ul>
+ *   <li>{@link AutoActivityLaunch}
+ *   <li>{@link ManualActivityLaunch}
+ * </ul>
+ *
+ * <p>These classes provide the common infrastructure such as:
+ *
+ * <ul>
+ *   <li>Preserving the autofill service settings.
+ *   <li>Cleaning up test state.
+ *   <li>Wrapping the test under autofill-specific test rules.
+ *   <li>Launching the activity used by the test.
+ * </ul>
  */
-@RunWith(AndroidJUnit4.class)
-// NOTE: @ClassRule requires it to be public
-public abstract class AutoFillServiceTestCase {
-    private static final String TAG = "AutoFillServiceTestCase";
+public final class AutoFillServiceTestCase {
 
-    static final UiBot sDefaultUiBot = new UiBot();
+    /**
+     * Base class for all test cases that use an {@link AutofillActivityTestRule} to
+     * launch the activity.
+     */
+    // Must be public because of @ClassRule
+    public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
+            extends BaseTestCase {
 
-    protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
 
-    private static final Context sContext = InstrumentationRegistry.getTargetContext();
-
-    @ClassRule
-    public static final SettingsStateKeeperRule mServiceSettingsKeeper =
-            new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE);
-
-    @Rule
-    public final TestWatcher watcher = new TestWatcher() {
-        @Override
-        protected void starting(Description description) {
-            JUnitHelper.setCurrentTestName(description.getDisplayName());
+        protected AutoActivityLaunch() {
+            super(sDefaultUiBot);
         }
 
         @Override
-        protected void finished(Description description) {
-            JUnitHelper.setCurrentTestName(null);
+        protected TestRule getMainTestRule() {
+            return getActivityRule();
         }
-    };
 
-    @Rule
-    public final RetryRule mRetryRule = new RetryRule(2);
+        /**
+         * Gets the rule to launch the main activity for this test.
+         *
+         * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
+         * this method could return {@code null} when the rule chain that uses it is constructed.
+         *
+         */
+        protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
 
-    @Rule
-    public final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
-
-    @Rule
-    public final RequiredFeatureRule mRequiredFeatureRule =
-            new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
-
-    @Rule
-    public final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
-            .setDumper(mLoggingRule)
-            .run(() -> sReplier.assertNoUnhandledFillRequests())
-            .run(() -> sReplier.assertNoUnhandledSaveRequests())
-            .add(() -> { return sReplier.getExceptions(); });
-
-    protected final Context mContext = sContext;
-    protected final String mPackageName;
-    protected final UiBot mUiBot;
-
-    /**
-     * Stores the previous logging level so it's restored after the test.
-     */
-    private String mLoggingLevel;
-
-    protected AutoFillServiceTestCase() {
-        this(sDefaultUiBot);
-    }
-
-    protected AutoFillServiceTestCase(UiBot uiBot) {
-        mPackageName = mContext.getPackageName();
-        mUiBot = uiBot;
-        mUiBot.reset();
-    }
-
-    @BeforeClass
-    public static void prepareScreen() throws Exception {
-        if (!hasAutofillFeature()) return;
-
-        // Unlock screen.
-        runShellCommand("input keyevent KEYCODE_WAKEUP");
-
-        // Collapse notifications.
-        runShellCommand("cmd statusbar collapse");
-
-        // Set orientation as portrait, otherwise some tests might fail due to elements not fitting
-        // in, IME orientation, etc...
-        sDefaultUiBot.setScreenOrientation(UiBot.PORTRAIT);
-    }
-
-    @Before
-    public void cleanupStaticState() {
-        Helper.preTestCleanup();
-        sReplier.reset();
-    }
-
-    @Before
-    public void setVerboseLogging() {
-        try {
-            mLoggingLevel = getLoggingLevel();
-        } catch (Exception e) {
-            Log.w(TAG, "Could not get previous logging level: " + e);
-            mLoggingLevel = "debug";
+        protected @NonNull A launchActivity(@NonNull Intent intent) {
+            return getActivityRule().launchActivity(intent);
         }
-        try {
-            setLoggingLevel("verbose");
-        } catch (Exception e) {
-            Log.w(TAG, "Could not change logging level to verbose: " + e);
+
+        protected @NonNull A getActivity() {
+            return getActivityRule().getActivity();
         }
     }
 
     /**
-     * Cleans up activities that might have been left over.
+     * Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
      */
-    @Before
-    @After
-    public void finishActivities() {
-        WelcomeActivity.finishIt(mUiBot);
-    }
+    // Must be public because of @ClassRule
+    public abstract static class ManualActivityLaunch extends BaseTestCase {
 
-    @After
-    public void resetVerboseLogging() {
-        try {
-            setLoggingLevel(mLoggingLevel);
-        } catch (Exception e) {
-            Log.w(TAG, "Could not restore logging level to " + mLoggingLevel + ": " + e);
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
+
+        protected ManualActivityLaunch() {
+            this(sDefaultUiBot);
+        }
+
+        protected ManualActivityLaunch(@NonNull UiBot uiBot) {
+            super(uiBot);
+        }
+
+        @Override
+        protected TestRule getMainTestRule() {
+            return new TestRule() {
+
+                @Override
+                public Statement apply(Statement base, Description description) {
+                    // Returns a no-op statements
+                    return new Statement() {
+                        @Override
+                        public void evaluate() throws Throwable {
+                            base.evaluate();
+                        }
+                    };
+                }
+            };
         }
     }
 
-    @After
-    public void ignoreFurtherRequests() {
-        InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
+    @RunWith(AndroidJUnit4.class)
+    private abstract static class BaseTestCase {
+
+        private static final String TAG = "AutoFillServiceTestCase";
+
+        protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
+
+        protected static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+        // Hack because JUnit requires that @ClassRule instance belong to a public class.
+        protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
+                new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
+            @Override
+            protected void preEvaluate(Description description) {
+                JUnitHelper.setCurrentTestClass(description.getClassName());
+            }
+
+            @Override
+            protected void postEvaluate(Description description) {
+                JUnitHelper.setCurrentTestClass(null);
+            }
+        };
+
+        private final TestWatcher mTestWatcher = new AutofillTestWatcher();
+
+        private final RetryRule mRetryRule = new RetryRule(getNumberRetries());
+
+        private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
+
+        private final RequiredFeatureRule mRequiredFeatureRule =
+                new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
+
+        protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+                .setDumper(mLoggingRule)
+                .run(() -> sReplier.assertNoUnhandledFillRequests())
+                .run(() -> sReplier.assertNoUnhandledSaveRequests())
+                .add(() -> { return sReplier.getExceptions(); });
+
+        @Rule
+        public final RuleChain mLookAllTheseRules = RuleChain
+                //
+                // mRequiredFeatureRule should be first so the test can be skipped right away
+                .outerRule(mRequiredFeatureRule)
+                //
+                // mTestWatcher should always be one the first rules, as it defines the name of the
+                // test being ran and finishes dangling activities at the end
+                .around(mTestWatcher)
+                //
+                // mLoggingRule wraps the test but doesn't interfere with it
+                .around(mLoggingRule)
+                //
+                // mSafeCleanerRule will catch errors
+                .around(mSafeCleanerRule)
+                //
+                // mRetryRule should be closest to the main test as possible
+                .around(mRetryRule)
+                //
+                //
+                // Finally, let subclasses add their own rules (like ActivityTestRule)
+                .around(getMainTestRule());
+
+
+        protected final Context mContext = sContext;
+        protected final String mPackageName;
+        protected final UiBot mUiBot;
+
+        private BaseTestCase(@NonNull UiBot uiBot) {
+            mPackageName = mContext.getPackageName();
+            mUiBot = uiBot;
+            mUiBot.reset();
+        }
+
+        /**
+         * Gets how many times a test should be retried.
+         *
+         * @return {@code 1} by default, unless overridden by subclasses or by a global settings
+         * named {@code CLASS_NAME + #getNumberRetries} or
+         * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
+         * priority).
+         */
+        protected int getNumberRetries() {
+            final String localProp = getClass().getName() + "#getNumberRetries";
+            final Integer localValue = getNumberRetries(localProp);
+            if (localValue != null) return localValue.intValue();
+
+            final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
+            final Integer globalValue = getNumberRetries(globalProp);
+            if (globalValue != null) return globalValue.intValue();
+
+            return 1;
+        }
+
+        private Integer getNumberRetries(String prop) {
+            final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
+            if (value != null) {
+                Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
+                        + "' global setting");
+                try {
+                    return Integer.parseInt(value);
+                } catch (Exception e) {
+                    Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Gets the test-specific {@link Rule @Rule}.
+         *
+         * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
+         * so the order is preserved.
+         *
+         */
+        @NonNull
+        protected abstract TestRule getMainTestRule();
+
+        @Before
+        public void prepareDevice() throws Exception {
+            Log.v(TAG, "@Before: prepareDevice()");
+
+            // Unlock screen.
+            runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+            // Dismiss keyguard, in case it's set as "Swipe to unlock".
+            runShellCommand("wm dismiss-keyguard");
+
+            // Collapse notifications.
+            runShellCommand("cmd statusbar collapse");
+
+            // Set orientation as portrait, otherwise some tests might fail due to elements not
+            // fitting in, IME orientation, etc...
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+
+            // Wait until device is idle to avoid flakiness
+            mUiBot.waitForIdle();
+
+            // Clear Clipboard
+            // TODO(b/117768051): remove try/catch once fixed
+            try {
+                ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
+                    .clearPrimaryClip();
+            } catch (Exception e) {
+                Log.e(TAG, "Ignoring exception clearing clipboard", e);
+            }
+        }
+
+        @Before
+        public void preTestCleanup() {
+            Log.v(TAG, "@Before: preTestCleanup()");
+
+            prepareServicePreTest();
+
+            InstrumentedAutoFillService.resetStaticState();
+            AuthenticationActivity.resetStaticState();
+            sReplier.reset();
+        }
+
+        /**
+         * Prepares the service before each test - by default, disables it
+         */
+        protected void prepareServicePreTest() {
+            Log.v(TAG, "prepareServicePreTest(): calling disableService()");
+            disableService();
+        }
+
+        /**
+         * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void enableService() {
+            Helper.enableAutofillService(getContext(), SERVICE_NAME);
+        }
+
+        /**
+         * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void disableService() {
+            Helper.disableAutofillService(getContext());
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
+         */
+        protected void assertServiceEnabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
+         */
+        protected void assertServiceDisabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
+        }
+
+        protected RemoteViews createPresentation(String message) {
+            final RemoteViews presentation = new RemoteViews(getContext()
+                    .getPackageName(), R.layout.list_item);
+            presentation.setTextViewText(R.id.text1, message);
+            return presentation;
+        }
     }
 
-    /**
-     * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
-     */
-    protected void enableService() {
-        Helper.enableAutofillService(getContext(), SERVICE_NAME);
-        InstrumentedAutoFillService.setIgnoreUnexpectedRequests(false);
-    }
+    protected static final UiBot sDefaultUiBot = new UiBot();
 
-    /**
-     * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
-     */
-    protected void disableService() {
-        if (!hasAutofillFeature()) return;
-
-        Helper.disableAutofillService(getContext(), SERVICE_NAME);
-        InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
-    }
-
-    /**
-     * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
-     */
-    protected void assertServiceEnabled() {
-        Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
-    }
-
-    /**
-     * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
-     */
-    protected void assertServiceDisabled() {
-        Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
-    }
-
-    protected RemoteViews createPresentation(String message) {
-        final RemoteViews presentation = new RemoteViews(getContext()
-                .getPackageName(), R.layout.list_item);
-        presentation.setTextViewText(R.id.text1, message);
-        return presentation;
+    private AutoFillServiceTestCase() {
+        throw new UnsupportedOperationException("Contain static stuff only");
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index 5fae8bf..5f2d476 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -27,13 +27,12 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.content.Intent;
 import android.service.autofill.SaveInfo;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.view.ViewGroup;
 import android.widget.EditText;
 
-import org.junit.Before;
-import org.junit.Rule;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicReference;
@@ -41,22 +40,30 @@
 /**
  * Tests that the session finishes when the views and fragments go away
  */
-public class AutoFinishSessionTest extends AutoFillServiceTestCase {
+public class AutoFinishSessionTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
 
     private static final String ID_BUTTON = "button";
 
-    @Rule
-    public final AutofillActivityTestRule<FragmentContainerActivity> mActivityRule =
-            new AutofillActivityTestRule<>(FragmentContainerActivity.class);
     private FragmentContainerActivity mActivity;
     private EditText mEditText1;
     private EditText mEditText2;
     private Fragment mFragment;
     private ViewGroup mParent;
 
-    @Before
-    public void initViews() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<FragmentContainerActivity>(
+                FragmentContainerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                initViews(getActivity());
+            }
+        };
+    }
+
+    private void initViews(FragmentContainerActivity activitiy) {
+        mActivity = activitiy;
         mEditText1 = mActivity.findViewById(R.id.editText1);
         mEditText2 = mActivity.findViewById(R.id.editText2);
         mFragment = mActivity.getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
@@ -75,16 +82,15 @@
                 .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, viewsToSave).build());
 
-        // Trigger autofill
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText2.requestFocus();
-            mEditText1.requestFocus();
-        });
-
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
         sReplier.getNextFillRequest();
-
         mUiBot.assertNoDatasetsEver();
 
+        // Now it's safe to focus on editText1 without triggering a new partition due to race
+        // conditions
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+
         // remove first set of views
         mActivity.syncRunOnUiThread(() -> {
             mEditText1.setText("editText1-filled");
@@ -205,16 +211,16 @@
                 .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText1").build());
 
-        // Trigger autofill
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText2.requestFocus();
-            mEditText1.requestFocus();
-        });
 
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
         sReplier.getNextFillRequest();
-
         mUiBot.assertNoDatasetsEver();
 
+        // Now it's safe to focus on editText1 without triggering a new partition due to race
+        // conditions
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+
         mActivity.syncRunOnUiThread(() -> {
             mEditText1.setText("editText1-filled");
             mEditText2.setText("editText2-filled");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
index 7e36593..1985ba6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
@@ -15,13 +15,13 @@
  */
 package android.autofillservice.cts;
 
-import android.app.Activity;
 import android.support.test.rule.ActivityTestRule;
 
 /**
  * Custom {@link ActivityTestRule}.
  */
-public class AutofillActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
+public class AutofillActivityTestRule<T extends AbstractAutoFillActivity>
+        extends ActivityTestRule<T> {
 
     public AutofillActivityTestRule(Class<T> activityClass) {
         super(activityClass);
@@ -30,4 +30,12 @@
     public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
         super(activityClass, false, launchActivity);
     }
+
+    @Override
+    protected void afterActivityFinished() {
+        // AutofillTestWatcher does not need to watch for this activity as the ActivityTestRule
+        // will take care of finishing it...
+        AutofillTestWatcher.unregisterActivity("AutofillActivityTestRule.afterActivityFinished()",
+                getActivity());
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
index 2aba48e..5c5e881 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -55,7 +55,6 @@
             @Override
             public void evaluate() throws Throwable {
                 final String testName = description.getDisplayName();
-                Log.v(TAG, "@Before " + testName);
                 final String levelBefore = runShellCommand("cmd autofill get log_level");
                 if (!levelBefore.equals("verbose")) {
                     runShellCommand("cmd autofill set log_level verbose");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
new file mode 100644
index 0000000..2cca253
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
@@ -0,0 +1,154 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.Set;
+
+/**
+ * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
+ *
+ * <p>This class is not thread safe, but should be fine...
+ */
+public final class AutofillTestWatcher extends TestWatcher {
+
+    private static final String TAG = "AutofillTestWatcher";
+
+    @GuardedBy("sUnfinishedBusiness")
+    private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
+
+    @GuardedBy("sAllActivities")
+    private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
+
+    @Override
+    protected void starting(Description description) {
+        resetStaticState();
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Starting " + testName);
+        JUnitHelper.setCurrentTestName(testName);
+    }
+
+    @Override
+    protected void finished(Description description) {
+        final String testName = description.getDisplayName();
+        try {
+            finishActivities();
+            waitUntilAllDestroyed();
+        } finally {
+            resetStaticState();
+        }
+        Log.i(TAG, "Finished " + testName);
+        JUnitHelper.setCurrentTestName(null);
+    }
+
+    private void resetStaticState() {
+        synchronized (sUnfinishedBusiness) {
+            sUnfinishedBusiness.clear();
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.clear();
+        }
+    }
+
+    /**
+     * Registers an activity so it's automatically finished (if necessary) after the test.
+     */
+    public static void registerActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.contains(activity)) {
+                throw new IllegalStateException("Already registered " + activity);
+            }
+            Log.v(TAG, "registering activity on " + where + ": " + activity);
+            sUnfinishedBusiness.add(activity);
+            sAllActivities.add(activity);
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.add(activity);
+
+        }
+    }
+
+    /**
+     * Unregisters an activity so it's not automatically finished after the test.
+     */
+    public static void unregisterActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            final boolean unregistered = sUnfinishedBusiness.remove(activity);
+            if (unregistered) {
+                Log.d(TAG, "unregistered activity on " + where + ": " + activity);
+            } else {
+                Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
+            }
+        }
+    }
+
+    /**
+     * Gets the instance of a previously registered activity.
+     */
+    @Nullable
+    public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
+        @SuppressWarnings("unchecked")
+        final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
+                .findFirst()
+                .get();
+        return activity;
+    }
+
+    private void finishActivities() {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.isEmpty()) {
+                return;
+            }
+            Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
+            for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
+                if (activity.isFinishing()) {
+                    Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
+                } else {
+                    Log.d(TAG, "Finishing activity: " + activity);
+                    activity.finishOnly();
+                }
+            }
+        }
+    }
+
+    private void waitUntilAllDestroyed() {
+        synchronized (sAllActivities) {
+            if (sAllActivities.isEmpty()) return;
+
+            Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
+            for (AbstractAutoFillActivity activity : sAllActivities) {
+                Log.d(TAG, "Waiting for " + activity);
+                try {
+                    activity.waintUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
index a63afe4..3a0c294 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
@@ -35,8 +35,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /*
@@ -54,10 +52,8 @@
  *  CheckoutActivityTest.
  */
 @AppModeFull // Unit test
-public class AutofillValueTest extends AutoFillServiceTestCase {
-    @Rule
-    public final AutofillActivityTestRule<AllAutofillableViewsActivity> mActivityRule =
-            new AutofillActivityTestRule<>(AllAutofillableViewsActivity.class);
+public class AutofillValueTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<AllAutofillableViewsActivity> {
 
     private AllAutofillableViewsActivity mActivity;
     private EditText mEditText;
@@ -69,9 +65,8 @@
     private DatePicker mDatePicker;
     private TimePicker mTimePicker;
 
-    @Before
-    public void setFields() {
-        mActivity = mActivityRule.getActivity();
+    private void setFields(AllAutofillableViewsActivity activity) {
+        mActivity = activity;
 
         mEditText = (EditText) mActivity.findViewById(R.id.editText);
         mCompoundButton = (CompoundButton) mActivity.findViewById(R.id.compoundButton);
@@ -83,6 +78,17 @@
         mTimePicker = (TimePicker) mActivity.findViewById(R.id.timePicker);
     }
 
+    @Override
+    protected AutofillActivityTestRule<AllAutofillableViewsActivity> getActivityRule() {
+        return new AutofillActivityTestRule<AllAutofillableViewsActivity>(
+                AllAutofillableViewsActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                setFields(getActivity());
+            }
+        };
+    }
+
     @Test
     public void createTextValue() throws Exception {
         assertThat(AutofillValue.forText(null)).isNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 33aa025..d68cacf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -59,7 +59,7 @@
  *               .build());
  * </pre class="prettyprint">
  */
-final class CannedFillResponse {
+public final class CannedFillResponse {
 
     private final ResponseType mResponseType;
     private final List<CannedDataset> mDatasets;
@@ -87,6 +87,8 @@
     private final long mDisableDuration;
     private final AutofillId[] mFieldClassificationIds;
     private final boolean mFieldClassificationIdsOverflow;
+    private final SaveInfoDecorator mSaveInfoDecorator;
+    private final UserData mUserData;
 
     private CannedFillResponse(Builder builder) {
         mResponseType = builder.mResponseType;
@@ -115,27 +117,35 @@
         mDisableDuration = builder.mDisableDuration;
         mFieldClassificationIds = builder.mFieldClassificationIds;
         mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
+        mSaveInfoDecorator = builder.mSaveInfoDecorator;
+        mUserData = builder.mUserData;
     }
 
     /**
      * Constant used to pass a {@code null} response to the
      * {@link FillCallback#onSuccess(FillResponse)} method.
      */
-    static final CannedFillResponse NO_RESPONSE =
+    public static final CannedFillResponse NO_RESPONSE =
             new Builder(ResponseType.NULL).build();
 
     /**
      * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
      */
-    static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
+    public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
             new Builder(ResponseType.TIMEOUT).build();
 
 
-    String getFailureMessage() {
+    /**
+     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
+     */
+    public static final CannedFillResponse FAIL =
+            new Builder(ResponseType.FAILURE).build();
+
+    public String getFailureMessage() {
         return mFailureMessage;
     }
 
-    ResponseType getResponseType() {
+    public ResponseType getResponseType() {
         return mResponseType;
     }
 
@@ -143,7 +153,7 @@
      * Creates a new response, replacing the dataset field ids by the real ids from the assist
      * structure.
      */
-    FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) {
+    public FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) {
         final FillResponse.Builder builder = new FillResponse.Builder()
                 .setFlags(mFillResponseFlags);
         if (mDatasets != null) {
@@ -153,8 +163,9 @@
                 builder.addDataset(dataset);
             }
         }
-        if (mRequiredSavableIds != null || mRequiredSavableAutofillIds != null) {
-            final SaveInfo.Builder saveInfo;
+        final SaveInfo.Builder saveInfo;
+        if (mRequiredSavableIds != null || mOptionalSavableIds != null
+                || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
             if (mRequiredSavableAutofillIds != null) {
                 saveInfo = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
             } else {
@@ -175,7 +186,9 @@
             if (mSaveDescription != null) {
                 saveInfo.setDescription(mSaveDescription);
             }
-            saveInfo.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
+            if (mNegativeActionListener != null) {
+                saveInfo.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
+            }
 
             if (mCustomDescription != null) {
                 saveInfo.setCustomDescription(mCustomDescription);
@@ -188,6 +201,15 @@
             if (mSaveTriggerId != null) {
                 saveInfo.setTriggerId(mSaveTriggerId);
             }
+        } else if (mSaveInfoFlags != 0) {
+            saveInfo = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
+        } else {
+            saveInfo = null;
+        }
+        if (saveInfo != null) {
+            if (mSaveInfoDecorator != null) {
+                mSaveInfoDecorator.decorate(saveInfo, nodeResolver);
+            }
             builder.setSaveInfo(saveInfo.build());
         }
         if (mIgnoredIds != null) {
@@ -219,6 +241,9 @@
         if (mFooter != null) {
             builder.setFooter(mFooter);
         }
+        if (mUserData != null) {
+            builder.setUserData(mUserData);
+        }
         return builder.build();
     }
 
@@ -245,16 +270,19 @@
                 + ", disableDuration=" + mDisableDuration
                 + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
                 + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
+                + ", saveInfoDecorator=" + mSaveInfoDecorator
+                + ", userData=" + mUserData
                 + "]";
     }
 
-    enum ResponseType {
+    public enum ResponseType {
         NORMAL,
         NULL,
-        TIMEOUT
+        TIMEOUT,
+        FAILURE
     }
 
-    static class Builder {
+    public static final class Builder {
         private final List<CannedDataset> mDatasets = new ArrayList<>();
         private final ArrayList<Pair<Sanitizer, AutofillId[]>> mSanitizers = new ArrayList<>();
         private final ResponseType mResponseType;
@@ -281,6 +309,8 @@
         private long mDisableDuration;
         private AutofillId[] mFieldClassificationIds;
         private boolean mFieldClassificationIdsOverflow;
+        private SaveInfoDecorator mSaveInfoDecorator;
+        private UserData mUserData;
 
         public Builder(ResponseType type) {
             mResponseType = type;
@@ -308,10 +338,6 @@
          * Sets the required savable ids based on their {@code resourceId}.
          */
         public Builder setRequiredSavableIds(int type, String... ids) {
-            if (mRequiredSavableAutofillIds != null) {
-                throw new IllegalStateException("Already set required autofill ids: "
-                        + Arrays.toString(mRequiredSavableAutofillIds));
-            }
             mSaveType = type;
             mRequiredSavableIds = ids;
             return this;
@@ -321,10 +347,6 @@
          * Sets the required savable ids based on their {@code autofillId}.
          */
         public Builder setRequiredSavableAutofillIds(int type, AutofillId... ids) {
-            if (mRequiredSavableIds != null) {
-                throw new IllegalStateException("Already set required resource ids: "
-                        + Arrays.toString(mRequiredSavableIds));
-            }
             mSaveType = type;
             mRequiredSavableAutofillIds = ids;
             return this;
@@ -471,6 +493,23 @@
             mFooter = footer;
             return this;
         }
+
+        public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
+            assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
+            mSaveInfoDecorator = decorator;
+            return this;
+        }
+
+        /**
+         * Sets the package-specific UserData.
+         *
+         * <p>Overrides the default UserData for field classification.
+         */
+        public Builder setUserData(UserData userData) {
+            assertWithMessage("already set").that(mUserData).isNull();
+            mUserData = userData;
+            return this;
+        }
     }
 
     /**
@@ -488,7 +527,7 @@
      *               .build());
      * </pre class="prettyprint">
      */
-    static class CannedDataset {
+    public static class CannedDataset {
         private final Map<String, AutofillValue> mFieldValues;
         private final Map<AutofillId, AutofillValue> mFieldValuesById;
         private final Map<AutofillId, RemoteViews> mFieldPresentationsById;
@@ -572,7 +611,7 @@
                     + ", fieldFilters=" + mFieldFilters + "]";
         }
 
-        static class Builder {
+        public static class Builder {
             private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
             private final Map<AutofillId, AutofillValue> mFieldValuesById = new HashMap<>();
             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
@@ -749,4 +788,8 @@
             }
         }
     }
+
+    interface SaveInfoDecorator {
+        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
index 141b8d0..f5e7f87 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
@@ -21,7 +21,6 @@
 
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.Log;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
@@ -46,7 +45,6 @@
  * </ul>
  */
 public class CheckoutActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "CheckoutActivity";
     private static final long BUY_TIMEOUT_MS = 1000;
 
     static final String ID_CC_NUMBER = "cc_number";
@@ -76,12 +74,6 @@
     private FillExpectation mExpectation;
     private CountDownLatch mBuyLatch;
 
-    private static CheckoutActivity sInstance;
-
-    public CheckoutActivity() {
-        sInstance = this;
-    }
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -106,21 +98,6 @@
         mClearButton.setOnClickListener((v) -> resetFields());
     }
 
-    @Override
-    public void finish() {
-        super.finish();
-
-        sInstance = null;
-    }
-
-    static void finishIt(UiBot uiBot) {
-        if (sInstance != null) {
-            Log.d(TAG, "So long and thanks for all the fish!");
-            sInstance.finish();
-            uiBot.assertGoneByRelativeId(ID_CC_NUMBER, Timeouts.ACTIVITY_RESURRECTION);
-        }
-    }
-
     protected int getContentView() {
         return R.layout.checkout_activity;
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 6739a73..ac181ec 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -52,8 +52,6 @@
 import android.widget.RemoteViews;
 import android.widget.Spinner;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -62,17 +60,19 @@
 /**
  * Test case for an activity containing non-TextField views.
  */
-public class CheckoutActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<CheckoutActivity> mActivityRule =
-        new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class);
+public class CheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<CheckoutActivity> {
 
     private CheckoutActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<CheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
new file mode 100644
index 0000000..bb429d4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CompositeUserData;
+import android.service.autofill.UserData;
+import android.util.ArrayMap;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull // Unit test
+public class CompositeUserDataTest {
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mId2 = "4815162343";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+    private final String mAlgo = "algo";
+    private final String mAlgo2 = "algo2";
+    private final String mAlgo3 = "algo3";
+    private final String mAlgo4 = "algo4";
+
+    private final UserData mEmptyGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .build();
+    private final UserData mLoadedGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .add(mValue2, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo, createBundle(false))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo2, createBundle(false))
+            .build();
+    private final UserData mEmptyPackageUserData = new UserData.Builder(mId2, mValue3, mCategoryId3)
+            .build();
+    private final UserData mLoadedPackageUserData = new UserData
+            .Builder(mId2, mValue3, mCategoryId3)
+            .add(mValue4, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo3, createBundle(true))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo4, createBundle(true))
+            .build();
+
+
+    @Test
+    public void testMergeInvalid_bothNull() {
+        assertThrows(NullPointerException.class, () -> new CompositeUserData(null, null));
+    }
+
+    @Test
+    public void testMergeInvalid_nullPackageUserData() {
+        assertThrows(NullPointerException.class,
+                () -> new CompositeUserData(mEmptyGenericUserData, null));
+    }
+
+    @Test
+    public void testMerge_nullGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(null, mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(1);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(1);
+        assertThat(values[0]).isEqualTo(mValue3);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_bothEmpty() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(2);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(2);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_emptyGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    @Test
+    public void testMerge_emptyPackageUserData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId2);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+        assertThat(values[2]).isEqualTo(mValue2);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isFalse();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo2);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isFalse();
+    }
+
+
+    @Test
+    public void testMerge_bothHaveData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNotNull();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    private Bundle createBundle(Boolean isPackageBundle) {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("isPackage", isPackageBundle);
+        return bundle;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
index 7858278..49e772d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
@@ -33,24 +33,25 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Calendar;
 
 @AppModeFull // Service-specific test
-public class CustomDescriptionDateTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<DatePickerSpinnerActivity> mActivityRule = new
-            AutofillActivityTestRule<DatePickerSpinnerActivity>(DatePickerSpinnerActivity.class);
+public class CustomDescriptionDateTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DatePickerSpinnerActivity> {
 
     private DatePickerSpinnerActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
new file mode 100644
index 0000000..6c12c77
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.service.autofill.CustomDescription;
+import android.support.test.InstrumentationRegistry;
+import android.widget.RemoteViews;
+
+public final class CustomDescriptionHelper {
+
+    public static final String ID_SHOW = "show";
+    public static final String ID_HIDE = "hide";
+    public static final String ID_USERNAME_PLAIN = "username_plain";
+    public static final String ID_USERNAME_MASKED = "username_masked";
+    public static final String ID_PASSWORD_PLAIN = "password_plain";
+    public static final String ID_PASSWORD_MASKED = "password_masked";
+
+    private static final String sPackageName =
+            InstrumentationRegistry.getTargetContext().getPackageName();
+
+
+    public static CustomDescription.Builder newCustomDescriptionWithUsernameAndPassword() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_username_and_password));
+    }
+
+    public static CustomDescription.Builder newCustomDescriptionWithHiddenFields() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_hidden_fields));
+    }
+
+    private CustomDescriptionHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
index 08050e0..2f48835 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
@@ -30,6 +30,7 @@
 import android.service.autofill.CustomDescription;
 import android.service.autofill.ImageTransformation;
 import android.service.autofill.RegexValidator;
+import android.service.autofill.TextValueSanitizer;
 import android.service.autofill.Validator;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
@@ -40,25 +41,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.function.BiFunction;
 import java.util.regex.Pattern;
 
 @AppModeFull // Service-specific test
-public class CustomDescriptionTest extends AutoFillServiceTestCase {
-    @Rule
-    public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-            new AutofillActivityTestRule<>(LoginActivity.class);
-
-    private LoginActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
+public class CustomDescriptionTest extends AbstractLoginActivityTestCase {
 
     /**
      * Base test
@@ -100,6 +89,86 @@
     }
 
     @Test
+    public void testSanitizationBeforeBatchUpdates() throws Exception {
+        enableService();
+
+        final RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+        final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+
+        // Validator for sanitization
+        final Validator validCondition = new RegexValidator(usernameId, Pattern.compile("user"));
+
+        final RemoteViews update = newTemplate(-666); // layout id not really used
+        update.setTextViewText(R.id.first, "batch updated");
+
+        final CustomDescription customDescription = new CustomDescription.Builder(presentation)
+                .batchUpdate(validCondition,
+                        new BatchUpdates.Builder().updateTemplate(update).build())
+                .build();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                        usernameId)
+                .setCustomDescription(customDescription)
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("batch updated");
+    }
+
+    @Test
+    public void testSanitizationBeforeTransformations() throws Exception {
+        enableService();
+
+        final RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+        final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+
+        // Transformation
+        final CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(usernameId, Pattern.compile("user"), "transformed")
+                .build();
+
+        final CustomDescription customDescription = new CustomDescription.Builder(presentation)
+                .addChild(R.id.first, trans)
+                .build();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                        usernameId)
+                .setCustomDescription(customDescription)
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("transformed");
+    }
+
+    @Test
     public void validTransformation() throws Exception {
         testCustomDescription((usernameId, passwordId) -> {
             RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
index ee6e8e0..39f3a58 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
@@ -16,16 +16,22 @@
 
 package android.autofillservice.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.BatchUpdates;
 import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalOnClickAction;
+import android.service.autofill.InternalTransformation;
 import android.service.autofill.InternalValidator;
+import android.service.autofill.OnClickAction;
 import android.service.autofill.Transformation;
 import android.service.autofill.Validator;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
 import android.widget.RemoteViews;
 
 import org.junit.Test;
@@ -39,7 +45,9 @@
             new CustomDescription.Builder(mock(RemoteViews.class));
     private final BatchUpdates mValidUpdate =
             new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
+    private final Transformation mValidTransformation = mock(InternalTransformation.class);
     private final Validator mValidCondition = mock(InternalValidator.class);
+    private final OnClickAction mValidAction = mock(InternalOnClickAction.class);
 
     @Test
     public void testNullConstructor() {
@@ -52,7 +60,7 @@
     }
 
     @Test
-    public void testAddChild_invalidTransformation() {
+    public void testAddChild_invalidImplementation() {
         assertThrows(IllegalArgumentException.class,
                 () ->  mBuilder.addChild(42, mock(Transformation.class)));
     }
@@ -64,7 +72,7 @@
     }
 
     @Test
-    public void testBatchUpdate_invalidCondition() {
+    public void testBatchUpdate_invalidImplementation() {
         assertThrows(IllegalArgumentException.class,
                 () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
     }
@@ -74,4 +82,50 @@
         assertThrows(NullPointerException.class,
                 () ->  mBuilder.batchUpdate(mValidCondition, null));
     }
+
+    @Test
+    public void testSetOnClickAction_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addOnClickAction(42, null));
+    }
+
+    @Test
+    public void testSetOnClickAction_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.addOnClickAction(42, mock(OnClickAction.class)));
+    }
+
+    @Test
+    public void testSetOnClickAction_thereCanBeOnlyOne() {
+        final CustomDescription customDescription = mBuilder
+                .addOnClickAction(42, mock(InternalOnClickAction.class))
+                .addOnClickAction(42, mValidAction)
+                .build();
+        final SparseArray<InternalOnClickAction> actions = customDescription.getActions();
+        assertThat(actions.size()).isEqualTo(1);
+        assertThat(actions.keyAt(0)).isEqualTo(42);
+        assertThat(actions.valueAt(0)).isSameAs(mValidAction);
+    }
+
+    @Test
+    public void testBuild_valid() {
+        new CustomDescription.Builder(mock(RemoteViews.class)).build();
+        new CustomDescription.Builder(mock(RemoteViews.class))
+            .addChild(108, mValidTransformation)
+            .batchUpdate(mValidCondition, mValidUpdate)
+            .addOnClickAction(42, mValidAction)
+            .build();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        mBuilder.build();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addChild(108, mValidTransformation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.batchUpdate(mValidCondition, mValidUpdate));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addOnClickAction(42, mValidAction));
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index a32351a..283edd0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -15,8 +15,6 @@
  */
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Activity;
@@ -25,9 +23,10 @@
 import android.service.autofill.CustomDescription;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
 import android.widget.RemoteViews;
 
+import androidx.annotation.NonNull;
+
 import static org.junit.Assume.assumeTrue;
 
 import org.junit.Test;
@@ -46,11 +45,32 @@
  * <p>The overall behavior should be the same in both cases, although the implementation of the
  * tests per se will be sligthly different.
  */
-abstract class CustomDescriptionWithLinkTestCase extends AutoFillServiceTestCase {
+abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends
+        AutoFillServiceTestCase.AutoActivityLaunch<A> {
 
-    private static final String TAG = "CustomDescriptionWithLinkTestCase";
     private static final String ID_LINK = "link";
 
+    private final Class<A> mActivityClass;
+
+    protected A mActivity;
+
+    protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    protected void startActivity() {
+        startActivity(false);
+    }
+
+    protected void startActivity(boolean remainOnRecents) {
+        final Intent intent = new Intent(mContext, mActivityClass);
+        if (remainOnRecents) {
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        mActivity = launchActivity(intent);
+    }
+
     /**
      * Tests scenarios when user taps a link in the custom description and then taps back:
      * the Save UI should have been restored.
@@ -69,27 +89,20 @@
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
         assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
 
-        final int width = mUiBot.getDevice().getDisplayWidth();
-        final int heigth = mUiBot.getDevice().getDisplayHeight();
-        final int min = Math.min(width, heigth);
-
-        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= 500);
-        Log.d(TAG, "testTapLink_changeOrientationThenTapBack(): screen size is "
-                + width + "x" + heigth);
+        mUiBot.setScreenResolution();
 
         mUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
-            runShellCommand("wm size 1080x1920");
-            runShellCommand("wm density 320");
             saveUiRestoredAfterTappingLinkTest(
                     PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
         } finally {
-            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
             try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
                 cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
             } finally {
-                runShellCommand("wm density reset");
-                runShellCommand("wm size reset");
+                mUiBot.resetScreenResolution();
             }
         }
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 81b3fa2..7159783 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -17,11 +17,12 @@
 package android.autofillservice.cts;
 
 import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.sendKeyEvent;
 
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.content.IntentSender;
 import android.platform.test.annotations.AppModeFull;
+import android.widget.EditText;
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -31,21 +32,14 @@
 
 public class DatasetFilteringTest extends AbstractLoginActivityTestCase {
 
-    private static String sMaxDatasets;
-
     @BeforeClass
-    public static void setMaxDatasets() {
-        sMaxDatasets = runShellCommand("cmd autofill get max_visible_datasets");
-        runShellCommand("cmd autofill set max_visible_datasets 4");
+    public static void setMaxDatasets() throws Exception {
+        Helper.setMaxVisibleDatasets(4);
     }
 
     @AfterClass
-    public static void restoreMaxDatasets() {
-        runShellCommand("cmd autofill set max_visible_datasets %s", sMaxDatasets);
-    }
-
-    private static void sendKeyEvents(String keyCode) {
-        runShellCommand("input keyevent " + keyCode);
+    public static void restoreMaxDatasets() throws Exception {
+        Helper.setMaxVisibleDatasets(0);
     }
 
     private void changeUsername(CharSequence username) {
@@ -139,26 +133,26 @@
         mUiBot.assertDatasets(aa, ab, b);
 
         // Only two datasets start with 'a'
-        sendKeyEvents("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
         mUiBot.assertDatasets(aa, ab);
 
         // Only one dataset start with 'aa'
-        sendKeyEvents("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
         mUiBot.assertDatasets(aa);
 
         // Only two datasets start with 'a'
-        sendKeyEvents("KEYCODE_DEL");
+        sendKeyEvent("KEYCODE_DEL");
         mUiBot.assertDatasets(aa, ab);
 
         // With no filter text all datasets should be shown
-        sendKeyEvents("KEYCODE_DEL");
+        sendKeyEvent("KEYCODE_DEL");
         mUiBot.assertDatasets(aa, ab, b);
 
         // No dataset start with 'aaa'
         final MyAutofillCallback callback = mActivity.registerCallback();
-        sendKeyEvents("KEYCODE_A");
-        sendKeyEvents("KEYCODE_A");
-        sendKeyEvents("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
         callback.assertUiHiddenEvent(mActivity.getUsername());
         mUiBot.assertNoDatasets();
     }
@@ -473,11 +467,114 @@
         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
 
         // All datasets start with 'a'
-        sendKeyEvents("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
 
         // Only the regex datasets should start with 'ab'
-        sendKeyEvents("KEYCODE_B");
+        sendKeyEvent("KEYCODE_B");
         mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
     }
+
+    @Test
+    public void testFilter_resetFilter_chooseFirst() throws Exception {
+        resetFilterTest(1);
+    }
+
+    @Test
+    public void testFilter_resetFilter_chooseSecond() throws Exception {
+        resetFilterTest(2);
+    }
+
+    @Test
+    public void testFilter_resetFilter_chooseThird() throws Exception {
+        resetFilterTest(3);
+    }
+
+    private void resetFilterTest(int number) throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(createPresentation(aa))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(createPresentation(ab))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .build());
+
+        final String chosenOne;
+        switch (number) {
+            case 1:
+                chosenOne = aa;
+                mActivity.expectAutoFill("aa");
+                break;
+            case 2:
+                chosenOne = ab;
+                mActivity.expectAutoFill("ab");
+                break;
+            case 3:
+                chosenOne = b;
+                mActivity.expectAutoFill("b");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dataset number: " + number);
+        }
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText username = mActivity.getUsername();
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        callback.assertUiShownEvent(username);
+
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // One dataset starts with 'aa'
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa);
+
+        // Filter all out
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert aa is showing again
+        changeUsername("aa");
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets(aa);
+
+        // Delete one more and assert two datasets showing
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Reset back to all choices
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // select the choice
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
index f0c3e04..35f3b73 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
@@ -13,21 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.autofillservice.cts;
 
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Rule;
-
 @AppModeFull // Unit test
 public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<DatePickerCalendarActivity> mActivityRule =
-        new AutofillActivityTestRule<DatePickerCalendarActivity>(DatePickerCalendarActivity.class);
-
     @Override
-    protected DatePickerCalendarActivity getDatePickerActivity() {
-        return mActivityRule.getActivity();
+    protected AutofillActivityTestRule<DatePickerCalendarActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerCalendarActivity>(
+                DatePickerCalendarActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
index 0fb026a..291d5aa 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
@@ -13,21 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.autofillservice.cts;
 
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Rule;
-
 @AppModeFull // Unit test
 public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<DatePickerSpinnerActivity> mActivityRule =
-        new AutofillActivityTestRule<DatePickerSpinnerActivity>(DatePickerSpinnerActivity.class);
-
     @Override
-    protected DatePickerSpinnerActivity getDatePickerActivity() {
-        return mActivityRule.getActivity();
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
index 3483257..13cb12b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
@@ -36,14 +36,14 @@
 /**
  * Base class for {@link AbstractDatePickerActivity} tests.
  */
-abstract class DatePickerTestCase<T extends AbstractDatePickerActivity>
-        extends AutoFillServiceTestCase {
+abstract class DatePickerTestCase<A extends AbstractDatePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
 
-    protected abstract T getDatePickerActivity();
+    protected A mActivity;
 
     @Test
     public void testAutoFillAndSave() throws Exception {
-        final T activity = getDatePickerActivity();
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
 
         // Set service.
         enableService();
@@ -62,10 +62,10 @@
                     .build())
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
                 .build());
-        activity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
+        mActivity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
 
         // Trigger auto-fill.
-        activity.onOutput((v) -> v.requestFocus());
+        mActivity.onOutput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
 
         // Assert properties of DatePicker field.
@@ -76,11 +76,11 @@
         mUiBot.selectDataset("The end of the world");
 
         // Check the results.
-        activity.assertAutoFilled();
+        mActivity.assertAutoFilled();
 
         // Trigger save.
-        activity.setDate(2010, Calendar.DECEMBER, 12);
-        activity.tapOk();
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
 
         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
index 1e9fcf2..175d0cb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
@@ -24,21 +24,21 @@
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
-public class DialogLauncherActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<DialogLauncherActivity> mActivityRule =
-            new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class);
+public class DialogLauncherActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DialogLauncherActivity> {
 
     private DialogLauncherActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<DialogLauncherActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
index 2f4de15..d4d1ff4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
@@ -16,6 +16,9 @@
 
 package android.autofillservice.cts;
 
+import static android.autofillservice.cts.Timeouts.ACTIVITY_RESURRECTION;
+import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
+
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.content.Intent;
 import android.os.SystemClock;
@@ -28,7 +31,7 @@
 /**
  * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
  */
-public class DisableAutofillTest extends AutoFillServiceTestCase {
+public class DisableAutofillTest extends AutoFillServiceTestCase.ManualActivityLaunch {
 
     private static final String TAG = "DisableAutofillTest";
 
@@ -81,7 +84,10 @@
         ASSERT_ENABLED_AND_AUTOFILL
     }
 
-    private void launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
+    /**
+     * Launches and finishes {@link SimpleSaveActivity}, returning how long it took.
+     */
+    private long launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
         Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
         sReplier.assertNoUnhandledFillRequests();
 
@@ -96,6 +102,7 @@
 
         }
 
+        final long before = SystemClock.elapsedRealtime();
         final SimpleSaveActivity activity = startSimpleSaveActivity();
         final MyAutofillCallback callback = activity.registerCallback();
 
@@ -128,9 +135,13 @@
         } finally {
             activity.finish();
         }
+        return SystemClock.elapsedRealtime() - before;
     }
 
-    private void launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
+    /**
+     * Launches and finishes {@link PreSimpleSaveActivity}, returning how long it took.
+     */
+    private long launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
         Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
         sReplier.assertNoUnhandledFillRequests();
 
@@ -143,6 +154,7 @@
                     .build());
         }
 
+        final long before = SystemClock.elapsedRealtime();
         final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
         final MyAutofillCallback callback = activity.registerCallback();
 
@@ -170,6 +182,7 @@
         } finally {
             activity.finish();
         }
+        return SystemClock.elapsedRealtime() - before;
     }
 
     @Test
@@ -198,19 +211,19 @@
         enableService();
 
         // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
 
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
 
         // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
 
         // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+        passedTime += launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
 
         // Wait for the timeout, then try again, autofilling it this time.
-        SystemClock.sleep(duration + 1);
+        sleep(passedTime, duration);
         launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
 
         // Also try it on another activity.
@@ -268,7 +281,7 @@
         enableService();
 
         // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
 
         // Set expectations.
         sReplier.addResponse(new CannedFillResponse.Builder()
@@ -277,16 +290,16 @@
                 .build());
 
         // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
 
         // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+        passedTime += launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
 
         // Make sure other app is working.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
 
         // Wait for the timeout, then try again, autofilling it this time.
-        SystemClock.sleep(duration + 1);
+        sleep(passedTime, duration);
         launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
     }
 
@@ -317,11 +330,26 @@
 
     private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
             throws Exception {
-        Timeouts.ACTIVITY_RESURRECTION.run(
+        ACTIVITY_RESURRECTION.run(
                 "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
                 () -> {
                     return activity.getAutofillManager().isEnabled() == expected
                             ? Boolean.TRUE : null;
                 });
     }
+
+    private void sleep(long passedTime, long disableDuration) {
+        final long napTime = disableDuration - passedTime + 500;
+        if (napTime <= 0) {
+            // Throw an exception so ACTIVITY_RESURRECTION is increased
+            throw new RetryableException("took longer than expcted to launch activities: "
+                            + "passedTime=" + passedTime + "ms, disableDuration=" + disableDuration
+                            + ", ACTIVITY_RESURRECTION=" + ACTIVITY_RESURRECTION
+                            + ", CALLBACK_NOT_CALLED_TIMEOUT_MS=" + CALLBACK_NOT_CALLED_TIMEOUT_MS);
+        }
+        Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
+                + passedTime + ")");
+
+        SystemClock.sleep(napTime);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 846dcc4..53ff280 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -30,29 +30,26 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
-public class DuplicateIdActivityTest extends AutoFillServiceTestCase {
-    private static final String LOG_TAG = DuplicateIdActivityTest.class.getSimpleName();
-    @Rule
-    public final AutofillActivityTestRule<DuplicateIdActivity> mActivityRule = new AutofillActivityTestRule<>(
-            DuplicateIdActivity.class);
+public class DuplicateIdActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DuplicateIdActivity> {
 
-    private DuplicateIdActivity mActivity;
+    private static final String TAG = "DuplicateIdActivityTest";
+
+    @Override
+    protected AutofillActivityTestRule<DuplicateIdActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DuplicateIdActivity>(DuplicateIdActivity.class);
+    }
 
     @Before
     public void setup() throws Exception {
         Helper.disableAutoRotation(mUiBot);
         mUiBot.setScreenOrientation(0);
-
-        mActivity = mActivityRule.getActivity();
     }
 
     @After
     public void teardown() {
-        mActivity.finish();
-
         Helper.allowAutoRotation();
     }
 
@@ -90,7 +87,7 @@
         runShellCommand("input keyevent KEYCODE_TAB");
 
         final InstrumentedAutoFillService.FillRequest request1 = sReplier.getNextFillRequest();
-        Log.v(LOG_TAG, "request1: " + request1);
+        Log.v(TAG, "request1: " + request1);
 
         final AssistStructure.ViewNode[] views1 = findViews(request1);
         final AssistStructure.ViewNode view1 = views1[0];
@@ -98,8 +95,8 @@
         final AutofillId id1 = view1.getAutofillId();
         final AutofillId id2 = view2.getAutofillId();
 
-        Log.i(LOG_TAG, "view1=" + id1);
-        Log.i(LOG_TAG, "view2=" + id2);
+        Log.v(TAG, "view1=" + id1);
+        Log.v(TAG, "view2=" + id2);
 
         // Both checkboxes use the same id
         assertThat(view1.getId()).isEqualTo(view2.getId());
@@ -115,7 +112,7 @@
         mUiBot.assertShownByRelativeId(DUPLICATE_ID);
         // Ignore 2nd request.
         final InstrumentedAutoFillService.FillRequest request2 = sReplier.getNextFillRequest();
-        Log.v(LOG_TAG, "request2: " + request2);
+        Log.v(TAG, "request2: " + request2);
 
         // Select other field to trigger new partition (because server didn't return 2nd field
         // on 1st response)
@@ -123,15 +120,15 @@
         runShellCommand("input keyevent KEYCODE_TAB");
 
         final InstrumentedAutoFillService.FillRequest request3 = sReplier.getNextFillRequest();
-        Log.v(LOG_TAG, "request3: " + request3);
+        Log.v(TAG, "request3: " + request3);
         final AssistStructure.ViewNode[] views2 = findViews(request3);
         final AssistStructure.ViewNode recreatedView1 = views2[0];
         final AssistStructure.ViewNode recreatedView2 = views2[1];
         final AutofillId recreatedId1 = recreatedView1.getAutofillId();
         final AutofillId recreatedId2 = recreatedView2.getAutofillId();
 
-        Log.i(LOG_TAG, "restored view1=" + recreatedId1);
-        Log.i(LOG_TAG, "restored view2=" + recreatedId2);
+        Log.v(TAG, "restored view1=" + recreatedId1);
+        Log.v(TAG, "restored view2=" + recreatedId2);
 
         // For the restoring logic the two views are the same. Hence it might happen that the first
         // view is restored with the autofill id of the second view or the other way round.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index 28ecdf7..456f5cf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -48,25 +48,24 @@
 import android.app.assist.AssistStructure.ViewNode;
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
  * Test case for an activity containing useless auto-fill data that should be optimized out.
  */
-public class FatActivityTest extends AutoFillServiceTestCase {
+public class FatActivityTest extends AutoFillServiceTestCase.AutoActivityLaunch<FatActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<FatActivity> mActivityRule =
-        new AutofillActivityTestRule<FatActivity>(FatActivity.class);
-
-    private FatActivity mFatActivity;
+    private FatActivity mActivity;
     private ViewNode mRoot;
 
-    @Before
-    public void setActivity() {
-        mFatActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<FatActivity> getActivityRule() {
+        return new AutofillActivityTestRule<FatActivity>(FatActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
@@ -78,7 +77,7 @@
         sReplier.addResponse(NO_RESPONSE);
 
         // Trigger auto-fill.
-        mFatActivity.onInput((v) -> v.requestFocus());
+        mActivity.onInput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         mUiBot.assertNoDatasetsEver();
 
@@ -154,7 +153,7 @@
         sReplier.addResponse(NO_RESPONSE);
 
         // Trigger autofill.
-        mFatActivity.onInput((v) -> mFatActivity.getAutofillManager().requestAutofill(v));
+        mActivity.onInput((v) -> mActivity.getAutofillManager().requestAutofill(v));
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         mUiBot.assertNoDatasetsEver();
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
index 1ba93c4..2a14468 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -23,31 +23,28 @@
 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_EDIT_DISTANCE;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.autofillservice.cts.Helper.FieldClassificationResult;
 import android.autofillservice.cts.common.SettingsStateChangerRule;
-import android.content.Context;
+import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.UserData;
-import android.support.test.InstrumentationRegistry;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.widget.EditText;
 
-import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.List;
 
 @AppModeFull // Service-specific test
-public class FieldsClassificationTest extends AutoFillServiceTestCase {
-
-    private static final Context sContext = InstrumentationRegistry.getContext();
+public class FieldsClassificationTest extends AbstractGridActivityTestCase {
 
     @ClassRule
     public static final SettingsStateChangerRule sFeatureEnabler =
@@ -64,7 +61,7 @@
 
     @ClassRule
     public static final SettingsStateChangerRule sUserDataMinValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5");
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
 
     @ClassRule
     public static final SettingsStateChangerRule sUserDataMaxValueChanger =
@@ -74,18 +71,13 @@
     public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
 
-    @Rule
-    public final AutofillActivityTestRule<GridActivity> mActivityRule =
-            new AutofillActivityTestRule<GridActivity>(GridActivity.class);
-
-
-    private GridActivity mActivity;
     private AutofillManager mAfm;
+    private Bundle mLast4Bundle = new Bundle();
 
-    @Before
-    public void setFixtures() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected void postActivityLaunched() {
         mAfm = mActivity.getAutofillManager();
+        mLast4Bundle.putInt("suffix", 4);
     }
 
     @Test
@@ -104,7 +96,7 @@
         // Check algorithms
         final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
         assertThat(names.size()).isAtLeast(1);
-        final String defaultAlgorithm = getDefaultAlgorithm();
+        final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
         assertThat(defaultAlgorithm).isNotEmpty();
         assertThat(names).contains(defaultAlgorithm);
 
@@ -121,10 +113,12 @@
         enableService();
         mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
                 .build());
+        assertThat(mAfm.getUserData()).isNotNull();
         assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
         final UserData userData = mAfm.getUserData();
         assertThat(userData.getId()).isEqualTo("user_data_id");
         assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getFieldClassificationAlgorithms()).isNull();
 
         disableService();
         assertThat(mAfm.getUserData()).isNull();
@@ -132,12 +126,21 @@
     }
 
     @Test
+    public void testRequiredAlgorithmsAvailable() throws Exception {
+        enableService();
+        final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(availableAlgorithms).isNotNull();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
+    }
+
+    @Test
     public void testUserDataConstraints() throws Exception {
         // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
         // make sure the getters below are reading the right property.
         assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
         assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
-        assertThat(UserData.getMinValueLength()).isEqualTo(5);
+        assertThat(UserData.getMinValueLength()).isEqualTo(4);
         assertThat(UserData.getMaxValueLength()).isEqualTo(50);
         assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
     }
@@ -160,6 +163,112 @@
         simpleHitTest(true, null);
     }
 
+    @Test
+    public void testMiss_exactMatchAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "t 1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AutofillId fieldId = field.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "t 5678");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), null);
+    }
+
+    @Test
+    public void testHit_exactMatchLast4Algorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AutofillId fieldId = field.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId, "cat", 1);
+    }
+
+    @Test
+    public void testHit_useDefaultAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("dog",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AutofillId fieldId = field.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId, "cat", 1);
+    }
+
     private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
         // Set service.
         enableService();
@@ -196,6 +305,46 @@
     }
 
     @Test
+    public void testHit_sameValueForMultipleCategories() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "FULLY", "cat1")
+                .add("FULLY", "cat2")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AutofillId fieldId = field.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId,
+                                new String[] { "cat1", "cat2"},
+                                new float[] {1, 1})
+                });
+    }
+
+    @Test
     public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
         manyUserData_oneDetectableField(true);
     }
@@ -339,6 +488,59 @@
     }
 
     @Test
+    public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .setFieldClassificationAlgorithmForCategory("myId",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("otherId",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AutofillId fieldId1 = field1.getAutofillId();
+        final EditText field2 = mActivity.getCell(1, 2);
+        final AutofillId fieldId2 = field2.getAutofillId();
+        final EditText field3 = mActivity.getCell(2, 1);
+        final AutofillId fieldId3 = field3.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId1, fieldId2)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "E1234"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:   0% u2: 100%
+        mActivity.setText(2, 1, "fULLy"); // u1:   0% u2:  20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" },
+                                new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2, new String[] { "otherId" },
+                                new float[] { 1.0F }),
+                        new FieldClassificationResult(fieldId3, new String[] { "otherId" },
+                                new float[] { 0.2F })});
+    }
+
+    @Test
     public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
         // Set service.
         enableService();
@@ -454,8 +656,105 @@
         assertFillEventForContextCommitted(events.get(0));
     }
 
-    private String getDefaultAlgorithm() {
-        return mAfm.getDefaultFieldClassificationAlgorithm();
+    @Test
+    public void testHit_usePackageUserData() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "TEST1", "cat")
+                .setFieldClassificationAlgorithm(null, null)
+                .build());
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AutofillId fieldId = field.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .setUserData(new UserData.Builder("id2", "TEST2", "cat")
+                        .setFieldClassificationAlgorithm(null, null)
+                        .build())
+                .build())
+                .addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "test1");
+
+        // Finish context
+        mAfm.commit();
+
+        final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(packageUserDataEvent, fieldId, "cat", 0.8F);
+
+        // Need to switch focus first
+        mActivity.focusCell(1, 2);
+
+        // Trigger second autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId, "cat", 1.0F);
+    }
+
+    @Test
+    public void testHit_mergeUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AutofillId fieldId1 = field1.getAutofillId();
+        final EditText field2 = mActivity.getCell(1, 2);
+        final AutofillId fieldId2 = field2.getAutofillId();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(fieldId1, fieldId2)
+                .setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
+                        .add("EMPTY", "myId")
+                        .build())
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1:  20%, u2: 60%
+        mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1, new String[] { "otherId", "myId" },
+                                new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId2, new String[] { "myId", "otherId" },
+                                new float[] { 1.0F, 0.2F }),
+                });
     }
 
     /*
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
index 1eab92d..2e1ae0d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -53,8 +53,6 @@
 
 import com.google.common.collect.ImmutableMap;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.List;
@@ -66,18 +64,7 @@
  * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
  */
 @AppModeFull // Service-specific test
-public class FillEventHistoryTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-            new AutofillActivityTestRule<LoginActivity>(LoginActivity.class);
-
-    private LoginActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
+public class FillEventHistoryTest extends AbstractLoginActivityTestCase {
 
     @Test
     public void testDatasetAuthenticationSelected() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
index 70d7471..f0b7bad 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
@@ -56,6 +56,7 @@
     @Mock private RemoteViews mHeader;
     @Mock private RemoteViews mFooter;
     @Mock private IntentSender mIntentSender;
+    private final UserData mUserData = new UserData.Builder("id", "value", "cat").build();
 
     @Test
     public void testBuilder_setAuthentication_invalid() {
@@ -109,6 +110,19 @@
     }
 
     @Test
+    public void testBuilder_setUserDataInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder()
+                .setUserData(null));
+    }
+
+    @Test
+    public void testBuilder_setUserDataAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setUserData(mUserData));
+    }
+
+    @Test
     public void testBuilder_setFlag_invalid() {
         assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
     }
@@ -244,5 +258,6 @@
                 () -> mBuilder.setFieldClassificationIds(mAutofillId));
         assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
         assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
index d33f142..b95fec6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
@@ -17,6 +17,8 @@
 package android.autofillservice.cts;
 
 import android.os.Bundle;
+import android.widget.FrameLayout;
+
 import androidx.annotation.Nullable;
 
 import java.util.concurrent.CountDownLatch;
@@ -30,6 +32,7 @@
             FragmentContainerActivity.class.getName() + "#FRAGMENT_TAG";
     private CountDownLatch mResumed = new CountDownLatch(1);
     private CountDownLatch mStopped = new CountDownLatch(0);
+    private FrameLayout mRootContainer;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -37,6 +40,8 @@
 
         setContentView(R.layout.fragment_container);
 
+        mRootContainer = findViewById(R.id.rootContainer);
+
         // have to manually add fragment as we cannot remove it otherwise
         getFragmentManager().beginTransaction().add(R.id.rootContainer,
                 new FragmentWithEditText(), FRAGMENT_TAG).commitNow();
@@ -70,6 +75,17 @@
         mStopped.countDown();
     }
 
+    /**
+     * Sets whether the root container is focusable or not.
+     *
+     * <p>It's initially set as {@code trye} in the XML layout so autofill is not automatically
+     * triggered in the edit text before the service is prepared to handle it.
+     */
+    public void setRootContainerFocusable(boolean focusable) {
+        mRootContainer.setFocusable(focusable);
+        mRootContainer.setFocusableInTouchMode(focusable);
+    }
+
     public boolean waitUntilResumed() throws InterruptedException {
         return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
index 65deff5..e1379c3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
@@ -134,11 +134,12 @@
     }
 
     public String getText(int row, int column) throws InterruptedException {
+        final long timeoutMs = 100;
         final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
-        onCell(row, column, (c) -> queue.offer(c.getText().toString()));
-        final String text = queue.poll(100, TimeUnit.MILLISECONDS);
+        onCell(row, column, (c) -> Helper.offer(queue, c.getText().toString(), timeoutMs));
+        final String text = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
         if (text == null) {
-            throw new RetryableException("text not set in 100ms");
+            throw new RetryableException("text not set in " + timeoutMs + "ms");
         }
         return text;
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index fc50da1..69da062 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -16,7 +16,6 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
 import static android.autofillservice.cts.UiBot.PORTRAIT;
 import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
@@ -34,7 +33,9 @@
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
+import android.autofillservice.cts.common.OneTimeSettingsListener;
 import android.autofillservice.cts.common.SettingsHelper;
+import android.autofillservice.cts.common.ShellHelper;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -63,44 +64,49 @@
 import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.BitmapUtils;
-import com.android.compatibility.common.util.RequiredFeatureRule;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 
 /**
  * Helper for common funcionalities.
  */
-final class Helper {
+public final class Helper {
 
-    static final String TAG = "AutoFillCtsHelper";
+    public static final String TAG = "AutoFillCtsHelper";
 
-    static final boolean VERBOSE = false;
+    public static final boolean VERBOSE = false;
 
-    static final String MY_PACKAGE = "android.autofillservice.cts";
+    public static final String MY_PACKAGE = "android.autofillservice.cts";
 
-    static final String ID_USERNAME_LABEL = "username_label";
-    static final String ID_USERNAME = "username";
-    static final String ID_PASSWORD_LABEL = "password_label";
-    static final String ID_PASSWORD = "password";
-    static final String ID_LOGIN = "login";
-    static final String ID_OUTPUT = "output";
-    static final String ID_STATIC_TEXT = "static_text";
+    public static final String ID_USERNAME_LABEL = "username_label";
+    public static final String ID_USERNAME = "username";
+    public static final String ID_PASSWORD_LABEL = "password_label";
+    public static final String ID_PASSWORD = "password";
+    public static final String ID_LOGIN = "login";
+    public static final String ID_OUTPUT = "output";
+    public static final String ID_STATIC_TEXT = "static_text";
 
     public static final String NULL_DATASET_ID = null;
 
+    public static final char LARGE_STRING_CHAR = '6';
+    // NOTE: cannot be much large as it could ANR and fail the test.
+    public static final int LARGE_STRING_SIZE = 100_000;
+    public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
+            .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
+
     /**
      * Can be used in cases where the autofill values is required by irrelevant (like adding a
      * value to an authenticated dataset).
      */
     public static final String UNUSED_AUTOFILL_VALUE = null;
 
-    private static final String CMD_LIST_SESSIONS = "cmd autofill list sessions";
-
     private static final String ACCELLEROMETER_CHANGE =
             "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
                     + "--bind value:i:%d";
@@ -108,6 +114,10 @@
     private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
             + "/CtsAutoFillServiceTestCases";
 
+    private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
+            "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
+            OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
+
     private static final String RESOURCE_BOOLEAN_CONFIG_FORCE_DEFAULT_ORIENTATION =
             "config_forceDefaultOrientation";
 
@@ -171,12 +181,12 @@
     }
 
     @NonNull
-    static String toString(@NonNull AssistStructure structure) {
+    public static String toString(@NonNull AssistStructure structure) {
         return toString(structure, new StringBuilder());
     }
 
     @Nullable
-    static String toString(@Nullable AutofillValue value) {
+    public static String toString(@Nullable AutofillValue value) {
         if (value == null) return null;
         if (value.isText()) {
             // We don't care about PII...
@@ -189,14 +199,14 @@
     /**
      * Dump the assist structure on logcat.
      */
-    static void dumpStructure(String message, AssistStructure structure) {
+    public static void dumpStructure(String message, AssistStructure structure) {
         Log.i(TAG, toString(structure, new StringBuilder(message)));
     }
 
     /**
      * Dump the contexts on logcat.
      */
-    static void dumpStructure(String message, List<FillContext> contexts) {
+    public static void dumpStructure(String message, List<FillContext> contexts) {
         for (FillContext context : contexts) {
             dumpStructure(message, context.getStructure());
         }
@@ -205,14 +215,14 @@
     /**
      * Dumps the state of the autofill service on logcat.
      */
-    static void dumpAutofillService() {
+    public static void dumpAutofillService() {
         Log.i(TAG, "dumpsys autofill\n\n" + runShellCommand("dumpsys autofill"));
     }
 
     /**
      * Sets whether the user completed the initial setup.
      */
-    static void setUserComplete(Context context, boolean complete) {
+    public static void setUserComplete(Context context, boolean complete) {
         SettingsHelper.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
     }
 
@@ -250,7 +260,7 @@
      * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
      */
     @NonNull
-    static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
             @Nullable Object value) {
         if (value == null) return builder;
 
@@ -270,7 +280,7 @@
      * Appends a field value to a {@link StringBuilder} when it's {@code true}.
      */
     @NonNull
-    static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
             boolean value) {
         if (value) {
             builder.append(", ").append(field);
@@ -281,7 +291,7 @@
     /**
      * Gets a node if it matches the filter criteria for the given id.
      */
-    static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
+    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
             @NonNull NodeFilter<ViewNode> filter) {
         Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
         final int nodes = structure.getWindowNodeCount();
@@ -299,7 +309,7 @@
     /**
      * Gets a node if it matches the filter criteria for the given id.
      */
-    static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
+    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
             @NonNull NodeFilter<ViewNode> filter) {
         for (FillContext context : contexts) {
             ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
@@ -313,7 +323,7 @@
     /**
      * Gets a node if it matches the filter criteria for the given id.
      */
-    static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
+    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
             @NonNull NodeFilter<ViewNode> filter) {
         if (filter.matches(node, id)) {
             return node;
@@ -333,42 +343,42 @@
     /**
      * Gets a node given its Android resource id, or {@code null} if not found.
      */
-    static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
+    public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
         return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
     }
 
     /**
      * Gets a node given its Android resource id, or {@code null} if not found.
      */
-    static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
+    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
         return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
     }
 
     /**
      * Gets a node given its Android resource id, or {@code null} if not found.
      */
-    static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
+    public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
         return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
     }
 
     /**
      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
      */
-    static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
+    public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
         return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
     }
 
     /**
      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
      */
-    static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
+    public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
         return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
     }
 
     /**
      * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
      */
-    static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
+    public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
         return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
     }
 
@@ -376,7 +386,7 @@
      * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
      * found.
      */
-    static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
+    public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
         return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
     }
 
@@ -384,7 +394,7 @@
      * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
      * not found.
      */
-    static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
+    public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
         return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
     }
 
@@ -392,7 +402,7 @@
      * Gets the {@code name} attribute of a node representing an HTML input tag.
      */
     @Nullable
-    static String getHtmlName(@NonNull ViewNode node) {
+    public static String getHtmlName(@NonNull ViewNode node) {
         final HtmlInfo htmlInfo = node.getHtmlInfo();
         if (htmlInfo == null) {
             return null;
@@ -414,21 +424,21 @@
     /**
      * Gets a node given its expected text, or {@code null} if not found.
      */
-    static ViewNode findNodeByText(AssistStructure structure, String text) {
+    public static ViewNode findNodeByText(AssistStructure structure, String text) {
         return findNodeByFilter(structure, text, TEXT_FILTER);
     }
 
     /**
      * Gets a node given its expected text, or {@code null} if not found.
      */
-    static ViewNode findNodeByText(ViewNode node, String text) {
+    public static ViewNode findNodeByText(ViewNode node, String text) {
         return findNodeByFilter(node, text, TEXT_FILTER);
     }
 
     /**
      * Gets a view that contains the an autofill hint, or {@code null} if not found.
      */
-    static View findViewByAutofillHint(Activity activity, String hint) {
+    public static View findViewByAutofillHint(Activity activity, String hint) {
         final View rootView = activity.getWindow().getDecorView().getRootView();
         return findViewByAutofillHint(rootView, hint);
     }
@@ -437,7 +447,7 @@
      * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
      * not found.
      */
-    static View findViewByAutofillHint(View view, String hint) {
+    public static View findViewByAutofillHint(View view, String hint) {
         if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
         if ((view instanceof ViewGroup)) {
             final ViewGroup group = (ViewGroup) view;
@@ -453,14 +463,14 @@
      * Gets a view (or a descendant of it) that has the given {@code id}, or {@code null} if
      * not found.
      */
-    static ViewNode findNodeByAutofillId(AssistStructure structure, AutofillId id) {
+    public static ViewNode findNodeByAutofillId(AssistStructure structure, AutofillId id) {
         return findNodeByFilter(structure, id, AUTOFILL_ID_FILTER);
     }
 
     /**
      * Asserts a text-based node is sanitized.
      */
-    static void assertTextIsSanitized(ViewNode node) {
+    public static void assertTextIsSanitized(ViewNode node) {
         final CharSequence text = node.getText();
         final String resourceId = node.getIdEntry();
         if (!TextUtils.isEmpty(text)) {
@@ -475,7 +485,7 @@
         assertThat(node.getTextIdEntry()).isNull();
     }
 
-    static void assertNodeHasNoAutofillValue(ViewNode node) {
+    public static void assertNodeHasNoAutofillValue(ViewNode node) {
         final AutofillValue value = node.getAutofillValue();
         if (value != null) {
             final String text = value.isText() ? value.getTextValue().toString() : "N/A";
@@ -486,7 +496,7 @@
     /**
      * Asserts the contents of a text-based node that is also auto-fillable.
      */
-    static void assertTextOnly(ViewNode node, String expectedValue) {
+    public static void assertTextOnly(ViewNode node, String expectedValue) {
         assertText(node, expectedValue, false);
         assertNotFromResources(node);
     }
@@ -494,7 +504,8 @@
     /**
      * Asserts the contents of a text-based node that is also auto-fillable.
      */
-    static void assertTextOnly(AssistStructure structure, String resourceId, String expectedValue) {
+    public static void assertTextOnly(AssistStructure structure, String resourceId,
+            String expectedValue) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertText(node, expectedValue, false);
         assertNotFromResources(node);
@@ -503,7 +514,7 @@
     /**
      * Asserts the contents of a text-based node that is also auto-fillable.
      */
-    static void assertTextAndValue(ViewNode node, String expectedValue) {
+    public static void assertTextAndValue(ViewNode node, String expectedValue) {
         assertText(node, expectedValue, true);
         assertNotFromResources(node);
     }
@@ -511,7 +522,7 @@
     /**
      * Asserts a text-based node exists and verify its values.
      */
-    static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
+    public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
             String expectedValue) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertTextAndValue(node, expectedValue);
@@ -521,7 +532,7 @@
     /**
      * Asserts a text-based node exists and is sanitized.
      */
-    static ViewNode assertValue(AssistStructure structure, String resourceId,
+    public static ViewNode assertValue(AssistStructure structure, String resourceId,
             String expectedValue) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertTextValue(node, expectedValue);
@@ -531,7 +542,7 @@
     /**
      * Asserts the values of a text-based node whose string come from resoruces.
      */
-    static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
+    public static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
             String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertText(node, expectedValue, isAutofillable);
@@ -556,7 +567,7 @@
     /**
      * Asserts the auto-fill value of a text-based node.
      */
-    static ViewNode assertTextValue(ViewNode node, String expectedText) {
+    public static ViewNode assertTextValue(ViewNode node, String expectedText) {
         final AutofillValue value = node.getAutofillValue();
         final AutofillId id = node.getAutofillId();
         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
@@ -569,7 +580,7 @@
     /**
      * Asserts the auto-fill value of a list-based node.
      */
-    static ViewNode assertListValue(ViewNode node, int expectedIndex) {
+    public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
         final AutofillValue value = node.getAutofillValue();
         final AutofillId id = node.getAutofillId();
         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
@@ -582,7 +593,7 @@
     /**
      * Asserts the auto-fill value of a toggle-based node.
      */
-    static void assertToggleValue(ViewNode node, boolean expectedToggle) {
+    public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
         final AutofillValue value = node.getAutofillValue();
         final AutofillId id = node.getAutofillId();
         assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
@@ -594,7 +605,8 @@
     /**
      * Asserts the auto-fill value of a date-based node.
      */
-    static void assertDateValue(Object object, AutofillValue value, int year, int month, int day) {
+    public static void assertDateValue(Object object, AutofillValue value, int year, int month,
+            int day) {
         assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
         assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
 
@@ -612,14 +624,14 @@
     /**
      * Asserts the auto-fill value of a date-based node.
      */
-    static void assertDateValue(ViewNode node, int year, int month, int day) {
+    public static void assertDateValue(ViewNode node, int year, int month, int day) {
         assertDateValue(node, node.getAutofillValue(), year, month, day);
     }
 
     /**
      * Asserts the auto-fill value of a date-based view.
      */
-    static void assertDateValue(View view, int year, int month, int day) {
+    public static void assertDateValue(View view, int year, int month, int day) {
         assertDateValue(view, view.getAutofillValue(), year, month, day);
     }
 
@@ -642,21 +654,21 @@
     /**
      * Asserts the auto-fill value of a time-based node.
      */
-    static void assertTimeValue(ViewNode node, int hour, int minute) {
+    public static void assertTimeValue(ViewNode node, int hour, int minute) {
         assertTimeValue(node, node.getAutofillValue(), hour, minute);
     }
 
     /**
      * Asserts the auto-fill value of a time-based view.
      */
-    static void assertTimeValue(View view, int hour, int minute) {
+    public static void assertTimeValue(View view, int hour, int minute) {
         assertTimeValue(view, view.getAutofillValue(), hour, minute);
     }
 
     /**
      * Asserts a text-based node exists and is sanitized.
      */
-    static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
+    public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
         assertTextIsSanitized(node);
@@ -666,7 +678,7 @@
     /**
      * Asserts a list-based node exists and is sanitized.
      */
-    static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
+    public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
         assertTextIsSanitized(node);
@@ -675,7 +687,7 @@
     /**
      * Asserts a toggle node exists and is sanitized.
      */
-    static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
+    public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertNodeHasNoAutofillValue(node);
         assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
@@ -685,7 +697,8 @@
     /**
      * Asserts a node exists and has the {@code expected} number of children.
      */
-    static void assertNumberOfChildren(AssistStructure structure, String resourceId, int expected) {
+    public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
+            int expected) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         final int actual = node.getChildCount();
         if (actual != expected) {
@@ -698,7 +711,7 @@
     /**
      * Asserts the number of children in the Assist structure.
      */
-    static void assertNumberOfChildren(AssistStructure structure, int expected) {
+    public static void assertNumberOfChildren(AssistStructure structure, int expected) {
         assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
                 .isEqualTo(1);
         final int actual = getNumberNodes(structure);
@@ -712,7 +725,7 @@
     /**
      * Gets the total number of nodes in an structure.
      */
-    static int getNumberNodes(AssistStructure structure) {
+    public static int getNumberNodes(AssistStructure structure) {
         int count = 0;
         final int nodes = structure.getWindowNodeCount();
         for (int i = 0; i < nodes; i++) {
@@ -726,7 +739,7 @@
     /**
      * Gets the total number of nodes in an node, including all descendants and the node itself.
      */
-    static int getNumberNodes(ViewNode node) {
+    public static int getNumberNodes(ViewNode node) {
         int count = 1;
         final int childrenSize = node.getChildCount();
         if (childrenSize > 0) {
@@ -741,7 +754,7 @@
      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
      * {@code resourceIds}.
      */
-    static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
+    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
             String[] resourceIds) {
         if (resourceIds == null) return null;
 
@@ -750,7 +763,7 @@
             final String resourceId = resourceIds[i];
             final ViewNode node = nodeResolver.apply(resourceId);
             if (node == null) {
-                throw new AssertionError("No node with savable resourceId " + resourceId);
+                throw new AssertionError("No node with resourceId " + resourceId);
             }
             requiredIds[i] = node.getAutofillId();
 
@@ -759,6 +772,21 @@
     }
 
     /**
+     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
+     * {@code resourceId}.
+     */
+    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
+            String resourceId) {
+        if (resourceId == null) return null;
+
+        final ViewNode node = nodeResolver.apply(resourceId);
+        if (node == null) {
+            throw new AssertionError("No node with resourceId " + resourceId);
+        }
+        return node.getAutofillId();
+    }
+
+    /**
      * Prevents the screen to rotate by itself
      */
     public static void disableAutoRotation(UiBot uiBot) throws Exception {
@@ -783,16 +811,28 @@
     /**
      * Sets the maximum number of partitions per session.
      */
-    public static void setMaxPartitions(int value) {
+    public static void setMaxPartitions(int value) throws Exception {
         runShellCommand("cmd autofill set max_partitions %d", value);
-        assertThat(getMaxPartitions()).isEqualTo(value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
+            return getMaxPartitions() == value ? Boolean.TRUE : null;
+        });
     }
 
     /**
-     * Checks if device supports the Autofill feature.
+     * Gets the maximum number of visible datasets.
      */
-    public static boolean hasAutofillFeature() {
-        return RequiredFeatureRule.hasFeature(PackageManager.FEATURE_AUTOFILL);
+    public static int getMaxVisibleDatasets() {
+        return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
+    }
+
+    /**
+     * Sets the maximum number of visible datasets.
+     */
+    public static void setMaxVisibleDatasets(int value) throws Exception {
+        runShellCommand("cmd autofill set max_visible_datasets %d", value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
+            return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
+        });
     }
 
     /**
@@ -863,43 +903,45 @@
     }
 
     /**
-     * Uses Settings to disable the given autofill service for the default user, and checks the
-     * value was properly check, throwing an exception if it was not.
+     * Uses Settings to disable the given autofill service for the default user, and waits until
+     * the setting is deleted.
      */
-    public static void disableAutofillService(@NonNull Context context,
-            @NonNull String serviceName) {
-        if (!isAutofillServiceEnabled(serviceName)) return;
-
+    public static void disableAutofillService(@NonNull Context context) {
+        final String currentService = SettingsHelper.get(AUTOFILL_SERVICE);
+        if (currentService == null) {
+            Log.v(TAG, "disableAutofillService(): already disabled");
+            return;
+        }
+        Log.v(TAG, "Disabling " + currentService);
         SettingsHelper.syncDelete(context, AUTOFILL_SERVICE);
     }
 
     /**
      * Checks whether the given service is set as the autofill service for the default user.
      */
-    private static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
-        final String actualName = SettingsHelper.get(AUTOFILL_SERVICE);
+    public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
+        final String actualName = getAutofillServiceName();
         return serviceName.equals(actualName);
     }
 
     /**
+     * Gets then name of the autofill service for the default user.
+     */
+    public static String getAutofillServiceName() {
+        return SettingsHelper.get(AUTOFILL_SERVICE);
+    }
+
+    /**
      * Asserts whether the given service is enabled as the autofill service for the default user.
      */
     public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
         final String actual = SettingsHelper.get(AUTOFILL_SERVICE);
-        final String expected = enabled ? serviceName : "null";
+        final String expected = enabled ? serviceName : null;
         assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
                 .that(actual).isEqualTo(expected);
     }
 
     /**
-     * Asserts that there is a pending session for the given package.
-     */
-    public static void assertHasSessions(String packageName) {
-        final String result = runShellCommand(CMD_LIST_SESSIONS);
-        assertThat(result).contains(packageName);
-    }
-
-    /**
      * Gets the instrumentation context.
      */
     public static Context getContext() {
@@ -907,21 +949,6 @@
     }
 
     /**
-     * Cleans up the autofill state; should be called before pretty much any test.
-     */
-    public static void preTestCleanup() {
-        if (!hasAutofillFeature()) return;
-
-        Log.d(TAG, "preTestCleanup()");
-
-        disableAutofillService(getContext(), SERVICE_NAME);
-        InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
-
-        InstrumentedAutoFillService.resetStaticState();
-        AuthenticationActivity.resetStaticState();
-    }
-
-    /**
      * Asserts the node has an {@code HTMLInfo} property, with the given tag.
      */
     public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
@@ -1060,11 +1087,11 @@
                 .isEqualTo(expectedResult.id);
         final List<Match> matches = actualResult.getValue().getMatches();
         assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
-                .isEqualTo(expectedResult.remoteIds.length);
+                .isEqualTo(expectedResult.categoryIds.length);
         for (int j = 0; j < matches.size(); j++) {
             final Match match = matches.get(j);
             assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
-                .that(match.getCategoryId()).isEqualTo(expectedResult.remoteIds[j]);
+                .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
             assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
                 .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
         }
@@ -1154,10 +1181,10 @@
     }
 
     public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
-            @NonNull AutofillId fieldId, @NonNull String remoteId, float score) {
+            @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
         assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
                 new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId, remoteId, score)
+                        new FieldClassificationResult(fieldId, categoryId, score)
                 });
     }
 
@@ -1250,10 +1277,8 @@
             return;
         }
 
-        final File dir = new File(LOCAL_DIRECTORY);
-        dir.mkdirs();
-        if (!dir.exists()) {
-            Log.e(TAG, "Could not create directory " + dir);
+        final File dir = getLocalDirectory();
+        if (dir == null) {
             throw new AssertionError("bitmap comparison failed for " + filename
                     + ", and bitmaps could not be dumped on " + dir);
         }
@@ -1264,38 +1289,129 @@
     }
 
     @Nullable
-    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
-            @NonNull String filename) throws IOException {
+    private static File getLocalDirectory() {
+        final File dir = new File(LOCAL_DIRECTORY);
+        dir.mkdirs();
+        if (!dir.exists()) {
+            Log.e(TAG, "Could not create directory " + dir);
+            return null;
+        }
+        return dir;
+    }
+
+    @Nullable
+    private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
         final File file = new File(dir, filename);
         if (file.exists()) {
+            Log.v(TAG, "Deleting file " + file);
             file.delete();
         }
         if (!file.createNewFile()) {
             Log.e(TAG, "Could not create file " + file);
             return null;
         }
-        Log.d(TAG, "Dumping bitmap at " + file);
+        return file;
+    }
+
+    @Nullable
+    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
+            @NonNull String filename) throws IOException {
+        final File file = createFile(dir, filename);
+        if (file != null) {
+            dumpBitmap(bitmap, file);
+
+        }
+        return file;
+    }
+
+    @Nullable
+    public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
+        Log.i(TAG, "Dumping bitmap at " + file);
         BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
         return file;
     }
 
+    /**
+     * Creates a file in the device, using the name of the current test as a prefix.
+     */
+    @Nullable
+    public static File createTestFile(@NonNull String name) throws IOException {
+        final File dir = getLocalDirectory();
+        if (dir == null) return null;
+
+        final String prefix = JUnitHelper.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
+                .replaceAll("\\)", "");
+        final String filename = prefix + "-" + name;
+
+        return createFile(dir, filename);
+    }
+
+    /**
+     * Offers an object to a queue or times out.
+     *
+     * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
+     * interrupted.
+     */
+    public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
+        boolean offered = false;
+        try {
+            offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.w(TAG, "interrupted offering", e);
+            Thread.currentThread().interrupt();
+        }
+        if (!offered) {
+            Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
+        }
+        return offered;
+    }
+
+    /**
+     * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
+     * comparing its value using standard assertions might ANR.
+     */
+    public static void assertEqualsToLargeString(@NonNull String string) {
+        assertThat(string).isNotNull();
+        assertThat(string).hasLength(LARGE_STRING_SIZE);
+        assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
+        assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
+    }
+
+    /**
+     * Allows the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void allowOverlays() {
+        ShellHelper.setOverlayPermissions(MY_PACKAGE, true);
+    }
+
+    /**
+     * Disallow the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void disallowOverlays() {
+        ShellHelper.setOverlayPermissions(MY_PACKAGE, false);
+    }
+
     private Helper() {
         throw new UnsupportedOperationException("contain static methods only");
     }
 
     static class FieldClassificationResult {
         public final AutofillId id;
-        public final String[] remoteIds;
+        public final String[] categoryIds;
         public final float[] scores;
 
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String remoteId, float score) {
-            this(id, new String[] { remoteId }, new float[] { score });
+        FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId, float score) {
+            this(id, new String[] { categoryId }, new float[] { score });
         }
 
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] remoteIds,
+        FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
                 float[] scores) {
             this.id = id;
-            this.remoteIds = remoteIds;
+            this.categoryIds = categoryIds;
             this.scores = scores;
         }
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
index c96bbbc..974f0cc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
@@ -30,25 +30,27 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
  * Test case for an activity containing non-TextField views with initial values set on XML.
  */
 @AppModeFull // CheckoutActivityTest() is enough to test ephemeral apps support
-public class InitializedCheckoutActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<InitializedCheckoutActivity> mActivityRule =
-        new AutofillActivityTestRule<InitializedCheckoutActivity>(InitializedCheckoutActivity.class);
+public class InitializedCheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<InitializedCheckoutActivity> {
 
     private InitializedCheckoutActivity mCheckoutActivity;
 
-    @Before
-    public void setActivity() {
-        mCheckoutActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<InitializedCheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<InitializedCheckoutActivity>(
+                InitializedCheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mCheckoutActivity = getActivity();
+            }
+        };
+
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 6b47c9f..4062817 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts;
 
+import static android.autofillservice.cts.CannedFillResponse.ResponseType.FAILURE;
 import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
 import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
 import static android.autofillservice.cts.Helper.dumpStructure;
@@ -34,6 +35,8 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
@@ -52,6 +55,7 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -73,19 +77,24 @@
             new AtomicReference<>();
     private static final Replier sReplier = new Replier();
 
-    private static final Object sLock = new Object();
-
-    // @GuardedBy("sLock") // NOTE: not using annotation because of dependencies
-    private static boolean sIgnoreUnexpectedRequests = false;
-
-    // @GuardedBy("sLock") // NOTE: not using annotation because of dependencies
-    private static boolean sConnected;
+    private static AtomicBoolean sConnected = new AtomicBoolean(false);
 
     protected static String sServiceLabel = SERVICE_CLASS;
 
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
+    private final Handler mHandler;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
     public InstrumentedAutoFillService() {
         sInstance.set(this);
         sServiceLabel = SERVICE_CLASS;
+        mHandler = Handler.createAsync(sMyThread.getLooper());
     }
 
     private static InstrumentedAutoFillService peekInstance() {
@@ -158,59 +167,76 @@
         return sServiceLabel;
     }
 
+    private void handleConnected(boolean connected) {
+        Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
+        sConnected.set(connected);
+    }
+
     @Override
     public void onConnected() {
-        synchronized (sLock) {
-            Log.v(TAG, "onConnected(): connected=" + sConnected);
-            sConnected = true;
-        }
+        mHandler.post(()->handleConnected(true));
     }
 
     @Override
     public void onDisconnected() {
-        synchronized (sLock) {
-            Log.v(TAG, "onDisconnected(): connected=" + sConnected);
-            sConnected = false;
-        }
+        mHandler.post(()->handleConnected(false));
     }
 
     @Override
     public void onFillRequest(android.service.autofill.FillRequest request,
             CancellationSignal cancellationSignal, FillCallback callback) {
-        if (DUMP_FILL_REQUESTS) dumpStructure("onFillRequest()", request.getFillContexts());
-        synchronized (sLock) {
-            if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts()))  {
-                Log.w(TAG, "Ignoring onFillRequest()");
-                return;
-            }
+
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (!JUnitHelper.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
         }
-        sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
-                cancellationSignal, callback, request.getFlags());
+        if (!fromSamePackage(component))  {
+            Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
+            return;
+        }
+        if (DUMP_FILL_REQUESTS) {
+            dumpStructure("onFillRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onFillRequest() for " + component.toShortString());
+        }
+        mHandler.post(
+                () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
+                        cancellationSignal, callback, request.getFlags()));
     }
 
     @Override
     public void onSaveRequest(android.service.autofill.SaveRequest request,
             SaveCallback callback) {
-        if (DUMP_SAVE_REQUESTS) dumpStructure("onSaveRequest()", request.getFillContexts());
-        synchronized (sLock) {
-            if (sIgnoreUnexpectedRequests || !fromSamePackage(request.getFillContexts())) {
-                Log.w(TAG, "Ignoring onSaveRequest()");
-                return;
-            }
+        mHandler.post(()->handleSaveRequest(request, callback));
+    }
+
+    private void handleSaveRequest(android.service.autofill.SaveRequest request,
+            SaveCallback callback) {
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (!JUnitHelper.isRunningTest()) {
+            Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
+            return;
         }
-        sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback,
-                request.getDatasetIds());
+        if (!fromSamePackage(component)) {
+            Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
+            return;
+        }
+        if (DUMP_SAVE_REQUESTS) {
+            dumpStructure("onSaveRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onSaveRequest() for " + component.toShortString());
+        }
+        mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
+                request.getClientState(), callback,
+                request.getDatasetIds()));
     }
 
     private static boolean isConnected() {
-        synchronized (sLock) {
-            return sConnected;
-        }
+        return sConnected.get();
     }
 
-    private boolean fromSamePackage(List<FillContext> contexts) {
-        final ComponentName component = contexts.get(contexts.size() - 1).getStructure()
-                .getActivityComponent();
+    private boolean fromSamePackage(ComponentName component) {
         final String actualPackage = component.getPackageName();
         if (!actualPackage.equals(getPackageName())
                 && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
@@ -220,15 +246,8 @@
         return true;
     }
 
-    /**
-     * Sets whether unexpected calls to
-     * {@link #onFillRequest(android.service.autofill.FillRequest, CancellationSignal, FillCallback)}
-     * should throw an exception.
-     */
-    public static void setIgnoreUnexpectedRequests(boolean ignore) {
-        synchronized (sLock) {
-            sIgnoreUnexpectedRequests = ignore;
-        }
+    private ComponentName getLastActivityComponent(List<FillContext> contexts) {
+        return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
     }
 
     /**
@@ -240,7 +259,7 @@
      * was replied with a {@code null} response) - if a text needs to block until the service
      * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
      */
-    static void waitUntilConnected() throws Exception {
+    public static void waitUntilConnected() throws Exception {
         waitConnectionState(CONNECTION_TIMEOUT, true);
     }
 
@@ -250,7 +269,7 @@
      * <p>This method is useful on tests that explicitly verifies the connection, but should be
      * avoided in other tests, as it adds extra time to the test execution.
      */
-    static void waitUntilDisconnected() throws Exception {
+    public static void waitUntilDisconnected() throws Exception {
         waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
     }
 
@@ -269,7 +288,7 @@
 
     static void resetStaticState() {
         sInstance.set(null);
-        sConnected = false;
+        sConnected.set(false);
         sServiceLabel = SERVICE_CLASS;
     }
 
@@ -278,7 +297,7 @@
      * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
      * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
      */
-    static final class FillRequest {
+    public static final class FillRequest {
         final AssistStructure structure;
         final List<FillContext> contexts;
         final Bundle data;
@@ -308,7 +327,7 @@
      * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
      * that can be asserted at the end of a test case.
      */
-    static final class SaveRequest {
+    public static final class SaveRequest {
         public final List<FillContext> contexts;
         public final AssistStructure structure;
         public final Bundle data;
@@ -340,7 +359,7 @@
      * CancellationSignal, FillCallback)}
      * on behalf of a unit test method.
      */
-    static final class Replier {
+    public static final class Replier {
 
         private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
         private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
@@ -369,7 +388,8 @@
         /**
          * Gets the exceptions thrown asynchronously, if any.
          */
-        @Nullable List<Throwable> getExceptions() {
+        @Nullable
+        public List<Throwable> getExceptions() {
             return mExceptions;
         }
 
@@ -386,7 +406,7 @@
          * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
          * one {@link Dataset}.
          */
-        Replier addResponse(CannedDataset dataset) {
+        public Replier addResponse(CannedDataset dataset) {
             return addResponse(new CannedFillResponse.Builder()
                     .addDataset(dataset)
                     .build());
@@ -395,7 +415,7 @@
         /**
          * Sets the expectation for the next {@code onFillRequest}.
          */
-        Replier addResponse(CannedFillResponse response) {
+        public Replier addResponse(CannedFillResponse response) {
             if (response == null) {
                 throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
             }
@@ -407,16 +427,15 @@
          * Sets the {@link IntentSender} that is passed to
          * {@link SaveCallback#onSuccess(IntentSender)}.
          */
-        void setOnSave(IntentSender intentSender) {
+        public Replier setOnSave(IntentSender intentSender) {
             mOnSaveIntentSender = intentSender;
+            return this;
         }
 
         /**
          * Gets the next fill request, in the order received.
-         *
-         * <p>Typically called at the end of a test case, to assert the initial request.
          */
-        FillRequest getNextFillRequest() {
+        public FillRequest getNextFillRequest() {
             FillRequest request;
             try {
                 request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
@@ -437,7 +456,7 @@
          * <p>Should only be called in cases where it's not expected to be called, as it will
          * sleep for a few ms.
          */
-        void assertOnFillRequestNotCalled() {
+        public void assertOnFillRequestNotCalled() {
             SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
             assertThat(mFillRequests).isEmpty();
         }
@@ -448,7 +467,7 @@
          * received by the service were properly {@link #getNextFillRequest() handled} by the test
          * case.
          */
-        void assertNoUnhandledFillRequests() {
+        public void assertNoUnhandledFillRequests() {
             if (mFillRequests.isEmpty()) return; // Good job, test case!
 
             if (!mReportUnhandledFillRequest) {
@@ -466,7 +485,7 @@
         /**
          * Gets the current number of unhandled requests.
          */
-        int getNumberUnhandledFillRequests() {
+        public int getNumberUnhandledFillRequests() {
             return mFillRequests.size();
         }
 
@@ -475,7 +494,7 @@
          *
          * <p>Typically called at the end of a test case, to assert the initial request.
          */
-        SaveRequest getNextSaveRequest() {
+        public SaveRequest getNextSaveRequest() {
             SaveRequest request;
             try {
                 request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
@@ -495,7 +514,7 @@
          * save requests} received by the service were properly
          * {@link #getNextFillRequest() handled} by the test case.
          */
-        void assertNoUnhandledSaveRequests() {
+        public void assertNoUnhandledSaveRequests() {
             if (mSaveRequests.isEmpty()) return; // Good job, test case!
 
             if (!mReportUnhandledSaveRequest) {
@@ -513,7 +532,7 @@
         /**
          * Resets its internal state.
          */
-        void reset() {
+        public void reset() {
             mResponses.clear();
             mFillRequests.clear();
             mSaveRequests.clear();
@@ -554,6 +573,12 @@
                     return;
                 }
 
+                if (response.getResponseType() == FAILURE) {
+                    Log.d(TAG, "onFillRequest(): replying with failure");
+                    callback.onFailure("D'OH!");
+                    return;
+                }
+
                 final String failureMessage = response.getFailureMessage();
                 if (failureMessage != null) {
                     Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
@@ -585,19 +610,24 @@
             } catch (Throwable t) {
                 addException(t);
             } finally {
-                mFillRequests.offer(new FillRequest(contexts, data, cancellationSignal, callback,
-                        flags));
+                Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
+                        callback, flags), CONNECTION_TIMEOUT.ms());
             }
         }
 
         private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
                 List<String> datasetIds) {
             Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
-            mSaveRequests.offer(new SaveRequest(contexts, data, callback, datasetIds));
-            if (mOnSaveIntentSender != null) {
-                callback.onSuccess(mOnSaveIntentSender);
-            } else {
-                callback.onSuccess();
+
+            try {
+                if (mOnSaveIntentSender != null) {
+                    callback.onSuccess(mOnSaveIntentSender);
+                } else {
+                    callback.onSuccess();
+                }
+            } finally {
+                Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
+                        CONNECTION_TIMEOUT.ms());
             }
         }
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
index 28e1fde..2920500 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
@@ -17,21 +17,33 @@
 package android.autofillservice.cts;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * Generic helper for JUnit needs.
  */
 public final class JUnitHelper {
 
-    private static String sCurrentTestNamer;
+    private static String sCurrentTestName;
+    private static String sCurrentTestClass;
 
     @NonNull
-    static String getCurrentTestName() {
-        return sCurrentTestNamer != null ? sCurrentTestNamer : "N/A";
+    public static String getCurrentTestName() {
+        if (sCurrentTestName != null) return sCurrentTestName;
+        if (sCurrentTestClass != null) return sCurrentTestClass;
+        return "(Unknown test)";
     }
 
-    public static void setCurrentTestName(String name) {
-        sCurrentTestNamer = name;
+    public static void setCurrentTestName(@Nullable String name) {
+        sCurrentTestName = name;
+    }
+
+    public static void setCurrentTestClass(@Nullable String testClass) {
+        sCurrentTestClass = testClass;
+    }
+
+    public static boolean isRunningTest() {
+        return sCurrentTestName != null;
     }
 
     private JUnitHelper() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 7ea52d2..939792c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -22,13 +22,12 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
+import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import java.util.concurrent.CountDownLatch;
@@ -51,11 +50,12 @@
     private static String WELCOME_TEMPLATE = "Welcome to the new activity, %s!";
     private static final long LOGIN_TIMEOUT_MS = 1000;
 
-    static final String ID_USERNAME_CONTAINER = "username_container";
-    static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
-    static final String BACKDOOR_USERNAME = "LemmeIn";
-    static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
+    public static final String ID_USERNAME_CONTAINER = "username_container";
+    public static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
+    public static final String BACKDOOR_USERNAME = "LemmeIn";
+    public static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
 
+    private LinearLayout mUsernameContainer;
     private TextView mUsernameLabel;
     private EditText mUsernameEditText;
     private TextView mPasswordLabel;
@@ -74,7 +74,7 @@
     /**
      * Gets the expected welcome message for a given username.
      */
-    static String getWelcomeMessage(String username) {
+    public static String getWelcomeMessage(String username) {
         return String.format(WELCOME_TEMPLATE,  username);
     }
 
@@ -83,6 +83,7 @@
         super.onCreate(savedInstanceState);
         setContentView(getContentView());
 
+        mUsernameContainer = findViewById(R.id.username_container);
         mLoginButton = findViewById(R.id.login);
         mSaveButton = findViewById(R.id.save);
         mClearButton = findViewById(R.id.clear);
@@ -102,42 +103,6 @@
             getAutofillManager().cancel();
         });
         mCancelButton.setOnClickListener((OnClickListener) v -> finish());
-
-        // Create a custom insertion callback so it just show the AUTOFILL item, otherwise CTS
-        // testAutofillManuallyOneDataset() will fail if a previous test set the clipboard
-        // TODO(b/71711122): remove once there's a proper way to reset the clipboard
-        mUsernameEditText.setCustomInsertionActionModeCallback(new ActionMode.Callback() {
-
-            @Override
-            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                final String autofillTitle = AutoFillServiceTestCase.sDefaultUiBot
-                        .getAutofillContextualMenuTitle();
-                for (int i = 0; i < menu.size(); i++) {
-                    final MenuItem item = menu.getItem(i);
-                    final String title = item.getTitle().toString();
-                    if (!title.equals(autofillTitle)) {
-                        Log.v(TAG, "onPrepareActionMode(): ignoring " + title);
-                        menu.removeItem(item.getItemId());
-                    }
-                }
-                return true;
-            }
-
-            @Override
-            public void onDestroyActionMode(ActionMode mode) {
-
-            }
-
-            @Override
-            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                return true;
-            }
-
-            @Override
-            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                return false;
-            }
-        });
     }
 
     protected int getContentView() {
@@ -192,7 +157,7 @@
      * Sets the expectation for an autofill request (for all fields), so it can be asserted through
      * {@link #assertAutoFilled()} later.
      */
-    void expectAutoFill(String username, String password) {
+    public void expectAutoFill(String username, String password) {
         mExpectation = new FillExpectation(username, password);
         mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
         mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
@@ -202,7 +167,7 @@
      * Sets the expectation for an autofill request (for username only), so it can be asserted
      * through {@link #assertAutoFilled()} later.
      */
-    void expectAutoFill(String username) {
+    public void expectAutoFill(String username) {
         mExpectation = new FillExpectation(username);
         mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
     }
@@ -211,7 +176,7 @@
      * Sets the expectation for an autofill request (for password only), so it can be asserted
      * through {@link #assertAutoFilled()} later.
      */
-    void expectPasswordAutoFill(String password) {
+    public void expectPasswordAutoFill(String password) {
         mExpectation = new FillExpectation(null, password);
         mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
     }
@@ -220,7 +185,7 @@
      * Asserts the activity was auto-filled with the values passed to
      * {@link #expectAutoFill(String, String)}.
      */
-    void assertAutoFilled() throws Exception {
+    public void assertAutoFilled() throws Exception {
         assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
         if (mExpectation.ccUsernameWatcher != null) {
             mExpectation.ccUsernameWatcher.assertAutoFilled();
@@ -230,67 +195,74 @@
         }
     }
 
-    void forceAutofillOnUsername() {
+    public void forceAutofillOnUsername() {
         syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
     }
 
-    void forceAutofillOnPassword() {
+    public void forceAutofillOnPassword() {
         syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
     }
 
     /**
      * Visits the {@code username_label} in the UiThread.
      */
-    void onUsernameLabel(Visitor<TextView> v) {
+    public void onUsernameLabel(Visitor<TextView> v) {
         syncRunOnUiThread(() -> v.visit(mUsernameLabel));
     }
 
     /**
      * Visits the {@code username} in the UiThread.
      */
-    void onUsername(Visitor<EditText> v) {
+    public void onUsername(Visitor<EditText> v) {
         syncRunOnUiThread(() -> v.visit(mUsernameEditText));
     }
 
     /**
+     * Clears focus from input fields by focusing on the parent layout.
+     */
+    public void clearFocus() {
+        syncRunOnUiThread(() -> ((View) mUsernameContainer.getParent()).requestFocus());
+    }
+
+    /**
      * Gets the {@code username_label} view.
      */
-    TextView getUsernameLabel() {
+    public TextView getUsernameLabel() {
         return mUsernameLabel;
     }
 
     /**
      * Gets the {@code username} view.
      */
-    EditText getUsername() {
+    public EditText getUsername() {
         return mUsernameEditText;
     }
 
     /**
      * Visits the {@code password_label} in the UiThread.
      */
-    void onPasswordLabel(Visitor<TextView> v) {
+    public void onPasswordLabel(Visitor<TextView> v) {
         syncRunOnUiThread(() -> v.visit(mPasswordLabel));
     }
 
     /**
      * Visits the {@code password} in the UiThread.
      */
-    void onPassword(Visitor<EditText> v) {
+    public void onPassword(Visitor<EditText> v) {
         syncRunOnUiThread(() -> v.visit(mPasswordEditText));
     }
 
     /**
      * Gets the {@code password} view.
      */
-    EditText getPassword() {
+    public EditText getPassword() {
         return mPasswordEditText;
     }
 
     /**
      * Taps the login button in the UI thread.
      */
-    String tapLogin() throws Exception {
+    public String tapLogin() throws Exception {
         mLoginLatch = new CountDownLatch(1);
         syncRunOnUiThread(() -> mLoginButton.performClick());
         boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -302,7 +274,7 @@
     /**
      * Taps the save button in the UI thread.
      */
-    void tapSave() throws Exception {
+    public void tapSave() throws Exception {
         syncRunOnUiThread(() -> mSaveButton.performClick());
     }
 
@@ -316,7 +288,7 @@
     /**
      * Sets the window flags.
      */
-    void setFlags(int flags) {
+    public void setFlags(int flags) {
         Log.d(TAG, "setFlags():" + flags);
         syncRunOnUiThread(() -> getWindow().setFlags(flags, flags));
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 2b06402..5edfb53 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -17,17 +17,20 @@
 package android.autofillservice.cts;
 
 import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.CannedFillResponse.FAIL;
 import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 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.allowOverlays;
 import static android.autofillservice.cts.Helper.assertHasFlags;
 import static android.autofillservice.cts.Helper.assertNumberOfChildren;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.assertTextOnly;
 import static android.autofillservice.cts.Helper.assertValue;
+import static android.autofillservice.cts.Helper.disallowOverlays;
 import static android.autofillservice.cts.Helper.dumpStructure;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.Helper.isAutofillWindowFullScreen;
@@ -40,8 +43,9 @@
 import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
 import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.sendKeyEvent;
 import static android.autofillservice.cts.common.ShellHelper.tap;
+import static android.content.Context.CLIPBOARD_SERVICE;
 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;
@@ -63,6 +67,8 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -86,6 +92,7 @@
 import android.view.autofill.AutofillManager;
 import android.widget.RemoteViews;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
@@ -370,6 +377,40 @@
                 findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
     }
 
+
+    @Test
+    public void testAutofillAgainAfterOnFailure() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(FAIL);
+
+        // Trigger autofill.
+        requestFocusOnUsernameNoWindowChange();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill.
+        clearFocus();
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mActivity.expectAutoFill("dude", "sweet");
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
     @Test
     public void testDatasetPickerPosition() throws Exception {
         // TODO: currently disabled because the screenshot contains elements external to the
@@ -700,8 +741,7 @@
         mUiBot.assertDatasets("The Dude");
 
         // tapping outside autofill window should close it and raise ui hidden event
-        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()));
         callback.assertUiHiddenEvent(username);
 
         mUiBot.assertNoDatasets();
@@ -904,7 +944,7 @@
         final View[] overlay = new View[1];
         try {
             // Allow ourselves to add overlays
-            runShellCommand("appops set %s SYSTEM_ALERT_WINDOW allow", mPackageName);
+            allowOverlays();
 
             // Make sure the fill UI is shown.
             mUiBot.assertDatasets("The Dude");
@@ -972,13 +1012,17 @@
             final String extraValue = saveRequest.data.getString("numbers");
             assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
         } finally {
-            // Make sure we can no longer add overlays
-            runShellCommand("appops set %s SYSTEM_ALERT_WINDOW ignore", mPackageName);
-            // Make sure the overlay is removed
-            mActivity.runOnUiThread(() -> {
-                WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-                windowManager.removeView(overlay[0]);
-            });
+            try {
+                // Make sure we can no longer add overlays
+                disallowOverlays();
+                // Make sure the overlay is removed
+                mActivity.runOnUiThread(() -> {
+                    WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+                    windowManager.removeView(overlay[0]);
+                });
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
         }
     }
 
@@ -1925,6 +1969,38 @@
 
         // And activity.
         mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Explicitly uses the contextual menu to test that functionality.
+        mUiBot.getAutofillMenuOption(ID_USERNAME, false).click();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Should have been automatically filled.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillManuallyOneDatasetWhenClipboardFull() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set clipboard.
+        ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(CLIPBOARD_SERVICE);
+        cm.setPrimaryClip(ClipData.newPlainText(null, "test"));
+
+        // And activity.
+        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
 
         // Set expectations.
         sReplier.addResponse(new CannedDataset.Builder()
@@ -1935,7 +2011,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Explicitly uses the contextual menu to test that functionality.
-        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
+        mUiBot.getAutofillMenuOption(ID_USERNAME, true).click();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
@@ -1945,6 +2021,9 @@
 
         // Check the results.
         mActivity.assertAutoFilled();
+
+        // clear clipboard
+        cm.clearPrimaryClip();
     }
 
     @Test
@@ -2154,6 +2233,7 @@
         mActivity.assertAutoFilled();
     }
 
+    @Ignore("blocked on b/4222506") // STOPSHIP: must fix and remove @Ignore before Q is launched
     @Test
     public void testCommitMultipleTimes() throws Throwable {
         // Set service.
@@ -2163,7 +2243,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
                 .build();
 
-        for (int i = 1; i <= 3; i++) {
+        for (int i = 1; i <= 10; i++) {
             Log.i(TAG, "testCommitMultipleTimes(): step " + i);
             final String username = "user-" + i;
             final String password = "pass-" + i;
@@ -2219,7 +2299,7 @@
         // Set service.
         enableService();
 
-        for (int i = 1; i <= 3; i++) {
+        for (int i = 1; i <= 10; i++) {
             Log.i(TAG, "testCancelMultipleTimes(): step " + i);
             final String username = "user-" + i;
             final String password = "pass-" + i;
@@ -2526,4 +2606,82 @@
         assertThat(password.getMaxTextEms()).isEqualTo(-1);
         assertThat(password.getMaxTextLength()).isEqualTo(-1);
     }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("dude"))
+                .setField(ID_PASSWORD, "sweet", createPresentation("sweet"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete a character.
+        sendKeyEvent("KEYCODE_DEL");
+        assertThat(mUiBot.getTextByRelativeId(ID_USERNAME)).isEqualTo("dud");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofillOnePresentation() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete username
+        mUiBot.setTextByRelativeId(ID_USERNAME, "");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
index 3193cc3..cefc58c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
@@ -25,24 +25,24 @@
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.concurrent.TimeoutException;
 
-public class LoginWithCustomHighlightActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<LoginWithCustomHighlightActivity> mActivityRule =
-            new AutofillActivityTestRule<LoginWithCustomHighlightActivity>(
-            LoginWithCustomHighlightActivity.class);
+public class LoginWithCustomHighlightActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithCustomHighlightActivity> {
 
     private LoginWithCustomHighlightActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<LoginWithCustomHighlightActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginWithCustomHighlightActivity>(
+                LoginWithCustomHighlightActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
@@ -84,7 +84,6 @@
      * Requests focus on username and expect Window event happens.
      */
     protected void requestFocusOnUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
index 42ff86a..bc8ed9f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
@@ -40,22 +40,24 @@
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 @AppModeFull // LoginActivityTest is enough to test ephemeral apps support
-public class LoginWithStringsActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<LoginWithStringsActivity> mActivityRule =
-            new AutofillActivityTestRule<LoginWithStringsActivity>(LoginWithStringsActivity.class);
+public class LoginWithStringsActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithStringsActivity> {
 
     private LoginWithStringsActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+
+    @Override
+    protected AutofillActivityTestRule<LoginWithStringsActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginWithStringsActivity>(
+                LoginWithStringsActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
new file mode 100644
index 0000000..3d042f1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+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.assertTextAndValue;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Test case for the senario where a login screen is split in multiple activities.
+ */
+public class MultiScreenLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<UsernameOnlyActivity> {
+
+    private static final String TAG = "MultiScreenLoginTest";
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private UsernameOnlyActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<UsernameOnlyActivity> getActivityRule() {
+        return new AutofillActivityTestRule<UsernameOnlyActivity>(UsernameOnlyActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Tests the "traditional" scenario where the service must save each field (username and
+     * password) separately.
+     */
+    @Test
+    public void testSaveEachFieldSeparately() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME);
+
+        // ..and assert results
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_USERNAME), "dude");
+        assertThat(saveRequest1.data.getString("first")).isEqualTo("one");
+        assertThat(saveRequest1.data.getString("last")).isEqualTo("one");
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity activity2 = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        activity2.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        activity2.setPassword("sweet");
+        activity2.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.data.getString("first")).isNull();
+        assertThat(saveRequest2.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest2.data.getString("last")).isEqualTo("two");
+        assertTextAndValue(findNodeByResourceId(saveRequest2.structure, ID_PASSWORD), "sweet");
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just in the first request (so its passed to both
+     * the second fill request and the save request.
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceNoClientStateOnSecondRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just on both requests (so the 1st client state is
+     * passed to the 2nd request, and the 2nd client state is passed to the save request).
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceWithClientStateOnBothRequests() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state on 2nd request should come from previous (1st) request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("second")).isNull();
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state on save request should come from last (2nd) request
+        assertThat(saveRequest.data.getString("first")).isNull();
+        assertThat(saveRequest.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest.data.getString("last")).isEqualTo("two");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    @Test
+    public void testSaveBothFieldsCustomDescription_differentIds() throws Exception {
+        saveBothFieldsCustomDescription(false);
+    }
+
+    @Ignore("TODO(b/113593220): need new API to set context id")
+    @Test
+    public void testSaveBothFieldsCustomDescription_sameIds() throws Exception {
+        saveBothFieldsCustomDescription(true);
+    }
+
+    private void saveBothFieldsCustomDescription(boolean sameAutofillId) throws Exception {
+        // Set service
+        enableService();
+
+        // Set ids
+        final AutofillId usernameId = mActivity.getUsernameAutofillId();
+        final AutofillId passwordId = sameAutofillId ? usernameId
+                : mActivity.getAutofillManager().getNextAutofillId();
+        mActivity.setPasswordAutofillId(passwordId);
+        Log.d(TAG, "usernameId: " + usernameId + ", passwordId: " + passwordId);
+
+        // First handle username...
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+
+        // Set expectations.
+        final CharSequenceTransformation usernameTrans =
+                new CharSequenceTransformation.Builder(usernameId, MATCH_ALL, "$1").build();
+        final CharSequenceTransformation passwordTrans =
+                new CharSequenceTransformation.Builder(passwordId, MATCH_ALL, "$1").build();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableAutofillIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        passwordId)
+                .setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                        .addChild(R.id.username, usernameTrans)
+                        .addChild(R.id.password, passwordTrans)
+                        .build())
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+
+        // ...and assert UI
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+    }
+
+    // TODO(b/113281366): add test cases for more scenarios such as:
+    // - make sure that activity not marked with keepAlive is not sent in the 2nd request
+    // - somehow verify that the first activity's session is gone
+    // - WebView
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
index c6cd06a..5aad499 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
@@ -49,8 +49,11 @@
     }
 
     public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
-        sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS);
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
         sLastInstanceLatch = null;
         return sLastInstance;
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
index e0b3109..02460050 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
@@ -49,8 +49,11 @@
     }
 
     public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
-        sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS);
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
         sLastInstanceLatch = null;
         return sLastInstance;
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index d1a94ec..eb9d288 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -15,7 +15,7 @@
  */
 package android.autofillservice.cts;
 
-import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
@@ -26,47 +26,57 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.content.Intent;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
 
 import java.util.concurrent.TimeoutException;
 
 @AppModeFull // This test requires android.permission.MANAGE_ACTIVITY_STACKS
-public class MultiWindowLoginActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<MultiWindowLoginActivity> mActivityRule =
-            new AutofillActivityTestRule<MultiWindowLoginActivity>(MultiWindowLoginActivity.class);
+public class MultiWindowLoginActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<MultiWindowLoginActivity> {
 
     private LoginActivity mActivity;
-    protected ActivityManager mAm;
+    private ActivityTaskManager mAtm;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<MultiWindowLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<MultiWindowLoginActivity>(
+                MultiWindowLoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                mAtm = mContext.getSystemService(ActivityTaskManager.class);
+            }
+        };
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(new AdoptShellPermissionsRule()).around(getActivityRule());
     }
 
     @Before
     public void setup() {
         assumeTrue("Skipping test: no split multi-window support",
-                ActivityManager.supportsSplitScreenMultiWindow(mContext));
-        mAm = mContext.getSystemService(ActivityManager.class);
+                ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
     }
 
     /**
      * Touch a view and exepct autofill window change
      */
     protected void tapViewAndExpectWindowEvent(View view) throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> tap(view), Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> tap(view));
     }
 
-
     protected String runAmStartActivity(Class<? extends Activity> activityClass, int flags) {
         return runAmStartActivity(activityClass.getName(), flags);
     }
@@ -77,10 +87,10 @@
     }
 
     /**
-     * Put activity1 in TOP, will be followed by amStartActivity()
+     * Put activity in TOP, will be followed by amStartActivity()
      */
     protected void splitWindow(Activity activity) throws Exception {
-        mAm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(),
+        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(),
                 SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, false, null, true);
         mUiBot.waitForWindowSplit();
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
index 8da4add..ad08fd3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
@@ -30,24 +30,28 @@
 import android.view.autofill.AutofillValue;
 import android.widget.EditText;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
-public class MultipleFragmentLoginTest extends AutoFillServiceTestCase {
-    private static final String LOG_TAG = MultipleFragmentLoginTest.class.getSimpleName();
-    @Rule
-    public final AutofillActivityTestRule<FragmentContainerActivity> mActivityRule =
-            new AutofillActivityTestRule<>(FragmentContainerActivity.class);
+public class MultipleFragmentLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
+
+    private static final String LOG_TAG = "MultipleFragmentLoginTest";
+
     private FragmentContainerActivity mActivity;
     private EditText mEditText1;
     private EditText mEditText2;
 
-    @Before
-    public void init() {
-        mActivity = mActivityRule.getActivity();
-        mEditText1 = mActivity.findViewById(R.id.editText1);
-        mEditText2 = mActivity.findViewById(R.id.editText2);
+    @Override
+    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<FragmentContainerActivity>(
+                FragmentContainerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                mEditText1 = mActivity.findViewById(R.id.editText1);
+                mEditText2 = mActivity.findViewById(R.id.editText2);
+            }
+        };
     }
 
     @Test
@@ -64,21 +68,18 @@
                 .setExtras(clientState)
                 .build());
 
-        final InstrumentedAutoFillService.FillRequest[] fillRequest =
-                new InstrumentedAutoFillService.FillRequest[1];
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
 
-        // Trigger autofill
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText2.requestFocus();
-            mEditText1.requestFocus();
-        });
+        final InstrumentedAutoFillService.FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.data).isNull();
 
-        fillRequest[0] = sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
 
-        assertThat(fillRequest[0].data).isNull();
+        mActivity.setRootContainerFocusable(false);
 
-        AssistStructure structure = fillRequest[0].contexts.get(0).getStructure();
-        assertThat(fillRequest[0].contexts.size()).isEqualTo(1);
+        final AssistStructure structure = fillRequest1.contexts.get(0).getStructure();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
         assertThat(findNodeByResourceId(structure, "editText1")).isNotNull();
         assertThat(findNodeByResourceId(structure, "editText2")).isNotNull();
         assertThat(findNodeByResourceId(structure, "editText3")).isNull();
@@ -86,6 +87,7 @@
         assertThat(findNodeByResourceId(structure, "editText5")).isNull();
 
         // Wait until autofill has been applied
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
         mUiBot.selectDataset("dataset1");
         mUiBot.assertShownByText("editText1-autofilled");
 
@@ -111,15 +113,15 @@
                         R.id.rootContainer, new FragmentWithMoreEditTexts(),
                         FRAGMENT_TAG).commitNow());
         EditText editText5 = mActivity.findViewById(R.id.editText5);
-        fillRequest[0] = sReplier.getNextFillRequest();
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
 
         // The fillRequest should have a fillContext for each partition. The first partition
         // should be filled in
-        assertThat(fillRequest[0].contexts.size()).isEqualTo(2);
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
 
-        assertThat(fillRequest[0].data.getString("key")).isEqualTo("value1");
+        assertThat(fillRequest2.data.getString("key")).isEqualTo("value1");
 
-        AssistStructure structure1 = fillRequest[0].contexts.get(0).getStructure();
+        final AssistStructure structure1 = fillRequest2.contexts.get(0).getStructure();
         ViewNode editText1Node = findNodeByResourceId(structure1, "editText1");
         // The actual value in the structure is not updated in FillRequest-contexts, but the
         // autofill value is. For text views in SaveRequest both are updated, but this is the
@@ -136,7 +138,7 @@
         assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
         assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
 
-        AssistStructure structure2 = fillRequest[0].contexts.get(1).getStructure();
+        final AssistStructure structure2 = fillRequest2.contexts.get(1).getStructure();
 
         assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
         assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
@@ -157,33 +159,33 @@
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
         // The saveRequest should have a fillContext for each partition with all the data
-        InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
         assertThat(saveRequest.contexts.size()).isEqualTo(2);
 
         assertThat(saveRequest.data.getString("key")).isEqualTo("value2");
 
-        structure1 = saveRequest.contexts.get(0).getStructure();
-        editText1Node = findNodeByResourceId(structure1, "editText1");
+        final AssistStructure saveStructure1 = saveRequest.contexts.get(0).getStructure();
+        editText1Node = findNodeByResourceId(saveStructure1, "editText1");
         assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled");
 
-        editText2Node = findNodeByResourceId(structure1, "editText2");
+        editText2Node = findNodeByResourceId(saveStructure1, "editText2");
         assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled");
 
-        assertThat(findNodeByResourceId(structure1, "editText3")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText3")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText4")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText5")).isNull();
 
-        structure2 = saveRequest.contexts.get(1).getStructure();
-        assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
-        assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
+        final AssistStructure saveStructure2 = saveRequest.contexts.get(1).getStructure();
+        assertThat(findNodeByResourceId(saveStructure2, "editText1")).isNull();
+        assertThat(findNodeByResourceId(saveStructure2, "editText2")).isNull();
 
-        ViewNode editText3Node = findNodeByResourceId(structure2, "editText3");
+        ViewNode editText3Node = findNodeByResourceId(saveStructure2, "editText3");
         assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled");
 
-        ViewNode editText4Node = findNodeByResourceId(structure2, "editText4");
+        ViewNode editText4Node = findNodeByResourceId(saveStructure2, "editText4");
         assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled");
 
-        ViewNode editText5Node = findNodeByResourceId(structure2, "editText5");
+        ViewNode editText5Node = findNodeByResourceId(saveStructure2, "editText5");
         assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled");
     }
 
@@ -213,14 +215,15 @@
 
         sReplier.addResponse(response.build());
 
-        // Trigger autofill
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText2.requestFocus();
-            mEditText1.requestFocus();
-        });
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
+
+        mActivity.setRootContainerFocusable(false);
 
         // Check UI is shown, but don't select it.
-        sReplier.getNextFillRequest();
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
         mUiBot.assertDatasets("dataset1");
 
         // Switch fragments
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
index c04685d..f9b57f1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
@@ -38,31 +38,17 @@
 import android.view.autofill.AutofillId;
 import android.widget.EditText;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.List;
-import java.util.concurrent.TimeoutException;
 
 /**
  * Test cases for the cases where the autofill id of a view is changed by the app.
  */
-public class MutableAutofillIdTest extends AutoFillServiceTestCase {
+public class MutableAutofillIdTest extends AbstractGridActivityTestCase {
 
     private static final String TAG = "MutableAutofillIdTest";
 
-    @Rule
-    public final AutofillActivityTestRule<GridActivity> mActivityRule =
-            new AutofillActivityTestRule<GridActivity>(GridActivity.class);
-
-    private GridActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
-
     @Test
     public void testDatasetPickerIsNotShownAfterViewIsSwappedOut() throws Exception {
         enableService();
@@ -303,12 +289,4 @@
         assertThat(node2Context2.getIdEntry()).isEqualTo(ID_L1C2);
         assertThat(node2Context2.getText().toString()).isEqualTo("NOD2");
     }
-
-    /**
-     * Focus to a cell and expect window event
-     */
-    void focusCell(int row, int column) throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
-                Timeouts.UI_TIMEOUT.getMaxValue());
-    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
index 2108a4b..ff3eef9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
@@ -17,10 +17,13 @@
 package android.autofillservice.cts;
 
 import static android.autofillservice.cts.Helper.callbackEventAsString;
+import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
 import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.util.Log;
 import android.view.View;
 import android.view.autofill.AutofillManager.AutofillCallback;
@@ -39,17 +42,33 @@
 
     public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
 
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread");
+    private final Handler mHandler;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    MyAutofillCallback() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
     @Override
     public void onAutofillEvent(View view, int event) {
-        Log.v(TAG, "onAutofillEvent: view=" + view + ", event=" + callbackEventAsString(event));
-        mEvents.offer(new MyEvent(view, event));
+        mHandler.post(() -> offer(new MyEvent(view, event)));
     }
 
     @Override
     public void onAutofillEvent(View view, int childId, int event) {
-        Log.v(TAG, "onAutofillEvent: view=" + view + ", child=" + childId
-                + ", event=" + callbackEventAsString(event));
-        mEvents.offer(new MyEvent(view, childId, event));
+        mHandler.post(() -> offer(new MyEvent(view, childId, event)));
+    }
+
+    private void offer(MyEvent event) {
+        Log.v(TAG, "offer: " + event);
+        Helper.offer(mEvents, event, MY_TIMEOUT.ms());
     }
 
     /**
@@ -67,7 +86,7 @@
      * Assert no more events were received.
      */
     void assertNotCalled() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         if (event != null) {
             // Not retryable.
             throw new IllegalStateException("should not have received " + event);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
index 754d5e9..5ccfb4a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
@@ -21,8 +21,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.autofill.AutofillValue;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.webkit.JavascriptInterface;
 import android.webkit.WebView;
 
 import java.util.concurrent.CountDownLatch;
@@ -33,14 +34,20 @@
  */
 public class MyWebView extends WebView {
 
+    private static final String TAG = "MyWebView";
+
     private FillExpectation mExpectation;
 
     public MyWebView(Context context) {
         super(context);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
     }
 
     public MyWebView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
     }
 
     public void expectAutofill(String username, String password) {
@@ -49,63 +56,77 @@
 
     public void assertAutofilled() throws Exception {
         assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
-        final boolean set = mExpectation.mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (mExpectation.mException != null) {
-            throw mExpectation.mException;
-        }
-        assertWithMessage("Timeout (%s ms) expecting autofill()", FILL_TIMEOUT.ms())
-                .that(set).isTrue();
-        assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
-                .isEqualTo(mExpectation.mExpectedUsername);
-        assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
-                .isEqualTo(mExpectation.mExpectedPassword);
+        mExpectation.assertUsernameCalled();
+        mExpectation.assertPasswordCalled();
     }
 
-    @Override
-    public void autofill(SparseArray<AutofillValue> values) {
-        super.autofill(values);
+    private void setJsHandler() {
+        getSettings().setJavaScriptEnabled(true);
+        addJavascriptInterface(new JavascriptHandler(), "JsHandler");
+    }
 
-        if (mExpectation == null) return;
-
-        try {
-            if (values == null || values.size() != 2) {
-                mExpectation.mException =
-                        new IllegalArgumentException("Invalid values on autofill(): " + values);
-            } else {
-                try {
-                    // We don't know the order of the values in the array. As we're just expecting
-                    // 2, it's easy to just check them individually; if we had more, than we would
-                    // need to override onProvideAutofillVirtualStructure() to keep track of the
-                    // nodes added by WebView so we could save their AutofillIds and reuse here.
-                    final String value1 = values.valueAt(0).getTextValue().toString();
-                    final String value2 = values.valueAt(1).getTextValue().toString();
-                    if (mExpectation.mExpectedUsername.equals(value1)) {
-                        mExpectation.mActualUsername = value1;
-                        mExpectation.mActualPassword = value2;
-                    } else {
-                        mExpectation.mActualUsername = value2;
-                        mExpectation.mActualPassword = value1;
-                    }
-                } catch (Exception e) {
-                    mExpectation.mException = e;
-                }
-            }
-        } finally {
-            mExpectation.mLatch.countDown();
-        }
+    boolean isAutofillEnabled() {
+        return getContext().getSystemService(AutofillManager.class).isEnabled();
     }
 
     private class FillExpectation {
-        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final CountDownLatch mUsernameLatch = new CountDownLatch(1);
+        private final CountDownLatch mPasswordLatch = new CountDownLatch(1);
         private final String mExpectedUsername;
         private final String mExpectedPassword;
         private String mActualUsername;
         private String mActualPassword;
-        private Exception mException;
 
         FillExpectation(String username, String password) {
             this.mExpectedUsername = username;
             this.mExpectedPassword = password;
         }
+
+        void setUsername(String username) {
+            mActualUsername = username;
+            mUsernameLatch.countDown();
+        }
+
+        void setPassword(String password) {
+            mActualPassword = password;
+            mPasswordLatch.countDown();
+        }
+
+        void assertUsernameCalled() throws Exception {
+            assertCalled(mUsernameLatch, "username");
+            assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
+                .isEqualTo(mExpectation.mExpectedUsername);
+        }
+
+        void assertPasswordCalled() throws Exception {
+            assertCalled(mPasswordLatch, "password");
+            assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
+                    .isEqualTo(mExpectation.mExpectedPassword);
+        }
+
+        private void assertCalled(CountDownLatch latch, String field) throws Exception {
+            if (!latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+                throw new RetryableException(FILL_TIMEOUT, "%s not called", field);
+            }
+        }
+    }
+
+    private class JavascriptHandler {
+
+        @JavascriptInterface
+        public void onUsernameChanged(String username) {
+            Log.d(TAG, "onUsernameChanged():" + username);
+            if (mExpectation != null) {
+                mExpectation.setUsername(username);
+            }
+        }
+
+        @JavascriptInterface
+        public void onPasswordChanged(String password) {
+            Log.d(TAG, "onPasswordChanged():" + password);
+            if (mExpectation != null) {
+                mExpectation.setPassword(password);
+            }
+        }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
new file mode 100644
index 0000000..a7918c9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_HIDE;
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_MASKED;
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_PLAIN;
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_SHOW;
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_MASKED;
+import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_PLAIN;
+import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields;
+import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.OnClickAction;
+import android.service.autofill.VisibilitySetterAction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Integration tests for the {@link OnClickAction} implementations.
+ */
+@AppModeFull // Service-specific test
+public class OnClickActionTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private SimpleSaveActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testHideAndShow() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId usernameId = mActivity.mInput.getAutofillId();
+        final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+
+        final CharSequenceTransformation usernameTrans =
+                new CharSequenceTransformation.Builder(usernameId, MATCH_ALL, "$1").build();
+        final CharSequenceTransformation passwordTrans =
+                new CharSequenceTransformation.Builder(passwordId, MATCH_ALL, "$1").build();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setCustomDescription(newCustomDescriptionWithHiddenFields()
+                        .addChild(R.id.username_plain, usernameTrans)
+                        .addChild(R.id.password_plain, passwordTrans)
+                        .addOnClickAction(R.id.show, new VisibilitySetterAction
+                                .Builder(R.id.hide, View.VISIBLE)
+                                .setVisibility(R.id.show, View.GONE)
+                                .setVisibility(R.id.username_plain, View.VISIBLE)
+                                .setVisibility(R.id.password_plain, View.VISIBLE)
+                                .setVisibility(R.id.username_masked, View.GONE)
+                                .setVisibility(R.id.password_masked, View.GONE)
+                                .build())
+                        .addOnClickAction(R.id.hide, new VisibilitySetterAction
+                                .Builder(R.id.show, View.VISIBLE)
+                                .setVisibility(R.id.hide, View.GONE)
+                                .setVisibility(R.id.username_masked, View.VISIBLE)
+                                .setVisibility(R.id.password_masked, View.VISIBLE)
+                                .setVisibility(R.id.username_plain, View.GONE)
+                                .setVisibility(R.id.password_plain, View.GONE)
+                                .build())
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("42");
+            mActivity.mPassword.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Assert initial UI is hidden the password.
+        final UiObject2 showButton = assertHidden(saveUi);
+
+        // Then tap SHOW and assert it's showing how
+        showButton.click();
+        final UiObject2 hideButton = assertShown(saveUi);
+
+        // Hide again
+        hideButton.click();
+        assertHidden(saveUi);
+
+        // Rinse-and repeat a couple times
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+
+        // Then save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108");
+    }
+
+    /**
+     * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW}
+     * button.
+     */
+    private UiObject2 assertHidden(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****");
+        assertInvisible(saveUi, ID_USERNAME_PLAIN);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "....");
+        assertInvisible(saveUi, ID_PASSWORD_PLAIN);
+
+        // Buttons
+        assertInvisible(saveUi, ID_HIDE);
+        return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW");
+    }
+
+    /**
+     * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE}
+     * button.
+     */
+    private UiObject2 assertShown(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42");
+        assertInvisible(saveUi, ID_USERNAME_MASKED);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108");
+        assertInvisible(saveUi, ID_PASSWORD_MASKED);
+
+        // Buttons
+        assertInvisible(saveUi, ID_SHOW);
+        return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE");
+    }
+
+    private void assertInvisible(UiObject2 saveUi, String resourceId) {
+        mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
new file mode 100644
index 0000000..69bd868
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.getAutofillServiceName;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity used to verify whether the service is enable or not when it's launched.
+ */
+public class OnCreateServiceStatusVerifierActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "OnCreateServiceStatusVerifierActivity";
+
+    static final String SERVICE_NAME = android.autofillservice.cts.NoOpAutofillService.SERVICE_NAME;
+
+    private String mSettingsOnCreate;
+    private boolean mEnabledOnCreate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mSettingsOnCreate = getAutofillServiceName();
+        mEnabledOnCreate = getAutofillManager().isEnabled();
+        Log.i(TAG, "On create: settings=" + mSettingsOnCreate + ", enabled=" + mEnabledOnCreate);
+    }
+
+    void assertServiceStatusOnCreate(boolean enabled) {
+        if (enabled) {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate)
+                .isEqualTo(SERVICE_NAME);
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isTrue();
+
+        } else {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate).isNull();
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isFalse();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
index 0b055e0..9ba3f6b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
@@ -27,15 +27,16 @@
  * Custom {@link android.os.CancellationSignal.OnCancelListener} used to assert that
  * {@link android.os.CancellationSignal.OnCancelListener} was called, and just once.
  */
-final class OneTimeCancellationSignalListener implements CancellationSignal.OnCancelListener {
+public final class OneTimeCancellationSignalListener
+        implements CancellationSignal.OnCancelListener {
     private final CountDownLatch mLatch = new CountDownLatch(1);
     private final long mTimeoutMs;
 
-    OneTimeCancellationSignalListener(long timeoutMs) {
+    public OneTimeCancellationSignalListener(long timeoutMs) {
         mTimeoutMs = timeoutMs;
     }
 
-    void assertOnCancelCalled() throws Exception {
+    public void assertOnCancelCalled() throws Exception {
         final boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
         assertWithMessage("Timeout (%s ms) waiting for onCancel()", mTimeoutMs)
                 .that(called).isTrue();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
index ab1ce58..fe49410 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
@@ -33,8 +33,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
@@ -49,20 +47,22 @@
  * </ul>
  */
 @AppModeFull // Service-specific test
-public class OptionalSaveActivityTest extends AutoFillServiceTestCase {
+public class OptionalSaveActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OptionalSaveActivity> {
 
     private static final boolean EXPECT_NO_SAVE_UI = false;
-    private static final boolean EXPECT_SAVE_UI = false;
-
-    @Rule
-    public final AutofillActivityTestRule<OptionalSaveActivity> mActivityRule =
-        new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class);
+    private static final boolean EXPECT_SAVE_UI = true;
 
     private OptionalSaveActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<OptionalSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     /**
@@ -422,7 +422,7 @@
     }
 
     @Test
-    public void testDontShowSaveUiWhenUserManuallyFilled_oneDatasetAllRequiredFields()
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetAllRequiredFields()
             throws Exception {
         saveWhenUserFilledDatasetFields(
                 new String[] {ID_ADDRESS1, ID_ADDRESS2},
@@ -441,7 +441,7 @@
     }
 
     @Test
-    public void testDontShowSaveUiWhenUserManuallyFilled_oneDatasetRequiredAndOptionalFields()
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetRequiredAndOptionalFields()
             throws Exception {
         saveWhenUserFilledDatasetFields(
                 new String[] {ID_ADDRESS1},
@@ -460,7 +460,7 @@
     }
 
     @Test
-    public void testDontShowSaveUiWhenUserManuallyFilled_multipleDatasetsDataOnFirst()
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnFirst()
             throws Exception {
         saveWhenUserFilledDatasetFields(
                 new String[] {ID_ADDRESS1},
@@ -484,7 +484,7 @@
     }
 
     @Test
-    public void testDontShowSaveUiWhenUserManuallyFilled_multipleDatasetsDataOnSecond()
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnSecond()
             throws Exception {
         saveWhenUserFilledDatasetFields(
                 new String[] {ID_ADDRESS1},
@@ -508,7 +508,7 @@
     }
 
     @Test
-    public void testShowSaveUiWhenUserManuallyFilled_requiredOnly()
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_requiredOnly()
             throws Exception {
         saveWhenUserFilledDatasetFields(
                 new String[] {ID_ADDRESS1},
@@ -516,6 +516,61 @@
                 () -> {
                     mActivity.mAddress1.setText("742 Evergreen Terrace");
                 },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalsOnlyNoRequired()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                null,
+                new String[] {ID_ADDRESS2, ID_CITY},
+                () -> {
+                    mActivity.mCity.setText("Springfield");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .setField(ID_CITY, "Springfield")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_requiredOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                },
                 EXPECT_SAVE_UI,
                 new CannedDataset.Builder()
                     .setPresentation(createPresentation("SF"))
@@ -526,13 +581,13 @@
     }
 
     @Test
-    public void testShowSaveUiWhenUserManuallyFilled_optionalOnly()
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_optionalOnly()
             throws Exception {
         saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
+                null,
                 new String[] {ID_ADDRESS2},
                 () -> {
-                    mActivity.mAddress2.setText("Simpsons House");
+                    mActivity.mAddress2.setText("Shelbyville Bluffs");
                 },
                 EXPECT_SAVE_UI,
                 new CannedDataset.Builder()
@@ -543,7 +598,7 @@
         );
     }
 
-    private void saveWhenUserFilledDatasetFields(@NonNull String[] requiredIds,
+    private void saveWhenUserFilledDatasetFields(@Nullable String[] requiredIds,
             @Nullable String[] optionalIds, @NonNull Runnable changes, boolean expectSaveUi,
             @NonNull CannedDataset...datasets) throws Exception {
         // Set service.
@@ -567,18 +622,15 @@
         // Manually fill it.
         mActivity.syncRunOnUiThread(changes);
 
-        // Make sure the snack bar is not shown.
+        // ...then tap save.
+        mActivity.save();
+
+        // Make sure the snack bar is shown as expected.
         if (expectSaveUi) {
             mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
         } else {
             mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
         }
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is not shown.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
index d37bd16..767c56d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
@@ -50,52 +50,13 @@
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillResponse;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
-import java.util.concurrent.TimeoutException;
-
 /**
  * Test case for an activity containing multiple partitions.
  */
 @AppModeFull // Service-specific test
-public class PartitionedActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<GridActivity> mActivityRule =
-        new AutofillActivityTestRule<GridActivity>(GridActivity.class);
-
-    private GridActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
-
-    /**
-     * Focus to a cell and expect window event
-     */
-    void focusCell(int row, int column) throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
-                Timeouts.UI_TIMEOUT.getMaxValue());
-    }
-
-    /**
-     * Focus to a cell and expect no window event.
-     */
-    void focusCellNoWindowChange(int row, int column) {
-        try {
-            // TODO: define a small value in Timeout
-            mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
-                    Timeouts.UI_TIMEOUT.ms());
-        } catch (TimeoutException ex) {
-            // no window events! looking good
-            return;
-        }
-        throw new IllegalStateException(String.format("Expect no window event when focusing to"
-                + " column %d row %d, but event happened", row, column));
-    }
+public class PartitionedActivityTest extends AbstractGridActivityTestCase {
 
     @Test
     public void testAutofillTwoPartitionsSkipFirst() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
new file mode 100644
index 0000000..0a0d7a5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public final class PasswordOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "PasswordOnlyActivity";
+
+    static final String EXTRA_USERNAME = "username";
+    static final String EXTRA_PASSWORD_AUTOFILL_ID = "password_autofill_id";
+
+    private TextView mWelcomeLabel;
+    private EditText mPasswordEditText;
+    private Button mLoginButton;
+    private String mUsername;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mWelcomeLabel = findViewById(R.id.welcome);
+        mPasswordEditText = findViewById(R.id.password);
+        mLoginButton = findViewById(R.id.login);
+        mLoginButton.setOnClickListener((v) -> login());
+
+        mUsername = getIntent().getStringExtra(EXTRA_USERNAME);
+        final String welcomeMsg = "Welcome to the jungle, " + mUsername;
+        Log.v(TAG, welcomeMsg);
+        mWelcomeLabel.setText(welcomeMsg);
+        final AutofillId id = getIntent().getParcelableExtra(EXTRA_PASSWORD_AUTOFILL_ID);
+        if (id != null) {
+            Log.v(TAG, "Setting autofill id to " + id);
+            mPasswordEditText.setAutofillId(id);
+        }
+    }
+
+    protected int getContentView() {
+        return R.layout.password_only_activity;
+    }
+
+    public void focusOnPassword() {
+        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
+    }
+
+    void setPassword(String password) {
+        syncRunOnUiThread(() -> mPasswordEditText.setText(password));
+    }
+
+    void login() {
+        final String password = mPasswordEditText.getText().toString();
+        Log.i(TAG, "Login as " + mUsername + "/" + password);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
index 7ca496b..c476722 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -30,25 +30,25 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
  * Covers scenarios where the behavior is different because some fields were pre-filled.
  */
 @AppModeFull // LoginActivityTest is enough to test ephemeral apps support
-public class PreFilledLoginActivityTest extends AutoFillServiceTestCase {
-
-    @Rule
-    public final AutofillActivityTestRule<PreFilledLoginActivity> mActivityRule =
-            new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class);
+public class PreFilledLoginActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<PreFilledLoginActivity> {
 
     private PreFilledLoginActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<PreFilledLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
index b3257f0..244d72f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
@@ -28,7 +28,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.Intent;
 import android.service.autofill.BatchUpdates;
 import android.service.autofill.CustomDescription;
 import android.service.autofill.RegexValidator;
@@ -38,31 +37,21 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
-import org.junit.After;
-import org.junit.Rule;
-
 import java.util.regex.Pattern;
 
-public class PreSimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
+public class PreSimpleSaveActivityTest
+        extends CustomDescriptionWithLinkTestCase<PreSimpleSaveActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<PreSimpleSaveActivity> mActivityRule =
+    private static final AutofillActivityTestRule<PreSimpleSaveActivity> sActivityRule =
             new AutofillActivityTestRule<PreSimpleSaveActivity>(PreSimpleSaveActivity.class, false);
 
-    private PreSimpleSaveActivity mActivity;
-
-    private void startActivity(boolean remainOnRecents) {
-        final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class);
-        if (remainOnRecents) {
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-        mActivity = mActivityRule.launchActivity(intent);
+    public PreSimpleSaveActivityTest() {
+        super(PreSimpleSaveActivity.class);
     }
 
-    @After
-    public void finishSimpleSaveActivity() {
-        SimpleSaveActivity.finishIt(mUiBot);
+    @Override
+    protected AutofillActivityTestRule<PreSimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
     }
 
     @Override
@@ -81,7 +70,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -110,7 +98,7 @@
                 break;
             case FINISH_ACTIVITY:
                 // ..then finishes it.
-                WelcomeActivity.finishIt(mUiBot);
+                WelcomeActivity.finishIt();
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
@@ -140,7 +128,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -200,7 +187,6 @@
         }
 
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         newActivty.syncRunOnUiThread(() -> {
@@ -234,7 +220,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -292,7 +277,6 @@
         // Trigger autofill.
         mActivity.getAutofillManager().requestAutofill(mActivity.mPreInput);
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -327,7 +311,6 @@
         newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
 
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         newActivty.syncRunOnUiThread(() -> {
@@ -373,7 +356,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
index 1dbafeb..e190cda 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
@@ -30,12 +30,18 @@
     private static final String TAG = "RetryRule";
     private final int mMaxAttempts;
 
-    public RetryRule(int maxAttempts) {
-        if (maxAttempts < 2) {
-            throw new IllegalArgumentException(
-                    "Must retry at least once; otherwise, what's the point?");
+    /**
+     * 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 = maxAttempts;
+        mMaxAttempts = retries + 1;
     }
 
     @Override
@@ -44,6 +50,13 @@
 
             @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++) {
@@ -53,7 +66,7 @@
                             Log.v(TAG, "Good News, Everyone! " + name + " passed right away");
                         } else {
                             Log.d(TAG,
-                                    "Better late than never: " + name + "passed at attempt #" + i);
+                                    "Better late than never: " + name + " passed at attempt #" + i);
                         }
                         return;
                     } catch (RetryableException e) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
index 3d182ed..0c23f9f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
@@ -18,15 +18,22 @@
 
 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 android.platform.test.annotations.AppModeFull;
-import android.support.test.runner.AndroidJUnit4;
 
 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(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 @AppModeFull // Unit test
 public class RetryRuleTest {
 
@@ -59,9 +66,16 @@
         }
     }
 
+    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(2);
+        final RetryRule rule = new RetryRule(1);
         rule.apply(new RetryableStatement<RetryableException>(1, sRetryableException), mDescription)
                 .evaluate();
     }
@@ -70,22 +84,58 @@
     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(2);
+
+        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(2);
-        try {
-            rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException),
-                    mDescription).evaluate();
-            throw new AssertionError("2ND CALL, Y U NO FAIL?");
-        } catch (RetryableException e) {
-            assertThat(e).isSameAs(sRetryableException);
-        }
+        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/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
index a13f4a0..1c009e9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
@@ -16,6 +16,12 @@
 
 package android.autofillservice.cts;
 
+import static android.service.autofill.SaveInfo.FLAG_DELAY_SAVE;
+import static android.service.autofill.SaveInfo.FLAG_DONT_SAVE_ON_FINISH;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
@@ -33,61 +39,62 @@
 @AppModeFull // Unit test
 public class SaveInfoTest {
 
-    private  final AutofillId mId = new AutofillId(42);
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillId[] mIdArray = { mId };
     private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
 
     @Test
     public void testRequiredIdsBuilder_null() {
         assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, null));
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, null));
     }
 
     @Test
     public void testRequiredIdsBuilder_empty() {
         assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
     }
 
     @Test
     public void testRequiredIdsBuilder_nullEntry() {
         assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
                         new AutofillId[] { null }));
     }
 
     @Test
     public void testBuild_noOptionalIds() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC);
         assertThrows(IllegalStateException.class, ()-> builder.build());
     }
 
     @Test
     public void testSetOptionalIds_null() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { mId });
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
         assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
     }
 
     @Test
     public void testSetOptional_empty() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { mId });
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
         assertThrows(IllegalArgumentException.class,
                 () -> builder.setOptionalIds(new AutofillId[] {}));
     }
 
     @Test
     public void testSetOptional_nullEntry() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { mId });
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
         assertThrows(IllegalArgumentException.class,
                 () -> builder.setOptionalIds(new AutofillId[] { null }));
     }
 
     @Test
     public void testAddSanitizer_illegalArgs() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { mId });
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
         // Null sanitizer
         assertThrows(IllegalArgumentException.class,
                 () -> builder.addSanitizer(null, mId));
@@ -107,10 +114,35 @@
 
     @Test
     public void testAddSanitizer_sameIdOnDifferentCalls() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
-                new AutofillId[] { mId });
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
         builder.addSanitizer(mSanitizer, mId);
         assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
     }
 
+    @Test
+    public void testBuild_invalid() {
+        // No nothing
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .build());
+        // Flag only, but invalid flag
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .setFlags(FLAG_DONT_SAVE_ON_FINISH).build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // Required ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, mIdArray)
+                .build()).isNotNull();
+
+        // Optional ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setOptionalIds(mIdArray)
+                .build()).isNotNull();
+
+        // Delayed save
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setFlags(FLAG_DELAY_SAVE)
+                .build()).isNotNull();
+    }
+
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java
new file mode 100644
index 0000000..a27a5bd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.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 android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.disableAutofillService;
+import static android.autofillservice.cts.Helper.enableAutofillService;
+import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is disabled before the activity launches.
+ */
+public class ServiceDisabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceDisabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        disableAutofillService(sContext);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isFalse());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(false);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        assertThat(afm.isEnabled()).isFalse();
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        assertThat(afm.isEnabled()).isTrue();
+
+        disableAutofillService(mContext);
+        assertThat(afm.isEnabled()).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java
new file mode 100644
index 0000000..24c2be0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.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 android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.disableAutofillService;
+import static android.autofillservice.cts.Helper.enableAutofillService;
+import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is enabled before the activity launches.
+ */
+public class ServiceEnabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceEnabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        enableAutofillService(sContext, SERVICE_NAME);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isTrue());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(true);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        assertThat(afm.isEnabled()).isTrue();
+
+        disableAutofillService(mContext);
+        assertThat(afm.isEnabled()).isFalse();
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        assertThat(afm.isEnabled()).isTrue();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index 18ef5aa..2e3d308 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -60,7 +60,7 @@
  * Test the lifecycle of a autofill session
  */
 @AppModeFull // This test requires android.permission.WRITE_EXTERNAL_STORAGE
-public class SessionLifecycleTest extends AutoFillServiceTestCase {
+public class SessionLifecycleTest extends AutoFillServiceTestCase.ManualActivityLaunch {
     private static final String TAG = "SessionLifecycleTest";
 
     private static final String ID_BUTTON = "button";
@@ -73,7 +73,7 @@
     private static final long WAIT_ACTIVITY_MS = 1000;
 
     private static final Timeout SESSION_LIFECYCLE_TIMEOUT = new Timeout(
-            "SESSION_LIFECYCLE_TIMEOUT", 2000, 2F, 5000);
+            "SESSION_LIFECYCLE_TIMEOUT", 5000, 2F, 5000);
 
     /**
      * Runs an {@code assertion}, retrying until {@code timeout} is reached.
@@ -158,111 +158,118 @@
         // Set service.
         enableService();
 
-        // Start activity that is autofilled in a separate process so it can be killed
-        startAndWaitExternalActivity();
+        mUiBot.setScreenResolution();
 
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
+        try {
 
-        // Create the authentication intent (launching a full screen activity)
-        IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
-                new Intent(getContext(), ManualAuthenticationActivity.class),
-                0).getIntentSender();
+            // Start activity that is autofilled in a separate process so it can be killed
+            startAndWaitExternalActivity();
 
-        // Prepare the authenticated response
-        ManualAuthenticationActivity.setResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, AutofillValue.forText("autofilled username"))
-                        .setPresentation(createPresentation("dataset")).build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras).build());
+            // Set expectations.
+            final Bundle extras = new Bundle();
+            extras.putString("numbers", "4815162342");
 
-        CannedFillResponse response = new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("authenticate"))
-                .build();
-        sReplier.addResponse(response);
+            // Create the authentication intent (launching a full screen activity)
+            IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
+                    new Intent(getContext(), ManualAuthenticationActivity.class),
+                    0).getIntentSender();
 
-        // Trigger autofill on username
-        mUiBot.selectByRelativeId(ID_USERNAME);
+            // Prepare the authenticated response
+            ManualAuthenticationActivity.setResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                            .setField(ID_USERNAME, AutofillValue.forText("autofilled username"))
+                            .setPresentation(createPresentation("dataset")).build())
+                    .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                    .setExtras(extras).build());
 
-        // Wait for fill request to be processed
-        sReplier.getNextFillRequest();
+            CannedFillResponse response = new CannedFillResponse.Builder()
+                    .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                    .setPresentation(createPresentation("authenticate"))
+                    .build();
+            sReplier.addResponse(response);
 
-        // Wait until authentication is shown
-        mUiBot.assertDatasets("authenticate");
+            // Trigger autofill on username
+            mUiBot.selectByRelativeId(ID_USERNAME);
 
-        // Change orientation which triggers a destroy -> create in the app as the activity
-        // cannot deal with such situations
-        mUiBot.setScreenOrientation(LANDSCAPE);
-        mUiBot.setScreenOrientation(PORTRAIT);
+            // Wait for fill request to be processed
+            sReplier.getNextFillRequest();
 
-        // Wait context and Views being recreated in rotation
-        mUiBot.assertShownByRelativeId(ID_USERNAME);
+            // Wait until authentication is shown
+            mUiBot.assertDatasets("authenticate");
 
-        // Delete stopped marker
-        getStoppedMarker(getContext()).delete();
+            // Change orientation which triggers a destroy -> create in the app as the activity
+            // cannot deal with such situations
+            mUiBot.setScreenOrientation(LANDSCAPE);
+            mUiBot.setScreenOrientation(PORTRAIT);
 
-        // Authenticate
-        mUiBot.selectDataset("authenticate");
+            // Wait context and Views being recreated in rotation
+            mUiBot.assertShownByRelativeId(ID_USERNAME);
 
-        // Kill activity that is in the background
-        killOfProcessLoginActivityProcess();
+            // Delete stopped marker
+            getStoppedMarker(getContext()).delete();
 
-        // Change orientation which triggers a destroy -> create in the app as the activity
-        // cannot deal with such situations
-        mUiBot.setScreenOrientation(PORTRAIT);
+            // Authenticate
+            mUiBot.selectDataset("authenticate");
 
-        // Approve authentication
-        mUiBot.selectByRelativeId(ID_BUTTON);
+            // Kill activity that is in the background
+            killOfProcessLoginActivityProcess();
 
-        // Wait for dataset to be shown
-        mUiBot.assertDatasets("dataset");
+            // Change orientation which triggers a destroy -> create in the app as the activity
+            // cannot deal with such situations
+            mUiBot.setScreenOrientation(PORTRAIT);
 
-        // Change orientation which triggers a destroy -> create in the app as the activity
-        // cannot deal with such situations
-        mUiBot.setScreenOrientation(LANDSCAPE);
+            // Approve authentication
+            mUiBot.selectByRelativeId(ID_BUTTON);
 
-        // Select dataset
-        mUiBot.selectDataset("dataset");
+            // Wait for dataset to be shown
+            mUiBot.assertDatasets("dataset");
 
-        // Check the results.
-        eventually("getTextById(" + ID_USERNAME + ")", () -> {
-            return mUiBot.getTextByRelativeId(ID_USERNAME).equals("autofilled username");
-        });
+            // Change orientation which triggers a destroy -> create in the app as the activity
+            // cannot deal with such situations
+            mUiBot.setScreenOrientation(LANDSCAPE);
 
-        // Set password
-        mUiBot.setTextByRelativeId(ID_PASSWORD, "new password");
+            // Select dataset
+            mUiBot.selectDataset("dataset");
 
-        // Login
-        mUiBot.selectByRelativeId(ID_LOGIN);
+            // Check the results.
+            eventually("getTextById(" + ID_USERNAME + ")", () -> {
+                return mUiBot.getTextByRelativeId(ID_USERNAME).equals("autofilled username");
+            });
 
-        // Wait for save UI to be shown
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+            // Set password
+            mUiBot.setTextByRelativeId(ID_PASSWORD, "new password");
 
-        // Change orientation to make sure save UI can handle this
-        mUiBot.setScreenOrientation(PORTRAIT);
+            // Login
+            mUiBot.selectByRelativeId(ID_LOGIN);
 
-        // Tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+            // Wait for save UI to be shown
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
 
-        // Get save request
-        InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+            // Change orientation to make sure save UI can handle this
+            mUiBot.setScreenOrientation(PORTRAIT);
 
-        // Make sure data is correctly saved
-        final AssistStructure.ViewNode username = findNodeByResourceId(saveRequest.structure,
-                ID_USERNAME);
-        assertTextAndValue(username, "autofilled username");
-        final AssistStructure.ViewNode password = findNodeByResourceId(saveRequest.structure,
-                ID_PASSWORD);
-        assertTextAndValue(password, "new password");
+            // Tap "Save".
+            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
 
-        // Make sure extras were passed back on onSave()
-        assertThat(saveRequest.data).isNotNull();
-        final String extraValue = saveRequest.data.getString("numbers");
-        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+            // Get save request
+            InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
+            assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+            // Make sure data is correctly saved
+            final AssistStructure.ViewNode username = findNodeByResourceId(saveRequest.structure,
+                    ID_USERNAME);
+            assertTextAndValue(username, "autofilled username");
+            final AssistStructure.ViewNode password = findNodeByResourceId(saveRequest.structure,
+                    ID_PASSWORD);
+            assertTextAndValue(password, "new password");
+
+            // Make sure extras were passed back on onSave()
+            assertThat(saveRequest.data).isNotNull();
+            final String extraValue = saveRequest.data.getString("numbers");
+            assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+        } finally {
+            mUiBot.resetScreenResolution();
+        }
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
index af582f6..9e471e1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
@@ -27,25 +27,26 @@
 import android.support.test.uiautomator.UiObject2;
 
 import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 @AppModeFull // Service-specific test
-public class SettingsIntentTest extends AutoFillServiceTestCase {
+public class SettingsIntentTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<TrampolineForResultActivity> {
 
     private static final int MY_REQUEST_CODE = 42;
 
-    @Rule
-    public final AutofillActivityTestRule<TrampolineForResultActivity> mActivityRule =
-            new AutofillActivityTestRule<TrampolineForResultActivity>(
-                    TrampolineForResultActivity.class);
 
     protected TrampolineForResultActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<TrampolineForResultActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TrampolineForResultActivity>(
+                TrampolineForResultActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @After
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
index a9924f4..e292c3c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
@@ -50,14 +50,6 @@
         return sInstance;
     }
 
-    static void finishIt(UiBot uiBot) {
-        if (sInstance != null) {
-            Log.d(TAG, "So long and thanks for all the fish!");
-            sInstance.finish();
-            uiBot.assertGoneByRelativeId(ID_LABEL, Timeouts.ACTIVITY_RESURRECTION);
-        }
-    }
-
     public SimpleSaveActivity() {
         sInstance = this;
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index 673d13d..93ff57b 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.LARGE_STRING;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.assertTextValue;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
@@ -28,13 +29,17 @@
 import static android.autofillservice.cts.SimpleSaveActivity.TEXT_LABEL;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
 
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
 import android.content.Intent;
@@ -54,34 +59,32 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
-import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
 
 import java.util.regex.Pattern;
 
-public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
+public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<SimpleSaveActivity> mActivityRule =
+    private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
             new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
 
-    @Rule
-    public final AutofillActivityTestRule<WelcomeActivity> mWelcomeActivityRule =
+    private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
             new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
 
-    private SimpleSaveActivity mActivity;
-
-    private void startActivity() {
-        startActivity(false);
+    public SimpleSaveActivityTest() {
+        super(SimpleSaveActivity.class);
     }
 
-    private void startActivity(boolean remainOnRecents) {
-        final Intent intent = new Intent(mContext, SimpleSaveActivity.class);
-        if (remainOnRecents) {
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-        mActivity = mActivityRule.launchActivity(intent);
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
     }
 
     private void restartActivity() {
@@ -133,6 +136,66 @@
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
     }
 
+    @Test
+    public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
+        startActivity();
+
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
+        final String[] hintsOnFill = inputOnFill.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnFill).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnFill[0]);
+        Helper.assertEqualsToLargeString(hintsOnFill[1]);
+        Helper.assertEqualsToLargeString(hintsOnFill[2]);
+
+        // Select dataset.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+        autofillExpecation.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
+        assertTextAndValue(inputOnSave, "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+
+        final String[] hintsOnSave = inputOnSave.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnSave).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnSave[0]);
+        Helper.assertEqualsToLargeString(hintsOnSave[1]);
+        Helper.assertEqualsToLargeString(hintsOnSave[2]);
+    }
+
     /**
      * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
      * tests the integration of Autofill with Accessibility.
@@ -198,8 +261,12 @@
         try {
             saveTest(true);
         } finally {
-            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-            cleanUpAfterScreenOrientationIsBackToPortrait();
+            try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
         }
     }
 
@@ -217,7 +284,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -242,6 +308,125 @@
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
     }
 
+    /**
+     * Emulates an app dyanmically adding the password field after username is typed.
+     */
+    @Test
+    @AppModeFull // testAutoFillOneDatasetAndSave() is enough to test ephemeral apps support
+    public void testPartitionedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Set 1st field but don't commit session
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_INPUT, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
+    }
+
+    /**
+     * Emulates an app using fragments to display username and password in 2 steps.
+     */
+    @Test
+    @AppModeFull // testAutoFillOneDatasetAndSave() is enough to test ephemeral apps support
+    public void testDelayedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on second "fragment"
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Get username from 1st request.
+        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
+
+        // Get password from 2nd request.
+        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
+    }
+
     @Test
     public void testSave_launchIntent() throws Exception {
         startActivity();
@@ -250,25 +435,29 @@
         enableService();
 
         // Set expectations.
-        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"));
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
+        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
+                .addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .build());
 
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
+
+            // Disable autofill so it's not triggered again after WelcomeActivity finishes
+            // and mActivity is resumed (with focus on mInput) after the session is closed
+            mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
         });
 
         // Save it...
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         sReplier.getNextSaveRequest();
+
         // ... and assert activity was launched
         WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
     }
@@ -288,16 +477,23 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
-        // Trigger save and start a new session right away.
+        // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
-            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
         });
 
-        // Make sure Save UI for 1st session was canceled....
+        // 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));
+        sReplier.getNextFillRequest();
+
+        // Make sure Save UI for 1st session was canceled.
         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
@@ -371,7 +567,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Cancel session.
         mActivity.getAutofillManager().cancel();
@@ -422,7 +617,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -559,7 +753,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -590,7 +783,7 @@
                 break;
             case FINISH_ACTIVITY:
                 // ..then finishes it.
-                WelcomeActivity.finishIt(mUiBot);
+                WelcomeActivity.finishIt();
                 break;
             default:
                 throw new IllegalArgumentException("invalid type: " + type);
@@ -628,7 +821,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -683,7 +875,6 @@
         }
 
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -715,7 +906,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -829,7 +1019,6 @@
         // Trigger autofill.
         mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -891,7 +1080,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -1008,6 +1196,7 @@
 
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
 
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("id");
@@ -1162,7 +1351,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
@@ -1223,7 +1411,6 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        Helper.assertHasSessions(mPackageName);
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("108");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
index d08c7e7..2b6d5c6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
@@ -93,4 +93,38 @@
         assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
                 .isEqualTo("42");
     }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch_withOptionalGroup() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("(\\d*)\\s?(\\d*)?"), "$1$2");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42 108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+                .isEqualTo("42");
+        final TextValueSanitizer ccSanitizer = new TextValueSanitizer(Pattern.compile(
+                "^(\\d{4,5})-?\\s?(\\d{4,6})-?\\s?(\\d{4,5})" // first 3 are required
+                        + "-?\\s?((?:\\d{4,5})?)-?\\s?((?:\\d{3,5})?)$"), // last 2 are optional
+                "$1$2$3$4$5");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444 5555")).getTextValue())
+                        .isEqualTo("11112222333344445555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-55555")).getTextValue())
+                        .isEqualTo("11111222222333334444455555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444")).getTextValue())
+                        .isEqualTo("1111222233334444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-")).getTextValue())
+                        .isEqualTo("111112222223333344444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333")).getTextValue())
+                        .isEqualTo("111122223333");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333 ")).getTextValue())
+                        .isEqualTo("1111122222233333");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
index 903011f..c7f3480 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
@@ -17,17 +17,17 @@
 
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Rule;
-
 @AppModeFull // Unit test
 public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<TimePickerClockActivity> mActivityRule =
-        new AutofillActivityTestRule<TimePickerClockActivity>(TimePickerClockActivity.class);
-
     @Override
-    protected TimePickerClockActivity getTimePickerActivity() {
-        return mActivityRule.getActivity();
+    protected AutofillActivityTestRule<TimePickerClockActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerClockActivity>(
+                TimePickerClockActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
index 07e4592..a6ac44f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
@@ -17,17 +17,17 @@
 
 import android.platform.test.annotations.AppModeFull;
 
-import org.junit.Rule;
-
 @AppModeFull // Unit test
 public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
 
-    @Rule
-    public final AutofillActivityTestRule<TimePickerSpinnerActivity> mActivityRule =
-        new AutofillActivityTestRule<TimePickerSpinnerActivity>(TimePickerSpinnerActivity.class);
-
     @Override
-    protected TimePickerSpinnerActivity getTimePickerActivity() {
-        return mActivityRule.getActivity();
+    protected AutofillActivityTestRule<TimePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerSpinnerActivity>(
+                TimePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
index 16973f0..480cf13 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
@@ -36,14 +36,14 @@
 /**
  * Base class for {@link AbstractTimePickerActivity} tests.
  */
-abstract class TimePickerTestCase<T extends AbstractTimePickerActivity>
-        extends AutoFillServiceTestCase {
+abstract class TimePickerTestCase<A extends AbstractTimePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
 
-    protected abstract T getTimePickerActivity();
+    protected A mActivity;
 
     @Test
     public void testAutoFillAndSave() throws Exception {
-        final T activity = getTimePickerActivity();
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
 
         // Set service.
         enableService();
@@ -62,10 +62,10 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_TIME_PICKER)
                 .build());
 
-        activity.expectAutoFill("4:20", 4, 20);
+        mActivity.expectAutoFill("4:20", 4, 20);
 
         // Trigger auto-fill.
-        activity.onOutput((v) -> v.requestFocus());
+        mActivity.onOutput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
 
         // Assert properties of TimePicker field.
@@ -75,11 +75,11 @@
         mUiBot.selectDataset("Adventure Time");
 
         // Check the results.
-        activity.assertAutoFilled();
+        mActivity.assertAutoFilled();
 
         // Trigger save.
-        activity.setTime(10, 40);
-        activity.tapOk();
+        mActivity.setTime(10, 40);
+        mActivity.tapOk();
 
         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeout.java b/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
index 543a4d2..d442cda 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
@@ -16,10 +16,12 @@
 package android.autofillservice.cts;
 
 import android.os.SystemClock;
-import androidx.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
 import java.util.concurrent.Callable;
 
 /**
@@ -36,6 +38,10 @@
     private final float mMultiplier;
     private final long mMaxValue;
 
+    private final Sleeper mSleeper;
+
+    private static final Sleeper DEFAULT_SLEEPER = (t) -> SystemClock.sleep(t);
+
     /**
      * Default constructor.
      *
@@ -49,6 +55,12 @@
      * 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);
@@ -59,6 +71,7 @@
         if (TextUtils.isEmpty(name)) {
             throw new IllegalArgumentException("no name");
         }
+        mSleeper = sleeper;
         mName = name;
         mCurrentValue = initialValue;
         mMultiplier = multiplier;
@@ -151,24 +164,28 @@
         if (retryMs < 1) {
             throw new IllegalArgumentException("need to sleep at least 1ms, right?");
         }
-        long startTime = System.currentTimeMillis();
+        long startTime = SystemClock.elapsedRealtime();
         int attempt = 0;
-        while (System.currentTimeMillis() - startTime <= mCurrentValue) {
+        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 "
-                        + retryMs + "ms before trying again");
+                        + napTime + "ms before trying again");
             }
-            SystemClock.sleep(retryMs);
+            mSleeper.sleep(napTime);
+            totalSlept += napTime;
+
             retryMs *= mMultiplier;
         }
-        Log.w(TAG, description + " failed after " + attempt + " attempts and "
-                + (System.currentTimeMillis() - startTime) + "ms: " + this);
+        Log.w(TAG, description + " failed after " + attempt + " attempts and " + totalSlept + "ms: "
+                + this);
         throw new RetryableException(this, description);
     }
 
@@ -178,4 +195,8 @@
                 + mMaxValue + "ms]";
     }
 
+    @VisibleForTesting
+    interface Sleeper {
+        void sleep(long napTimeMs);
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
index 69c69e4..9b3decc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
@@ -24,6 +24,9 @@
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.expectThrows;
 
+import android.autofillservice.cts.Timeout.Sleeper;
+import android.os.SystemClock;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -40,6 +43,8 @@
     @Mock
     private Callable<Object> mJob;
 
+    private final MySleeper mSleeper = new MySleeper();
+
     @Test
     public void testInvalidConstructor() {
         // Invalid name
@@ -98,26 +103,41 @@
 
     @Test
     public void testRun_successOnFirstAttempt() throws Exception {
-        final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+        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(NAME, 100, 2, 500);
+        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(NAME, 100, 2, 500);
+        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;
+        }
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
index d59d1a5..56788e7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
@@ -21,48 +21,76 @@
  */
 final class Timeouts {
 
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 20_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 2_000;
+
     /**
      * Timeout until framework binds / unbinds from service.
      */
-    static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for {@link MyAutofillCallback#assertNotCalled()} - test will sleep for that amount of
+     * time as there is no callback that be received to assert it's not shown.
+     */
+    static final long CALLBACK_NOT_CALLED_TIMEOUT_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
 
     /**
      * Timeout until framework unbinds from a service.
      */
     // TODO: must be higher than RemoteFillService.TIMEOUT_IDLE_BIND_MILLIS, so we should use a
     // @hidden @Testing constants instead...
-    static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT", 10000, 2F, 10000);
+    static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout to get the expected number of fill events.
      */
-    static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout for expected autofill requests.
      */
-    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
+            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout for expected save requests.
      */
-    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
+            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout used when save is not expected to be shown - test will sleep for that amount of time
      * as there is no callback that be received to assert it's not shown.
      */
-    static final long SAVE_NOT_SHOWN_NAPTIME_MS = 5000;
+    static final long SAVE_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
 
     /**
      * Timeout for UI operations. Typically used by {@link UiBot}.
      */
-    static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for a11y window change events.
+     */
+    static final long WINDOW_CHANGE_TIMEOUT_MS = ONE_TIMEOUT_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout used when an a11y window change events is not expected to be generated - test will
+     * sleep for that amount of time as there is no callback that be received to assert it's not
+     * shown.
+     */
+    static final long WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
 
     /**
      * Timeout for webview operations. Typically used by {@link UiBot}.
      */
-    static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 8000, 2F, 16000);
+    // TODO(b/80317628): switch back to ONE_TIMEOUT_TO_RULE_THEN_ALL_MS once fixed...
+    static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 3_000, 2F, 5_000);
 
     /**
      * Timeout for showing the autofill dataset picker UI.
@@ -72,26 +100,27 @@
      *
      * <p>Typically used by {@link UiBot}.
      */
-    static final Timeout UI_DATASET_PICKER_TIMEOUT =
-            new Timeout("UI_DATASET_PICKER_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout UI_DATASET_PICKER_TIMEOUT = new Timeout("UI_DATASET_PICKER_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
      * amount of time as there is no callback that be received to assert it's not shown.
      */
-    static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = 5000;
+    static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
 
     /**
      * Timeout (in milliseconds) for an activity to be brought out to top.
      */
-    static final Timeout ACTIVITY_RESURRECTION =
-            new Timeout("ACTIVITY_RESURRECTION", 6000, 3F, 20000);
+    static final Timeout ACTIVITY_RESURRECTION = new Timeout("ACTIVITY_RESURRECTION",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
      * Timeout for changing the screen orientation.
      */
-    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT =
-            new Timeout("UI_SCREEN_ORIENTATION_TIMEOUT", 5000, 2F, 10000);
+    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT = new Timeout(
+            "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     private Timeouts() {
         throw new UnsupportedOperationException("contain static methods only");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 6f8979f..85e8983 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -22,6 +22,7 @@
 import static android.autofillservice.cts.Timeouts.UI_DATASET_PICKER_TIMEOUT;
 import static android.autofillservice.cts.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
 import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 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_EMAIL_ADDRESS;
@@ -32,6 +33,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
@@ -42,6 +45,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.SearchCondition;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
@@ -53,8 +57,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
+import java.io.File;
+import java.io.FileInputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -63,7 +67,7 @@
 /**
  * Helper for UI-related needs.
  */
-final class UiBot {
+public final class UiBot {
 
     private static final String TAG = "AutoFillCtsUiBot";
 
@@ -75,6 +79,7 @@
     private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
     private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
     private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_ID_OVERFLOW = "overflow";
 
     private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
     private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
@@ -125,11 +130,11 @@
 
     private boolean mOkToCallAssertNoDatasets;
 
-    UiBot() {
+    public UiBot() {
         this(UI_TIMEOUT);
     }
 
-    UiBot(Timeout defaultTimeout) {
+    public UiBot(Timeout defaultTimeout) {
         mDefaultTimeout = defaultTimeout;
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(instrumentation);
@@ -138,12 +143,52 @@
         mAutoman = instrumentation.getUiAutomation();
     }
 
-    void reset() {
+    public void waitForIdle() {
+        final long before = SystemClock.elapsedRealtimeNanos();
+        mDevice.waitForIdle();
+        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
+        Log.v(TAG, "device idle in " + delta + "ms");
+    }
+
+    public void reset() {
         mOkToCallAssertNoDatasets = false;
     }
 
-    UiDevice getDevice() {
-        return mDevice;
+    /**
+     * Assumes the device has a minimum height and width of {@code minSize}, throwing a
+     * {@code AssumptionViolatedException} if it doesn't (so the test is skiped by the JUnit
+     * Runner).
+     */
+    public void assumeMinimumResolution(int minSize) {
+        final int width = mDevice.getDisplayWidth();
+        final int heigth = mDevice.getDisplayHeight();
+        final int min = Math.min(width, heigth);
+        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= minSize);
+        Log.d(TAG, "assumeMinimumResolution(" + minSize + ") passed: screen size is "
+                + width + "x" + heigth);
+    }
+
+    /**
+     * Sets the screen resolution in a way that the IME doesn't interfere with the Autofill UI
+     * when the device is rotated to landscape.
+     *
+     * When called, test must call <p>{@link #resetScreenResolution()} in a {@code finally} block.
+     */
+    public void setScreenResolution() {
+        assumeMinimumResolution(500);
+
+        runShellCommand("wm size 1080x1920");
+        runShellCommand("wm density 320");
+    }
+
+    /**
+     * Resets the screen resolution.
+     *
+     * <p>Should always be called after {@link #setScreenResolution()}.
+     */
+    public void resetScreenResolution() {
+        runShellCommand("wm density reset");
+        runShellCommand("wm size reset");
     }
 
     /**
@@ -153,7 +198,7 @@
      * dataset picker is shown - if that's not the case, call
      * {@link #assertNoDatasetsEver()} instead.
      */
-    void assertNoDatasets() throws Exception {
+    public void assertNoDatasets() throws Exception {
         if (!mOkToCallAssertNoDatasets) {
             throw new IllegalStateException(
                     "Cannot call assertNoDatasets() without calling assertDatasets first");
@@ -168,7 +213,7 @@
      * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
      * cases where the dataset picker was not previous shown.
      */
-    void assertNoDatasetsEver() throws Exception {
+    public void assertNoDatasetsEver() throws Exception {
         assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
                 DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
     }
@@ -178,7 +223,7 @@
      *
      * @return the dataset picker object.
      */
-    UiObject2 assertDatasets(String...names) throws Exception {
+    public UiObject2 assertDatasets(String...names) throws Exception {
         // TODO: change run() so it can rethrow the original message
         return UI_DATASET_PICKER_TIMEOUT.run("assertDatasets: " + Arrays.toString(names), () -> {
             final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
@@ -200,7 +245,7 @@
      *
      * @return the dataset picker object.
      */
-    UiObject2 assertDatasetsContains(String...names) throws Exception {
+    public UiObject2 assertDatasetsContains(String...names) throws Exception {
         // TODO: change run() so it can rethrow the original message
         return UI_DATASET_PICKER_TIMEOUT.run("assertDatasets: " + Arrays.toString(names), () -> {
             final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
@@ -223,7 +268,7 @@
      *
      * @return the dataset picker object.
      */
-    UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
+    public UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
             throws Exception {
         final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
         final List<String> expectedChild = new ArrayList<>();
@@ -250,7 +295,7 @@
     /**
      * Gets the text of this object children.
      */
-    List<String> getChildrenAsText(UiObject2 object) {
+    public List<String> getChildrenAsText(UiObject2 object) {
         final List<String> list = new ArrayList<>();
         getChildrenAsText(object, list);
         return list;
@@ -269,7 +314,7 @@
     /**
      * Selects a dataset that should be visible in the floating UI.
      */
-    void selectDataset(String name) throws Exception {
+    public void selectDataset(String name) throws Exception {
         final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
         selectDataset(picker, name);
     }
@@ -277,7 +322,7 @@
     /**
      * Selects a dataset that should be visible in the floating UI.
      */
-    void selectDataset(UiObject2 picker, String name) {
+    public void selectDataset(UiObject2 picker, String name) {
         final UiObject2 dataset = picker.findObject(By.text(name));
         if (dataset == null) {
             throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(picker));
@@ -291,7 +336,7 @@
      * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
      * {@link #selectDataset(String)}.
      */
-    void selectByText(String name) throws Exception {
+    public void selectByText(String name) throws Exception {
         Log.v(TAG, "selectByText(): " + name);
 
         final UiObject2 object = waitForObject(By.text(name));
@@ -315,6 +360,16 @@
     }
 
     /**
+     * Finds a node by text, without waiting for it to be shown (but failing if it isn't).
+     */
+    @NonNull
+    public UiObject2 findRightAwayByText(@NonNull String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("no UIObject for text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
      * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
      * for it.
      *
@@ -322,7 +377,7 @@
      */
     public void assertNotShowingForSure(String text) throws Exception {
         final UiObject2 object = mDevice.findObject(By.text(text));
-        assertWithMessage("Find node with text '%s'", text).that(object).isNull();
+        assertWithMessage("Found node with text '%s'", text).that(object).isNull();
     }
 
     /**
@@ -339,7 +394,7 @@
     /**
      * Checks if a View with a certain text exists.
      */
-    boolean hasViewWithText(String name) {
+    public boolean hasViewWithText(String name) {
         Log.v(TAG, "hasViewWithText(): " + name);
 
         return mDevice.findObject(By.text(name)) != null;
@@ -348,7 +403,7 @@
     /**
      * Selects a view by id.
      */
-    UiObject2 selectByRelativeId(String id) throws Exception {
+    public UiObject2 selectByRelativeId(String id) throws Exception {
         Log.v(TAG, "selectByRelativeId(): " + id);
         UiObject2 object = waitForObject(By.res(mPackageName, id));
         object.click();
@@ -358,7 +413,7 @@
     /**
      * Asserts the id is shown on the screen.
      */
-    UiObject2 assertShownById(String id) throws Exception {
+    public UiObject2 assertShownById(String id) throws Exception {
         final UiObject2 object = waitForObject(By.res(id));
         assertThat(object).isNotNull();
         return object;
@@ -367,11 +422,11 @@
     /**
      * Asserts the id is shown on the screen, using a resource id from the test package.
      */
-    UiObject2 assertShownByRelativeId(String id) throws Exception {
+    public UiObject2 assertShownByRelativeId(String id) throws Exception {
         return assertShownByRelativeId(id, mDefaultTimeout);
     }
 
-    UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
+    public UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
         final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
         assertThat(obj).isNotNull();
         return obj;
@@ -383,8 +438,30 @@
      * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
      * it might pass without really asserting anything.
      */
-    void assertGoneByRelativeId(String id, Timeout timeout) {
-        boolean gone = mDevice.wait(Until.gone(By.res(mPackageName, id)), timeout.ms());
+    public void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, id, timeout);
+    }
+
+    public void assertGoneByRelativeId(int resId, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, getIdName(resId), timeout);
+    }
+
+    private String getIdName(int resId) {
+        return mContext.getResources().getResourceEntryName(resId);
+    }
+
+    /**
+     * Asserts the id is not shown on the parent anymore, using a resource id from the test package.
+     *
+     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+     * it might pass without really asserting anything.
+     */
+    public void assertGoneByRelativeId(@Nullable UiObject2 parent, @NonNull String id,
+            @NonNull Timeout timeout) {
+        final SearchCondition<Boolean> condition = Until.gone(By.res(mPackageName, id));
+        final boolean gone = parent != null
+                ? parent.wait(condition, timeout.ms())
+                : mDevice.wait(condition, timeout.ms());
         if (!gone) {
             final String message = "Object with id '" + id + "' should be gone after "
                     + timeout + " ms";
@@ -393,6 +470,16 @@
         }
     }
 
+    public UiObject2 assertShownByRelativeId(int resId) throws Exception {
+        return assertShownByRelativeId(getIdName(resId));
+    }
+
+    public void assertNeverShownByRelativeId(@NonNull String description, int resId, long timeout)
+            throws Exception {
+        final BySelector selector = By.res(Helper.MY_PACKAGE, getIdName(resId));
+        assertNeverShown(description, selector, timeout);
+    }
+
     /**
      * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
      */
@@ -410,42 +497,42 @@
     /**
      * Gets the text set on a view.
      */
-    String getTextByRelativeId(String id) throws Exception {
+    public String getTextByRelativeId(String id) throws Exception {
         return waitForObject(By.res(mPackageName, id)).getText();
     }
 
     /**
      * Focus in the view with the given resource id.
      */
-    void focusByRelativeId(String id) throws Exception {
+    public void focusByRelativeId(String id) throws Exception {
         waitForObject(By.res(mPackageName, id)).click();
     }
 
     /**
      * Sets a new text on a view.
      */
-    void setTextByRelativeId(String id, String newText) throws Exception {
+    public void setTextByRelativeId(String id, String newText) throws Exception {
         waitForObject(By.res(mPackageName, id)).setText(newText);
     }
 
     /**
      * Asserts the save snackbar is showing and returns it.
      */
-    UiObject2 assertSaveShowing(int type) throws Exception {
+    public UiObject2 assertSaveShowing(int type) throws Exception {
         return assertSaveShowing(SAVE_TIMEOUT, type);
     }
 
     /**
      * Asserts the save snackbar is showing and returns it.
      */
-    UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
+    public UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
         return assertSaveShowing(null, timeout, type);
     }
 
     /**
      * Asserts the save snackbar is showing with the Update message and returns it.
      */
-    UiObject2 assertUpdateShowing(int... types) throws Exception {
+    public UiObject2 assertUpdateShowing(int... types) throws Exception {
         return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
                 null, SAVE_TIMEOUT, types);
     }
@@ -453,7 +540,7 @@
     /**
      * Presses the Back button.
      */
-    void pressBack() {
+    public void pressBack() {
         Log.d(TAG, "pressBack()");
         mDevice.pressBack();
     }
@@ -461,7 +548,7 @@
     /**
      * Presses the Home button.
      */
-    void pressHome() {
+    public void pressHome() {
         Log.d(TAG, "pressHome()");
         mDevice.pressHome();
     }
@@ -469,10 +556,14 @@
     /**
      * Asserts the save snackbar is not showing.
      */
-    void assertSaveNotShowing(int type) throws Exception {
+    public void assertSaveNotShowing(int type) throws Exception {
         assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
     }
 
+    public void assertSaveNotShowing() throws Exception {
+        assertNeverShown("save UI", SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
+    }
+
     private String getSaveTypeString(int type) {
         final String typeResourceName;
         switch (type) {
@@ -497,26 +588,26 @@
         return getString(typeResourceName);
     }
 
-    UiObject2 assertSaveShowing(String description, int... types) throws Exception {
+    public UiObject2 assertSaveShowing(String description, int... types) throws Exception {
         return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
                 description, SAVE_TIMEOUT, types);
     }
 
-    UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
+    public UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
             throws Exception {
         return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
                 description, timeout, types);
     }
 
-    UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
+    public UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
             int... types) throws Exception {
         return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
                 SAVE_TIMEOUT, types);
     }
 
 
-    UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle, String description,
-            Timeout timeout, int... types) throws Exception {
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            String description, Timeout timeout, int... types) throws Exception {
         final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
 
         final UiObject2 titleView =
@@ -601,7 +692,7 @@
      * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
      * @param types expected types of save info.
      */
-    void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
+    public void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
         final UiObject2 saveSnackBar = assertSaveShowing(
                 SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
         saveForAutofill(saveSnackBar, yesDoIt);
@@ -618,7 +709,8 @@
      * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
      * @param types expected types of save info.
      */
-    void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types) throws Exception {
+    public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
+            throws Exception {
         final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle,null, types);
         saveForAutofill(saveSnackBar, yesDoIt);
     }
@@ -630,7 +722,7 @@
      *            {@link #assertSaveShowing(int)}.
      * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
      */
-    void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
+    public void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
         final String id = yesDoIt ? "autofill_save_yes" : "autofill_save_no";
 
         final UiObject2 button = saveSnackBar.findObject(By.res("android", id));
@@ -647,15 +739,38 @@
      * faster.
      *
      * @param id resource id of the field.
+     * @param expectOverflow whether overflow menu should be shown (when clipboard contains text)
      */
-    UiObject2 getAutofillMenuOption(String id) throws Exception {
+    public UiObject2 getAutofillMenuOption(String id, boolean expectOverflow) throws Exception {
         final UiObject2 field = waitForObject(By.res(mPackageName, id));
         // TODO: figure out why obj.longClick() doesn't always work
         field.click(3000);
 
-        final List<UiObject2> menuItems = waitForObjects(
+        List<UiObject2> menuItems = waitForObjects(
                 By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
         final String expectedText = getAutofillContextualMenuTitle();
+
+        if (expectOverflow) {
+            // Check first menu does not have AUTOFILL
+            for (UiObject2 menuItem : menuItems) {
+                final String menuName = menuItem.getText();
+                if (menuName.equalsIgnoreCase(expectedText)) {
+                    throw new IllegalStateException(expectedText + " in context menu");
+                }
+            }
+
+            final BySelector overflowSelector = By.res("android", RESOURCE_ID_OVERFLOW);
+
+            // Click overflow menu button.
+            final UiObject2 overflowMenu = waitForObject(overflowSelector, mDefaultTimeout);
+            overflowMenu.click();
+
+            // Wait for overflow menu to show.
+            mDevice.wait(Until.gone(overflowSelector), 1000);
+        }
+
+        menuItems = waitForObjects(
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
         final StringBuffer menuNames = new StringBuffer();
         for (UiObject2 menuItem : menuItems) {
             final String menuName = menuItem.getText();
@@ -718,13 +833,15 @@
             });
         } catch (RetryableException e) {
             if (dumpOnError) {
-                dumpScreen("waitForObject() for " + selector + "failed");
+                dumpScreen("waitForObject() for " + selector + "on "
+                        + (parent == null ? "mDevice" : parent) + " failed");
             }
             throw e;
         }
     }
 
-    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout)
+    public UiObject2 waitForObject(@Nullable UiObject2 parent, @NonNull BySelector selector,
+            @NonNull Timeout timeout)
             throws Exception {
         return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
     }
@@ -735,18 +852,30 @@
      * @param selector {@link BySelector} that identifies the object.
      * @param timeout timeout in ms
      */
-    private UiObject2 waitForObject(BySelector selector, Timeout timeout) throws Exception {
-        return waitForObject(null, selector, timeout);
+    private UiObject2 waitForObject(@NonNull BySelector selector, @NonNull Timeout timeout)
+            throws Exception {
+        return waitForObject(/* parent= */ null, selector, timeout);
     }
 
     /**
-     * Execute a Runnable and wait for TYPE_WINDOWS_CHANGED or TYPE_WINDOW_STATE_CHANGED.
-     * TODO: No longer need Retry, Refactoring the Timeout (e.g. we probably need two values:
-     * one large timeout value that expects window event, one small value that expect no window
-     * event)
+     * Waits for and returns a child from a parent {@link UiObject2}.
      */
-    public void waitForWindowChange(Runnable runnable, long timeoutMillis) throws TimeoutException {
-        mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
+    public UiObject2 assertChildText(UiObject2 parent, String resourceId, String expectedText)
+            throws Exception {
+        final UiObject2 child = waitForObject(parent, By.res(mPackageName, resourceId),
+                Timeouts.UI_TIMEOUT);
+        assertWithMessage("wrong text for view '%s'", resourceId).that(child.getText())
+                .isEqualTo(expectedText);
+        return child;
+    }
+
+    /**
+     * Execute a Runnable and wait for {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} or
+     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
+     */
+    public AccessibilityEvent waitForWindowChange(Runnable runnable, long timeoutMillis)
+            throws TimeoutException {
+        return mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
             switch (event.getEventType()) {
                 case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
@@ -756,6 +885,10 @@
         }, timeoutMillis);
     }
 
+    public AccessibilityEvent waitForWindowChange(Runnable runnable) throws TimeoutException {
+        return waitForWindowChange(runnable, Timeouts.WINDOW_CHANGE_TIMEOUT_MS);
+    }
+
     /**
      * Waits for and returns a list of objects.
      *
@@ -833,25 +966,21 @@
     }
 
     /**
-     * Dumps the current view hierarchy int the output stream.
+     * Dumps the current view hierarchy and take a screenshot and save both locally so they can be
+     * inspected later.
      */
-    public void dumpScreen(String cause) {
-        new Exception("dumpScreen(cause=" + cause + ") stacktrace").printStackTrace(System.out);
+    public void dumpScreen(@NonNull String cause) {
         try {
-            try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
-                mDevice.dumpWindowHierarchy(os);
-                os.flush();
-                Log.w(TAG, "Dumping window hierarchy because " + cause);
-                for (String line : os.toString("UTF-8").split("\n")) {
-                    Log.w(TAG, line);
-                    // Sleep a little bit to avoid logs being ignored due to spam
-                    SystemClock.sleep(100);
-                }
+            final File file = Helper.createTestFile("hierarchy.xml");
+            if (file == null) return;
+            Log.w(TAG, "Dumping window hierarchy because " + cause + " on " + file);
+            try (FileInputStream fis = new FileInputStream(file)) {
+                mDevice.dumpWindowHierarchy(file);
             }
-        } catch (IOException e) {
-            // Just ignore it...
-            Log.e(TAG, "exception dumping window hierarchy", e);
-            return;
+        } catch (Exception e) {
+            Log.e(TAG, "error dumping screen on " + cause, e);
+        } finally {
+            takeScreenshotAndSave();
         }
     }
 
@@ -867,6 +996,22 @@
     }
 
     /**
+     * Takes a screenshot and save it in the file system for post-mortem analysis.
+     */
+    public void takeScreenshotAndSave() {
+        File file = null;
+        try {
+            file = Helper.createTestFile("screenshot.png");
+            if (file != null) {
+                final Bitmap screenshot = takeScreenshot();
+                Helper.dumpBitmap(screenshot, file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
+        }
+    }
+
+    /**
      * Asserts the contents of a child element.
      *
      * @param parent parent object
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
index 354bbc9..95845f7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
@@ -118,8 +118,10 @@
 
     @Test
     public void testAdd_duplicatedValue() {
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId));
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId).build())
+                .isNotNull();
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId2).build())
+                .isNotNull();
     }
 
     @Test
@@ -146,11 +148,33 @@
     }
 
     @Test
+    public void testSetFcAlgorithmForCategory_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder
+                .setFieldClassificationAlgorithmForCategory(null, "algo_mas", null));
+    }
+
+    @Test
+    public void testSetFcAlgorithmForCateogry() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithmForCategory(
+                mCategoryId, "algo_mas", null).build();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isEqualTo(
+                "algo_mas");
+    }
+
+    @Test
     public void testBuild_valid() {
         final UserData userData = mBuilder.build();
         assertThat(userData).isNotNull();
         assertThat(userData.getId()).isEqualTo(mId);
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isNull();
+    }
+
+    @Test
+    public void testGetFcAlgorithmForCategory_invalid() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThrows(NullPointerException.class, () -> userData
+                .getFieldClassificationAlgorithmForCategory(null));
     }
 
     @Test
@@ -159,7 +183,8 @@
 
         assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
         assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationAlgorithm("algo_mas", null));
+                () -> mBuilder.setFieldClassificationAlgorithmForCategory(mCategoryId,
+                        "algo_mas", null));
         assertThrows(IllegalStateException.class, () -> mBuilder.build());
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
new file mode 100644
index 0000000..f5c505c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.autofillservice.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+
+public final class UsernameOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "UsernameOnlyActivity";
+
+    private EditText mUsernameEditText;
+    private Button mNextButton;
+    private AutofillId mPasswordAutofillId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mUsernameEditText = findViewById(R.id.username);
+        mNextButton = findViewById(R.id.next);
+        mNextButton.setOnClickListener((v) -> next());
+    }
+
+    protected int getContentView() {
+        return R.layout.username_only_activity;
+    }
+
+    public void focusOnUsername() {
+        syncRunOnUiThread(() -> mUsernameEditText.requestFocus());
+    }
+
+    void setUsername(String username) {
+        syncRunOnUiThread(() -> mUsernameEditText.setText(username));
+    }
+
+    AutofillId getUsernameAutofillId() {
+        return mUsernameEditText.getAutofillId();
+    }
+
+    /**
+     * Sets the autofill id of the password using the intent that launches the new activity, so it's
+     * set before the view strucutre is generated.
+     */
+    void setPasswordAutofillId(AutofillId id) {
+        mPasswordAutofillId = id;
+    }
+
+    void next() {
+        final String username = mUsernameEditText.getText().toString();
+        Log.v(TAG, "Going to next screen as user " + username + " and aid " + mPasswordAutofillId);
+        final Intent intent = new Intent(this, PasswordOnlyActivity.class)
+                .putExtra(PasswordOnlyActivity.EXTRA_USERNAME, username)
+                .putExtra(PasswordOnlyActivity.EXTRA_PASSWORD_AUTOFILL_ID, mPasswordAutofillId);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
index 622e127..85c6737 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
@@ -32,25 +32,13 @@
 import android.view.View;
 import android.view.autofill.AutofillId;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 /**
  * Simple integration test to verify that the UI is only shown if the validator passes.
  */
 @AppModeFull // Service-specific test
-public class ValidatorTest extends AutoFillServiceTestCase {
-    @Rule
-    public final AutofillActivityTestRule<LoginActivity> mActivityRule =
-        new AutofillActivityTestRule<>(LoginActivity.class);
-
-    private LoginActivity mActivity;
-
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
+public class ValidatorTest extends AbstractLoginActivityTestCase {
 
     @Test
     public void testShowUiWhenValidatorPass() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
index 9f5b69c..bb4561b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
@@ -39,7 +39,7 @@
 
 @RunWith(MockitoJUnitRunner.class)
 @AppModeFull // Unit test
-public class ValidatorsTest extends AutoFillServiceTestCase {
+public class ValidatorsTest {
 
     @Mock private Validator mInvalidValidator;
     @Mock private ValueFinder mValueFinder;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
index 84370ca..8c88928 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
@@ -31,8 +31,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,16 +38,20 @@
 
 @RunWith(AndroidJUnit4.class)
 @AppModeFull // Unit test
-public class ViewAttributesTest extends AutoFillServiceTestCase {
-    @Rule
-    public final AutofillActivityTestRule<ViewAttributesTestActivity> mActivityRule =
-            new AutofillActivityTestRule<>(ViewAttributesTestActivity.class);
+public class ViewAttributesTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<ViewAttributesTestActivity> {
 
     private ViewAttributesTestActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
+    @Override
+    protected AutofillActivityTestRule<ViewAttributesTestActivity> getActivityRule() {
+        return new AutofillActivityTestRule<ViewAttributesTestActivity>(
+                ViewAttributesTestActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Nullable private String[] getHintsFromView(@IdRes int resId) {
@@ -178,6 +180,9 @@
 
     @Test
     public void checkViewLocationInAssistStructure() throws Exception {
+        // If screen is not large enough to contain child, the height/weight will be the residual
+        // space instead of the specific size.
+        mUiBot.assumeMinimumResolution(500);
         onAssistStructure(false, (structure) -> {
                     // check size of outerView
                     AssistStructure.ViewNode outerView = findNodeByResourceId(structure,
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
index b50fb22..5029e9e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
@@ -21,7 +21,6 @@
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Helper.hasAutofillFeature;
 import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
 import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
 import static android.autofillservice.cts.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
@@ -39,11 +38,9 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.autofillservice.cts.common.SettingsHelper;
 import android.autofillservice.cts.common.SettingsStateChangerRule;
-import android.content.Context;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.SaveInfo;
-import android.support.test.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.ClassRule;
@@ -54,7 +51,6 @@
  * the Autofill APIs.
  */
 public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
-    private static final Context sContext = InstrumentationRegistry.getContext();
 
     @ClassRule
     public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
@@ -76,23 +72,19 @@
     }
 
     @Override
-    protected void postActivityLaunched(VirtualContainerActivity activity) {
+    protected void postActivityLaunched() {
         // Set our own compat mode as well..
-        activity.mCustomView.setCompatMode(true);
+        mActivity.mCustomView.setCompatMode(true);
     }
 
     @Override
     protected void enableService() {
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
-        InstrumentedAutoFillServiceCompatMode.setIgnoreUnexpectedRequests(false);
     }
 
     @Override
     protected void disableService() {
-        if (!hasAutofillFeature()) return;
-
-        Helper.disableAutofillService(getContext(), SERVICE_NAME);
-        InstrumentedAutoFillServiceCompatMode.setIgnoreUnexpectedRequests(true);
+        Helper.disableAutofillService(getContext());
     }
 
     @Override
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 84cb1f6..07557bf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -49,9 +49,7 @@
 import android.view.ViewGroup;
 import android.view.autofill.AutofillManager;
 
-import org.junit.Before;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.concurrent.TimeoutException;
@@ -60,21 +58,14 @@
  * Test case for an activity containing virtual children, either using the explicit Autofill APIs
  * or Compat mode.
  */
-public class VirtualContainerActivityTest extends AutoFillServiceTestCase {
+public class VirtualContainerActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
 
     // TODO(b/74256300): remove when fixed it :-)
     private static final boolean BUG_74256300_FIXED = false;
 
-    @Rule
-    public final AutofillActivityTestRule<VirtualContainerActivity> mActivityRule =
-            new AutofillActivityTestRule<VirtualContainerActivity>(VirtualContainerActivity.class) {
-        @Override
-        protected void beforeActivityLaunched() {
-            preActivityCreated();
-        }
-    };
-
     private final boolean mCompatMode;
+    private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
     protected VirtualContainerActivity mActivity;
 
     public VirtualContainerActivityTest() {
@@ -85,12 +76,6 @@
         mCompatMode = compatMode;
     }
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-        postActivityLaunched(mActivity);
-    }
-
     /**
      * Hook for subclass to customize test before activity is created.
      */
@@ -99,8 +84,27 @@
     /**
      * Hook for subclass to customize activity after it's launched.
      */
-    protected void postActivityLaunched(
-            @SuppressWarnings("unused") VirtualContainerActivity activity) {
+    protected void postActivityLaunched() {}
+
+    @Override
+    protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
+        if (mActivityRule == null) {
+            mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
+                    VirtualContainerActivity.class) {
+                @Override
+                protected void beforeActivityLaunched() {
+                    preActivityCreated();
+                }
+
+                @Override
+                protected void afterActivityLaunched() {
+                    mActivity = getActivity();
+                    postActivityLaunched();
+                }
+            };
+
+        }
+        return mActivityRule;
     }
 
     @Test
@@ -129,15 +133,14 @@
      * Focus to username and expect window event
      */
     void focusToUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
     }
 
     /**
      * Focus to username and expect no autofill window event
      */
     void focusToUsernameExpectNoWindowEvent() throws Throwable {
-        // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
+        // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
         mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
     }
 
@@ -145,8 +148,7 @@
      * Focus to password and expect window event
      */
     void focusToPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true),
-                Timeouts.UI_TIMEOUT.getMaxValue());
+        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
     }
 
     /**
@@ -215,11 +217,15 @@
         assertThat(passwordLabel.getInputType()).isEqualTo(0);
 
         final String[] autofillHints = username.getAutofillHints();
+        final boolean hasCompatModeFlag = (request.flags
+                & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
         if (mCompatMode) {
+            assertThat(hasCompatModeFlag).isTrue();
             assertThat(autofillHints).isNull();
             assertThat(username.getHtmlInfo()).isNull();
             assertThat(password.getHtmlInfo()).isNull();
         } else {
+            assertThat(hasCompatModeFlag).isFalse();
             // Make sure order is preserved and dupes not removed.
             assertThat(autofillHints).asList()
                     .containsExactly("c", "a", "a", "b", "a", "a")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
new file mode 100644
index 0000000..8780911
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.VisibilitySetterAction;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class VisibilitySetterActionTest {
+
+    private static final Context sContext = InstrumentationRegistry.getContext();
+    private final ViewGroup mRootView = new ViewGroup(sContext) {
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    };
+
+    @Test
+    public void testValidVisibilities() {
+        assertThat(new VisibilitySetterAction.Builder(42, View.VISIBLE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.GONE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.INVISIBLE).build()).isNotNull();
+    }
+
+    @Test
+    public void testInvalidVisibilities() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VisibilitySetterAction.Builder(42, 666).build());
+        final VisibilitySetterAction.Builder validBuilder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+        assertThrows(IllegalArgumentException.class,
+                () -> validBuilder.setVisibility(108, 666).build());
+    }
+
+    @Test
+    public void testOneChild() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testOneChildAddedTwice() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(42, View.INVISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testMultipleChildren() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(108, View.INVISIBLE)
+                .build();
+        final View view1 = new View(sContext);
+        view1.setId(42);
+        view1.setVisibility(View.GONE);
+        mRootView.addView(view1);
+
+        final View view2 = new View(sContext);
+        view2.setId(108);
+        view2.setVisibility(View.GONE);
+        mRootView.addView(view2);
+
+        action.onClick(mRootView);
+
+        assertThat(view1.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(view2.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final VisibilitySetterAction.Builder builder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class, () -> builder.setVisibility(108, View.GONE));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
index a8e0314..26e62e3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
@@ -22,13 +22,12 @@
  * the UI thread. Example:
  * <pre><code>
  * void onUsername(ViewVisitor<EditText> v) {
- *     runOnUiThread(() -> {
- *         v.visit(mUsername);
- *     });
+ *     runOnUiThread(() -> v.visit(mUsername));
  * }
  * </code></pre>
  */
-interface Visitor<T> {
+// TODO: move to common code
+public interface Visitor<T> {
 
     void visit(T view);
 }
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
index 08d4c70..f738959 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
@@ -17,12 +17,12 @@
 
 import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Context;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
-import android.view.KeyEvent;
 import android.view.View;
 import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
@@ -35,27 +35,27 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class WebViewActivity extends AbstractAutoFillActivity {
+public class WebViewActivity extends AbstractWebViewActivity {
 
     private static final String TAG = "WebViewActivity";
-    static final String FAKE_DOMAIN = "y.u.no.real.server";
     private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
     static final String ID_WEBVIEW = "webview";
 
-    static final String HTML_NAME_USERNAME = "username";
-    static final String HTML_NAME_PASSWORD = "password";
-
     static final String ID_OUTSIDE1 = "outside1";
     static final String ID_OUTSIDE2 = "outside2";
 
-    MyWebView mWebView;
-
     private LinearLayout mParent;
     private LinearLayout mOutsideContainer1;
     private LinearLayout mOutsideContainer2;
     EditText mOutside1;
     EditText mOutside2;
 
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -109,20 +109,32 @@
                     Log.v(TAG, "onPageFinished(): " + url);
                     latch.countDown();
                 }
-
             });
             mWebView.loadUrl(FAKE_URL);
         });
 
         // Wait until it's loaded.
-
         if (!latch.await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
             throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
         }
 
-        // TODO(b/80317628): re-add check below
+        // Sanity check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
         // NOTE: we cannot search by resourceId because WebView does not set them...
-        // uiBot.assertShownByText("Login"); // Login button
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mPasswordLabel = uiBot.findRightAwayByText("Password: ");
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
 
         return mWebView;
     }
@@ -134,59 +146,23 @@
         });
     }
 
-    public UiObject2 getUsernameLabel(UiBot uiBot) throws Exception {
-        return getLabel(uiBot, "Username: ");
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
     }
 
-    public UiObject2 getPasswordLabel(UiBot uiBot) throws Exception {
-        return getLabel(uiBot, "Password: ");
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
     }
 
-    public UiObject2 getUsernameInput(UiBot uiBot) throws Exception {
-        return getInput(uiBot, "Username: ");
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
     }
 
-    public UiObject2 getPasswordInput(UiBot uiBot) throws Exception {
-        return getInput(uiBot, "Password: ");
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
     }
 
-    public UiObject2 getLoginButton(UiBot uiBot) throws Exception {
-        return getLabel(uiBot, "Login");
-    }
-
-    private UiObject2 getLabel(UiBot uiBot, String label) throws Exception {
-        return uiBot.assertShownByText(label, Timeouts.WEBVIEW_TIMEOUT);
-    }
-
-    private UiObject2 getInput(UiBot uiBot, String contentDescription) throws Exception {
-        // First get the label..
-        final UiObject2 label = getLabel(uiBot, contentDescription);
-
-        // Then the input is next.
-        final UiObject2 parent = label.getParent();
-        UiObject2 previous = null;
-        for (UiObject2 child : parent.getChildren()) {
-            if (label.equals(previous)) {
-                if (child.getClassName().equals(EditText.class.getName())) {
-                    return child;
-                }
-                uiBot.dumpScreen("getInput() for " + child + "failed");
-                throw new IllegalStateException("Invalid class for " + child);
-            }
-            previous = child;
-        }
-        uiBot.dumpScreen("getInput() for label " + label + "failed");
-        throw new IllegalStateException("could not find username (label=" + label + ")");
-    }
-
-    public void dispatchKeyPress(int keyCode) {
-        runOnUiThread(() -> {
-            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-        });
-        // wait webview to process the key event.
-        SystemClock.sleep(300);
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
index e26d5c9..4712413 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
@@ -29,43 +29,41 @@
 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.ViewStructure.HtmlInfo;
 import android.view.autofill.AutofillManager;
 
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 
-@AppModeFull(reason = "Flaky in instant mode")
-public class WebViewActivityTest extends AutoFillServiceTestCase {
+public class WebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
 
-    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
-    // set using accessibility.
-    private static final boolean INJECT_EVENTS = true;
-
-    @Rule
-    public final AutofillActivityTestRule<WebViewActivity> mActivityRule =
-            new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class);
+    private static final String TAG = "WebViewActivityTest";
 
     private WebViewActivity mActivity;
 
-    @Before
-    public void setActivity() {
-        mActivity = mActivityRule.getActivity();
-    }
+    @Override
+    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
 
-    @BeforeClass
-    public static void setReplierMode() {
-        sReplier.setIdMode(IdMode.HTML_NAME);
-    }
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
 
-    @AfterClass
-    public static void resetReplierMode() {
-        sReplier.setIdMode(IdMode.RESOURCE_ID);
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
     }
 
     @Test
@@ -81,7 +79,7 @@
         sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         sReplier.getNextFillRequest();
 
         // Assert not shown.
@@ -120,16 +118,16 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         mUiBot.assertDatasets("The Dude");
 
         // Change focus around.
         final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-        mActivity.getUsernameLabel(mUiBot).click();
+        mActivity.getUsernameLabel().click();
         callback.assertUiHiddenEvent(myWebView, usernameChildId);
         mUiBot.assertNoDatasets();
-        mActivity.getPasswordInput(mUiBot).click();
+        mActivity.getPasswordInput().click();
         final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
         final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
 
@@ -139,14 +137,6 @@
         mUiBot.assertNoDatasets();
         callback.assertUiHiddenEvent(myWebView, passwordChildId);
 
-        // Make sure screen was autofilled.
-        assertThat(mActivity.getUsernameInput(mUiBot).getText()).isEqualTo("dude");
-        // TODO: proper way to verify text (which is ..... because it's a password) - ideally it
-        // should call passwordInput.isPassword(), but that's not exposed
-        final String password = mActivity.getPasswordInput(mUiBot).getText();
-        assertThat(password).isNotEqualTo("sweet");
-        assertThat(password).hasLength(5);
-
         // Assert structure passed to service.
         try {
             final ViewNode webViewNode =
@@ -196,7 +186,7 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         sReplier.getNextFillRequest();
 
         // Assert not shown.
@@ -204,15 +194,15 @@
 
         // Trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.getUsernameInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.getPasswordInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(mUiBot).setText("DUDE");
-            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
         }
-        mActivity.getLoginButton(mUiBot).click();
+        mActivity.getLoginButton().click();
 
         // Assert save UI shown.
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
@@ -254,7 +244,7 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         mUiBot.assertDatasets("The Dude");
         final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
@@ -276,25 +266,17 @@
         myWebView.assertAutofilled();
         callback.assertUiHiddenEvent(myWebView, usernameChildId);
 
-        // Make sure screen was autofilled.
-        assertThat(mActivity.getUsernameInput(mUiBot).getText()).isEqualTo("dude");
-        // TODO: proper way to verify text (which is ..... because it's a password) - ideally it
-        // should call passwordInput.isPassword(), but that's not exposed
-        final String password = mActivity.getPasswordInput(mUiBot).getText();
-        assertThat(password).isNotEqualTo("sweet");
-        assertThat(password).hasLength(5);
-
         // Now trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.getUsernameInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.getPasswordInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(mUiBot).setText("DUDE");
-            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
         }
-        mActivity.getLoginButton(mUiBot).click();
+        mActivity.getLoginButton().click();
 
         // Assert save UI shown.
         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
@@ -347,7 +329,7 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         final FillRequest fillRequest = sReplier.getNextFillRequest();
         mUiBot.assertDatasets("USER");
         final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
@@ -378,7 +360,7 @@
         mUiBot.assertDatasets("OUT1");
         callback.assertUiShownEvent(mActivity.mOutside1);
 
-        mActivity.getPasswordInput(mUiBot).click();
+        mActivity.getPasswordInput().click();
         callback.assertUiHiddenEvent(mActivity.mOutside1);
         mUiBot.assertDatasets("PASS");
         final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
@@ -398,20 +380,20 @@
 
         // Now trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.getUsernameInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.getPasswordInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(mUiBot).setText("DUDE");
-            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
         }
         mActivity.runOnUiThread(() -> {
             mActivity.mOutside1.setText("DUDER");
             mActivity.mOutside2.setText("SWEETER");
         });
 
-        mActivity.getLoginButton(mUiBot).click();
+        mActivity.getLoginButton().click();
 
         // Assert save UI shown.
         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
@@ -502,7 +484,7 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         final FillRequest fillRequest2 = sReplier.getNextFillRequest();
         callback.assertUiHiddenEvent(mActivity.mOutside2);
         mUiBot.assertDatasets("USER");
@@ -519,7 +501,7 @@
         mUiBot.assertDatasets("OUT2");
         callback.assertUiShownEvent(mActivity.mOutside2);
 
-        mActivity.getPasswordInput(mUiBot).click();
+        mActivity.getPasswordInput().click();
         callback.assertUiHiddenEvent(mActivity.mOutside2);
         mUiBot.assertDatasets("PASS");
         final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
@@ -549,27 +531,27 @@
         outside2Watcher.assertAutoFilled();
 
         // Autofill Webview (1st partition)
-        mActivity.getUsernameInput(mUiBot).click();
+        mActivity.getUsernameInput().click();
         callback.assertUiShownEventForVirtualChild(myWebView);
         mUiBot.selectDataset("USER");
         myWebView.assertAutofilled();
 
         // Now trigger save.
         if (INJECT_EVENTS) {
-            mActivity.getUsernameInput(mUiBot).click();
+            mActivity.getUsernameInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput(mUiBot).click();
+            mActivity.getPasswordInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
         } else {
-            mActivity.getUsernameInput(mUiBot).setText("DUDE");
-            mActivity.getPasswordInput(mUiBot).setText("SWEET");
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
         }
         mActivity.runOnUiThread(() -> {
             mActivity.mOutside1.setText("DUDER");
             mActivity.mOutside2.setText("SWEETER");
         });
 
-        mActivity.getLoginButton(mUiBot).click();
+        mActivity.getLoginButton().click();
 
         // Assert save UI shown.
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
new file mode 100644
index 0000000..2680f14
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
@@ -0,0 +1,171 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebViewMultiScreenLoginActivity extends AbstractWebViewActivity {
+
+    private static final String TAG = "WebViewMultiScreenLoginActivity";
+    private static final String FAKE_USERNAME_URL = "https://" + FAKE_DOMAIN + ":666/username.html";
+    private static final String FAKE_PASSWORD_URL = "https://" + FAKE_DOMAIN + ":666/password.html";
+
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mNextButton;
+
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
+    private final Map<String, CountDownLatch> mLatches = new HashMap<>();
+
+    public WebViewMultiScreenLoginActivity() {
+        mLatches.put(FAKE_USERNAME_URL, new CountDownLatch(1));
+        mLatches.put(FAKE_PASSWORD_URL, new CountDownLatch(1));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.webview_only_activity);
+        mWebView = findViewById(R.id.my_webview);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> {
+            mWebView.setWebViewClient(new WebViewClient() {
+                // WebView does not set the WebDomain on file:// requests, so we need to use an
+                // https:// request and intercept it to provide the real data.
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view,
+                        WebResourceRequest request) {
+                    final String url = request.getUrl().toString();
+                    if (!url.equals(FAKE_USERNAME_URL) && !url.equals(FAKE_PASSWORD_URL)) {
+                        Log.d(TAG, "Ignoring " + url);
+                        return super.shouldInterceptRequest(view, request);
+                    }
+
+                    final String rawPath = request.getUrl().getPath()
+                            .substring(1); // Remove leading /
+                    Log.d(TAG, "Converting " + url + " to " + rawPath);
+                    // NOTE: cannot use try-with-resources because it would close the stream before
+                    // WebView uses it.
+                    try {
+                        return new WebResourceResponse("text/html", "utf-8",
+                                getAssets().open(rawPath));
+                    } catch (IOException e) {
+                        throw new IllegalArgumentException("Error opening " + rawPath, e);
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    final CountDownLatch latch = mLatches.get(url);
+                    Log.v(TAG, "onPageFinished(): " + url + " latch: " + latch);
+                    if (latch != null) {
+                        latch.countDown();
+                    }
+                }
+            });
+            mWebView.loadUrl(FAKE_USERNAME_URL);
+        });
+
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_USERNAME_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
+        }
+
+        // Sanity check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mNextButton = uiBot.findRightAwayByText("Next");
+
+        return mWebView;
+    }
+
+    void waitForPasswordScreen(UiBot uiBot) throws Exception {
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_PASSWORD_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "Password page not loaded");
+        }
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mPasswordLabel = uiBot.assertShownByText("Password: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
+    }
+
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
+    }
+
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
+    }
+
+    public UiObject2 getNextButton() throws Exception {
+        return mNextButton;
+    }
+
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
+    }
+
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
+    }
+
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
new file mode 100644
index 0000000..bda5786
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.autofillservice.cts;
+
+import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+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.assertTextAndValue;
+import static android.autofillservice.cts.Helper.findNodeByHtmlName;
+import static android.autofillservice.cts.Helper.getAutofillId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+
+public class WebViewMultiScreenLoginActivityTest
+        extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
+
+    private static final String TAG = "WebViewMultiScreenLoginTest";
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private WebViewMultiScreenLoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
+                WebViewMultiScreenLoginActivity.class) {
+
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testSave_eachFieldSeparately() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Sanity check to make sure autofill is enabled in the application context
+        assertThat(myWebView.getContext().getSystemService(AutofillManager.class).isEnabled())
+                .isTrue();
+
+        /*
+         * First screen: username
+         */
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .build());
+                })
+                .build());
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+        mActivity.getNextButton().click();
+
+        // Assert UI
+        final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
+
+        mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
+
+        // Assert save request
+        mUiBot.saveForAutofill(saveUi1, true);
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest1.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
+
+        /*
+         * Second screen: password
+         */
+
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
+
+        // Assert save request
+        mUiBot.saveForAutofill(saveUi2, true);
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
+    }
+
+    @Test
+    public void testSave_bothFieldsAtOnce() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Sanity check to make sure autofill is enabled in the application context
+        assertThat(myWebView.getContext().getSystemService(AutofillManager.class).isEnabled())
+                .isTrue();
+
+        /*
+         * First screen: username
+         */
+        final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setIgnoreFields(HTML_NAME_USERNAME)
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
+
+                })
+                .build());
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Change username
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+
+        mActivity.getNextButton().click();
+
+        // Assert UI
+        mUiBot.assertSaveNotShowing();
+
+        /*
+         * Second screen: password
+         */
+
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId.get(), MATCH_ALL, "$1").build();
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(2);
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+
+        // Assert save request
+        mUiBot.saveForAutofill(saveUi, true);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
index e1c726a..9e11da9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
@@ -22,12 +22,13 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
-import androidx.annotation.Nullable;
 import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 /**
  * Activity that displays a "Welcome USER" message after login.
  */
@@ -75,17 +76,23 @@
         sInstance = null;
     }
 
-    static void finishIt(UiBot uiBot) {
-        if (sInstance != null) {
-            Log.d(TAG, "So long and thanks for all the fish!");
-            sInstance.finish();
-            uiBot.assertGoneByRelativeId(ID_WELCOME, Timeouts.ACTIVITY_RESURRECTION);
-        }
+    @Override
+    public void finish() {
+        super.finish();
+        Log.d(TAG, "So long and thanks for all the finish!");
+
         if (sPendingIntent != null) {
+            Log.v(TAG, " canceling pending intent on finish(): " + sPendingIntent);
             sPendingIntent.cancel();
         }
     }
 
+    static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
     // TODO: reuse in other places
     static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
         assertShowing(uiBot, null);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
new file mode 100644
index 0000000..c7e55fa
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.augmented;
+
+import static android.autofillservice.cts.Helper.allowOverlays;
+import static android.autofillservice.cts.Helper.disallowOverlays;
+import static android.provider.Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS;
+
+import android.autofillservice.cts.AbstractAutoFillActivity;
+import android.autofillservice.cts.AutoFillServiceTestCase;
+import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
+import android.autofillservice.cts.common.SettingsHelper;
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.view.autofill.AutofillManager;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+
+// Must be public because of the @ClassRule
+public abstract class AugmentedAutofillAutoActivityLaunchTestCase
+        <A extends AbstractAutoFillActivity> extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    @ClassRule
+    public static final SettingsStateChangerRule sFeatureEnabler = new SettingsStateChangerRule(
+            sContext, SettingsHelper.NAMESPACE_GLOBAL, AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
+            Integer.toString(AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM));
+
+    protected static AugmentedReplier sAugmentedReplier;
+    protected AugmentedUiBot mAugmentedUiBot;
+
+    @BeforeClass
+    public static void allowAugmentedAutofillWindow() {
+        allowOverlays();
+    }
+
+    @AfterClass
+    public static void disallowAugmentedAutofillWindow() {
+        disallowOverlays();
+    }
+
+    @Before
+    public void setFixtures() {
+        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
+        sAugmentedReplier.reset();
+        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
+        mSafeCleanerRule
+            .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
+            .add(() -> { return sAugmentedReplier.getExceptions(); });
+    }
+
+    @After
+    public void resetService() {
+        AugmentedHelper.resetAugmentedService();
+    }
+
+    protected void enableAugmentedService() {
+        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
new file mode 100644
index 0000000..626b835
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.augmented;
+
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.content.ComponentName;
+import android.service.autofill.augmented.FillRequest;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+
+/**
+ * Helper for common funcionalities.
+ */
+public final class AugmentedHelper {
+
+    private static final String TAG = AugmentedHelper.class.getSimpleName();
+
+    @NonNull
+    public static String getActivityName(@Nullable FillRequest request) {
+        if (request == null) return "N/A (null request)";
+
+        final ComponentName componentName = request.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    /**
+     * Sets the augmented capture service.
+     */
+    public static void setAugmentedService(@NonNull String service) {
+        Log.d(TAG, "Setting service to " + service);
+        runShellCommand("cmd autofill set temporary-augmented-service 0 %s %d", service,
+                MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS);
+    }
+
+    /**
+     * Resets the content capture service.
+     */
+    public static void resetAugmentedService() {
+        Log.d(TAG, "Resetting back to default service");
+        runShellCommand("cmd autofill set temporary-augmented-service 0");
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @NonNull AutofillValue expectedFocusedValue) {
+        Preconditions.checkNotNull(activity);
+        Preconditions.checkNotNull(expectedFocusedId);
+        assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
+        assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull();
+        assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull();
+        assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull();
+        // TODO(b/122728762): re-add when set
+//        assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal)
+//                .isNotNull();
+        // NOTE: task id can change, we might need to set it in the activity's onCreate()
+        assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId())
+                .isEqualTo(activity.getTaskId());
+
+        final ComponentName actualComponentName = request.request.getActivityComponent();
+        assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull();
+        assertWithMessage("wrong activity name on %s", request).that(actualComponentName)
+                .isEqualTo(activity.getComponentName());
+        final AutofillId actualFocusedId = request.request.getFocusedId();
+        assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull();
+        assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
+                .isEqualTo(expectedFocusedId);
+        final AutofillValue actualFocusedValue = request.request.getFocusedValue();
+        assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
+        assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+    }
+
+    public static void assertAutofillValue(final AutofillValue expectedValue,
+            final AutofillValue actualValue) {
+        // It only supports text values for now...
+        assertWithMessage("expected value is not text: %s", expectedValue)
+                .that(expectedValue.isText()).isTrue();
+        assertWithMessage("actual value is not text: %s", actualValue)
+                .that(actualValue.isText()).isTrue();
+
+        assertWithMessage("wrong autofill value").that(actualValue.getTextValue())
+                .isEqualTo(expectedValue.getTextValue());
+    }
+
+    @NonNull
+    public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) {
+        if (values == null) return "null";
+        final StringBuilder string = new StringBuilder("[");
+        final int size = values.size();
+        for (int i = 0; i < size; i++) {
+            final Pair<AutofillId, AutofillValue> value = values.get(i);
+            string.append(i).append(':').append(value.first).append('=')
+                   .append(Helper.toString(value.second));
+            if (i < size - 1) {
+                string.append(", ");
+            }
+
+        }
+        return string.append(']').toString();
+    }
+
+    @NonNull
+    public static String toString(@Nullable FillRequest request) {
+        if (request == null) return "(null request)";
+
+        final StringBuilder string =
+                new StringBuilder("FillRequest[act=").append(getActivityName(request))
+                .append(", taskId=").append(request.getTaskId());
+
+        final AutofillId focusedId = request.getFocusedId();
+        if (focusedId != null) {
+            string.append(", focusedId=").append(focusedId);
+        }
+        final AutofillValue focusedValue = request.getFocusedValue();
+        if (focusedValue != null) {
+            string.append(", focusedValue=").append(focusedValue);
+        }
+
+        return string.append(']').toString();
+    }
+
+    // Used internally by UiBot to assert the UI
+    static String getContentDescriptionForUi(@NonNull AutofillId focusedId) {
+        return "ui_for_" + focusedId;
+    }
+
+    private AugmentedHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
new file mode 100644
index 0000000..a4d189f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.augmented;
+
+import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.DO_NOT_REPLY_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+
+import android.autofillservice.cts.AutofillActivityTestRule;
+import android.autofillservice.cts.LoginActivity;
+import android.autofillservice.cts.OneTimeCancellationSignalListener;
+import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class AugmentedLoginActivityTest
+        extends AugmentedAutofillAutoActivityLaunchTestCase<LoginActivity> {
+
+    protected LoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginActivity>(
+                LoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutoFill_neitherServiceCanAutofill() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        final AutofillId expectedFocusedId = username.getAutofillId();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(NO_AUGMENTED_RESPONSE);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, expectedFocusedId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is not shown.
+        mAugmentedUiBot.assertUiNeverShown();
+    }
+
+    @Test
+    public void testAutoFill_mainServiceReturnedNull_augmentedAutofillOneField() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutoFill_mainServiceReturnedNull_augmentedAutofillTwoFields() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .setField(mActivity.getPassword().getAutofillId(), "sweet")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Ignore("blocked on b/122728762")
+    @Test
+    public void testCancellationSignalCalledAfterTimeout() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(DO_NOT_REPLY_AUGMENTED_RESPONSE);
+        final OneTimeCancellationSignalListener listener =
+                new OneTimeCancellationSignalListener(AUGMENTED_FILL_TIMEOUT.ms() + 2000);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // TODO(b/122728762): might need to wait until connected
+        sAugmentedReplier.getNextFillRequest().cancellationSignal.setOnCancelListener(listener);
+
+        // Assert results
+        listener.assertOnCancelCalled();
+    }
+
+    /*
+     * TODO(b/123542344) - add moarrrr tests:
+     *
+     * - Augmented service returned null
+     * - Focus back and forth between username and passwod
+     *   - When Augmented service shows UI on one field (like username) but not other.
+     *   - When Augmented service shows UI on one field (like username) on both.
+     * - Tap back
+     * - Tap home (then bring activity back)
+     * - Acitivy is killed (and restored)
+     * - Main service returns non-null response that doesn't show UI (for example, has just
+     *   SaveInfo)
+     *   - Augmented autofill show UI, user fills, Save UI is shown
+     *   - etc ...
+     * - No augmented autofill calls when the main service is not set.
+     * - etc...
+     */
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java
new file mode 100644
index 0000000..730b0d4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.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.autofillservice.cts.augmented;
+
+import android.autofillservice.cts.Timeout;
+
+/**
+ * Timeouts for common tasks.
+ */
+final class AugmentedTimeouts {
+
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 1_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 3_000;
+
+    /**
+     * Timeout for expected augmented autofill requests.
+     */
+    static final Timeout AUGMENTED_FILL_TIMEOUT = new Timeout("AUGMENTED_FILL_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    static final Timeout AUGMENTED_CONNECTION_TIMEOUT = new Timeout("AUGMENTED_CONNECTION_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when the augmented autofill UI not expected to be shown - test will sleep for
+     * that amount of time as there is no callback that be received to assert it's not shown.
+     */
+    static final long AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    private AugmentedTimeouts() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java
new file mode 100644
index 0000000..ed00223
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.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 android.autofillservice.cts.augmented;
+
+import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
+import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.UiBot;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Helper for UI-related needs.
+ */
+public final class AugmentedUiBot {
+
+    private final UiBot mUiBot;
+    private boolean mOkToCallAssertUiGone;
+
+    public AugmentedUiBot(@NonNull UiBot uiBot) {
+        mUiBot = uiBot;
+    }
+
+    /**
+     * Asserts the augmented autofill UI was never shown.
+     *
+     * <p>This method is slower than {@link #assertUiGone()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    public void assertUiNeverShown() throws Exception {
+        mUiBot.assertNeverShownByRelativeId("augmented autofil UI", R.id.augmentedAutofillUi,
+                AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Asserts the augmented autofill UI was shown.
+     *
+     * @param focusedId where it should have been shown
+     * @param expectedText the expected text in the UI
+     */
+    public UiObject2 assertUiShown(@NonNull AutofillId focusedId,
+            @NonNull String expectedText) throws Exception {
+        Preconditions.checkNotNull(focusedId);
+        Preconditions.checkNotNull(expectedText);
+
+        final UiObject2 ui = mUiBot.assertShownByRelativeId(R.id.augmentedAutofillUi);
+
+        assertWithMessage("Wrong text on UI").that(ui.getText()).isEqualTo(expectedText);
+
+        final String expectedContentDescription = getContentDescriptionForUi(focusedId);
+        assertWithMessage("Wrong content description on UI")
+                .that(ui.getContentDescription()).isEqualTo(expectedContentDescription);
+
+        mOkToCallAssertUiGone = true;
+
+        return ui;
+    }
+
+    /**
+     * Asserts the augmented autofill UI is gone AFTER it was previously shown.
+     *
+     * @throws IllegalStateException if this method is called without calling
+     * {@link #assertUiShown(AutofillId, String)} before.
+     */
+    public void assertUiGone() {
+        Preconditions.checkState(mOkToCallAssertUiGone, "must call assertUiShown() first");
+        mUiBot.assertGoneByRelativeId(R.id.augmentedAutofillUi, AUGMENTED_FILL_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
new file mode 100644
index 0000000..8aada3f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.augmented;
+
+import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
+
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.service.autofill.augmented.FillWindow;
+import android.service.autofill.augmented.PresentationParams;
+import android.service.autofill.augmented.PresentationParams.Area;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class used to produce a {@link FillResponse}.
+ */
+public final class CannedAugmentedFillResponse {
+
+    private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
+
+    private final AugmentedResponseType mResponseType;
+    private final Map<AutofillId, Dataset> mDatasets;
+
+    private CannedAugmentedFillResponse(@NonNull Builder builder) {
+        mResponseType = builder.mResponseType;
+        mDatasets = builder.mDatasets;
+    }
+
+    /**
+     * Constant used to pass a {@code null} response to the
+     * {@link FillCallback#onSuccess(FillResponse)} method.
+     */
+    public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.NULL).build();
+
+    /**
+     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
+     */
+    public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.TIMEOUT).build();
+
+    public AugmentedResponseType getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Creates the "real" response.
+     */
+    public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
+            @NonNull FillController controller) {
+        final AutofillId focusedId = request.getFocusedId();
+
+        final Dataset dataset = mDatasets.get(focusedId);
+        if (dataset == null) {
+            Log.d(TAG, "no dataset for field " + focusedId);
+            return null;
+        }
+
+        Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
+
+        final PresentationParams presentationParams = request.getPresentationParams();
+        if (presentationParams == null) {
+            Log.w(TAG, "No PresentationParams");
+            return null;
+        }
+
+        final Area strip = presentationParams.getSuggestionArea();
+        if (strip == null) {
+            Log.w(TAG, "No suggestion strip");
+            return null;
+        }
+
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
+
+        rootView.setText(dataset.mPresentation);
+
+        rootView.setContentDescription(getContentDescriptionForUi(focusedId));
+        final FillWindow fillWindow = new FillWindow();
+        rootView.setOnClickListener((v) -> {
+            Log.d(TAG, "Destroying window first");
+            fillWindow.destroy();
+            final List<Pair<AutofillId, AutofillValue>> values = dataset.getValues();
+            Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
+            controller.autofill(values);
+        });
+
+        boolean ok = fillWindow.update(strip, rootView, 0);
+        if (!ok) {
+            Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
+            return null;
+        }
+
+        return new FillResponse.Builder().setFillWindow(fillWindow).build();
+    }
+
+    @Override
+    public String toString() {
+        return "CannedAugmentedFillResponse: [type=" + mResponseType
+                + ",datasets=" + mDatasets
+                + "]";
+    }
+    public enum AugmentedResponseType {
+        NORMAL,
+        NULL,
+        TIMEOUT,
+    }
+
+    public static final class Builder {
+        private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
+        private final AugmentedResponseType mResponseType;
+
+        public Builder(@NonNull AugmentedResponseType type) {
+            mResponseType = type;
+        }
+
+        public Builder() {
+            this(AugmentedResponseType.NORMAL);
+        }
+
+        /**
+         * Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
+         * the UI is tapped.
+         */
+        public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
+            for (AutofillId id : ids) {
+                mDatasets.put(id, dataset);
+            }
+            return this;
+        }
+
+        public CannedAugmentedFillResponse build() {
+            return new CannedAugmentedFillResponse(this);
+        }
+    } // CannedAugmentedFillResponse.Builder
+
+
+    /**
+     * Helper class used to define which fields will be autofilled when the user taps the Augmented
+     * Autofill UI.
+     */
+    public static class Dataset {
+        private final Map<AutofillId, AutofillValue> mFieldValuesById;
+        private final String mPresentation;
+
+        private Dataset(@NonNull Builder builder) {
+            mFieldValuesById = builder.mFieldValuesById;
+            mPresentation = builder.mPresentation;
+        }
+
+        public List<Pair<AutofillId, AutofillValue>> getValues() {
+            return mFieldValuesById.entrySet().stream()
+                    .map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public String toString() {
+            return "Dataset: [presentation=" + mPresentation
+                    + ", fields=" + mFieldValuesById
+                    + "]";
+        }
+
+        public static class Builder {
+            private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
+
+            private final String mPresentation;
+
+            public Builder(@NonNull String presentation) {
+                mPresentation = Preconditions.checkNotNull(presentation);
+            }
+
+            /**
+             * Sets the value that will be autofilled on the field with {@code id}.
+             */
+            public Builder setField(@NonNull AutofillId id, String text) {
+                mFieldValuesById.put(id, AutofillValue.forText(text));
+                return this;
+            }
+            public Dataset build() {
+                return new Dataset(this);
+            }
+        } // Dataset.Builder
+    } // Dataset
+} // CannedAugmentedFillResponse
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
new file mode 100644
index 0000000..8fb17a2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.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.autofillservice.cts.augmented;
+
+import static android.autofillservice.cts.augmented.AugmentedHelper.getActivityName;
+import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
+
+import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.JUnitHelper;
+import android.autofillservice.cts.RetryableException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.service.autofill.augmented.AugmentedAutofillService;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link AugmentedAutofillService} used in the tests.
+ */
+public class CtsAugmentedAutofillService extends AugmentedAutofillService {
+
+    private static final String TAG = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    public static final String SERVICE_CLASS = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.augmented." + SERVICE_CLASS;
+
+    private static final AugmentedReplier sAugmentedReplier = new AugmentedReplier();
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyAugmentedServiceThread");
+    private final Handler mHandler;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public CtsAugmentedAutofillService() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillController controller, FillCallback callback) {
+        Log.i(TAG, "onFillRequest(): " + AugmentedHelper.toString(request));
+
+        final ComponentName component = request.getActivityComponent();
+
+        if (!JUnitHelper.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
+        }
+        mHandler.post(() -> sAugmentedReplier.handleOnFillRequest(getApplicationContext(), request,
+                cancellationSignal, controller, callback));
+    }
+
+    /**
+     * Gets the {@link AugmentedReplier} singleton.
+     */
+    static AugmentedReplier getAugmentedReplier() {
+        return sAugmentedReplier;
+    }
+
+    /**
+     * POJO representation of the contents of a {@link FillRequest}
+     * that can be asserted at the end of a test case.
+     */
+    public static final class AugmentedFillRequest {
+        public final FillRequest request;
+        public final CancellationSignal cancellationSignal;
+        public final FillController controller;
+        public final FillCallback callback;
+
+        private AugmentedFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+                FillController controller, FillCallback callback) {
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.controller = controller;
+            this.callback = callback;
+        }
+
+        @Override
+        public String toString() {
+            return "AugmentedFillRequest[activity=" + getActivityName(request) + ", request="
+                    + AugmentedHelper.toString(request) + "]";
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AugmentedAutofillService#onFillRequest(FillRequest, CancellationSignal,
+     * FillController, FillCallback)} on behalf of a unit test method.
+     */
+    public static final class AugmentedReplier {
+
+        private final BlockingQueue<CannedAugmentedFillResponse> mResponses =
+                new LinkedBlockingQueue<>();
+        private final BlockingQueue<AugmentedFillRequest> mFillRequests =
+                new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private boolean mReportUnhandledFillRequest = true;
+
+        private AugmentedReplier() {
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public AugmentedReplier addResponse(@NonNull CannedAugmentedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public AugmentedFillRequest getNextFillRequest() {
+            AugmentedFillRequest request;
+            try {
+                request = mFillRequests.poll(AUGMENTED_FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(AUGMENTED_FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts all {@link AugmentedAutofillService#onFillRequest(FillRequest,
+         * CancellationSignal, FillController, FillCallback)} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mExceptions = null;
+            mReportUnhandledFillRequest = true;
+        }
+
+        private void handleOnFillRequest(@NonNull Context context, @NonNull FillRequest request,
+                @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
+                @NonNull FillCallback callback) {
+            try {
+                final CannedAugmentedFillResponse response;
+                try {
+                    response = mResponses.poll(AUGMENTED_CONNECTION_TIMEOUT.ms(),
+                            TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedAugmentedFillResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    Log.w(TAG, "onFillRequest() for " + getActivityName(request)
+                            + " received when no canned response was set.");
+                    return;
+                }
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                Log.v(TAG, "onFillRequest(): response = " + response);
+                final FillResponse fillResponse = response.asFillResponse(context, request,
+                        controller);
+                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
+                callback.onSuccess(fillResponse);
+            } catch (Throwable t) {
+                addException(t);
+            } finally {
+                final AugmentedFillRequest myRequest = new AugmentedFillRequest(request,
+                        cancellationSignal, controller, callback);
+                Log.d(TAG, "offering " + myRequest);
+                Helper.offer(mFillRequests, myRequest, AUGMENTED_CONNECTION_TIMEOUT.ms());
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
index 1ca7955..296ac21 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
@@ -19,13 +19,17 @@
 import static android.autofillservice.cts.common.SettingsHelper.NAMESPACE_GLOBAL;
 import static android.autofillservice.cts.common.SettingsHelper.NAMESPACE_SECURE;
 
+import android.autofillservice.cts.JUnitHelper;
+import android.autofillservice.cts.RetryableException;
 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;
@@ -34,16 +38,18 @@
  * 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;
-
-    public OneTimeSettingsListener(Context context, String key) {
-        this(context, NAMESPACE_SECURE, key);
-    }
+    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;
@@ -73,10 +79,15 @@
      */
     public void assertCalled() {
         try {
-            final boolean updated = mLatch.await(5, TimeUnit.SECONDS);
+            final boolean updated = mLatch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
             if (!updated) {
-                throw new IllegalStateException("Settings " + mKey + " not called in 5s");
+                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, JUnitHelper.getCurrentTestName() + "/" + mKey + ": " + delta + "ms");
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new IllegalStateException("Interrupted", e);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
index 219c55b..59167f4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
@@ -97,7 +97,7 @@
             @NonNull String key) {
 
         final String currentValue = get(namespace, key);
-        if (currentValue == null || currentValue.equals("null")) {
+        if (currentValue == null) {
             // Already set, ignore
             return;
         }
@@ -108,7 +108,7 @@
         observer.assertCalled();
 
         final String newValue = get(namespace, key);
-        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo("null");
+        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isNull();
     }
 
     public static void syncDelete(@NonNull Context context, @NonNull String key) {
@@ -118,9 +118,14 @@
     /**
      * Gets the value of a given preference using Shell command.
      */
-    @NonNull
+    @Nullable
     public static String get(@NonNull String namespace, @NonNull String key) {
-        return runShellCommand("settings get %s %s", namespace, key);
+        final String value = runShellCommand("settings get %s %s", namespace, key);
+        if (value == null || value.equals("null")) {
+            return null;
+        } else {
+            return value;
+        }
     }
 
     @NonNull
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
index fb7c93a..117c755 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
@@ -16,12 +16,15 @@
 
 package android.autofillservice.cts.common;
 
-import androidx.annotation.NonNull;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+
 import android.support.test.InstrumentationRegistry;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.compatibility.common.util.SystemUtil;
 
 /**
@@ -65,4 +68,21 @@
     private ShellHelper() {
         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/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
index 77963d1..9784db5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
@@ -44,6 +44,18 @@
         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() {
@@ -51,6 +63,7 @@
             @Override
             public void evaluate() throws Throwable {
                 final T previousValue = mStateManager.get();
+                preEvaluate(description);
                 try {
                     base.evaluate();
                 } finally {
@@ -59,6 +72,7 @@
                         mStateManager.set(previousValue);
                     }
                 }
+                postEvaluate(description);
             }
         };
     }
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index e12f2c4..985f372 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -27,7 +27,12 @@
     android.test.base.stubs \
 
 
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ctstestserver mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+    ctstestserver \
+    mockito-target-minus-junit4 \
+    testng
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index 8e0bfa0..635bfdc 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -17,11 +17,15 @@
 <configuration description="Config for CTS Backup test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="backup" />
+    <!-- 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" />
     <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="CtsKeyValueBackupApp.apk" />
         <option name="test-file-name" value="CtsBackupTestCases.apk" />
+        <option name="test-file-name" value="CtsPermissionBackupApp.apk" />
     </target_preparer>
     <target_preparer class="android.cts.backup.BackupPreparer">
         <option name="enable-backup-if-needed" value="true" />
diff --git a/tests/backup/app/permission/Android.mk b/tests/backup/app/permission/Android.mk
new file mode 100644
index 0000000..24ccfbe
--- /dev/null
+++ b/tests/backup/app/permission/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_PACKAGE_NAME := CtsPermissionBackupApp
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/backup/app/permission/AndroidManifest.xml b/tests/backup/app/permission/AndroidManifest.xml
new file mode 100644
index 0000000..8dd735a
--- /dev/null
+++ b/tests/backup/app/permission/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.backup.permission" >
+
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+    <application
+        android:label="Android Permission Backup CTS App">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/backup/src/android/backup/cts/AgentBindingTest.java b/tests/backup/src/android/backup/cts/AgentBindingTest.java
new file mode 100644
index 0000000..29a7c70
--- /dev/null
+++ b/tests/backup/src/android/backup/cts/AgentBindingTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.backup.cts;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.expectThrows;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
+import android.platform.test.annotations.AppModeFull;
+
+import java.lang.reflect.Method;
+
+@AppModeFull
+public class AgentBindingTest extends BaseBackupCtsTest {
+    private Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+    public void testUnbindBackupAgent_isNotCallableFromCts() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        expectThrows(Exception.class, () -> unbindBackupAgent(mContext.getApplicationInfo()));
+    }
+
+    public void testBindBackupAgent_isNotCallableFromCts() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        expectThrows(Exception.class, () -> bindBackupAgent(mContext.getPackageName(), 0, 0));
+    }
+
+    private static void unbindBackupAgent(ApplicationInfo applicationInfo) throws Exception {
+        callActivityManagerMethod(
+                "unbindBackupAgent",
+                new Class<?>[] {ApplicationInfo.class},
+                new Object[] {applicationInfo});
+    }
+
+    private static void bindBackupAgent(String packageName, int backupRestoreMode, int userId)
+            throws Exception {
+        callActivityManagerMethod(
+                "bindBackupAgent",
+                new Class<?>[] {String.class, int.class, int.class},
+                new Object[] {packageName, backupRestoreMode, userId});
+    }
+
+    private static void callActivityManagerMethod(
+            String methodName, Class<?>[] types, Object[] args) throws Exception {
+        Class<?> activityManagerClass = Class.forName("android.app.IActivityManager");
+        Object activityManager = getActivityManager();
+        Method bindBackupAgentMethod = activityManagerClass.getMethod(methodName, types);
+        bindBackupAgentMethod.invoke(activityManager, args);
+    }
+
+    private static Object getActivityManager() throws Exception {
+        Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
+        Class<?> stubClass = Class.forName("android.app.IActivityManager$Stub");
+        Method asInterfaceMethod = stubClass.getMethod("asInterface", IBinder.class);
+        Method getServiceMethod = serviceManagerClass.getMethod("getService", String.class);
+        return asInterfaceMethod.invoke(
+                null, (IBinder) getServiceMethod.invoke(serviceManagerClass, "activity"));
+    }
+}
diff --git a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
index 343f2d9..9002eee 100644
--- a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
+++ b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
@@ -19,34 +19,36 @@
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
 import android.test.InstrumentationTestCase;
 
+import com.android.compatibility.common.util.BackupUtils;
 import com.android.compatibility.common.util.LogcatInspector;
 
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
 
 /**
  * Base class for backup instrumentation tests.
  *
  * Ensures that backup is enabled and local transport selected, and provides some utility methods.
  */
+@AppModeFull
 public class BaseBackupCtsTest extends InstrumentationTestCase {
     private static final String APP_LOG_TAG = "BackupCTSApp";
 
-    private static final String LOCAL_TRANSPORT =
-            "android/com.android.internal.backup.LocalTransport";
-
-    private boolean isBackupSupported;
+    private boolean mIsBackupSupported;
     private LogcatInspector mLogcatInspector =
             new LogcatInspector() {
                 @Override
-                protected InputStream executeShellCommand(String command) throws IOException {
-                    return executeStreamedShellCommand(getInstrumentation(), command);
+                protected InputStream executeShellCommand(String command) {
+                    return executeInstrumentationShellCommand(getInstrumentation(), command);
+                }
+            };
+    private BackupUtils mBackupUtils =
+            new BackupUtils() {
+                @Override
+                protected InputStream executeShellCommand(String command) {
+                    return executeInstrumentationShellCommand(getInstrumentation(), command);
                 }
             };
 
@@ -54,28 +56,24 @@
     protected void setUp() throws Exception {
         super.setUp();
         PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
-        isBackupSupported = packageManager != null
-                && packageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP);
+        mIsBackupSupported =
+                packageManager != null
+                        && packageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP);
 
-        if (isBackupSupported) {
-            assertTrue("Backup not enabled", isBackupEnabled());
-            assertTrue("LocalTransport not selected", isLocalTransportSelected());
-            exec("setprop log.tag." + APP_LOG_TAG +" VERBOSE");
+        if (mIsBackupSupported) {
+            assertTrue("Backup not enabled", mBackupUtils.isBackupEnabled());
+            assertTrue("LocalTransport not selected", mBackupUtils.isLocalTransportSelected());
+            getBackupUtils()
+                    .executeShellCommandSync("setprop log.tag." + APP_LOG_TAG +" VERBOSE");
         }
     }
 
-    public boolean isBackupSupported() {
-        return isBackupSupported;
+    protected BackupUtils getBackupUtils() {
+        return mBackupUtils;
     }
 
-    private boolean isBackupEnabled() throws Exception {
-        String output = exec("bmgr enabled");
-        return output.contains("currently enabled");
-    }
-
-    private boolean isLocalTransportSelected() throws Exception {
-        String output = exec("bmgr list transports");
-        return output.contains("* " + LOCAL_TRANSPORT);
+    protected boolean isBackupSupported() {
+        return mIsBackupSupported;
     }
 
     /** See {@link LogcatInspector#mark(String)}. */
@@ -91,51 +89,20 @@
     }
 
     protected void createTestFileOfSize(String packageName, int size) throws Exception {
-        exec("am start -a android.intent.action.MAIN " +
-            "-c android.intent.category.LAUNCHER " +
-            "-n " + packageName + "/android.backup.app.MainActivity " +
-            "-e file_size " + size);
+        getBackupUtils().executeShellCommandSync(
+                "am start -a android.intent.action.MAIN "
+                        + "-c android.intent.category.LAUNCHER "
+                        + "-n "
+                        + packageName
+                        + "/android.backup.app.MainActivity "
+                        + "-e file_size " + size);
         waitForLogcat(30, "File created!");
     }
 
-    protected String exec(String command) throws Exception {
-        try (InputStream in = executeStreamedShellCommand(getInstrumentation(), command)) {
-            BufferedReader br = new BufferedReader(
-                    new InputStreamReader(in, StandardCharsets.UTF_8));
-            String str;
-            StringBuilder out = new StringBuilder();
-            while ((str = br.readLine()) != null) {
-                out.append(str);
-            }
-            return out.toString();
-        }
-    }
-
-    private static FileInputStream executeStreamedShellCommand(
-            Instrumentation instrumentation, String command) throws IOException {
+    private static InputStream executeInstrumentationShellCommand(
+            Instrumentation instrumentation, String command) {
         final ParcelFileDescriptor pfd =
                 instrumentation.getUiAutomation().executeShellCommand(command);
         return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
     }
-
-    private static void drainAndClose(BufferedReader reader) {
-        try {
-            while (reader.read() >= 0) {
-                // do nothing.
-            }
-        } catch (IOException ignored) {
-        }
-        closeQuietly(reader);
-    }
-
-    private static void closeQuietly(AutoCloseable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
 }
diff --git a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
index 9f81ecd..16825d7 100644
--- a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
@@ -16,9 +16,14 @@
 
 package android.backup.cts;
 
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import android.platform.test.annotations.AppModeFull;
+
 /**
  * Verifies that key methods are called in expected order during backup / restore.
  */
+@AppModeFull
 public class FullBackupLifecycleTest extends BaseBackupCtsTest {
 
     private static final String BACKUP_APP_NAME = "android.backup.app";
@@ -37,7 +42,7 @@
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
 
         // Request backup and wait for it to complete
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
 
         waitForLogcat(TIMEOUT_SECONDS,
             backupSeparator,
@@ -48,7 +53,7 @@
         String restoreSeparator = markLogcat();
 
         // Now request restore and wait for it to complete
-        exec("bmgr restore " + BACKUP_APP_NAME);
+        getBackupUtils().restoreSync(LOCAL_TRANSPORT_TOKEN, BACKUP_APP_NAME);
 
         waitForLogcat(TIMEOUT_SECONDS,
             restoreSeparator,
diff --git a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
index 56d489d..0939823 100644
--- a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
@@ -16,12 +16,15 @@
 
 package android.backup.cts;
 
+import android.platform.test.annotations.AppModeFull;
+
 /**
  * Verifies receiving quotaExceeded() callback on full backup.
  *
  * Uses test app that creates large file and receives the callback.
- * {@link com.android.internal.backup.LocalTransport} is used, it has size quota 25MB.
+ * {@link com.android.localtransport.LocalTransport} is used, it has size quota 25MB.
  */
+@AppModeFull
 public class FullBackupQuotaTest extends BaseBackupCtsTest {
 
     private static final String BACKUP_APP_NAME = "android.backup.app";
@@ -41,7 +44,7 @@
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
 
         // Request backup and wait for quota exceeded event in logcat
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS,separator,
             "Quota exceeded!");
     }
@@ -51,8 +54,9 @@
             return;
         }
         // get the app out of (possibly) stopped state so that backup can be run
-        exec("cmd activity broadcast -a android.backup.app.ACTION_WAKE_UP " +
-                "-n android.backup.app/.WakeUpReceiver");
+        getBackupUtils().executeShellCommandSync(
+                "cmd activity broadcast -a android.backup.app.ACTION_WAKE_UP "
+                        + "-n android.backup.app/.WakeUpReceiver");
 
         // give it 3s for the broadcast to be delivered
         try {
@@ -60,7 +64,7 @@
         } catch (InterruptedException e) {}
 
         String separator = markLogcat();
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS,separator,
             "quota is " + LOCAL_TRANSPORT_BACKUP_QUOTA);
     }
diff --git a/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java b/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
index 0bb5243..3d4b97e 100644
--- a/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
@@ -16,9 +16,14 @@
 
 package android.backup.cts;
 
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import android.platform.test.annotations.AppModeFull;
+
 /**
  * Verifies that key methods are called in expected order during backup / restore.
  */
+@AppModeFull
 public class KeyValueLifecycleTest extends BaseBackupCtsTest {
 
     private static final String BACKUP_APP_NAME = "android.backup.kvapp";
@@ -37,7 +42,7 @@
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
 
         // Request backup and wait for it to complete
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
 
         waitForLogcat(TIMEOUT_SECONDS,backupSeparator,
             "onCreate",
@@ -47,7 +52,7 @@
         String restoreSeparator = markLogcat();
 
         // Now request restore and wait for it to complete
-        exec("bmgr restore " + BACKUP_APP_NAME);
+        getBackupUtils().restoreSync(LOCAL_TRANSPORT_TOKEN, BACKUP_APP_NAME);
 
         waitForLogcat(TIMEOUT_SECONDS, restoreSeparator,
             "onCreate",
diff --git a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
index 2e42e21..1fdd2e6 100644
--- a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
@@ -16,12 +16,15 @@
 
 package android.backup.cts;
 
+import android.platform.test.annotations.AppModeFull;
+
 /**
  * Verifies receiving quotaExceeded() callback on full backup.
  *
  * Uses test app that creates large file and receives the callback.
- * {@link com.android.internal.backup.LocalTransport} is used, it has size quota 25MB.
+ * {@link com.android.localtransport.LocalTransport} is used, it has size quota 25MB.
  */
+@AppModeFull
 public class KeyValueQuotaTest extends BaseBackupCtsTest {
 
     private static final String BACKUP_APP_NAME = "android.backup.kvapp";
@@ -41,7 +44,7 @@
         createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
 
         // Request backup and wait for quota exceeded event in logcat
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS, separator,
             "Quota exceeded!");
     }
@@ -54,7 +57,7 @@
         createTestFileOfSize(BACKUP_APP_NAME, 1);
 
         String separator = markLogcat();
-        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        getBackupUtils().backupNowSync(BACKUP_APP_NAME);
         waitForLogcat(TIMEOUT_SECONDS, separator,
             "quota is " + LOCAL_TRANSPORT_BACKUP_QUOTA);
     }
diff --git a/tests/backup/src/android/backup/cts/PermissionTest.java b/tests/backup/src/android/backup/cts/PermissionTest.java
new file mode 100644
index 0000000..bf81def
--- /dev/null
+++ b/tests/backup/src/android/backup/cts/PermissionTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.backup.cts;
+
+import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Verifies that restored permissions are the same with backup value.
+ */
+@AppModeFull
+public class PermissionTest extends BaseBackupCtsTest {
+
+    /** The name of the package of the app under test */
+    private static final String APP_PACKAGE = "android.backup.permission";
+
+    /** The name of the package for backup */
+    private static final String ANDROID_PACKAGE = "android";
+
+    private BackupUtils mBackupUtils =
+            new BackupUtils() {
+                @Override
+                protected InputStream executeShellCommand(String command) throws IOException {
+                    ParcelFileDescriptor pfd =
+                            getInstrumentation().getUiAutomation().executeShellCommand(command);
+                    return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                }
+            };
+
+    /**
+     * Test backup and restore of grant runtime permission.
+     *
+     * Test logic:
+     * 1. Grant SEND_SMS and WRITE_CONTACTS permissions to APP_PACKAGE.
+     * 2. Backup android package, revoke SEND_SMS and WRITE_CONTACTS permissions to APP_PACKAGE.
+     * Then restore android package.
+     * 3. Check restored SEND_SMS and WRITE_CONTACTS permissions.
+     *
+     * @see PackageManagerService#serializeRuntimePermissionGrantsLPr(XmlSerializer, int) and
+     * PackageManagerService#processRestoredPermissionGrantsLPr(XmlPullParser, int)
+     */
+    public void testGrantRuntimePermission() throws Exception {
+        grantRuntimePermission(Manifest.permission.SEND_SMS);
+        grantRuntimePermission(Manifest.permission.WRITE_CONTACTS);
+
+        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+        revokeRuntimePermission(Manifest.permission.SEND_SMS);
+        revokeRuntimePermission(Manifest.permission.WRITE_CONTACTS);
+        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                checkPermission(Manifest.permission.SEND_SMS));
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                checkPermission(Manifest.permission.WRITE_CONTACTS));
+    }
+
+    private int checkPermission(String permission) {
+        return getInstrumentation().getContext().getPackageManager().checkPermission(permission,
+                APP_PACKAGE);
+    }
+
+    private void grantRuntimePermission(String permission) {
+        if (checkPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+            getInstrumentation().getUiAutomation().grantRuntimePermission(APP_PACKAGE, permission);
+            assertEquals(PackageManager.PERMISSION_GRANTED, checkPermission(permission));
+        }
+    }
+
+    private void revokeRuntimePermission(String permission) {
+        if (checkPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+            getInstrumentation().getUiAutomation().revokeRuntimePermission(APP_PACKAGE, permission);
+            assertEquals(PackageManager.PERMISSION_DENIED, checkPermission(permission));
+        }
+    }
+}
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index 369d566..ebbd555 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -32,6 +32,35 @@
 -include cts/error_prone_rules_tests.mk
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+# Reusable Camera performance test classes and helpers
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := cts-camera-performance-tests
+
+LOCAL_MODULE_TAGS := tests
+
+# Include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util \
+	ctstestrunner \
+	mockito-target-minus-junit4 \
+	CtsCameraUtils \
+	truth-prebuilt \
+	android-support-test
+
+LOCAL_SRC_FILES := \
+	src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java \
+	src/android/hardware/camera2/cts/PerformanceTest.java \
+	src/android/hardware/cts/CameraTestCase.java \
+	src/android/hardware/cts/LegacyCameraPerformanceTest.java
+
+LOCAL_SDK_VERSION := test_current
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
 # CtsCameraTestCases package
 
 include $(CLEAR_VARS)
@@ -46,11 +75,19 @@
 	mockito-target-minus-junit4 \
 	android-ex-camera2 \
 	CtsCameraUtils \
-	truth-prebuilt
+	truth-prebuilt \
+	androidx.heifwriter_heifwriter \
+	android-support-test
 
 LOCAL_JNI_SHARED_LIBRARIES := \
 	libctscamera2_jni \
-	libnativehelper_compat_libc++
+	libnativehelper_compat_libc++ \
+	libdynamic_depth \
+	libimage_io \
+	libxml2 \
+	libandroidicu \
+	libbase \
+	libc++ \
 
 LOCAL_NDK_STL_VARIANT := c++_shared
 
diff --git a/tests/camera/AndroidTest.xml b/tests/camera/AndroidTest.xml
index 2a2cdc9..83cdd03 100644
--- a/tests/camera/AndroidTest.xml
+++ b/tests/camera/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Camera test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="camera" />
+    <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" />
         <option name="test-file-name" value="CtsCameraTestCases.apk" />
@@ -23,7 +24,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.camera.cts" />
         <option name="runtime-hint" value="12m7s" />
-        <!-- test-timeout unit is ms, value = 200 min -->
-        <option name="test-timeout" value="12000000" />
+        <!-- test-timeout unit is ms, value = 20 min -->
+        <option name="test-timeout" value="1200000" />
     </test>
 </configuration>
diff --git a/tests/camera/libctscamera2jni/Android.mk b/tests/camera/libctscamera2jni/Android.mk
index 2cd4f64..e9c301e 100644
--- a/tests/camera/libctscamera2jni/Android.mk
+++ b/tests/camera/libctscamera2jni/Android.mk
@@ -22,7 +22,8 @@
 
 LOCAL_SRC_FILES := \
 	native-camera-jni.cpp \
-	dng-validate-jni.cpp
+	dng-validate-jni.cpp \
+	dynamic-depth-validate-jni.cpp
 
 LOCAL_C_INCLUDES := \
 	$(JNI_H_INCLUDE) \
@@ -42,6 +43,7 @@
     libcamera2ndk \
     libmediandk \
     libz \
+    libdl \
 
 # NDK build, shared C++ runtime
 LOCAL_SDK_VERSION := current
diff --git a/tests/camera/libctscamera2jni/dynamic-depth-validate-jni.cpp b/tests/camera/libctscamera2jni/dynamic-depth-validate-jni.cpp
new file mode 100644
index 0000000..a1d4b83
--- /dev/null
+++ b/tests/camera/libctscamera2jni/dynamic-depth-validate-jni.cpp
@@ -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.
+ */
+
+#define LOG_TAG "DYNAMIC-DEPTH-JNI"
+#include <jni.h>
+#include <log/log.h>
+#include <dlfcn.h>
+
+typedef int32_t (*validate_dynamic_depth_buffer) (const char *, size_t);
+static const char *kDynamicDepthLibraryName = "libdynamic_depth.so";
+static const char *kDynamicDepthValidateFunction = "ValidateAndroidDynamicDepthBuffer";
+
+extern "C" jboolean
+Java_android_hardware_camera2_cts_ImageReaderTest_validateDynamicDepthNative(
+        JNIEnv* env, jclass /*clazz*/, jbyteArray dynamicDepthBuffer) {
+
+    jbyte* buffer = env->GetByteArrayElements(dynamicDepthBuffer, NULL);
+    jsize bufferLength = env->GetArrayLength(dynamicDepthBuffer);
+    if (buffer == nullptr) {
+        ALOGE("Unable to map dynamic depth buffer to native");
+        return JNI_FALSE;
+    }
+
+    void* depthLibHandle = dlopen(kDynamicDepthLibraryName, RTLD_NOW | RTLD_LOCAL);
+    if (depthLibHandle == nullptr) {
+        ALOGE("Failed to load dynamic depth library!");
+        return JNI_FALSE;
+    }
+
+    validate_dynamic_depth_buffer validate = reinterpret_cast<validate_dynamic_depth_buffer> (
+            dlsym(depthLibHandle, kDynamicDepthValidateFunction));
+    if (validate == nullptr) {
+        ALOGE("Failed to link to dynamic depth validate function!");
+        dlclose(depthLibHandle);
+        return JNI_FALSE;
+    }
+
+    auto ret = (validate(reinterpret_cast<const char *> (buffer), bufferLength) == 0) ?
+            JNI_TRUE : JNI_FALSE;
+    dlclose(depthLibHandle);
+
+    return ret;
+}
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index 1a35a9f..9ab8222 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -273,6 +273,99 @@
             return;
         }
 
+        ACameraMetadata* copy = ACameraMetadata_copy(result);
+        ACameraMetadata_const_entry entryCopy;
+        ret = ACameraMetadata_getConstEntry(copy, ACAMERA_SYNC_FRAME_NUMBER, &entryCopy);
+        if (ret != ACAMERA_OK) {
+            ALOGE("Error: Sync frame number missing from result copy!");
+            return;
+        }
+
+        if (entry.data.i64[0] != entryCopy.data.i64[0]) {
+            ALOGE("Error: Sync frame number %" PRId64 " mismatch result copy %" PRId64,
+                    entry.data.i64[0], entryCopy.data.i64[0]);
+            return;
+        }
+        ACameraMetadata_free(copy);
+
+        if (thiz->mSaveCompletedRequests) {
+            thiz->mCompletedRequests.push_back(ACaptureRequest_copy(request));
+        }
+
+        thiz->mLastCompletedFrameNumber = entry.data.i64[0];
+        thiz->mResultCondition.notify_one();
+    }
+
+    static void onLogicalCameraCaptureCompleted(void* obj, ACameraCaptureSession* /*session*/,
+            ACaptureRequest* request, const ACameraMetadata* result,
+            size_t physicalResultCount, const char** physicalCameraIds,
+            const ACameraMetadata** physicalResults) {
+        ALOGV("%s", __FUNCTION__);
+        if ((obj == nullptr) || (result == nullptr)) {
+            return;
+        }
+        CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+        ACameraMetadata_const_entry entry;
+        auto ret = ACameraMetadata_getConstEntry(result, ACAMERA_SYNC_FRAME_NUMBER, &entry);
+        if (ret != ACAMERA_OK) {
+            ALOGE("Error: Sync frame number missing from result!");
+            return;
+        }
+
+        ACameraMetadata* copy = ACameraMetadata_copy(result);
+        ACameraMetadata_const_entry entryCopy;
+        ret = ACameraMetadata_getConstEntry(copy, ACAMERA_SYNC_FRAME_NUMBER, &entryCopy);
+        if (ret != ACAMERA_OK) {
+            ALOGE("Error: Sync frame number missing from result copy!");
+            return;
+        }
+
+        if (entry.data.i64[0] != entryCopy.data.i64[0]) {
+            ALOGE("Error: Sync frame number %" PRId64 " mismatch result copy %" PRId64,
+                    entry.data.i64[0], entryCopy.data.i64[0]);
+            return;
+        }
+
+        if (thiz->mRegisteredPhysicalIds.size() != physicalResultCount) {
+            ALOGE("Error: Number of registered physical camera Ids %zu is different than received"
+                    " physical camera Ids %zu", thiz->mRegisteredPhysicalIds.size(),
+                    physicalResultCount);
+            return;
+        }
+        for (size_t i = 0; i < physicalResultCount; i++) {
+            if (physicalCameraIds[i] == nullptr) {
+                ALOGE("Error: Invalid physical camera id in capture result");
+                return;
+            }
+            if (physicalResults[i] == nullptr) {
+                ALOGE("Error: Invalid physical camera metadata in capture result");
+                return;
+            }
+            ACameraMetadata_const_entry physicalEntry;
+            auto ret = ACameraMetadata_getConstEntry(physicalResults[i], ACAMERA_SYNC_FRAME_NUMBER,
+                    &physicalEntry);
+            if (ret != ACAMERA_OK) {
+                ALOGE("Error: Sync frame number missing from physical camera result metadata!");
+                return;
+            }
+            if (physicalEntry.data.i64[0] != entryCopy.data.i64[0]) {
+                ALOGE("Error: Physical camera sync frame number %" PRId64
+                        " mismatch result copy %" PRId64,
+                        physicalEntry.data.i64[0], entryCopy.data.i64[0]);
+                return;
+            }
+
+            auto foundId = std::find(thiz->mRegisteredPhysicalIds.begin(),
+                    thiz->mRegisteredPhysicalIds.end(), physicalCameraIds[i]);
+            if (foundId == thiz->mRegisteredPhysicalIds.end()) {
+                ALOGE("Error: Returned physical camera Id %s is not registered",
+                        physicalCameraIds[i]);
+                return;
+            }
+        }
+        ACameraMetadata_free(copy);
+
         if (thiz->mSaveCompletedRequests) {
             thiz->mCompletedRequests.push_back(ACaptureRequest_copy(request));
         }
@@ -379,6 +472,14 @@
         *out = mCompletedRequests;
     }
 
+    void registerPhysicalResults(size_t physicalIdCnt, const char*const* physicalOutputs) {
+        std::unique_lock<std::mutex> l(mMutex);
+        mRegisteredPhysicalIds.clear();
+        for (size_t i = 0; i < physicalIdCnt; i++) {
+            mRegisteredPhysicalIds.push_back(physicalOutputs[i]);
+        }
+    }
+
     void reset() {
         std::lock_guard<std::mutex> lock(mMutex);
         mLastSequenceIdCompleted = -1;
@@ -400,6 +501,8 @@
     int64_t mLastFailedFrameNumber = -1;
     bool    mSaveCompletedRequests = false;
     std::vector<ACaptureRequest*> mCompletedRequests;
+    // Registered physical camera Ids that are being requested upon.
+    std::vector<std::string> mRegisteredPhysicalIds;
 
     void clearSavedRequestsLocked() {
         for (ACaptureRequest* req : mCompletedRequests) {
@@ -548,7 +651,71 @@
         }
         return false;
     }
+
+    int64_t getMinFrameDurationFor(int64_t format, int64_t width, int64_t height) {
+        return getDurationFor(ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, format, width, height);
+    }
+
+    int64_t getStallDurationFor(int64_t format, int64_t width, int64_t height) {
+        return getDurationFor(ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS, format, width, height);
+    }
+
+    bool getMaxSizeForFormat(int32_t format, int32_t *width, int32_t *height) {
+        ACameraMetadata_const_entry entry;
+        ACameraMetadata_getConstEntry(mChars,
+                ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
+        bool supported = false;
+        int32_t w = 0, h = 0;
+        for (uint32_t i = 0; i < entry.count; i += 4) {
+            if (entry.data.i32[i] == format &&
+                    entry.data.i32[i+3] == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
+                    entry.data.i32[i+1] * entry.data.i32[i+2] > w * h) {
+                w = entry.data.i32[i+1];
+                h = entry.data.i32[i+2];
+                supported = true;
+            }
+        }
+
+        if (supported) {
+            *width = w;
+            *height = h;
+        }
+        return supported;
+    }
+
+    bool isSizeSupportedForFormat(int32_t format, int32_t width, int32_t height) {
+        ACameraMetadata_const_entry entry;
+        ACameraMetadata_getConstEntry(mChars,
+                ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
+        for (uint32_t i = 0; i < entry.count; i += 4) {
+            if (entry.data.i32[i] == format &&
+                    entry.data.i32[i+3] == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
+                    entry.data.i32[i+1] == width &&
+                    entry.data.i32[i+2] == height) {
+                return true;
+            }
+        }
+        return false;
+    }
   private:
+    int64_t getDurationFor(uint32_t tag, int64_t format, int64_t width, int64_t height) {
+        if (tag != ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS &&
+                tag != ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS &&
+                tag != ACAMERA_DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS &&
+                tag != ACAMERA_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS) {
+            return -1;
+        }
+        ACameraMetadata_const_entry entry;
+        ACameraMetadata_getConstEntry(mChars, tag, &entry);
+        for (uint32_t i = 0; i < entry.count; i += 4) {
+            if (entry.data.i64[i] == format &&
+                    entry.data.i64[i+1] == width &&
+                    entry.data.i64[i+2] == height) {
+                return entry.data.i64[i+3];
+            }
+        }
+        return -1;
+    }
     const ACameraMetadata* mChars;
 };
 
@@ -673,6 +840,37 @@
         return mCameraIdList->cameraIds[idx];
     }
 
+    // Caller is responsible to free returned characteristics metadata
+    ACameraMetadata* getCameraChars(int idx) {
+        if (!mMgrInited || !mCameraIdList || idx < 0 || idx >= mCameraIdList->numCameras) {
+            return nullptr;
+        }
+
+        ACameraMetadata* chars;
+        camera_status_t ret = ACameraManager_getCameraCharacteristics(
+                mCameraManager, mCameraIdList->cameraIds[idx], &chars);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Get camera characteristics failed: ret %d", ret);
+            return nullptr;
+        }
+        return chars;
+    }
+
+    // Caller is responsible to free returned characteristics metadata.
+    ACameraMetadata* getCameraChars(const char* id) {
+        if (!mMgrInited || id == nullptr) {
+            return nullptr;
+        }
+
+        ACameraMetadata* chars;
+        camera_status_t ret = ACameraManager_getCameraCharacteristics(mCameraManager, id, &chars);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Get camera characteristics failed: ret %d", ret);
+            return nullptr;
+        }
+        return chars;
+    }
+
     camera_status_t updateOutput(JNIEnv* env, ACaptureSessionOutput *output) {
         if (mSession == nullptr) {
             ALOGE("Testcase cannot update output configuration session %p",
@@ -713,34 +911,49 @@
             return AMEDIA_ERROR_UNKNOWN;
         }
 
+        media_status_t ret = initImageReaderWithErrorLog(
+                width, height, format, maxImages, listener, &mImgReader,
+                &mImgReaderAnw);
+        if (ret != AMEDIA_OK) {
+            return ret;
+        }
+
+        mImgReaderInited = true;
+        return AMEDIA_OK;
+    }
+
+    media_status_t initImageReaderWithErrorLog(
+            int32_t width, int32_t height, int32_t format, int32_t maxImages,
+            AImageReader_ImageListener* listener, AImageReader **imgReader,
+            ANativeWindow **imgReaderAnw) {
+
         media_status_t ret = AImageReader_new(
                 width, height, format,
-                maxImages, &mImgReader);
+                maxImages, imgReader);
         if (ret != AMEDIA_OK) {
             LOG_ERROR(errorString, "Create image reader. ret %d", ret);
             return ret;
         }
-        if (mImgReader == nullptr) {
+        if (*imgReader == nullptr) {
             LOG_ERROR(errorString, "null image reader created");
             return AMEDIA_ERROR_UNKNOWN;
         }
 
-        ret = AImageReader_setImageListener(mImgReader, listener);
+        ret = AImageReader_setImageListener(*imgReader, listener);
         if (ret != AMEDIA_OK) {
             LOG_ERROR(errorString, "Set AImageReader listener failed. ret %d", ret);
             return ret;
         }
 
-        ret = AImageReader_getWindow(mImgReader, &mImgReaderAnw);
+        ret = AImageReader_getWindow(*imgReader, imgReaderAnw);
         if (ret != AMEDIA_OK) {
             LOG_ERROR(errorString, "AImageReader_getWindow failed. ret %d", ret);
             return ret;
         }
-        if (mImgReaderAnw == nullptr) {
+        if (*imgReaderAnw == nullptr) {
             LOG_ERROR(errorString, "Null ANW from AImageReader!");
             return AMEDIA_ERROR_UNKNOWN;
         }
-        mImgReaderInited = true;
         return AMEDIA_OK;
     }
 
@@ -756,6 +969,13 @@
 
     camera_status_t createCaptureSessionWithLog(bool isPreviewShared = false,
             ACaptureRequest *sessionParameters = nullptr) {
+        std::vector<ACaptureSessionOutput*> extraOutputs;
+        return createCaptureSessionWithLog(extraOutputs, isPreviewShared, sessionParameters);
+    }
+
+    camera_status_t createCaptureSessionWithLog(
+            const std::vector<ACaptureSessionOutput*> extraOutputs,
+            bool isPreviewShared = false, ACaptureRequest *sessionParameters = nullptr) {
         if (mSession) {
             LOG_ERROR(errorString, "Cannot create session before closing existing one");
             return ACAMERA_ERROR_UNKNOWN;
@@ -792,6 +1012,14 @@
             }
         }
 
+        for (auto extraOutput : extraOutputs) {
+            ret = ACaptureSessionOutputContainer_add(mOutputs, extraOutput);
+            if (ret != ACAMERA_OK) {
+                LOG_ERROR(errorString, "Sesssion image reader output add failed! ret %d", ret);
+                return ret;
+            }
+        }
+
         if (mPreviewInited) {
             if (isPreviewShared) {
                 ret = ACaptureSessionSharedOutput_create(mPreviewAnw, &mPreviewOutput);
@@ -849,6 +1077,12 @@
     }
 
     camera_status_t createRequestsWithErrorLog() {
+        std::vector<ACameraOutputTarget*> extraOutputs;
+        return createRequestsWithErrorLog(extraOutputs);
+    }
+
+    camera_status_t createRequestsWithErrorLog(
+                const std::vector<ACameraOutputTarget*> extraOutputs) {
         if (mPreviewRequest || mStillRequest) {
             LOG_ERROR(errorString, "Cannot create requests before deleteing existing one");
             return ACAMERA_ERROR_UNKNOWN;
@@ -885,6 +1119,16 @@
                         mCameraId, ret);
                 return ret;
             }
+
+            // Add extraOutputs to the request
+            for (auto extraOutput : extraOutputs) {
+                ret = ACaptureRequest_addTarget(mPreviewRequest, extraOutput);
+                if (ret != ACAMERA_OK) {
+                    LOG_ERROR(errorString, "Camera %s add extra request output failed. ret %d",
+                            mCameraId, ret);
+                    return ret;
+                }
+            }
         } else {
             ALOGI("Preview not inited. Will not create preview request!");
         }
@@ -948,7 +1192,8 @@
         return ACAMERA_OK;
     }
 
-    camera_status_t startPreview(int *sequenceId = nullptr) {
+    camera_status_t startPreview(int *sequenceId = nullptr, size_t physicalIdCnt = 0,
+            const char*const* extraPhysicalOutputs = nullptr) {
         if (mSession == nullptr || mPreviewRequest == nullptr) {
             ALOGE("Testcase cannot start preview: session %p, preview request %p",
                     mSession, mPreviewRequest);
@@ -959,13 +1204,31 @@
         if (sequenceId == nullptr) {
             ret = ACameraCaptureSession_setRepeatingRequest(
                    mSession, nullptr, 1, &mPreviewRequest, &previewSeqId);
-        } else {
+        } else if (physicalIdCnt == 0) {
             ret = ACameraCaptureSession_setRepeatingRequest(
                    mSession, &mResultCb, 1, &mPreviewRequest, sequenceId);
+        } else {
+            if (extraPhysicalOutputs == nullptr) {
+                ALOGE("Testcase missing valid physical camera Ids for logical camera");
+                return ACAMERA_ERROR_INVALID_PARAMETER;
+            }
+            CaptureResultListener* resultListener =
+                    static_cast<CaptureResultListener*>(mLogicalCameraResultCb.context);
+            resultListener->registerPhysicalResults(physicalIdCnt, extraPhysicalOutputs);
+            ret = ACameraCaptureSession_logicalCamera_setRepeatingRequest(
+                    mSession, &mLogicalCameraResultCb, 1, &mPreviewRequest, sequenceId);
         }
         return ret;
     }
 
+    camera_status_t stopPreview() {
+        if (mSession == nullptr) {
+            ALOGE("Testcase cannot stop preview: session %p", mSession);
+            return ACAMERA_ERROR_UNKNOWN;
+        }
+        return ACameraCaptureSession_stopRepeating(mSession);
+    }
+
     camera_status_t updateRepeatingRequest(ACaptureRequest *updatedRequest,
             int *sequenceId = nullptr) {
         if (mSession == nullptr || updatedRequest == nullptr) {
@@ -1101,6 +1364,17 @@
         CaptureResultListener::onCaptureBufferLost
     };
 
+    ACameraCaptureSession_logicalCamera_captureCallbacks mLogicalCameraResultCb {
+        &mResultListener,
+        CaptureResultListener::onCaptureStart,
+        CaptureResultListener::onCaptureProgressed,
+        CaptureResultListener::onLogicalCameraCaptureCompleted,
+        CaptureResultListener::onCaptureFailed,
+        CaptureResultListener::onCaptureSequenceCompleted,
+        CaptureResultListener::onCaptureSequenceAborted,
+        CaptureResultListener::onCaptureBufferLost
+    };
+
     ACameraIdList* mCameraIdList = nullptr;
     ACameraDevice* mDevice = nullptr;
     AImageReader* mImgReader = nullptr;
@@ -1282,6 +1556,7 @@
     ACameraManager* mgr = ACameraManager_create();
     ACameraIdList *cameraIdList = nullptr;
     ACameraMetadata* chars = nullptr;
+    ACameraMetadata* copy = nullptr;
     int numCameras = 0;
     camera_status_t ret = ACameraManager_getCameraIdList(mgr, &cameraIdList);
     if (ret != ACAMERA_OK || cameraIdList == nullptr) {
@@ -1309,7 +1584,7 @@
 
         for (int tid = 0; tid < numTags; tid++) {
             uint32_t tagId = tags[tid];
-            ALOGV("%s capture request contains key %u", __FUNCTION__, tagId);
+            ALOGV("%s camera characteristics contains key %u", __FUNCTION__, tagId);
             uint32_t sectionId = tagId >> 16;
             if (sectionId >= ACAMERA_SECTION_COUNT && sectionId < ACAMERA_VENDOR) {
                 LOG_ERROR(errorString, "Unknown tagId %u, sectionId %u", tagId, sectionId);
@@ -1350,6 +1625,26 @@
             goto cleanup;
         }
 
+        // Check copy works
+        copy = ACameraMetadata_copy(chars);
+
+        // Compare copy with original
+        ACameraMetadata_const_entry entryCopy;
+        ret = ACameraMetadata_getConstEntry(
+                copy, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES, &entryCopy);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Get const available capabilities key failed. ret %d", ret);
+            goto cleanup;
+        }
+        for (uint32_t i = 0; i < entry.count; i++) {
+            if (entry.data.u8[i] != entryCopy.data.u8[i]) {
+                LOG_ERROR(errorString,
+                    "Copy of available capability key[%d]: %d mismatches original %d",
+                    i, entryCopy.data.u8[i], entry.data.u8[i]);
+                goto cleanup;
+            }
+        }
+
         // Check get unknown value fails
         uint32_t badTag = (uint32_t) ACAMERA_VENDOR_START - 1;
         ret = ACameraMetadata_getConstEntry(chars, badTag, &entry);
@@ -1359,7 +1654,9 @@
         }
 
         ACameraMetadata_free(chars);
+        ACameraMetadata_free(copy);
         chars = nullptr;
+        copy = nullptr;
     }
 
     pass = true;
@@ -1367,6 +1664,9 @@
     if (chars) {
         ACameraMetadata_free(chars);
     }
+    if (copy) {
+        ACameraMetadata_free(copy);
+    }
     ACameraManager_deleteCameraIdList(cameraIdList);
     ACameraManager_delete(mgr);
     ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
@@ -2263,8 +2563,238 @@
     return pass;
 }
 
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
+testCameraDeviceLogicalPhysicalStreamingNative(
+        JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface) {
+    const int NUM_TEST_IMAGES = 10;
+    const int TEST_WIDTH  = 640;
+    const int TEST_HEIGHT = 480;
+    ALOGV("%s", __FUNCTION__);
+    int numCameras = 0;
+    bool pass = false;
+    ACameraManager* mgr = ACameraManager_create();
+    ACameraMetadata* chars = nullptr;
+    media_status_t mediaRet = AMEDIA_ERROR_UNKNOWN;
+    PreviewTestCase testCase;
+    int64_t lastFrameNumber = 0;
+    bool frameArrived = false;
+    uint32_t timeoutSec = 1;
+    uint32_t runPreviewSec = 2;
+
+    camera_status_t ret = testCase.initWithErrorLog();
+    if (ret != ACAMERA_OK) {
+        // Don't log error here. testcase did it
+        goto cleanup;
+    }
+
+    numCameras = testCase.getNumCameras();
+    if (numCameras < 0) {
+        LOG_ERROR(errorString, "Testcase returned negavtive number of cameras: %d", numCameras);
+        goto cleanup;
+    }
+
+    for (int i = 0; i < numCameras; i++) {
+        const char* cameraId = testCase.getCameraId(i);
+        if (cameraId == nullptr) {
+            LOG_ERROR(errorString, "Testcase returned null camera id for camera %d", i);
+            goto cleanup;
+        }
+
+        if (chars != nullptr) {
+            ACameraMetadata_free(chars);
+            chars = nullptr;
+        }
+        chars = testCase.getCameraChars(i);
+        if (chars == nullptr) {
+            LOG_ERROR(errorString, "Get camera %s characteristics failure", cameraId);
+            goto cleanup;
+        }
+
+        size_t physicalCameraCnt = 0;
+        const char *const* physicalCameraIds = nullptr;
+        if (!ACameraMetadata_isLogicalMultiCamera(
+                chars, &physicalCameraCnt, &physicalCameraIds)) {
+            continue;
+        }
+        if (physicalCameraCnt < 2) {
+            LOG_ERROR(errorString, "Logical camera device %s only has %zu physical cameras",
+                   cameraId, physicalCameraCnt);
+            goto cleanup;
+        }
+
+        std::vector<const char*> candidateIds;
+        for (size_t i = 0; i < physicalCameraCnt && candidateIds.size() < 2; i++) {
+            ACameraMetadata* physicalChars = testCase.getCameraChars(physicalCameraIds[i]);
+            if (physicalChars == nullptr) {
+                LOG_ERROR(errorString,
+                        "Get camera %s characteristics failure", physicalCameraIds[i]);
+                goto cleanup;
+            }
+            StaticInfo info(physicalChars);
+            bool testSizeSupported = info.isSizeSupportedForFormat(AIMAGE_FORMAT_YUV_420_888,
+                    TEST_WIDTH, TEST_HEIGHT);
+            ACameraMetadata_free(physicalChars);
+            if (!testSizeSupported) {
+                continue;
+            }
+            candidateIds.push_back(physicalCameraIds[i]);
+        }
+        if (candidateIds.size() < 2) {
+            continue;
+        }
+
+        ret = testCase.openCamera(cameraId);
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Open camera device %s failure. ret %d", cameraId, ret);
+            goto cleanup;
+        }
+
+        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 cleanup;
+        }
+
+        std::vector<ImageReaderListener> readerListeners(2);
+        std::vector<AImageReader_ImageListener> readerCbs;
+        std::vector<AImageReader*> readers;
+        std::vector<ANativeWindow*> readerAnws;
+        std::vector<ACaptureSessionOutput*> readerSessionOutputs;
+        std::vector<ACameraOutputTarget*> readerOutputs;
+        for (size_t i = 0; i < 2; i++) {
+            AImageReader_ImageListener readerCb {
+                &readerListeners[i],
+                ImageReaderListener::validateImageCb
+            };
+            readerCbs.push_back(readerCb);
+
+            AImageReader* reader = nullptr;
+            ANativeWindow* readerAnw = nullptr;
+            ACaptureSessionOutput* readerSessionOutput = nullptr;
+            ACameraOutputTarget* readerOutput = nullptr;
+            mediaRet = testCase.initImageReaderWithErrorLog(
+                    TEST_WIDTH, TEST_HEIGHT, AIMAGE_FORMAT_YUV_420_888, NUM_TEST_IMAGES,
+                    &readerCb, &reader, &readerAnw);
+            if (mediaRet != AMEDIA_OK) {
+                // Don't log error here. testcase did it
+                goto cleanup;
+            }
+
+            camera_status_t ret = ACaptureSessionPhysicalOutput_create(readerAnw,
+                    candidateIds[i], &readerSessionOutput);
+            if (ret != ACAMERA_OK || readerSessionOutput == nullptr) {
+                if (ret == ACAMERA_OK) {
+                    ret = ACAMERA_ERROR_UNKNOWN; // ret OK but output is null
+                }
+                // Don't log error here. testcase did it
+                goto cleanup;
+            }
+
+            ret = ACameraOutputTarget_create(readerAnw, &readerOutput);
+            if (ret != ACAMERA_OK) {
+                // Don't log error here. testcase did it
+                goto cleanup;
+            }
+
+            readers.push_back(reader);
+            readerAnws.push_back(readerAnw);
+            readerSessionOutputs.push_back(readerSessionOutput);
+            readerOutputs.push_back(readerOutput);
+        }
+
+        ANativeWindow* previewAnw = testCase.initPreviewAnw(env, jPreviewSurface);
+        if (previewAnw == nullptr) {
+            LOG_ERROR(errorString, "Null ANW from preview surface!");
+            goto cleanup;
+        }
+
+        ret = testCase.createCaptureSessionWithLog(readerSessionOutputs);
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ret = testCase.createRequestsWithErrorLog(readerOutputs);
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        ACaptureRequest *previewRequest = nullptr;
+        ret = testCase.getPreviewRequest(&previewRequest);
+        if ((ret != ACAMERA_OK) || (previewRequest == nullptr)) {
+            LOG_ERROR(errorString, "Preview request query failed!");
+            goto cleanup;
+        }
+
+        int sequenceId = 0;
+        ret = testCase.startPreview(&sequenceId, 2, candidateIds.data());
+        if (ret != ACAMERA_OK) {
+            LOG_ERROR(errorString, "Start preview failed!");
+            goto cleanup;
+        }
+
+        sleep(runPreviewSec);
+
+        ret = testCase.stopPreview();
+        if (ret != ACAMERA_OK) {
+            ALOGE("%s: stopPreview failed", __FUNCTION__);
+            LOG_ERROR(errorString, "stopPreview failed!");
+            goto cleanup;
+        }
+
+        //Then wait for all old requests to flush
+        lastFrameNumber = testCase.getCaptureSequenceLastFrameNumber(sequenceId, timeoutSec);
+        if (lastFrameNumber < 0) {
+            LOG_ERROR(errorString, "Camera %s failed to acquire last frame number!",
+                    cameraId);
+            goto cleanup;
+        }
+
+        frameArrived = testCase.waitForFrameNumber(lastFrameNumber, timeoutSec);
+        if (!frameArrived) {
+            LOG_ERROR(errorString, "Camera %s timed out waiting on last frame number!",
+                    cameraId);
+            goto cleanup;
+        }
+
+        ret = testCase.resetWithErrorLog();
+        if (ret != ACAMERA_OK) {
+            // Don't log error here. testcase did it
+            goto cleanup;
+        }
+
+        usleep(100000); // sleep to give some time for callbacks to happen
+
+        if (!testCase.isCameraAvailable(cameraId)) {
+            LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
+            goto cleanup;
+        }
+    }
+
+    ret = testCase.deInit();
+    if (ret != ACAMERA_OK) {
+        LOG_ERROR(errorString, "Testcase deInit failed: ret %d", ret);
+        goto cleanup;
+    }
+
+    pass = true;
+cleanup:
+    if (chars) {
+        ACameraMetadata_free(chars);
+    }
+    ACameraManager_delete(mgr);
+    ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
+    if (!pass) {
+        throwAssertionError(env, errorString);
+    }
+    return pass;
+}
+
 bool nativeImageReaderTestBase(
-        JNIEnv* env, jstring jOutPath, AImageReader_ImageCallback cb) {
+        JNIEnv* env, jstring jOutPath, jint format, AImageReader_ImageCallback cb) {
     const int NUM_TEST_IMAGES = 10;
     const int TEST_WIDTH  = 640;
     const int TEST_HEIGHT = 480;
@@ -2272,6 +2802,7 @@
     int numCameras = 0;
     bool pass = false;
     PreviewTestCase testCase;
+    ACameraMetadata* chars = nullptr;
 
     const char* outPath = (jOutPath == nullptr) ? nullptr :
             env->GetStringUTFChars(jOutPath, nullptr);
@@ -2304,6 +2835,13 @@
             goto cleanup;
         }
 
+        chars = testCase.getCameraChars(i);
+        if (chars == nullptr) {
+            LOG_ERROR(errorString, "Get camera %s characteristics failure", cameraId);
+            goto cleanup;
+        }
+        StaticInfo staticInfo(chars);
+
         usleep(100000); // sleep to give some time for callbacks to happen
 
         if (testCase.isCameraAvailable(cameraId)) {
@@ -2315,8 +2853,26 @@
         AImageReader_ImageListener readerCb { &readerListener, cb };
         readerListener.setDumpFilePathBase(outPath);
 
+        int32_t testWidth, testHeight;
+        switch (format) {
+            case AIMAGE_FORMAT_JPEG:
+                testWidth = TEST_WIDTH;
+                testHeight = TEST_HEIGHT;
+                break;
+            case AIMAGE_FORMAT_Y8:
+                if (!staticInfo.getMaxSizeForFormat(format, &testWidth, &testHeight)) {
+                    // This isn't an error condition: device does't support this
+                    // format.
+                    pass = true;
+                    goto cleanup;
+                }
+                break;
+            default:
+                LOG_ERROR(errorString, "Testcase doesn't yet support format %d", format);
+                goto cleanup;
+        }
         mediaRet = testCase.initImageReaderWithErrorLog(
-                TEST_WIDTH, TEST_HEIGHT, AIMAGE_FORMAT_JPEG, NUM_TEST_IMAGES,
+                testWidth, testHeight, format, NUM_TEST_IMAGES,
                 &readerCb);
         if (mediaRet != AMEDIA_OK) {
             // Don't log error here. testcase did it
@@ -2399,9 +2955,32 @@
             }
         }
 
+        int64_t minFrameDurationNs = staticInfo.getMinFrameDurationFor(
+                AIMAGE_FORMAT_JPEG, TEST_WIDTH, TEST_HEIGHT);
+        if (minFrameDurationNs < 0) {
+            LOG_ERROR(errorString, "Get camera %s minFrameDuration failed", cameraId);
+            goto cleanup;
+        }
+        int64_t stallDurationNs = staticInfo.getStallDurationFor(
+                AIMAGE_FORMAT_JPEG, TEST_WIDTH, TEST_HEIGHT);
+        if (stallDurationNs < 0) {
+            LOG_ERROR(errorString, "Get camera %s stallDuration failed", cameraId);
+            goto cleanup;
+        }
+
+        int64_t expectedDurationNs = (minFrameDurationNs + stallDurationNs) * NUM_TEST_IMAGES;
+        constexpr int64_t waitPerIterationUs = 100000;
+        constexpr int64_t usToNs = 1000;
+        int totalWaitIteration = 50;
+
+        // Allow 1.5x margin
+        if (expectedDurationNs * 3 / 2 > totalWaitIteration * waitPerIterationUs * usToNs) {
+            totalWaitIteration = expectedDurationNs * 3 / 2 / waitPerIterationUs / usToNs;
+        }
+
         // wait until all capture finished
-        for (int i = 0; i < 50; i++) {
-            usleep(100000); // sleep 100ms
+        for (int i = 0; i < totalWaitIteration; i++) {
+            usleep(waitPerIterationUs);
             if (readerListener.onImageAvailableCount() == NUM_TEST_IMAGES) {
                 ALOGI("Session take ~%d ms to capture %d images",
                         i*100, NUM_TEST_IMAGES);
@@ -2441,6 +3020,12 @@
     if (outPath != nullptr) {
         env->ReleaseStringUTFChars(jOutPath, outPath);
     }
+
+    if (chars != nullptr) {
+        ACameraMetadata_free(chars);
+        chars = nullptr;
+    }
+
     ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
     if (!pass) {
         throwAssertionError(env, errorString);
@@ -2453,19 +3038,28 @@
 testJpegNative(
         JNIEnv* env, jclass /*clazz*/, jstring jOutPath) {
     ALOGV("%s", __FUNCTION__);
-    return nativeImageReaderTestBase(env, jOutPath, ImageReaderListener::validateImageCb);
+    return nativeImageReaderTestBase(env, jOutPath, AIMAGE_FORMAT_JPEG,
+            ImageReaderListener::validateImageCb);
 }
 
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeImageReaderTest_\
+testY8Native(
+        JNIEnv* env, jclass /*clazz*/, jstring jOutPath) {
+    ALOGV("%s", __FUNCTION__);
+    return nativeImageReaderTestBase(env, jOutPath, AIMAGE_FORMAT_Y8,
+            ImageReaderListener::validateImageCb);
+}
 
 extern "C" jboolean
 Java_android_hardware_camera2_cts_NativeImageReaderTest_\
 testImageReaderCloseAcquiredImagesNative(
         JNIEnv* env, jclass /*clazz*/) {
     ALOGV("%s", __FUNCTION__);
-    return nativeImageReaderTestBase(env, nullptr, ImageReaderListener::acquireImageCb);
+    return nativeImageReaderTestBase(env, nullptr, AIMAGE_FORMAT_JPEG,
+            ImageReaderListener::acquireImageCb);
 }
 
-
 extern "C" jboolean
 Java_android_hardware_camera2_cts_NativeStillCaptureTest_\
 testStillCaptureNative(
@@ -2478,6 +3072,7 @@
     int numCameras = 0;
     bool pass = false;
     PreviewTestCase testCase;
+    ACameraMetadata* chars = nullptr;
 
     const char* outPath = env->GetStringUTFChars(jOutPath, nullptr);
     ALOGI("%s: out path is %s", __FUNCTION__, outPath);
@@ -2507,6 +3102,13 @@
             goto cleanup;
         }
 
+        chars = testCase.getCameraChars(i);
+        if (chars == nullptr) {
+            LOG_ERROR(errorString, "Get camera %s characteristics failure", cameraId);
+            goto cleanup;
+        }
+        StaticInfo staticInfo(chars);
+
         usleep(100000); // sleep to give some time for callbacks to happen
 
         if (testCase.isCameraAvailable(cameraId)) {
@@ -2565,9 +3167,32 @@
             }
         }
 
+        int64_t minFrameDurationNs = staticInfo.getMinFrameDurationFor(
+                AIMAGE_FORMAT_JPEG, TEST_WIDTH, TEST_HEIGHT);
+        if (minFrameDurationNs < 0) {
+            LOG_ERROR(errorString, "Get camera %s minFrameDuration failed", cameraId);
+            goto cleanup;
+        }
+        int64_t stallDurationNs = staticInfo.getStallDurationFor(
+                AIMAGE_FORMAT_JPEG, TEST_WIDTH, TEST_HEIGHT);
+        if (stallDurationNs < 0) {
+            LOG_ERROR(errorString, "Get camera %s stallDuration failed", cameraId);
+            goto cleanup;
+        }
+
+        int64_t expectedDurationNs = (minFrameDurationNs + stallDurationNs) * NUM_TEST_IMAGES;
+        constexpr int64_t waitPerIterationUs = 100000;
+        constexpr int64_t usToNs = 1000;
+        int totalWaitIteration = 50;
+
+        // Allow 1.5x margin
+        if (expectedDurationNs * 3 / 2 > totalWaitIteration * waitPerIterationUs * usToNs) {
+            totalWaitIteration = expectedDurationNs * 3 / 2 / waitPerIterationUs / usToNs;
+        }
+
         // wait until all capture finished
-        for (int i = 0; i < 50; i++) {
-            usleep(100000); // sleep 100ms
+        for (int i = 0; i < totalWaitIteration; i++) {
+            usleep(waitPerIterationUs);
             if (readerListener.onImageAvailableCount() == NUM_TEST_IMAGES) {
                 ALOGI("Session take ~%d ms to capture %d images",
                         i*100, NUM_TEST_IMAGES);
@@ -2604,6 +3229,12 @@
     pass = true;
 cleanup:
     env->ReleaseStringUTFChars(jOutPath, outPath);
+
+    if (chars != nullptr) {
+        ACameraMetadata_free(chars);
+        chars = nullptr;
+    }
+
     ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
     if (!pass) {
         throwAssertionError(env, errorString);
diff --git a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
index bd9bec8..92b171a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
@@ -48,7 +48,6 @@
 import android.hardware.camera2.cts.rs.ScriptYuvToRgb;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
 import android.renderscript.Allocation;
 import android.renderscript.Script.LaunchOptions;
 import android.test.AndroidTestCase;
@@ -72,7 +71,6 @@
  *
  * <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
  */
-@AppModeFull
 public class AllocationTest extends AndroidTestCase {
     private static final String TAG = "AllocationTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
index 6e0949d..7b23abf 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
@@ -27,20 +27,21 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
 
 import java.util.ArrayList;
 
+import org.junit.Test;
+
 /**
  * Basic tests for burst capture in RAW formats.
  */
-@AppModeFull
 public class BurstCaptureRawTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "BurstCaptureRawTest";
     private static final int RAW_FORMATS[] = {
@@ -55,31 +56,31 @@
             EXPOSURE_MULTIPLIERS.length * SENSITIVITY_MLTIPLIERS.length;
 
     @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();
     }
 
     /**
      * Verify raw sensor size information is correctly configured.
      */
+    @Test
     public void testRawSensorSize() throws Exception {
         Log.i(TAG, "Begin testRawSensorSize");
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-
                 ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
-                if (!checkCapability(supportedRawList, RAW_FORMATS)) {
+                if (!checkCapability(id, supportedRawList, RAW_FORMATS)) {
                     Log.i(TAG, "Capability is not supported on camera " + id
                             + ". Skip the test.");
                     continue;
                 }
 
+                openDevice(id);
                 Size[] rawSizes = mStaticInfo.getRawOutputSizesChecked();
                 assertTrue("No capture sizes available for RAW format!", rawSizes.length != 0);
 
@@ -99,6 +100,7 @@
      * be honored.
      * </p>
      */
+    @Test
     public void testMetadataRoundDown() throws Exception {
         Log.i(TAG, "Begin testMetadataRoundDown");
 
@@ -114,6 +116,7 @@
      * sync.
      * </p>
      */
+    @Test
     public void testManualAutoSwitch() throws Exception {
         Log.i(TAG, "Begin testManualAutoSwitch");
 
@@ -125,6 +128,7 @@
     /**
      * Per frame timestamp test in non-stalled RAW formats
      */
+    @Test
     public void testTimestamp() throws Exception {
         Log.i(TAG, "Begin testTimestamp");
 
@@ -437,15 +441,17 @@
      *
      * @return true if the it is has the capability to execute the test.
      */
-    private boolean checkCapability(ArrayList<Integer> supportedRawList, int[] testedFormats) {
+    private boolean checkCapability(String id, ArrayList<Integer> supportedRawList,
+            int[] testedFormats) {
+        StaticMetadata staticInfo = mAllStaticInfo.get(id);
         // make sure the sensor has manual support
-        if (!mStaticInfo.isHardwareLevelAtLeastFull()) {
+        if (!staticInfo.isHardwareLevelAtLeastFull()) {
             Log.w(TAG, "Full hardware level is not supported");
             return false;
         }
 
         // get the list of supported RAW format
-        StreamConfigurationMap config = mStaticInfo.getValueFromKeyNonNull(
+        StreamConfigurationMap config = staticInfo.getValueFromKeyNonNull(
                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
 
         // check for the RAW support
@@ -671,15 +677,14 @@
         final int PREPARE_TIMEOUT_MS = 10000;
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-
                 ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
-                if (!checkCapability(supportedRawList, testedFormats)) {
+                if (!checkCapability(id, supportedRawList, testedFormats)) {
                     Log.i(TAG, "Capability is not supported on camera " + id
                             + ". Skip the test.");
                     continue;
                 }
 
+                openDevice(id);
                 // test each supported RAW format
                 for (int rawFormat : supportedRawList) {
                     Log.i(TAG, "Testing format " + imageFormatToString(rawFormat) + ".");
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
index b0aae41..bbedf05 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
@@ -23,10 +23,10 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
@@ -34,7 +34,8 @@
 import java.util.List;
 import java.util.ArrayList;
 
-@AppModeFull
+import org.junit.Test;
+
 public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "BurstCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -44,28 +45,30 @@
      * Test YUV burst capture with full-AUTO control.
      * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
      */
+    @Test
     public void testYuvBurst() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 String id = mCameraIds[i];
                 Log.i(TAG, "Testing YUV Burst for camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                 }
-                if (!mStaticInfo.isAeLockSupported() || !mStaticInfo.isAwbLockSupported()) {
+                if (!staticInfo.isAeLockSupported() || !staticInfo.isAwbLockSupported()) {
                     Log.i(TAG, "AE/AWB lock is not supported in camera " + id +
                             ". Skip the test");
                     continue;
                 }
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Legacy camera doesn't report min frame duration" +
                             ". Skip the test");
                     continue;
                 }
 
+                openDevice(id);
                 yuvBurstTestByCamera(id);
             } finally {
                 closeDevice();
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index f72fcd7..390495a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -46,7 +46,6 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
 import android.view.Surface;
@@ -77,7 +76,6 @@
 /**
  * <p>Basic test for CameraDevice APIs.</p>
  */
-@AppModeFull
 public class CameraDeviceTest extends Camera2AndroidTestCase {
     private static final String TAG = "CameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -645,13 +643,13 @@
     public void testPrepare() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareTestByCamera();
             }
@@ -668,17 +666,18 @@
     public void testPrepareForSharedSurfaces() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareTestForSharedSurfacesByCamera();
             }
@@ -694,13 +693,13 @@
     public void testCreateSessions() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 testCreateSessionsByCamera(mCameraIds[i]);
             }
@@ -716,13 +715,13 @@
     public void testCreateCustomSession() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 testCreateCustomSessionByCamera(mCameraIds[i]);
             }
@@ -880,13 +879,14 @@
     public void testSessionParametersStateLeak() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
                 testSessionParametersStateLeakByCamera(mCameraIds[i]);
             }
             finally {
@@ -1049,13 +1049,13 @@
     public void testCreateSessionWithParameters() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
-                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false);
                 testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index dd7197c..170cc3f 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -32,7 +32,6 @@
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -51,7 +50,6 @@
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-@AppModeFull
 public class CameraManagerTest extends AndroidTestCase {
     private static final String TAG = "CameraManagerTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 8f8bf87..6213bff 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -29,6 +29,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+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.ColorSpaceTransform;
@@ -39,7 +40,6 @@
 import android.hardware.camera2.params.TonemapCurve;
 import android.media.Image;
 import android.os.Parcel;
-import android.platform.test.annotations.AppModeFull;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Range;
@@ -52,6 +52,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.Test;
+
 /**
  * <p>
  * Basic test for camera CaptureRequest key controls.
@@ -61,7 +63,6 @@
  * manual ISP control and other per-frame control and synchronization.
  * </p>
  */
-@AppModeFull
 public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "CaptureRequestTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -125,18 +126,19 @@
     }
 
     @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 CaptureRequest settings parcelling.
      */
+    @Test
     public void testSettingsBinderParcel() throws Exception {
         SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
         Surface surface = new Surface(outputTexture);
@@ -228,16 +230,16 @@
      * value changes (when requests have lock ON).
      * </p>
      */
+    @Test
     public void testBlackLevelLock() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -282,13 +284,14 @@
      *   close enough to the optical black level values.
      * </p>
      */
+    @Test
     public void testDynamicBlackWhiteLevel() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isDynamicBlackLevelSupported()) {
+                if (!mAllStaticInfo.get(id).isDynamicBlackLevelSupported()) {
                     continue;
                 }
+                openDevice(id);
                 dynamicBlackWhiteLevelTestByCamera();
             } finally {
                 closeDevice();
@@ -309,24 +312,25 @@
      * requested by setting {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} to ON.
      * </p>
      */
+    @Test
     public void testLensShadingMap() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-
-                if (!mStaticInfo.isManualLensShadingMapSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (!staticInfo.isManualLensShadingMapSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " doesn't support lens shading controls, skipping test");
                     continue;
                 }
 
                 List<Integer> lensShadingMapModes = Arrays.asList(CameraTestUtils.toObject(
-                        mStaticInfo.getAvailableLensShadingMapModesChecked()));
+                        staticInfo.getAvailableLensShadingMapModesChecked()));
 
                 if (!lensShadingMapModes.contains(STATISTICS_LENS_SHADING_MAP_MODE_ON)) {
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -386,17 +390,17 @@
      * correct.
      * </p>
      */
+    @Test
     public void testAntiBandingModes() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-
                 // Without manual sensor control, exposure time cannot be verified
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
 
                 Size previewSz =
@@ -422,16 +426,17 @@
      * API specifications.
      * </p>
      */
+    @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++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
 
                 // Update preview surface with given size for all sub-tests.
@@ -454,16 +459,17 @@
      * and {@link CaptureResult#FLASH_STATE} result.
      * </p>
      */
+    @Test
     public void testFlashControl() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -496,15 +502,16 @@
     /**
      * Test face detection modes and results.
      */
+    @Test
     public void testFaceDetection() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 faceDetectionTestByCamera();
             } finally {
                 closeDevice();
@@ -515,15 +522,16 @@
     /**
      * Test tone map modes and controls.
      */
+    @Test
     public void testToneMapControl() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isManualToneMapSupported()) {
+                if (!mAllStaticInfo.get(id).isManualToneMapSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support tone mapping controls, skipping test");
                     continue;
                 }
+                openDevice(id);
                 toneMapTestByCamera();
             } finally {
                 closeDevice();
@@ -534,15 +542,16 @@
     /**
      * Test color correction modes and controls.
      */
+    @Test
     public void testColorCorrectionControl() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorCorrectionSupported()) {
+                if (!mAllStaticInfo.get(id).isColorCorrectionSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support color correction controls, skipping test");
                     continue;
                 }
+                openDevice(id);
                 colorCorrectionTestByCamera();
             } finally {
                 closeDevice();
@@ -553,16 +562,17 @@
     /**
      * Test edge mode control for Fps not exceeding 30.
      */
+    @Test
     public void testEdgeModeControl() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isEdgeModeControlSupported()) {
+                if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support EDGE_MODE controls, skipping test");
                     continue;
                 }
 
+                openDevice(id);
                 List<Range<Integer>> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo);
                 edgeModesTestByCamera(fpsRanges);
             } finally {
@@ -574,16 +584,17 @@
     /**
      * Test edge mode control for Fps greater than 30.
      */
+    @Test
     public void testEdgeModeControlFastFps() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isEdgeModeControlSupported()) {
+                if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support EDGE_MODE controls, skipping test");
                     continue;
                 }
 
+                openDevice(id);
                 List<Range<Integer>> fpsRanges = getTargetFpsRangesGreaterThan30(mStaticInfo);
                 edgeModesTestByCamera(fpsRanges);
             } finally {
@@ -596,22 +607,24 @@
     /**
      * Test focus distance control.
      */
+    @Test
     public void testFocusDistanceControl() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.hasFocuser()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.hasFocuser()) {
                     Log.i(TAG, "Camera " + id + " has no focuser, skipping test");
                     continue;
                 }
 
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!staticInfo.isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     Log.i(TAG, "Camera " + id +
                             " does not support MANUAL_SENSOR, skipping test");
                     continue;
                 }
 
+                openDevice(id);
                 focusDistanceTestByCamera();
             } finally {
                 closeDevice();
@@ -622,16 +635,17 @@
     /**
      * Test noise reduction mode for fps ranges not exceeding 30
      */
+    @Test
     public void testNoiseReductionModeControl() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isNoiseReductionModeControlSupported()) {
+                if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support noise reduction mode, skipping test");
                     continue;
                 }
 
+                openDevice(id);
                 List<Range<Integer>> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo);
                 noiseReductionModeTestByCamera(fpsRanges);
             } finally {
@@ -643,16 +657,17 @@
     /**
      * Test noise reduction mode for fps ranges greater than 30
      */
+    @Test
     public void testNoiseReductionModeControlFastFps() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isNoiseReductionModeControlSupported()) {
+                if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " doesn't support noise reduction mode, skipping test");
                     continue;
                 }
 
+                openDevice(id);
                 List<Range<Integer>> fpsRanges = getTargetFpsRangesGreaterThan30(mStaticInfo);
                 noiseReductionModeTestByCamera(fpsRanges);
             } finally {
@@ -666,14 +681,15 @@
      *
      * <p>The color correction gain and transform shouldn't be changed when AWB is locked.</p>
      */
+    @Test
     public void testAwbModeAndLock() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 awbModeAndLockTestByCamera();
             } finally {
                 closeDevice();
@@ -684,14 +700,15 @@
     /**
      * Test different AF modes.
      */
+    @Test
     public void testAfModes() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 afModeTestByCamera();
             } finally {
                 closeDevice();
@@ -702,11 +719,12 @@
     /**
      * Test video and optical stabilizations.
      */
+    @Test
     public void testCameraStabilizations() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                List<Key<?>> keys = mStaticInfo.getCharacteristics().getKeys();
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                List<Key<?>> keys = staticInfo.getCharacteristics().getKeys();
                 if (!(keys.contains(
                         CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ||
                         keys.contains(
@@ -714,10 +732,11 @@
                     Log.i(TAG, "Camera " + id + " doesn't support any stabilization modes");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 stabilizationTestByCamera();
             } finally {
                 closeDevice();
@@ -729,14 +748,15 @@
      * Test digitalZoom (center wise and non-center wise), validate the returned crop regions.
      * The max preview size is used for each camera.
      */
+    @Test
     public void testDigitalZoom() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 Size maxPreviewSize = mOrderedPreviewSizes.get(0);
                 digitalZoomTestByCamera(maxPreviewSize);
             } finally {
@@ -749,14 +769,15 @@
      * Test digital zoom and all preview size combinations.
      * TODO: this and above test should all be moved to preview test class.
      */
+    @Test
     public void testDigitalZoomPreviewCombinations() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 digitalZoomPreviewCombinationTestByCamera();
             } finally {
                 closeDevice();
@@ -767,11 +788,12 @@
     /**
      * Test scene mode controls.
      */
+    @Test
     public void testSceneModes() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (mStaticInfo.isSceneModeSupported()) {
+                if (mAllStaticInfo.get(id).isSceneModeSupported()) {
+                    openDevice(id);
                     sceneModeTestByCamera();
                 }
             } finally {
@@ -783,14 +805,15 @@
     /**
      * Test effect mode controls.
      */
+    @Test
     public void testEffectModes() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 effectModeTestByCamera();
             } finally {
                 closeDevice();
@@ -1949,6 +1972,19 @@
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
         int[] toneMapModes = mStaticInfo.getAvailableToneMapModesChecked();
+        // Test AUTO modes first. Note that FAST/HQ must both present or not present
+        for (int i = 0; i < toneMapModes.length; i++) {
+            if (toneMapModes[i] == CaptureRequest.TONEMAP_MODE_FAST && i > 0) {
+                int tmpMode = toneMapModes[0];
+                toneMapModes[0] = CaptureRequest.TONEMAP_MODE_FAST;
+                toneMapModes[i] = tmpMode;
+            }
+            if (toneMapModes[i] == CaptureRequest.TONEMAP_MODE_HIGH_QUALITY && i > 1) {
+                int tmpMode = toneMapModes[1];
+                toneMapModes[1] = CaptureRequest.TONEMAP_MODE_HIGH_QUALITY;
+                toneMapModes[i] = tmpMode;
+            }
+        }
         for (int mode : toneMapModes) {
             if (VERBOSE) {
                 Log.v(TAG, "Testing tonemap mode " + mode);
@@ -2054,6 +2090,25 @@
                     CameraTestUtils.toObject(mapBlue), /*min*/ZERO, /*max*/ONE);
             mCollector.expectInRange("Tonemap curve blue length is out of range",
                     mapBlue.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
+
+            // Make sure capture result tonemap has identical channels.
+            if (mStaticInfo.isMonochromeCamera()) {
+                mCollector.expectEquals("Capture result tonemap of monochrome camera should " +
+                        "have same dimension for all channels", mapRed.length, mapGreen.length);
+                mCollector.expectEquals("Capture result tonemap of monochrome camera should " +
+                        "have same dimension for all channels", mapRed.length, mapBlue.length);
+
+                if (mapRed.length == mapGreen.length && mapRed.length == mapBlue.length) {
+                    boolean isIdentical = true;
+                    for (int j = 0; j < mapRed.length; j++) {
+                        isIdentical = (mapRed[j] == mapGreen[j] && mapRed[j] == mapBlue[j]);
+                        if (!isIdentical)
+                            break;
+                    }
+                    mCollector.expectTrue("Capture result tonemap of monochrome camera should " +
+                            "be identical between all channels", isIdentical);
+                }
+            }
         }
         stopPreview();
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index 4146333..ab65015 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -23,12 +23,12 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Build;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Pair;
 import android.util.Size;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -51,7 +51,6 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-@AppModeFull
 public class CaptureResultTest extends Camera2AndroidTestCase {
     private static final String TAG = "CaptureResultTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -155,14 +154,13 @@
         final int WAIT_FOR_RESULT_TIMOUT_MS = 2000;
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-
                 // Skip the test if partial result is not supported
-                int partialResultCount = mStaticInfo.getPartialResultCount();
+                int partialResultCount = mAllStaticInfo.get(id).getPartialResultCount();
                 if (partialResultCount == 1) {
                     continue;
                 }
 
+                openDevice(id);
                 // Create image reader and surface.
                 if (mStaticInfo.isColorOutputSupported()) {
                     Size size = mOrderedPreviewSizes.get(0);
@@ -274,12 +272,12 @@
             SimpleImageReaderListener jpegListener = new SimpleImageReaderListener();
             SimpleImageReaderListener prevListener = new SimpleImageReaderListener();
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 CaptureRequest.Builder previewBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                 CaptureRequest.Builder multiBuilder =
@@ -410,10 +408,12 @@
                     physicalCaptureResults.size() + " vs " + requestedPhysicalIds.size(),
                     physicalCaptureResults.size(), requestedPhysicalIds.size());
 
-            validateOneCaptureResult(errorCollector, waiverKeys, allKeys, requestBuilder, result,
-                    null/*cameraId*/, i);
+            validateOneCaptureResult(errorCollector, staticInfo, waiverKeys, allKeys,
+                    requestBuilder, result, null/*cameraId*/, i);
             for (String physicalId : requestedPhysicalIds) {
-                validateOneCaptureResult(errorCollector, physicalWaiverKeys.get(physicalId),
+                StaticMetadata physicalStaticInfo = allStaticInfo.get(physicalId);
+                validateOneCaptureResult(errorCollector, physicalStaticInfo,
+                        physicalWaiverKeys.get(physicalId),
                         allKeys, null/*requestBuilder*/, physicalCaptureResults.get(physicalId),
                         physicalId, i);
             }
@@ -421,7 +421,8 @@
     }
 
     private static void validateOneCaptureResult(CameraErrorCollector errorCollector,
-            List<CaptureResult.Key<?>> skippedKeys, List<CaptureResult.Key<?>> allKeys,
+            StaticMetadata staticInfo, List<CaptureResult.Key<?>> skippedKeys,
+            List<CaptureResult.Key<?>> allKeys,
             CaptureRequest.Builder requestBuilder, CaptureResult result, String cameraId,
             int resultCount) throws Exception {
         String failMsg = "Failed capture result " + resultCount + " test";
@@ -475,9 +476,22 @@
                                 requestBuilder.get(CaptureRequest.STATISTICS_OIS_DATA_MODE),
                                 result.get(CaptureResult.STATISTICS_OIS_DATA_MODE));
                     } else if (key.equals(CaptureResult.DISTORTION_CORRECTION_MODE)) {
-                         errorCollector.expectEquals(msg,
+                        errorCollector.expectEquals(msg,
                                 requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE),
                                 result.get(CaptureResult.DISTORTION_CORRECTION_MODE));
+                    } else if (key.equals(CaptureResult.SENSOR_DYNAMIC_BLACK_LEVEL)) {
+                        float[] blackLevel = errorCollector.expectKeyValueNotNull(
+                                result, CaptureResult.SENSOR_DYNAMIC_BLACK_LEVEL);
+                        if (blackLevel != null && staticInfo.isMonochromeCamera()) {
+                            errorCollector.expectEquals(
+                                    "Monochrome camera dynamic blacklevel must be 2x2",
+                                    blackLevel.length, 4);
+                            for (int index = 1; index < blackLevel.length; index++) {
+                                errorCollector.expectEquals(
+                                    "Monochrome camera 2x2 channels blacklevel value must be the same.",
+                                    blackLevel[index], blackLevel[0]);
+                            }
+                        }
                     } else {
                         // Only do non-null check for the rest of keys.
                         errorCollector.expectKeyValueNotNull(failMsg, result, key);
@@ -541,20 +555,35 @@
         // Only present in reprocessing capture result.
         waiverKeys.add(CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR);
 
+        // LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID not required if key is not supported.
+        if (!staticInfo.isLogicalMultiCamera() ||
+                !staticInfo.isActivePhysicalCameraIdSupported()) {
+            waiverKeys.add(CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
+        }
+
         //Keys not required if RAW is not supported
         if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
             waiverKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
             waiverKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
             waiverKeys.add(CaptureResult.SENSOR_NOISE_PROFILE);
+        } else if (staticInfo.isMonochromeCamera()) {
+            waiverKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+            waiverKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
         }
 
-        //Keys for depth output capability
-        if (!staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT)) {
-            waiverKeys.add(CaptureResult.LENS_POSE_ROTATION);
-            waiverKeys.add(CaptureResult.LENS_POSE_TRANSLATION);
-        }
+        boolean calibrationReported = staticInfo.areKeysAvailable(
+                CameraCharacteristics.LENS_POSE_ROTATION,
+                CameraCharacteristics.LENS_POSE_TRANSLATION,
+                CameraCharacteristics.LENS_INTRINSIC_CALIBRATION);
+
+        // If any of distortion coefficients is reported in CameraCharacteristics, HAL must
+        // also report (one of) them in CaptureResult
+        boolean distortionReported = 
+                staticInfo.areKeysAvailable(
+                        CameraCharacteristics.LENS_RADIAL_DISTORTION) || 
+                staticInfo.areKeysAvailable(
+                        CameraCharacteristics.LENS_DISTORTION);
 
         //Keys for lens distortion correction
         boolean distortionCorrectionSupported = staticInfo.isDistortionCorrectionSupported();
@@ -562,11 +591,13 @@
             waiverKeys.add(CaptureResult.DISTORTION_CORRECTION_MODE);
         }
 
+        boolean mustReportDistortion = true;
         // These keys must present on either DEPTH or distortion correction devices
         if (!staticInfo.isCapabilitySupported(
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) &&
-                !distortionCorrectionSupported) {
-            waiverKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
+                !distortionCorrectionSupported &&
+                !distortionReported) {
+            mustReportDistortion = false;
             waiverKeys.add(CaptureResult.LENS_RADIAL_DISTORTION);
             waiverKeys.add(CaptureResult.LENS_DISTORTION);
         } else {
@@ -579,6 +610,18 @@
             }
         }
 
+        // Calibration keys must exist for
+        //   - DEPTH capable devices
+        //   - Devices that reports calibration keys in static metadata
+        //   - Devices that reports lens distortion keys in static metadata
+        if (!staticInfo.isCapabilitySupported(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) &&
+                !calibrationReported && !mustReportDistortion) {
+            waiverKeys.add(CaptureResult.LENS_POSE_ROTATION);
+            waiverKeys.add(CaptureResult.LENS_POSE_TRANSLATION);
+            waiverKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
+        }
+
         // Waived if RAW output is not supported
         int[] outputFormats = staticInfo.getAvailableFormats(
                 StaticMetadata.StreamDirection.Output);
@@ -595,8 +638,7 @@
         }
 
         // Waived if MONOCHROME capability
-        if (!staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME)) {
+        if (staticInfo.isMonochromeCamera()) {
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_MODE);
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_TRANSFORM);
             waiverKeys.add(CaptureResult.COLOR_CORRECTION_GAINS);
@@ -932,6 +974,7 @@
         resultKeys.add(CaptureResult.TONEMAP_PRESET_CURVE);
         resultKeys.add(CaptureResult.BLACK_LEVEL_LOCK);
         resultKeys.add(CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR);
+        resultKeys.add(CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
         resultKeys.add(CaptureResult.DISTORTION_CORRECTION_MODE);
 
         return resultKeys;
diff --git a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
index 0689b9b..4af437c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -41,7 +41,6 @@
 import android.media.ImageReader;
 import android.os.ConditionVariable;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
@@ -66,7 +65,6 @@
 /**
  * Tests for the DngCreator API.
  */
-@AppModeFull
 public class DngCreatorTest extends Camera2AndroidTestCase {
     private static final String TAG = "DngCreatorTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -140,15 +138,14 @@
             FileOutputStream fileStream = null;
             ByteArrayOutputStream outputStream = null;
             try {
-                openDevice(deviceId);
-
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                     Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                             ". Skip the test.");
                     continue;
                 }
 
+                openDevice(deviceId);
                 Size activeArraySize = mStaticInfo.getRawDimensChecked();
 
                 // Create capture image reader
@@ -167,7 +164,7 @@
 
                 if (VERBOSE) {
                     // Write DNG to file
-                    String dngFilePath = DEBUG_FILE_NAME_BASE + "/camera_basic_" + deviceId + "_" +
+                    String dngFilePath = mDebugFileNameBase + "/camera_basic_" + deviceId + "_" +
                             DEBUG_DNG_FILE;
                     // Write out captured DNG file for the first camera device if setprop is enabled
                     fileStream = new FileOutputStream(dngFilePath);
@@ -217,15 +214,14 @@
             FileOutputStream fileStream = null;
             ByteArrayOutputStream outputStream = null;
             try {
-                openDevice(deviceId);
-
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                     Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                             ". Skip the test.");
                     continue;
                 }
 
+                openDevice(deviceId);
                 Size activeArraySize = mStaticInfo.getRawDimensChecked();
 
                 Size[] targetPreviewSizes =
@@ -276,7 +272,7 @@
                 outputStream = new ByteArrayOutputStream();
                 dngCreator.writeImage(outputStream, resultPair.first.get(0));
 
-                String filePath = DEBUG_FILE_NAME_BASE + "/camera_thumb_" + deviceId + "_" +
+                String filePath = mDebugFileNameBase + "/camera_thumb_" + deviceId + "_" +
                         DEBUG_DNG_FILE;
                 // Write out captured DNG file for the first camera device
                 fileStream = new FileOutputStream(filePath);
@@ -410,7 +406,7 @@
                             new DngCreator(data.characteristics, data.imagePair.second);
 
                     // Write DNG to file
-                    String dngFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_" +
+                    String dngFilePath = mDebugFileNameBase + "/camera_" + deviceId + "_" +
                             DEBUG_DNG_FILE;
                     // Write out captured DNG file for the first camera device if setprop is enabled
                     fileStream = new FileOutputStream(dngFilePath);
@@ -420,7 +416,7 @@
                     Log.v(TAG, "Test DNG file for camera " + deviceId + " saved to " + dngFilePath);
 
                     // Write JPEG to file
-                    String jpegFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_jpeg.jpg";
+                    String jpegFilePath = mDebugFileNameBase + "/camera_" + deviceId + "_jpeg.jpg";
                     // Write out captured DNG file for the first camera device if setprop is enabled
                     fileChannel = new FileOutputStream(jpegFilePath).getChannel();
                     ByteBuffer jPlane = jpeg.getPlanes()[0].getBuffer();
@@ -431,7 +427,7 @@
                             jpegFilePath);
 
                     // Write jpeg generated from demosaiced RAW frame to file
-                    String rawFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_raw.jpg";
+                    String rawFilePath = mDebugFileNameBase + "/camera_" + deviceId + "_raw.jpg";
                     // Write out captured DNG file for the first camera device if setprop is enabled
                     fileStream = new FileOutputStream(rawFilePath);
                     rawBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
@@ -480,8 +476,9 @@
             DngCreator dngCreator = new DngCreator(data.characteristics, data.imagePair.second);
 
             // Write DNG to file
-            String dngFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_"
-                    + TEST_DNG_FILE;
+            String dngFilePath = mDebugFileNameBase + "/camera_" +
+                deviceId + "_" + TEST_DNG_FILE;
+
             // Write out captured DNG file for the first camera device if setprop is enabled
             try (FileOutputStream fileStream = new FileOutputStream(dngFilePath)) {
                 dngCreator.writeImage(fileStream, raw);
@@ -509,15 +506,14 @@
         CapturedData data = new CapturedData();
         List<CameraTestUtils.SimpleImageReaderListener> captureListeners = new ArrayList<>();
         try {
-            openDevice(deviceId);
-
-            if (!mStaticInfo.isCapabilitySupported(
+            if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                 Log.i(TAG, "RAW capability is not supported in camera " + deviceId
                         + ". Skip the test.");
                 return null;
             }
 
+            openDevice(deviceId);
             Size activeArraySize = mStaticInfo.getRawDimensChecked();
 
             // Get largest jpeg size
@@ -610,7 +606,7 @@
             FileOutputStream fileStream = null;
             try {
                 // Write JPEG patch to file
-                String jpegFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId +
+                String jpegFilePath = mDebugFileNameBase + "/camera_" + deviceId +
                         "_jpeg_patch.jpg";
                 fileStream = new FileOutputStream(jpegFilePath);
                 jpegPatch.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
@@ -620,7 +616,7 @@
                         jpegFilePath);
 
                 // Write RAW patch to file
-                String rawFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId +
+                String rawFilePath = mDebugFileNameBase + "/camera_" + deviceId +
                         "_raw_patch.jpg";
                 fileStream = new FileOutputStream(rawFilePath);
                 rawPatch.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 9339535..b095b8a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -22,22 +22,24 @@
 import android.graphics.SurfaceTexture;
 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.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.params.BlackLevelPattern;
 import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
 import android.media.ImageReader;
 import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.Rational;
 import android.util.Range;
 import android.util.Size;
+import android.util.Pair;
 import android.util.Patterns;
 import android.view.Surface;
 import android.view.WindowManager;
@@ -55,7 +57,6 @@
 /**
  * Extended tests for static camera characteristics.
  */
-@AppModeFull
 public class ExtendedCameraCharacteristicsTest extends AndroidTestCase {
     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);
@@ -69,7 +70,7 @@
 
     private CameraManager mCameraManager;
     private List<CameraCharacteristics> mCharacteristics;
-    private String[] mIds;
+    private String[] mIds; // include both standalone camera IDs and "hidden" physical camera IDs
     private CameraErrorCollector mCollector;
 
     private static final Size FULLHD = new Size(1920, 1080);
@@ -122,15 +123,29 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mIds = mCameraManager.getCameraIdList();
+        String[] ids = mCameraManager.getCameraIdList();
+        ArrayList<String> allIds = new ArrayList<String>();
         mCharacteristics = new ArrayList<>();
         mCollector = new CameraErrorCollector();
-        for (int i = 0; i < mIds.length; i++) {
-            CameraCharacteristics props = mCameraManager.getCameraCharacteristics(mIds[i]);
-            assertNotNull(String.format("Can't get camera characteristics from: ID %s", mIds[i]),
+        for (int i = 0; i < ids.length; i++) {
+            CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
+            assertNotNull(String.format("Can't get camera characteristics from: ID %s", ids[i]),
                     props);
+            allIds.add(ids[i]);
             mCharacteristics.add(props);
+
+            for (String physicalId : props.getPhysicalCameraIds()) {
+                if (!Arrays.asList(ids).contains(physicalId) &&
+                        !allIds.contains(physicalId)) {
+                    allIds.add(physicalId);
+                    props = mCameraManager.getCameraCharacteristics(physicalId);
+                    mCharacteristics.add(props);
+                }
+            }
         }
+
+        mIds = new String[allIds.size()];
+        allIds.toArray(mIds);
     }
 
     @Override
@@ -170,19 +185,33 @@
                 continue;
             }
 
+            boolean isMonochromeWithY8 = arrayContains(actualCapabilities, MONOCHROME)
+                    && arrayContains(outputFormats, ImageFormat.Y8);
+
             assertArrayContains(
                     String.format("No valid YUV_420_888 preview formats found for: ID %s",
                             mIds[counter]), outputFormats, ImageFormat.YUV_420_888);
+            if (isMonochromeWithY8) {
+                assertArrayContains(
+                        String.format("No valid Y8 preview formats found for: ID %s",
+                                mIds[counter]), outputFormats, ImageFormat.Y8);
+            }
             assertArrayContains(String.format("No JPEG image format for: ID %s",
                     mIds[counter]), outputFormats, ImageFormat.JPEG);
 
             Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888);
+            Size[] y8Sizes = config.getOutputSizes(ImageFormat.Y8);
             Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
             Size[] privateSizes = config.getOutputSizes(ImageFormat.PRIVATE);
 
             CameraTestUtils.assertArrayNotEmpty(yuvSizes,
                     String.format("No sizes for preview format %x for: ID %s",
                             ImageFormat.YUV_420_888, mIds[counter]));
+            if (isMonochromeWithY8) {
+                CameraTestUtils.assertArrayNotEmpty(y8Sizes,
+                    String.format("No sizes for preview format %x for: ID %s",
+                            ImageFormat.Y8, mIds[counter]));
+            }
 
             Rect activeRect = CameraTestUtils.getValueNotNull(
                     c, CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
@@ -260,6 +289,13 @@
                             privateSizesList.contains(FULLHD_ALT);
                     assertTrue("Full device FullHD YUV size not found", yuvSupportFullHD);
                     assertTrue("Full device FullHD PRIVATE size not found", privateSupportFullHD);
+
+                    if (isMonochromeWithY8) {
+                        ArrayList<Size> y8SizesList = new ArrayList<>(Arrays.asList(y8Sizes));
+                        boolean y8SupportFullHD = y8SizesList.contains(FULLHD) ||
+                                y8SizesList.contains(FULLHD_ALT);
+                        assertTrue("Full device FullHD Y8 size not found", y8SupportFullHD);
+                    }
                 }
                 // remove all FullHD or FullHD_Alt sizes for the remaining of the test
                 jpegSizesList.remove(FULLHD);
@@ -288,6 +324,17 @@
                         }
                     }
                 }
+
+                if (isMonochromeWithY8) {
+                    ArrayList<Size> y8SizesList = new ArrayList<>(Arrays.asList(y8Sizes));
+                    if (!y8SizesList.containsAll(jpegSizesList)) {
+                        for (Size s : jpegSizesList) {
+                            if (!y8SizesList.contains(s)) {
+                                fail("Size " + s + " not found in Y8 format");
+                            }
+                        }
+                    }
+                }
             }
 
             if (!privateSizesList.containsAll(yuvSizesList)) {
@@ -302,6 +349,403 @@
         }
     }
 
+    private void verifyCommonRecommendedConfiguration(String id, CameraCharacteristics c,
+            RecommendedStreamConfigurationMap config, boolean checkNoInput,
+            boolean checkNoHighRes, boolean checkNoHighSpeed, boolean checkNoPrivate,
+            boolean checkNoDepth) {
+        StreamConfigurationMap fullConfig = c.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        assertNotNull(String.format("No stream configuration map found for ID: %s!", id),
+                fullConfig);
+
+        Set<Integer> recommendedOutputFormats = config.getOutputFormats();
+
+        if (checkNoInput) {
+            Set<Integer> inputFormats = config.getInputFormats();
+            assertTrue(String.format("Recommended configuration must not include any input " +
+                    "streams for ID: %s", id),
+                    ((inputFormats == null) || (inputFormats.size() == 0)));
+        }
+
+        if (checkNoHighRes) {
+            for (int format : recommendedOutputFormats) {
+                Set<Size> highResSizes = config.getHighResolutionOutputSizes(format);
+                assertTrue(String.format("Recommended configuration should not include any " +
+                        "high resolution sizes, which cannot operate at full " +
+                        "BURST_CAPTURE rate for ID: %s", id),
+                        ((highResSizes == null) || (highResSizes.size() == 0)));
+            }
+        }
+
+        if (checkNoHighSpeed) {
+            Set<Size> highSpeedSizes = config.getHighSpeedVideoSizes();
+            assertTrue(String.format("Recommended configuration must not include any high " +
+                    "speed configurations for ID: %s", id),
+                    ((highSpeedSizes == null) || (highSpeedSizes.size() == 0)));
+        }
+
+        int[] exhaustiveOutputFormats = fullConfig.getOutputFormats();
+        for (Integer formatInteger : recommendedOutputFormats) {
+            int format = formatInteger.intValue();
+            assertArrayContains(String.format("Unsupported recommended output format: %d for " +
+                    "ID: %s ", format, id), exhaustiveOutputFormats, format);
+            Set<Size> recommendedSizes = config.getOutputSizes(format);
+
+            switch (format) {
+                case ImageFormat.PRIVATE:
+                    if (checkNoPrivate) {
+                        fail(String.format("Recommended configuration must not include " +
+                                "PRIVATE format entries for ID: %s", id));
+                    }
+
+                    Set<Size> classOutputSizes = config.getOutputSizes(ImageReader.class);
+                    assertCollectionContainsAnyOf(String.format("Recommended output sizes for " +
+                            "ImageReader class don't match the output sizes for the " +
+                            "corresponding format for ID: %s", id), classOutputSizes,
+                            recommendedSizes);
+                    break;
+                case ImageFormat.DEPTH16:
+                case ImageFormat.DEPTH_POINT_CLOUD:
+                    if (checkNoDepth) {
+                        fail(String.format("Recommended configuration must not include any DEPTH " +
+                                "formats for ID: %s", id));
+                    }
+                    break;
+                default:
+            }
+            Size [] exhaustiveSizes = fullConfig.getOutputSizes(format);
+            for (Size sz : recommendedSizes) {
+                assertArrayContains(String.format("Unsupported recommended size %s for " +
+                        "format: %d for ID: %s", sz.toString(), format, id),
+                        exhaustiveSizes, sz);
+
+                long recommendedMinDuration = config.getOutputMinFrameDuration(format, sz);
+                long availableMinDuration = fullConfig.getOutputMinFrameDuration(format, sz);
+                assertTrue(String.format("Recommended minimum frame duration %d for size " +
+                        "%s format: %d doesn't match with currently available minimum" +
+                        " frame duration of %d for ID: %s", recommendedMinDuration,
+                        sz.toString(), format, availableMinDuration, id),
+                        (recommendedMinDuration == availableMinDuration));
+                long recommendedStallDuration = config.getOutputStallDuration(format, sz);
+                long availableStallDuration = fullConfig.getOutputStallDuration(format, sz);
+                assertTrue(String.format("Recommended stall duration %d for size %s" +
+                        " format: %d doesn't match with currently available stall " +
+                        "duration of %d for ID: %s", recommendedStallDuration,
+                        sz.toString(), format, availableStallDuration, id),
+                        (recommendedStallDuration == availableStallDuration));
+
+                ImageReader reader = ImageReader.newInstance(sz.getWidth(), sz.getHeight(), format,
+                        /*maxImages*/1);
+                Surface readerSurface = reader.getSurface();
+                assertTrue(String.format("ImageReader surface using format %d and size %s is not" +
+                        " supported for ID: %s", format, sz.toString(), id),
+                        config.isOutputSupportedFor(readerSurface));
+                if (format == ImageFormat.PRIVATE) {
+                    long classMinDuration = config.getOutputMinFrameDuration(ImageReader.class, sz);
+                    assertTrue(String.format("Recommended minimum frame duration %d for size " +
+                            "%s format: %d doesn't match with the duration %d for " +
+                            "ImageReader class of the same size", recommendedMinDuration,
+                            sz.toString(), format, classMinDuration),
+                            classMinDuration == recommendedMinDuration);
+                    long classStallDuration = config.getOutputStallDuration(ImageReader.class, sz);
+                    assertTrue(String.format("Recommended stall duration %d for size " +
+                            "%s format: %d doesn't match with the stall duration %d for " +
+                            "ImageReader class of the same size", recommendedStallDuration,
+                            sz.toString(), format, classStallDuration),
+                            classStallDuration == recommendedStallDuration);
+                }
+            }
+        }
+    }
+
+    private void verifyRecommendedPreviewConfiguration(String cameraId, CameraCharacteristics c,
+            RecommendedStreamConfigurationMap previewConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, previewConfig, /*checkNoInput*/ true,
+                /*checkNoHighRes*/ true, /*checkNoHighSpeed*/ true, /*checkNoPrivate*/ false,
+                /*checkNoDepth*/ true);
+
+        Set<Integer> outputFormats = previewConfig.getOutputFormats();
+        assertTrue(String.format("No valid YUV_420_888 and PRIVATE preview " +
+                "formats found in recommended preview configuration for ID: %s", cameraId),
+                outputFormats.containsAll(Arrays.asList(new Integer(ImageFormat.YUV_420_888),
+                        new Integer(ImageFormat.PRIVATE))));
+    }
+
+    private void verifyRecommendedVideoConfiguration(String cameraId, CameraCharacteristics c,
+            RecommendedStreamConfigurationMap videoConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, videoConfig, /*checkNoInput*/ true,
+                /*checkNoHighRes*/ true, /*checkNoHighSpeed*/ false, /*checkNoPrivate*/false,
+                /*checkNoDepth*/ true);
+
+        Set<Size> highSpeedSizes = videoConfig.getHighSpeedVideoSizes();
+        StreamConfigurationMap fullConfig = c.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        assertNotNull("No stream configuration map found!", fullConfig);
+        Size [] availableHighSpeedSizes = fullConfig.getHighSpeedVideoSizes();
+        if ((highSpeedSizes != null) && (highSpeedSizes.size() > 0)) {
+            for (Size sz : highSpeedSizes) {
+                assertArrayContains(String.format("Recommended video configuration includes " +
+                        "unsupported high speed configuration with size %s for ID: %s",
+                        sz.toString(), cameraId), availableHighSpeedSizes, sz);
+                Set<Range<Integer>>  highSpeedFpsRanges =
+                    videoConfig.getHighSpeedVideoFpsRangesFor(sz);
+                Range<Integer> [] availableHighSpeedFpsRanges =
+                    fullConfig.getHighSpeedVideoFpsRangesFor(sz);
+                for (Range<Integer> fpsRange : highSpeedFpsRanges) {
+                    assertArrayContains(String.format("Recommended video configuration includes " +
+                            "unsupported high speed fps range [%d %d] for ID: %s",
+                            fpsRange.getLower().intValue(), fpsRange.getUpper().intValue(),
+                            cameraId), availableHighSpeedFpsRanges, fpsRange);
+                }
+            }
+        }
+
+        final int[] profileList = {
+            CamcorderProfile.QUALITY_2160P,
+            CamcorderProfile.QUALITY_1080P,
+            CamcorderProfile.QUALITY_480P,
+            CamcorderProfile.QUALITY_720P,
+            CamcorderProfile.QUALITY_CIF,
+            CamcorderProfile.QUALITY_HIGH,
+            CamcorderProfile.QUALITY_LOW,
+            CamcorderProfile.QUALITY_QCIF,
+            CamcorderProfile.QUALITY_QVGA,
+        };
+        Set<Size> privateSizeSet = videoConfig.getOutputSizes(ImageFormat.PRIVATE);
+        for (int profile : profileList) {
+            int idx = Integer.valueOf(cameraId);
+            if (CamcorderProfile.hasProfile(idx, profile)) {
+                CamcorderProfile videoProfile = CamcorderProfile.get(idx, profile);
+                Size profileSize  = new Size(videoProfile.videoFrameWidth,
+                        videoProfile.videoFrameHeight);
+                assertCollectionContainsAnyOf(String.format("Recommended video configuration " +
+                        "doesn't include supported video profile size %s with Private format " +
+                        "for ID: %s", profileSize.toString(), cameraId), privateSizeSet,
+                        Arrays.asList(profileSize));
+            }
+        }
+    }
+
+    private Pair<Boolean, Size> isSizeWithinSensorMargin(Size sz, Size sensorSize) {
+        final float SIZE_ERROR_MARGIN = 0.03f;
+        float croppedWidth = (float)sensorSize.getWidth();
+        float croppedHeight = (float)sensorSize.getHeight();
+        float sensorAspectRatio = (float)sensorSize.getWidth() / (float)sensorSize.getHeight();
+        float maxAspectRatio = (float)sz.getWidth() / (float)sz.getHeight();
+        if (sensorAspectRatio < maxAspectRatio) {
+            croppedHeight = (float)sensorSize.getWidth() / maxAspectRatio;
+        } else if (sensorAspectRatio > maxAspectRatio) {
+            croppedWidth = (float)sensorSize.getHeight() * maxAspectRatio;
+        }
+        Size croppedSensorSize = new Size((int)croppedWidth, (int)croppedHeight);
+
+        Boolean match = new Boolean(
+            (sz.getWidth() <= croppedSensorSize.getWidth() * (1.0 + SIZE_ERROR_MARGIN) &&
+             sz.getWidth() >= croppedSensorSize.getWidth() * (1.0 - SIZE_ERROR_MARGIN) &&
+             sz.getHeight() <= croppedSensorSize.getHeight() * (1.0 + SIZE_ERROR_MARGIN) &&
+             sz.getHeight() >= croppedSensorSize.getHeight() * (1.0 - SIZE_ERROR_MARGIN)));
+
+        return Pair.create(match, croppedSensorSize);
+    }
+
+    private void verifyRecommendedSnapshotConfiguration(String cameraId, CameraCharacteristics c,
+            RecommendedStreamConfigurationMap snapshotConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, snapshotConfig, /*checkNoInput*/ true,
+                /*checkNoHighRes*/ false, /*checkNoHighSpeed*/ true, /*checkNoPrivate*/false,
+                /*checkNoDepth*/ false);
+        Rect activeRect = CameraTestUtils.getValueNotNull(
+                c, CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        Size arraySize = new Size(activeRect.width(), activeRect.height());
+
+        Set<Size> snapshotSizeSet = snapshotConfig.getOutputSizes(ImageFormat.JPEG);
+        Size[] snapshotSizes = new Size[snapshotSizeSet.size()];
+        snapshotSizes = snapshotSizeSet.toArray(snapshotSizes);
+        Size maxJpegSize = CameraTestUtils.getMaxSize(snapshotSizes);
+        assertTrue(String.format("Maximum recommended Jpeg size %s should be within 3 percent " +
+                "of the area of the advertised array size %s for ID: %s",
+                maxJpegSize.toString(), arraySize.toString(), cameraId),
+                isSizeWithinSensorMargin(maxJpegSize, arraySize).first.booleanValue());
+    }
+
+    private void verifyRecommendedVideoSnapshotConfiguration(String cameraId,
+            CameraCharacteristics c,
+            RecommendedStreamConfigurationMap videoSnapshotConfig,
+            RecommendedStreamConfigurationMap videoConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, videoSnapshotConfig,
+                /*checkNoInput*/ true, /*checkNoHighRes*/ false, /*checkNoHighSpeed*/ true,
+                /*checkNoPrivate*/ true, /*checkNoDepth*/ true);
+
+        Set<Integer> outputFormats = videoSnapshotConfig.getOutputFormats();
+        assertCollectionContainsAnyOf(String.format("No valid JPEG format found " +
+                "in recommended video snapshot configuration for ID: %s", cameraId),
+                outputFormats, Arrays.asList(new Integer(ImageFormat.JPEG)));
+        assertTrue(String.format("Recommended video snapshot configuration must only advertise " +
+                "JPEG format for ID: %s", cameraId), outputFormats.size() == 1);
+
+        Set<Size> privateVideoSizeSet = videoConfig.getOutputSizes(ImageFormat.PRIVATE);
+        Size[] privateVideoSizes = new Size[privateVideoSizeSet.size()];
+        privateVideoSizes = privateVideoSizeSet.toArray(privateVideoSizes);
+        Size maxVideoSize = CameraTestUtils.getMaxSize(privateVideoSizes);
+        Set<Size> outputSizes = videoSnapshotConfig.getOutputSizes(ImageFormat.JPEG);
+        assertCollectionContainsAnyOf(String.format("The maximum recommended video size %s " +
+                "should be present in the recommended video snapshot configurations for ID: %s",
+                maxVideoSize.toString(), cameraId), outputSizes, Arrays.asList(maxVideoSize));
+    }
+
+    private void verifyRecommendedRawConfiguration(String cameraId,
+            CameraCharacteristics c, RecommendedStreamConfigurationMap rawConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, rawConfig, /*checkNoInput*/ true,
+                /*checkNoHighRes*/ false, /*checkNoHighSpeed*/ true, /*checkNoPrivate*/ true,
+                /*checkNoDepth*/ true);
+
+        Set<Integer> outputFormats = rawConfig.getOutputFormats();
+        for (Integer outputFormatInteger : outputFormats) {
+            int outputFormat = outputFormatInteger.intValue();
+            switch (outputFormat) {
+                case ImageFormat.RAW10:
+                case ImageFormat.RAW12:
+                case ImageFormat.RAW_PRIVATE:
+                case ImageFormat.RAW_SENSOR:
+                    break;
+                default:
+                    fail(String.format("Recommended raw configuration map must not contain " +
+                            " non-RAW formats like: %d for ID: %s", outputFormat, cameraId));
+
+            }
+        }
+    }
+
+    private void verifyRecommendedZSLConfiguration(String cameraId, CameraCharacteristics c,
+            RecommendedStreamConfigurationMap zslConfig) {
+        verifyCommonRecommendedConfiguration(cameraId, c, zslConfig, /*checkNoInput*/ false,
+                /*checkNoHighRes*/ false, /*checkNoHighSpeed*/ true, /*checkNoPrivate*/ false,
+                /*checkNoDepth*/ false);
+
+        StreamConfigurationMap fullConfig =
+            c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        assertNotNull(String.format("No stream configuration map found for ID: %s!", cameraId),
+                fullConfig);
+        Set<Integer> inputFormats = zslConfig.getInputFormats();
+        int [] availableInputFormats = fullConfig.getInputFormats();
+        for (Integer inputFormatInteger : inputFormats) {
+            int inputFormat = inputFormatInteger.intValue();
+            assertArrayContains(String.format("Recommended ZSL configuration includes " +
+                    "unsupported input format %d for ID: %s", inputFormat, cameraId),
+                    availableInputFormats, inputFormat);
+
+            Set<Size> inputSizes = zslConfig.getInputSizes(inputFormat);
+            Size [] availableInputSizes = fullConfig.getInputSizes(inputFormat);
+            assertTrue(String.format("Recommended ZSL configuration input format %d includes " +
+                    "invalid input sizes for ID: %s", inputFormat, cameraId),
+                    ((inputSizes != null) && (inputSizes.size() > 0)));
+            for (Size inputSize : inputSizes) {
+                assertArrayContains(String.format("Recommended ZSL configuration includes " +
+                        "unsupported input format %d with size %s ID: %s", inputFormat,
+                        inputSize.toString(), cameraId), availableInputSizes, inputSize);
+            }
+            Set<Integer> validOutputFormats = zslConfig.getValidOutputFormatsForInput(inputFormat);
+            int [] availableValidOutputFormats = fullConfig.getValidOutputFormatsForInput(
+                    inputFormat);
+            for (Integer outputFormatInteger : validOutputFormats) {
+                int outputFormat = outputFormatInteger.intValue();
+                assertArrayContains(String.format("Recommended ZSL configuration includes " +
+                        "unsupported output format %d for input %s ID: %s", outputFormat,
+                        inputFormat, cameraId), availableValidOutputFormats, outputFormat);
+            }
+        }
+    }
+
+    public void testRecommendedStreamConfigurations() {
+        int counter = 0;
+        for (CameraCharacteristics c : mCharacteristics) {
+            int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    actualCapabilities);
+
+            if (!arrayContains(actualCapabilities, BC)) {
+                Log.i(TAG, "Camera " + mIds[counter] +
+                        ": BACKWARD_COMPATIBLE capability not supported, skipping test");
+                continue;
+            }
+
+            try {
+                RecommendedStreamConfigurationMap map = c.getRecommendedStreamConfigurationMap(
+                        RecommendedStreamConfigurationMap.USECASE_PREVIEW - 1);
+                fail("Recommended configuration map shouldn't be available for invalid " +
+                        "use case!");
+            } catch (IllegalArgumentException e) {
+                //Expected continue
+            }
+
+            try {
+                RecommendedStreamConfigurationMap map = c.getRecommendedStreamConfigurationMap(
+                        RecommendedStreamConfigurationMap.USECASE_RAW + 1);
+                fail("Recommended configuration map shouldn't be available for invalid " +
+                        "use case!");
+            } catch (IllegalArgumentException e) {
+                //Expected continue
+            }
+
+            RecommendedStreamConfigurationMap previewConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_PREVIEW);
+            RecommendedStreamConfigurationMap videoRecordingConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_RECORD);
+            RecommendedStreamConfigurationMap videoSnapshotConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_VIDEO_SNAPSHOT);
+            RecommendedStreamConfigurationMap snapshotConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_SNAPSHOT);
+            RecommendedStreamConfigurationMap rawConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_RAW);
+            RecommendedStreamConfigurationMap zslConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                    RecommendedStreamConfigurationMap.USECASE_ZSL);
+            if ((previewConfig == null) && (videoRecordingConfig == null) &&
+                    (videoSnapshotConfig == null) && (snapshotConfig == null) &&
+                    (rawConfig == null) && (zslConfig == null)) {
+                Log.i(TAG, "Camera " + mIds[counter] +
+                        " doesn't support recommended configurations, skipping test");
+                continue;
+            }
+
+            assertNotNull(String.format("Mandatory recommended preview configuration map not " +
+                    "found for: ID %s", mIds[counter]), previewConfig);
+            verifyRecommendedPreviewConfiguration(mIds[counter], c, previewConfig);
+
+            assertNotNull(String.format("Mandatory recommended video recording configuration map " +
+                    "not found for: ID %s", mIds[counter]), videoRecordingConfig);
+            verifyRecommendedVideoConfiguration(mIds[counter], c, videoRecordingConfig);
+
+            assertNotNull(String.format("Mandatory recommended video snapshot configuration map " +
+                    "not found for: ID %s", mIds[counter]), videoSnapshotConfig);
+            verifyRecommendedVideoSnapshotConfiguration(mIds[counter], c, videoSnapshotConfig,
+                    videoRecordingConfig);
+
+            assertNotNull(String.format("Mandatory recommended snapshot configuration map not " +
+                    "found for: ID %s", mIds[counter]), snapshotConfig);
+            verifyRecommendedSnapshotConfiguration(mIds[counter], c, snapshotConfig);
+
+            if (arrayContains(actualCapabilities, RAW)) {
+                assertNotNull(String.format("Mandatory recommended raw configuration map not " +
+                        "found for: ID %s", mIds[counter]), rawConfig);
+                verifyRecommendedRawConfiguration(mIds[counter], c, rawConfig);
+            }
+
+            if (arrayContains(actualCapabilities, OPAQUE_REPROCESS) ||
+                    arrayContains(actualCapabilities, YUV_REPROCESS)) {
+                assertNotNull(String.format("Mandatory recommended ZSL configuration map not " +
+                        "found for: ID %s", mIds[counter]), zslConfig);
+                verifyRecommendedZSLConfiguration(mIds[counter], c, zslConfig);
+            }
+
+            counter++;
+        }
+    }
+
     /**
      * Test {@link CameraCharacteristics#getKeys}
      */
@@ -387,9 +831,6 @@
                 expectKeyAvailable(c, CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP                 , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.SCALER_CROPPING_TYPE                            , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN                      , FULL     ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1                   , OPT      ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_COLOR_TRANSFORM1                         , OPT      ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_FORWARD_MATRIX1                          , OPT      ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE                   , OPT      ,   BC, RAW              );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT            , FULL     ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE                 , FULL     ,   MANUAL_SENSOR        );
@@ -400,7 +841,6 @@
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE                    , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_MAX_ANALOG_SENSITIVITY                   , FULL     ,   MANUAL_SENSOR        );
                 expectKeyAvailable(c, CameraCharacteristics.SENSOR_ORIENTATION                              , OPT      ,   BC                   );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1                    , OPT      ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.SHADING_AVAILABLE_MODES                         , LIMITED  ,   MANUAL_POSTPROC, RAW );
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES     , OPT      ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES   , OPT      ,   RAW                  );
@@ -415,12 +855,25 @@
                 // TODO: check that no other 'android' keys are listed in #getKeys if they aren't in the above list
             }
 
-            // Only check for these if the second reference illuminant is included
-            if (allKeys.contains(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2)) {
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2                    , OPT      ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_COLOR_TRANSFORM2                         , OPT      ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2                   , OPT      ,   RAW                  );
-                expectKeyAvailable(c, CameraCharacteristics.SENSOR_FORWARD_MATRIX2                          , OPT      ,   RAW                  );
+            int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    actualCapabilities);
+            boolean isMonochrome = arrayContains(actualCapabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME);
+            if (!isMonochrome) {
+                expectKeyAvailable(c, CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1                   , OPT      ,   RAW                  );
+                expectKeyAvailable(c, CameraCharacteristics.SENSOR_COLOR_TRANSFORM1                         , OPT      ,   RAW                  );
+                expectKeyAvailable(c, CameraCharacteristics.SENSOR_FORWARD_MATRIX1                          , OPT      ,   RAW                  );
+                expectKeyAvailable(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1                    , OPT      ,   RAW                  );
+
+
+                // Only check for these if the second reference illuminant is included
+                if (allKeys.contains(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2)) {
+                    expectKeyAvailable(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2                    , OPT      ,   RAW                  );
+                    expectKeyAvailable(c, CameraCharacteristics.SENSOR_COLOR_TRANSFORM2                         , OPT      ,   RAW                  );
+                    expectKeyAvailable(c, CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2                   , OPT      ,   RAW                  );
+                    expectKeyAvailable(c, CameraCharacteristics.SENSOR_FORWARD_MATRIX2                          , OPT      ,   RAW                  );
+                }
             }
 
             // Required key if any of RAW format output is supported
@@ -490,41 +943,54 @@
             mCollector.expectKeyValueGreaterThan(c, CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL,
                     MIN_ALLOWABLE_WHITELEVEL);
 
-            mCollector.expectKeyValueIsIn(c,
-                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
-                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB,
-                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG,
-                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG,
-                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR);
-            // TODO: SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB isn't supported yet.
 
-            mCollector.expectKeyValueInRange(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1,
-                    CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
-                    CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
-            // Only check the range if the second reference illuminant is avaliable
-            if (c.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
-                mCollector.expectKeyValueInRange(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2,
+            boolean isMonochrome = arrayContains(actualCapabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME);
+            if (!isMonochrome) {
+                mCollector.expectKeyValueIsIn(c,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR);
+                // TODO: SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB isn't supported yet.
+
+                mCollector.expectKeyValueInRange(c,
+                        CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1,
+                        CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
+                        CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
+                // Only check the range if the second reference illuminant is avaliable
+                if (c.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
+                        mCollector.expectKeyValueInRange(c,
+                        CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2,
                         (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
                         (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
+                }
+
+                Rational[] zeroes = new Rational[9];
+                Arrays.fill(zeroes, Rational.ZERO);
+
+                ColorSpaceTransform zeroed = new ColorSpaceTransform(zeroes);
+                mCollector.expectNotEquals("Forward Matrix1 should not contain all zeroes.", zeroed,
+                        c.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1));
+                mCollector.expectNotEquals("Forward Matrix2 should not contain all zeroes.", zeroed,
+                        c.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2));
+                mCollector.expectNotEquals("Calibration Transform1 should not contain all zeroes.",
+                        zeroed, c.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1));
+                mCollector.expectNotEquals("Calibration Transform2 should not contain all zeroes.",
+                        zeroed, c.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2));
+                mCollector.expectNotEquals("Color Transform1 should not contain all zeroes.",
+                        zeroed, c.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1));
+                mCollector.expectNotEquals("Color Transform2 should not contain all zeroes.",
+                        zeroed, c.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2));
+            } else {
+                mCollector.expectKeyValueIsIn(c,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO,
+                        CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR);
+                // TODO: SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB isn't supported yet.
             }
 
-            Rational[] zeroes = new Rational[9];
-            Arrays.fill(zeroes, Rational.ZERO);
-
-            ColorSpaceTransform zeroed = new ColorSpaceTransform(zeroes);
-            mCollector.expectNotEquals("Forward Matrix1 should not contain all zeroes.", zeroed,
-                    c.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1));
-            mCollector.expectNotEquals("Forward Matrix2 should not contain all zeroes.", zeroed,
-                    c.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2));
-            mCollector.expectNotEquals("Calibration Transform1 should not contain all zeroes.",
-                    zeroed, c.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1));
-            mCollector.expectNotEquals("Calibration Transform2 should not contain all zeroes.",
-                    zeroed, c.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2));
-            mCollector.expectNotEquals("Color Transform1 should not contain all zeroes.",
-                    zeroed, c.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1));
-            mCollector.expectNotEquals("Color Transform2 should not contain all zeroes.",
-                    zeroed, c.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2));
-
             BlackLevelPattern blackLevel = mCollector.expectKeyValueNotNull(c,
                     CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN);
             if (blackLevel != null) {
@@ -534,6 +1000,14 @@
                 }
                 int[] blackLevelPattern = new int[BlackLevelPattern.COUNT];
                 blackLevel.copyTo(blackLevelPattern, /*offset*/0);
+                if (isMonochrome) {
+                    for (int index = 1; index < BlackLevelPattern.COUNT; index++) {
+                        mCollector.expectEquals(
+                                "Monochrome camera 2x2 channels blacklevel value must be the same.",
+                                blackLevelPattern[index], blackLevelPattern[0]);
+                    }
+                }
+
                 Integer whitelevel = c.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
                 if (whitelevel != null) {
                     mCollector.expectValuesInRange("BlackLevelPattern", blackLevelPattern, 0,
@@ -573,7 +1047,6 @@
      */
     public void testStaticBurstCharacteristics() throws Exception {
         int counter = 0;
-        final float SIZE_ERROR_MARGIN = 0.03f;
         for (CameraCharacteristics c : mCharacteristics) {
             int[] actualCapabilities = CameraTestUtils.getValueNotNull(
                     c, CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
@@ -616,22 +1089,8 @@
                 (maxJpegSize.getWidth() <= maxYuvSize.getWidth() &&
                         maxJpegSize.getHeight() <= maxYuvSize.getHeight()) : false;
 
-            float croppedWidth = (float)sensorSize.getWidth();
-            float croppedHeight = (float)sensorSize.getHeight();
-            float sensorAspectRatio = (float)sensorSize.getWidth() / (float)sensorSize.getHeight();
-            float maxYuvAspectRatio = (float)maxYuvSize.getWidth() / (float)maxYuvSize.getHeight();
-            if (sensorAspectRatio < maxYuvAspectRatio) {
-                croppedHeight = (float)sensorSize.getWidth() / maxYuvAspectRatio;
-            } else if (sensorAspectRatio > maxYuvAspectRatio) {
-                croppedWidth = (float)sensorSize.getHeight() * maxYuvAspectRatio;
-            }
-            Size croppedSensorSize = new Size((int)croppedWidth, (int)croppedHeight);
-
-            boolean maxYuvMatchSensor =
-                    (maxYuvSize.getWidth() <= croppedSensorSize.getWidth() * (1.0 + SIZE_ERROR_MARGIN) &&
-                     maxYuvSize.getWidth() >= croppedSensorSize.getWidth() * (1.0 - SIZE_ERROR_MARGIN) &&
-                     maxYuvSize.getHeight() <= croppedSensorSize.getHeight() * (1.0 + SIZE_ERROR_MARGIN) &&
-                     maxYuvSize.getHeight() >= croppedSensorSize.getHeight() * (1.0 - SIZE_ERROR_MARGIN));
+            Pair<Boolean, Size> maxYuvMatchSensorPair = isSizeWithinSensorMargin(maxYuvSize,
+                    sensorSize);
 
             // No need to do null check since framework will generate the key if HAL don't supply
             boolean haveAeLock = CameraTestUtils.getValueNotNull(
@@ -717,8 +1176,8 @@
                         String.format("BURST-capable camera device %s max YUV size %s should be" +
                                 "close to active array size %s or cropped active array size %s",
                                 mIds[counter], maxYuvSize.toString(), sensorSize.toString(),
-                                croppedSensorSize.toString()),
-                        maxYuvMatchSensor);
+                                maxYuvMatchSensorPair.second.toString()),
+                        maxYuvMatchSensorPair.first.booleanValue());
                 assertTrue(
                         String.format("BURST-capable camera device %s does not support AE lock",
                                 mIds[counter]),
@@ -734,7 +1193,7 @@
                         String.format("Camera device %s has all the requirements for BURST" +
                                 " capability but does not report it!", mIds[counter]),
                         !(haveMaxYuv && haveMaxYuvRate && haveFastYuvRate && haveFastAeTargetFps &&
-                                haveFastSyncLatency && maxYuvMatchSensor &&
+                                haveFastSyncLatency && maxYuvMatchSensorPair.first.booleanValue() &&
                                 haveAeLock && haveAwbLock));
             }
 
@@ -767,6 +1226,9 @@
                     CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES);
 
             int[] inputFormats = configs.getInputFormats();
+            int[] outputFormats = configs.getOutputFormats();
+            boolean isMonochromeWithY8 = arrayContains(capabilities, MONOCHROME)
+                    && arrayContains(outputFormats, ImageFormat.Y8);
 
             boolean supportZslEdgeMode = false;
             boolean supportZslNoiseReductionMode = false;
@@ -810,6 +1272,9 @@
                 // Verify mandatory input formats are supported
                 mCollector.expectTrue("YUV_420_888 input must be supported for YUV reprocessing",
                         !supportYUV || arrayContains(inputFormats, ImageFormat.YUV_420_888));
+                mCollector.expectTrue("Y8 input must be supported for YUV reprocessing on " +
+                        "MONOCHROME devices with Y8 support", !supportYUV || !isMonochromeWithY8
+                        || arrayContains(inputFormats, ImageFormat.Y8));
                 mCollector.expectTrue("PRIVATE input must be supported for OPAQUE reprocessing",
                         !supportOpaque || arrayContains(inputFormats, ImageFormat.PRIVATE));
 
@@ -822,11 +1287,17 @@
 
                 for (int input : inputFormats) {
                     // Verify mandatory output formats are supported
-                    int[] outputFormats = configs.getValidOutputFormatsForInput(input);
-                    mCollector.expectTrue("YUV_420_888 output must be supported for reprocessing",
-                            arrayContains(outputFormats, ImageFormat.YUV_420_888));
+                    int[] outputFormatsForInput = configs.getValidOutputFormatsForInput(input);
+                    mCollector.expectTrue(
+                        "YUV_420_888 output must be supported for reprocessing",
+                        input == ImageFormat.Y8
+                        || arrayContains(outputFormatsForInput, ImageFormat.YUV_420_888));
+                    mCollector.expectTrue(
+                        "Y8 output must be supported for reprocessing on MONOCHROME devices with"
+                        + " Y8 support", !isMonochromeWithY8 || input == ImageFormat.YUV_420_888
+                        || arrayContains(outputFormatsForInput, ImageFormat.Y8));
                     mCollector.expectTrue("JPEG output must be supported for reprocessing",
-                            arrayContains(outputFormats, ImageFormat.JPEG));
+                            arrayContains(outputFormatsForInput, ImageFormat.JPEG));
 
                     // Verify camera can output the reprocess input formats and sizes.
                     Size[] inputSizes = configs.getInputSizes(input);
@@ -893,32 +1364,28 @@
             Rect activeArray = c.get(
                 CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
 
-            // Legacy device doesn't have preCorrectionActiveArraySize metadata.
-            Integer hwLevel = c.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
-            if (hwLevel != LEGACY) {
-                // Verify pre-correction array encloses active array
-                mCollector.expectTrue("preCorrectionArray [" + precorrectionArray.left + ", " +
-                        precorrectionArray.top + ", " + precorrectionArray.right + ", " +
-                        precorrectionArray.bottom + "] does not enclose activeArray[" +
-                        activeArray.left + ", " + activeArray.top + ", " + activeArray.right +
-                        ", " + activeArray.bottom,
-                        precorrectionArray.contains(activeArray.left, activeArray.top) &&
-                        precorrectionArray.contains(activeArray.right-1, activeArray.bottom-1));
+            // Verify pre-correction array encloses active array
+            mCollector.expectTrue("preCorrectionArray [" + precorrectionArray.left + ", " +
+                    precorrectionArray.top + ", " + precorrectionArray.right + ", " +
+                    precorrectionArray.bottom + "] does not enclose activeArray[" +
+                    activeArray.left + ", " + activeArray.top + ", " + activeArray.right +
+                    ", " + activeArray.bottom,
+                    precorrectionArray.contains(activeArray.left, activeArray.top) &&
+                    precorrectionArray.contains(activeArray.right-1, activeArray.bottom-1));
 
-                // Verify pixel array encloses pre-correction array
-                mCollector.expectTrue("preCorrectionArray [" + precorrectionArray.left + ", " +
-                        precorrectionArray.top + ", " + precorrectionArray.right + ", " +
-                        precorrectionArray.bottom + "] isn't enclosed by pixelArray[" +
-                        pixelArraySize.getWidth() + ", " + pixelArraySize.getHeight() + "]",
-                        precorrectionArray.left >= 0 &&
-                        precorrectionArray.left < pixelArraySize.getWidth() &&
-                        precorrectionArray.right > 0 &&
-                        precorrectionArray.right <= pixelArraySize.getWidth() &&
-                        precorrectionArray.top >= 0 &&
-                        precorrectionArray.top < pixelArraySize.getHeight() &&
-                        precorrectionArray.bottom > 0 &&
-                        precorrectionArray.bottom <= pixelArraySize.getHeight());
-            }
+            // Verify pixel array encloses pre-correction array
+            mCollector.expectTrue("preCorrectionArray [" + precorrectionArray.left + ", " +
+                    precorrectionArray.top + ", " + precorrectionArray.right + ", " +
+                    precorrectionArray.bottom + "] isn't enclosed by pixelArray[" +
+                    pixelArraySize.getWidth() + ", " + pixelArraySize.getHeight() + "]",
+                    precorrectionArray.left >= 0 &&
+                    precorrectionArray.left < pixelArraySize.getWidth() &&
+                    precorrectionArray.right > 0 &&
+                    precorrectionArray.right <= pixelArraySize.getWidth() &&
+                    precorrectionArray.top >= 0 &&
+                    precorrectionArray.top < pixelArraySize.getHeight() &&
+                    precorrectionArray.bottom > 0 &&
+                    precorrectionArray.bottom <= pixelArraySize.getHeight());
 
             if (supportDepth) {
                 mCollector.expectTrue("Supports DEPTH_OUTPUT but does not support DEPTH16",
@@ -968,6 +1435,35 @@
                         }
                     }
                 }
+                if (arrayContains(outputFormats, ImageFormat.DEPTH_JPEG)) {
+                    mCollector.expectTrue("Supports DEPTH_JPEG but has no DEPTH16 support!",
+                            hasDepth16);
+                    mCollector.expectTrue("Supports DEPTH_JPEG but DEPTH_IS_EXCLUSIVE is not " +
+                            "defined", depthIsExclusive != null);
+                    mCollector.expectTrue("Supports DEPTH_JPEG but DEPTH_IS_EXCLUSIVE is true",
+                            !depthIsExclusive.booleanValue());
+                    Size[] depthJpegSizes = configs.getOutputSizes(ImageFormat.DEPTH_JPEG);
+                    mCollector.expectTrue("Supports DEPTH_JPEG " +
+                            "but no sizes for DEPTH_JPEG supported!",
+                            depthJpegSizes != null && depthJpegSizes.length > 0);
+                    if (depthJpegSizes != null) {
+                        for (Size depthJpegSize : depthJpegSizes) {
+                            mCollector.expectTrue("All depth jpeg sizes must be nonzero",
+                                    depthJpegSize.getWidth() > 0 && depthJpegSize.getHeight() > 0);
+                            long minFrameDuration = configs.getOutputMinFrameDuration(
+                                    ImageFormat.DEPTH_JPEG, depthJpegSize);
+                            mCollector.expectTrue("Non-negative min frame duration for depth jpeg" +
+                                   " size " + depthJpegSize + " expected, got " + minFrameDuration,
+                                    minFrameDuration >= 0);
+                            long stallDuration = configs.getOutputStallDuration(
+                                    ImageFormat.DEPTH_JPEG, depthJpegSize);
+                            mCollector.expectTrue("Non-negative stall duration for depth jpeg size "
+                                    + depthJpegSize + " expected, got " + stallDuration,
+                                    stallDuration >= 0);
+                        }
+                    }
+                }
+
 
                 mCollector.expectTrue("Supports DEPTH_OUTPUT but DEPTH_IS_EXCLUSIVE is not defined",
                         depthIsExclusive != null);
@@ -984,6 +1480,23 @@
                 mCollector.expectTrue(
                         "All necessary depth fields defined, but DEPTH_OUTPUT capability is not listed",
                         !hasFields);
+
+                boolean reportCalibration = poseTranslation != null || 
+                        poseRotation != null || cameraIntrinsics !=null;                        
+                // Verify calibration keys are co-existing
+                if (reportCalibration) {
+                    mCollector.expectTrue(
+                            "Calibration keys must be co-existing",
+                            poseTranslation != null && poseRotation != null && 
+                            cameraIntrinsics !=null);
+                }
+
+                boolean reportDistortion = distortion != null;
+                if (reportDistortion) {
+                    mCollector.expectTrue(
+                            "Calibration keys must present where distortion is reported",
+                            reportCalibration);                    
+                }
             }
             counter++;
         }
@@ -1437,6 +1950,7 @@
     public void testLogicalCameraCharacteristics() throws Exception {
         int counter = 0;
         List<String> cameraIdList = Arrays.asList(mIds);
+        String[] publicIds = mCameraManager.getCameraIdList();
 
         for (CameraCharacteristics c : mCharacteristics) {
             int[] capabilities = CameraTestUtils.getValueNotNull(
@@ -1462,10 +1976,6 @@
                             String.format("Physical camera id %s shouldn't be the same as logical"
                                     + " camera id %s", physicalCameraId, mIds[counter]),
                             physicalCameraId != mIds[counter]);
-                    assertTrue(
-                            String.format("Physical camera id %s should be in available camera ids",
-                                    physicalCameraId),
-                            cameraIdList.contains(physicalCameraId));
 
                     //validation for depth static metadata of physical cameras
                     CameraCharacteristics pc =
@@ -1489,6 +1999,19 @@
                             "timestamp source", timestampSource, timestampSourcePhysical);
                 }
             }
+
+            // Verify that if multiple focal lengths or apertures are supported, they are in
+            // ascending order.
+            float[] focalLengths = c.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
+            for (int i = 0; i < focalLengths.length-1; i++) {
+                mCollector.expectTrue("Camera's available focal lengths must be ascending!",
+                        focalLengths[i] < focalLengths[i+1]);
+            }
+            float[] apertures = c.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES);
+            for (int i = 0; i < apertures.length-1; i++) {
+                mCollector.expectTrue("Camera's available apertures must be ascending!",
+                        apertures[i] < apertures[i+1]);
+            }
             counter++;
         }
     }
@@ -1511,12 +2034,58 @@
                 continue;
             }
 
+            List<Key<?>> allKeys = c.getKeys();
+            List<CaptureRequest.Key<?>> requestKeys = c.getAvailableCaptureRequestKeys();
+            List<CaptureResult.Key<?>> resultKeys = c.getAvailableCaptureResultKeys();
+
             assertTrue("Monochrome camera must have BACKWARD_COMPATIBLE capability",
                     arrayContains(capabilities, BC));
-            assertTrue("Monochrome camera must not have RAW capability",
-                    !arrayContains(capabilities, RAW));
+            int colorFilterArrangement = c.get(
+                    CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
+            assertTrue("Monochrome camera must have either MONO or NIR color filter pattern",
+                    colorFilterArrangement ==
+                            CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO
+                    || colorFilterArrangement ==
+                            CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR);
+
+            assertFalse("Monochrome camera must not contain SENSOR_CALIBRATION_TRANSFORM1 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1));
+            assertFalse("Monochrome camera must not contain SENSOR_COLOR_TRANSFORM1 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1));
+            assertFalse("Monochrome camera must not contain SENSOR_FORWARD_MATRIX1 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_FORWARD_MATRIX1));
+            assertFalse("Monochrome camera must not contain SENSOR_REFERENCE_ILLUMINANT1 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1));
+            assertFalse("Monochrome camera must not contain SENSOR_CALIBRATION_TRANSFORM2 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2));
+            assertFalse("Monochrome camera must not contain SENSOR_COLOR_TRANSFORM2 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2));
+            assertFalse("Monochrome camera must not contain SENSOR_FORWARD_MATRIX2 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_FORWARD_MATRIX2));
+            assertFalse("Monochrome camera must not contain SENSOR_REFERENCE_ILLUMINANT2 key",
+                    allKeys.contains(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2));
+
+            assertFalse(
+                    "Monochrome capture result must not contain SENSOR_NEUTRAL_COLOR_POINT key",
+                    resultKeys.contains(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT));
+            assertFalse("Monochrome capture result must not contain SENSOR_GREEN_SPLIT key",
+                    resultKeys.contains(CaptureResult.SENSOR_GREEN_SPLIT));
+
+            // Check that color correction tags are not available for monochrome cameras
             assertTrue("Monochrome camera must not have MANUAL_POST_PROCESSING capability",
                     !arrayContains(capabilities, MANUAL_POSTPROC));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_MODE in request keys",
+                    !requestKeys.contains(CaptureRequest.COLOR_CORRECTION_MODE));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_MODE in result keys",
+                    !resultKeys.contains(CaptureResult.COLOR_CORRECTION_MODE));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_TRANSFORM in request keys",
+                    !requestKeys.contains(CaptureRequest.COLOR_CORRECTION_TRANSFORM));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_TRANSFORM in result keys",
+                    !resultKeys.contains(CaptureResult.COLOR_CORRECTION_TRANSFORM));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_GAINS in request keys",
+                    !requestKeys.contains(CaptureRequest.COLOR_CORRECTION_GAINS));
+            assertTrue("Monochrome camera must not have COLOR_CORRECTION_GAINS in result keys",
+                    !resultKeys.contains(CaptureResult.COLOR_CORRECTION_GAINS));
 
             // Check that awbSupportedModes only contains AUTO
             int[] awbAvailableModes = c.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
diff --git a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
index 6ca9601..af6e5e0 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.Test;
+
 /**
  * Quick-running test for very basic camera operation for all cameras
  * and both camera APIs.
@@ -58,18 +60,19 @@
     private static final int FRAMES_TO_WAIT_FOR_CAPTURE = 100;
 
     @Presubmit
+    @Test
     public void testCamera2() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing camera2 API for camera device " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 camera2TestByCamera();
             } finally {
                 closeDevice();
@@ -184,6 +187,7 @@
     }
 
     @Presubmit
+    @Test
     public void testCamera1() throws Exception {
         for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
             Camera camera = null;
diff --git a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
index f0b3179..1aa6a17 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
@@ -24,7 +24,6 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.util.Log;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -36,7 +35,6 @@
 /**
  * <p>Tests for flashlight API.</p>
  */
-@AppModeFull
 public class FlashlightTest extends Camera2AndroidTestCase {
     private static final String TAG = "FlashlightTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
new file mode 100644
index 0000000..ec76aa0
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.camera.cts;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_SURFACE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.heifwriter.HeifWriter;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class HeifWriterTest extends Camera2AndroidTestCase {
+    private static final String TAG = HeifWriterTest.class.getSimpleName();
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String OUTPUT_FILENAME = "output.heic";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testHeif() throws Exception {
+        final int NUM_SINGLE_CAPTURE_TESTED = 3;
+        final int NUM_HEIC_CAPTURE_TESTED = 2;
+        final int SESSION_WARMUP_MS = 1000;
+        final int HEIF_STOP_TIMEOUT = 3000 * NUM_SINGLE_CAPTURE_TESTED;
+
+        if (!canEncodeHeic()) {
+            MediaUtils.skipTest("heic encoding is not supported on this device");
+            return;
+        }
+
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "Testing HEIF capture for Camera " + id);
+                openDevice(id);
+
+                Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(
+                        ImageFormat.PRIVATE,
+                        StaticMetadata.StreamDirection.Output);
+
+                // for each resolution, test imageReader:
+                for (Size sz : availableSizes) {
+                    HeifWriter heifWriter = null;
+                    OutputConfiguration outConfig = null;
+                    Surface latestSurface = null;
+                    CaptureRequest.Builder reqStill = null;
+                    int width = sz.getWidth();
+                    int height = sz.getHeight();
+                    for (int cap = 0; cap < NUM_HEIC_CAPTURE_TESTED; cap++) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Testing size " + sz.toString() + " format PRIVATE"
+                                    + " for camera " + mCamera.getId() + ". Iteration:" + cap);
+                        }
+
+                        try {
+                            TestConfig.Builder builder = new TestConfig.Builder(/*useGrid*/false);
+                            builder.setNumImages(NUM_SINGLE_CAPTURE_TESTED);
+                            builder.setSize(sz);
+                            String filename = "Cam" + id + "_" + width + "x" + height +
+                                    "_" + cap + ".heic";
+                            builder.setOutputPath(
+                                    new File(Environment.getExternalStorageDirectory(),
+                                    filename).getAbsolutePath());
+                            TestConfig config = builder.build();
+
+                            try {
+                                heifWriter = new HeifWriter.Builder(
+                                        config.mOutputPath,
+                                        width, height, INPUT_MODE_SURFACE)
+                                    .setGridEnabled(config.mUseGrid)
+                                    .setMaxImages(config.mMaxNumImages)
+                                    .setQuality(config.mQuality)
+                                    .setPrimaryIndex(config.mNumImages - 1)
+                                    .setHandler(mHandler)
+                                    .build();
+                            } catch (IOException e) {
+                                // Continue in case the size is not supported
+                                continue;
+                            }
+
+                            // First capture. Start capture session
+                            latestSurface = heifWriter.getInputSurface();
+                            outConfig = new OutputConfiguration(latestSurface);
+                            List<OutputConfiguration> configs =
+                                new ArrayList<OutputConfiguration>();
+                            configs.add(outConfig);
+
+                            SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1);
+                            Surface previewSurface = new Surface(preview);
+                            preview.setDefaultBufferSize(640, 480);
+                            configs.add(new OutputConfiguration(previewSurface));
+
+                            CaptureRequest.Builder reqPreview = mCamera.createCaptureRequest(
+                                    CameraDevice.TEMPLATE_PREVIEW);
+                            reqPreview.addTarget(previewSurface);
+
+                            reqStill = mCamera.createCaptureRequest(
+                                    CameraDevice.TEMPLATE_STILL_CAPTURE);
+                            reqStill.addTarget(previewSurface);
+                            reqStill.addTarget(latestSurface);
+
+                            // Start capture session and preview
+                            createSessionByConfigs(configs);
+                            startCapture(reqPreview.build(), /*repeating*/true, null, null);
+
+                            SystemClock.sleep(SESSION_WARMUP_MS);
+
+                            heifWriter.start();
+
+                            // Start capture.
+                            CaptureRequest request = reqStill.build();
+                            SimpleCaptureCallback listener = new SimpleCaptureCallback();
+
+                            int numImages = config.mNumImages;
+
+                            for (int i = 0; i < numImages; i++) {
+                                startCapture(request, /*repeating*/false, listener, mHandler);
+                            }
+
+                            // Validate capture result.
+                            CaptureResult result = validateCaptureResult(
+                                    ImageFormat.PRIVATE, sz, listener, numImages);
+
+                            // TODO: convert capture results into EXIF and send to heifwriter
+
+                            heifWriter.stop(HEIF_STOP_TIMEOUT);
+
+                            verifyResult(config.mOutputPath, width, height,
+                                    config.mRotation, config.mUseGrid,
+                                    Math.min(numImages, config.mMaxNumImages));
+                        } finally {
+                            if (heifWriter != null) {
+                                heifWriter.close();
+                                heifWriter = null;
+                            }
+                            stopCapture(/*fast*/false);
+                        }
+                    }
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    private static boolean canEncodeHeic() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
+            || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
+    }
+
+    private static class TestConfig {
+        final boolean mUseGrid;
+        final int mMaxNumImages;
+        final int mNumImages;
+        final int mWidth;
+        final int mHeight;
+        final int mRotation;
+        final int mQuality;
+        final String mOutputPath;
+
+        TestConfig(boolean useGrid, int maxNumImages, int numImages,
+                   int width, int height, int rotation, int quality,
+                   String outputPath) {
+            mUseGrid = useGrid;
+            mMaxNumImages = maxNumImages;
+            mNumImages = numImages;
+            mWidth = width;
+            mHeight = height;
+            mRotation = rotation;
+            mQuality = quality;
+            mOutputPath = outputPath;
+        }
+
+        static class Builder {
+            final boolean mUseGrid;
+            int mMaxNumImages;
+            int mNumImages;
+            int mWidth;
+            int mHeight;
+            int mRotation;
+            final int mQuality;
+            String mOutputPath;
+
+            Builder(boolean useGrids) {
+                mUseGrid = useGrids;
+                mMaxNumImages = mNumImages = 4;
+                mWidth = 1920;
+                mHeight = 1080;
+                mRotation = 0;
+                mQuality = 100;
+                mOutputPath = new File(Environment.getExternalStorageDirectory(),
+                        OUTPUT_FILENAME).getAbsolutePath();
+            }
+
+            Builder setNumImages(int numImages) {
+                mMaxNumImages = mNumImages = numImages;
+                return this;
+            }
+
+            Builder setRotation(int rotation) {
+                mRotation = rotation;
+                return this;
+            }
+
+            Builder setSize(Size sz) {
+                mWidth = sz.getWidth();
+                mHeight = sz.getHeight();
+                return this;
+            }
+
+            Builder setOutputPath(String path) {
+                mOutputPath = path;
+                return this;
+            }
+
+            private void cleanupStaleOutputs() {
+                File outputFile = new File(mOutputPath);
+                if (outputFile.exists()) {
+                    outputFile.delete();
+                }
+            }
+
+            TestConfig build() {
+                cleanupStaleOutputs();
+                return new TestConfig(mUseGrid, mMaxNumImages, mNumImages,
+                        mWidth, mHeight, mRotation, mQuality, mOutputPath);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "TestConfig"
+                    + ": mUseGrid " + mUseGrid
+                    + ", mMaxNumImages " + mMaxNumImages
+                    + ", mNumImages " + mNumImages
+                    + ", mWidth " + mWidth
+                    + ", mHeight " + mHeight
+                    + ", mRotation " + mRotation
+                    + ", mQuality " + mQuality
+                    + ", mOutputPath " + mOutputPath;
+        }
+    }
+
+    private void verifyResult(
+            String filename, int width, int height, int rotation, boolean useGrid, int numImages)
+            throws Exception {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(filename);
+        String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+        if (!"yes".equals(hasImage)) {
+            throw new Exception("No images found in file " + filename);
+        }
+        assertEquals("Wrong image count", numImages,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+        assertEquals("Wrong width", width,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
+        assertEquals("Wrong height", height,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
+        assertEquals("Wrong rotation", rotation,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
+        retriever.release();
+
+        if (useGrid) {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(filename);
+            MediaFormat format = extractor.getTrackFormat(0);
+            int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
+            int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
+            int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
+            int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
+            assertTrue("Wrong tile width or grid cols",
+                    ((width + tileWidth - 1) / tileWidth) == gridCols);
+            assertTrue("Wrong tile height or grid rows",
+                    ((height + tileHeight - 1) / tileHeight) == gridRows);
+            extractor.release();
+        }
+    }
+
+    /**
+     * Validate capture results.
+     *
+     * @param format The format of this capture.
+     * @param size The capture size.
+     * @param listener The capture listener to get capture result callbacks.
+     * @return the last verified CaptureResult
+     */
+    private CaptureResult validateCaptureResult(
+            int format, Size size, SimpleCaptureCallback listener, int numFrameVerified) {
+        CaptureResult result = null;
+        for (int i = 0; i < numFrameVerified; i++) {
+            result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+            if (mStaticInfo.isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS)) {
+                Long exposureTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+                Integer sensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+                mCollector.expectInRange(
+                        String.format(
+                                "Capture for format %d, size %s exposure time is invalid.",
+                                format, size.toString()),
+                        exposureTime,
+                        mStaticInfo.getExposureMinimumOrDefault(),
+                        mStaticInfo.getExposureMaximumOrDefault()
+                );
+                mCollector.expectInRange(
+                        String.format("Capture for format %d, size %s sensitivity is invalid.",
+                                format, size.toString()),
+                        sensitivity,
+                        mStaticInfo.getSensitivityMinimumOrDefault(),
+                        mStaticInfo.getSensitivityMaximumOrDefault()
+                );
+            }
+            // TODO: add more key validations.
+        }
+        return result;
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
index 70e928a..dd7053b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
@@ -32,7 +32,6 @@
 import android.hardware.camera2.CameraManager;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -52,7 +51,6 @@
  * get an error callback losing the camera handle. Similarly if the UID is
  * already idle it cannot obtain a camera handle.
  */
-@AppModeFull
 @RunWith(AndroidJUnit4.class)
 public final class IdleUidTest {
     private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index 8852778..3ed4fdb 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -20,9 +20,11 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ImageFormat;
 import android.graphics.Matrix;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.HardwareBuffer;
@@ -39,7 +41,6 @@
 import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.os.ConditionVariable;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -67,7 +68,6 @@
  * <p>Some invalid access test. </p>
  * <p>TODO: Add more format tests? </p>
  */
-@AppModeFull
 public class ImageReaderTest extends Camera2AndroidTestCase {
     private static final String TAG = "ImageReaderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -142,6 +142,30 @@
         }
     }
 
+    public void testDynamicDepth() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                openDevice(id);
+                bufferFormatTestByCamera(ImageFormat.DEPTH_JPEG, /*repeating*/true,
+                        /*checkSession*/ true);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    public void testY8() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                openDevice(id);
+                bufferFormatTestByCamera(ImageFormat.Y8, /*repeating*/true);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
     public void testJpeg() throws Exception {
         for (String id : mCameraIds) {
             try {
@@ -223,12 +247,12 @@
         for (String id : mCameraIds) {
             try {
                 Log.v(TAG, "Testing long processing on repeating raw for camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(id).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
+                openDevice(id);
 
                 bufferFormatLongProcessingTimeTestByCamera(ImageFormat.RAW_SENSOR);
             } finally {
@@ -241,13 +265,13 @@
         for (String id : mCameraIds) {
             try {
                 Log.v(TAG, "Testing long processing on repeating YUV for camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(id).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
 
+                openDevice(id);
                 bufferFormatLongProcessingTimeTestByCamera(ImageFormat.YUV_420_888);
             } finally {
                 closeDevice(id);
@@ -285,12 +309,12 @@
         for (String id : mCameraIds) {
             try {
                 Log.v(TAG, "YUV and JPEG testing for camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 bufferFormatWithYuvTestByCamera(ImageFormat.JPEG);
             } finally {
                 closeDevice(id);
@@ -299,6 +323,29 @@
     }
 
     /**
+     * Test two image stream (YUV420_888 and JPEG) capture by using ImageReader with the ImageReader
+     * factory method that has usage flag argument.
+     *
+     * <p>Both stream formats are mandatory for Camera2 API</p>
+     */
+    public void testYuvAndJpegWithUsageFlag() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "YUV and JPEG testing for camera " + id);
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                openDevice(id);
+                bufferFormatWithYuvTestByCamera(ImageFormat.JPEG, true);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
      * Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
      *
      */
@@ -306,12 +353,12 @@
         for (String id : mCameraIds) {
             try {
                 Log.v(TAG, "YUV and RAW testing for camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 bufferFormatWithYuvTestByCamera(ImageFormat.RAW_SENSOR);
             } finally {
                 closeDevice(id);
@@ -320,6 +367,28 @@
     }
 
     /**
+     * Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader with the
+     * ImageReader factory method that has usage flag argument.
+     *
+     */
+    public void testImageReaderYuvAndRawWithUsageFlag() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "YUV and RAW testing for camera " + id);
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                openDevice(id);
+                bufferFormatWithYuvTestByCamera(ImageFormat.RAW_SENSOR, true);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
      * Check that the center patches for YUV and JPEG outputs for the same frame match for each YUV
      * resolution and format supported.
      */
@@ -329,13 +398,13 @@
         for (String id : mCameraIds) {
             try {
                 Log.v(TAG, "Testing all YUV image resolutions for camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 // Skip warmup on FULL mode devices.
                 int warmupCaptureNumber = (mStaticInfo.isHardwareLevelLegacy()) ?
                         MAX_NUM_IMAGES - 1 : 0;
@@ -510,16 +579,16 @@
                             if (difference > tolerance) {
                                 // Dump files if running in verbose mode
                                 if (DEBUG) {
-                                    String jpegFileName = DEBUG_FILE_NAME_BASE + "/" + captureSz +
+                                    String jpegFileName = mDebugFileNameBase + "/" + captureSz +
                                             "_jpeg.jpg";
                                     dumpFile(jpegFileName, jpegBmap);
-                                    String fullSizeJpegFileName = DEBUG_FILE_NAME_BASE + "/" +
+                                    String fullSizeJpegFileName = mDebugFileNameBase + "/" +
                                             captureSz + "_full_jpeg.jpg";
                                     dumpFile(fullSizeJpegFileName, compressedJpegData);
-                                    String yuvFileName = DEBUG_FILE_NAME_BASE + "/" + captureSz +
+                                    String yuvFileName = mDebugFileNameBase + "/" + captureSz +
                                             "_yuv.jpg";
                                     dumpFile(yuvFileName, yuvBmap);
-                                    String fullSizeYuvFileName = DEBUG_FILE_NAME_BASE + "/" +
+                                    String fullSizeYuvFileName = mDebugFileNameBase + "/" +
                                             captureSz + "_full_yuv.jpg";
                                     int[] fullYUVColors = convertPixelYuvToRgba(yuvImage.getWidth(),
                                             yuvImage.getHeight(), 0, 0, yuvImage);
@@ -570,6 +639,29 @@
         }
     }
 
+    /** Tests that usage bits are preserved */
+    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);
+        Surface surface = reader.getSurface();
+        Canvas canvas = surface.lockHardwareCanvas();
+        canvas.drawColor(Color.RED);
+        surface.unlockCanvasAndPost(canvas);
+        Image image = null;
+        for (int i = 0; i < 100; i++) {
+            image = reader.acquireNextImage();
+            if (image != null) break;
+            Thread.sleep(10);
+        }
+        assertNotNull(image);
+        HardwareBuffer buffer = image.getHardwareBuffer();
+        assertNotNull(buffer);
+        // Mask off the upper vendor bits
+        int myBits = (int) (buffer.getUsage() & 0xFFFFFFF);
+        assertEquals(HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE,
+                myBits);
+    }
+
     /**
      * Convert a rectangular patch in a YUV image to an ARGB color array.
      *
@@ -675,6 +767,20 @@
      * @param format The capture format to be tested along with yuv format.
      */
     private void bufferFormatWithYuvTestByCamera(int format) throws Exception {
+        bufferFormatWithYuvTestByCamera(format, false);
+    }
+
+    /**
+     * Test capture a given format stream with yuv stream simultaneously.
+     *
+     * <p>Use fixed yuv size, varies targeted format capture size. Single capture is tested.</p>
+     *
+     * @param format The capture format to be tested along with yuv format.
+     * @param setUsageFlag The ImageReader factory method to be used (with or without specifying
+     *                     usage flag)
+     */
+    private void bufferFormatWithYuvTestByCamera(int format, boolean setUsageFlag)
+            throws Exception {
         if (format != ImageFormat.JPEG && format != ImageFormat.RAW_SENSOR
                 && format != ImageFormat.YUV_420_888) {
             throw new IllegalArgumentException("Unsupported format: " + format);
@@ -696,14 +802,25 @@
             try {
                 // Create YUV image reader
                 SimpleImageReaderListener yuvListener  = new SimpleImageReaderListener();
-                yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
-                        yuvListener);
+                if (setUsageFlag) {
+                    yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+                            HardwareBuffer.USAGE_CPU_READ_OFTEN, yuvListener);
+                } else {
+                    yuvReader = createImageReader(maxYuvSz, ImageFormat.YUV_420_888, MAX_NUM_IMAGES,
+                            yuvListener);
+                }
+
                 Surface yuvSurface = yuvReader.getSurface();
 
                 // Create capture image reader
                 SimpleImageReaderListener captureListener = new SimpleImageReaderListener();
-                captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
-                        captureListener);
+                if (setUsageFlag) {
+                    captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
+                            HardwareBuffer.USAGE_CPU_READ_OFTEN, captureListener);
+                } else {
+                    captureReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
+                            captureListener);
+                }
                 Surface captureSurface = captureReader.getSurface();
 
                 // Capture images.
@@ -813,7 +930,11 @@
     }
 
     private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
+        bufferFormatTestByCamera(format, repeating, /*checkSession*/ false);
+    }
 
+    private void bufferFormatTestByCamera(int format, boolean repeating, boolean checkSession)
+            throws Exception {
         Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
                 StaticMetadata.StreamDirection.Output);
 
@@ -829,6 +950,11 @@
                 mListener  = new SimpleImageListener();
                 createDefaultImageReader(sz, format, MAX_NUM_IMAGES, mListener);
 
+                if (checkSession) {
+                    assertTrue("Camera capture session validation for format: " + format +
+                            " failed", checkImageReaderSessionConfiguration());
+                }
+
                 // Start capture.
                 CaptureRequest request = prepareCaptureRequest();
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
@@ -901,7 +1027,7 @@
                     Image img = mReader.acquireNextImage();
                     assertNotNull("Unable to acquire next image", img);
                     CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
-                            DEBUG_FILE_NAME_BASE);
+                            mDebugFileNameBase);
 
                     // Verify the exposure time and iso match the requested values.
                     CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
@@ -1044,9 +1170,14 @@
             assertNotNull("Unable to acquire the latest image", img);
             if (VERBOSE) Log.v(TAG, "Got the latest image");
             CameraTestUtils.validateImage(img, sz.getWidth(), sz.getHeight(), format,
-                    DEBUG_FILE_NAME_BASE);
+                    mDebugFileNameBase);
             HardwareBuffer hwb = img.getHardwareBuffer();
             assertNotNull("Unable to retrieve the Image's HardwareBuffer", hwb);
+            if (format == ImageFormat.DEPTH_JPEG) {
+                byte [] dynamicDepthBuffer = CameraTestUtils.getDataFromImage(img);
+                assertTrue("Dynamic depth validation failed!",
+                        validateDynamicDepthNative(dynamicDepthBuffer));
+            }
             if (VERBOSE) Log.v(TAG, "finish validation of image " + numImageVerified);
             img.close();
             numImageVerified++;
@@ -1057,4 +1188,17 @@
         // take a while to return and there could be many images pending.
         mListener.closePendingImages();
     }
+
+    /** Load dynamic depth validation jni on initialization */
+    static {
+        System.loadLibrary("dynamic_depth");
+        System.loadLibrary("ctscamera2_jni");
+    }
+    /**
+     * Use the dynamic depth SDK to validate a dynamic depth file stored in the buffer.
+     *
+     * Returns false if the dynamic depth has validation errors. Validation warnings/errors
+     * will be printed to logcat.
+     */
+    private static native boolean validateDynamicDepthNative(byte[] dynamicDepthBuffer);
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 65c8eb1..38f8816 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -24,11 +24,11 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.HardwareBuffer;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.media.ImageWriter;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -45,7 +45,6 @@
  * interface or ImageReader.
  * </p>
  */
-@AppModeFull
 public class ImageWriterTest extends Camera2AndroidTestCase {
     private static final String TAG = "ImageWriterTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -72,7 +71,6 @@
     }
 
     /**
-     * `
      * <p>
      * Basic YUV420_888 format ImageWriter ImageReader test that checks the
      * images produced by camera can be passed correctly by ImageWriter.
@@ -94,12 +92,34 @@
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
-                readerWriterFormatTestByCamera(ImageFormat.YUV_420_888);
+                openDevice(id);
+                readerWriterFormatTestByCamera(ImageFormat.YUV_420_888, false);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Similar to testYuvImageWriterReaderOperation, but use the alternative
+     * factory method of ImageReader and ImageWriter.
+     * </p>
+     */
+    public void testYuvImageWriterReaderOperationAlt() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+                openDevice(id);
+                readerWriterFormatTestByCamera(ImageFormat.YUV_420_888, true);
             } finally {
                 closeDevice(id);
             }
@@ -150,7 +170,8 @@
         }
     }
 
-    private void readerWriterFormatTestByCamera(int format)  throws Exception {
+    private void readerWriterFormatTestByCamera(int format, boolean altFactoryMethod)
+            throws Exception {
         List<Size> sizes = getSortedSizesForFormat(mCamera.getId(), mCameraManager, format, null);
         Size maxSize = sizes.get(0);
         if (VERBOSE) {
@@ -159,14 +180,28 @@
 
         // Create ImageReader for camera output.
         SimpleImageReaderListener listenerForCamera  = new SimpleImageReaderListener();
-        createDefaultImageReader(maxSize, format, MAX_NUM_IMAGES, listenerForCamera);
+        if (altFactoryMethod) {
+            createDefaultImageReader(maxSize, format, MAX_NUM_IMAGES,
+                    HardwareBuffer.USAGE_CPU_READ_OFTEN, listenerForCamera);
+        } else {
+            createDefaultImageReader(maxSize, format, MAX_NUM_IMAGES, listenerForCamera);
+        }
+
         if (VERBOSE) {
             Log.v(TAG, "Created camera output ImageReader");
         }
 
         // Create ImageReader for ImageWriter output
         SimpleImageReaderListener listenerForWriter  = new SimpleImageReaderListener();
-        mReaderForWriter = createImageReader(maxSize, format, MAX_NUM_IMAGES, listenerForWriter);
+        if (altFactoryMethod) {
+            mReaderForWriter = createImageReader(
+                    maxSize, format, MAX_NUM_IMAGES,
+                    HardwareBuffer.USAGE_CPU_READ_OFTEN, listenerForWriter);
+        } else {
+            mReaderForWriter = createImageReader(
+                    maxSize, format, MAX_NUM_IMAGES, listenerForWriter);
+        }
+
         if (VERBOSE) {
             Log.v(TAG, "Created ImageWriter output ImageReader");
         }
@@ -174,7 +209,11 @@
         // Create ImageWriter
         Surface surface = mReaderForWriter.getSurface();
         assertNotNull("Surface from ImageReader shouldn't be null", surface);
-        mWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
+        if (altFactoryMethod) {
+            mWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES, format);
+        } else {
+            mWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
+        }
         SimpleImageWriterListener writerImageListener = new SimpleImageWriterListener(mWriter);
         mWriter.setOnImageReleasedListener(writerImageListener, mHandler);
 
@@ -243,8 +282,8 @@
             mCollector.expectTrue("ImageWriter 1st output image should match 1st input image",
                     isImageStronglyEqual(cameraImage, outputImage));
             if (DEBUG) {
-                String img1FileName = DEBUG_FILE_NAME_BASE + "/" + maxSize + "_image1_copy.yuv";
-                String outputImg1FileName = DEBUG_FILE_NAME_BASE + "/" + maxSize
+                String img1FileName = mDebugFileNameBase + "/" + maxSize + "_image1_copy.yuv";
+                String outputImg1FileName = mDebugFileNameBase + "/" + maxSize
                         + "_outputImage2_copy.yuv";
                 dumpFile(img1FileName, getDataFromImage(cameraImage));
                 dumpFile(outputImg1FileName, getDataFromImage(outputImage));
@@ -263,7 +302,7 @@
             // make a copy of image1 data, as it will be closed after queueInputImage;
             byte[] img1Data = getDataFromImage(cameraImage);
             if (DEBUG) {
-                String img2FileName = DEBUG_FILE_NAME_BASE + "/" + maxSize + "_image2.yuv";
+                String img2FileName = mDebugFileNameBase + "/" + maxSize + "_image2.yuv";
                 dumpFile(img2FileName, img1Data);
             }
 
@@ -280,7 +319,7 @@
                     + "2nd output image", Arrays.equals(img1Data, outputImageData));
 
             if (DEBUG) {
-                String outputImgFileName = DEBUG_FILE_NAME_BASE + "/" + maxSize +
+                String outputImgFileName = mDebugFileNameBase + "/" + maxSize +
                         "_outputImage2.yuv";
                 dumpFile(outputImgFileName, outputImageData);
             }
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 33365b2..ecd5798 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -20,6 +20,8 @@
 
 import android.content.Context;
 import android.graphics.ImageFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -39,7 +41,6 @@
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
-import android.platform.test.annotations.AppModeFull;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Range;
@@ -59,14 +60,15 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.junit.Test;
+
 import static org.mockito.Mockito.*;
 
 /**
  * Tests exercising logical camera setup, configuration, and usage.
  */
-@AppModeFull
 public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase {
-    private static final String TAG = "LogicalCameraTest";
+    private static final String TAG = "LogicalCameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final int CONFIGURE_TIMEOUT = 5000; //ms
@@ -77,35 +79,26 @@
 
     private static final double FRAME_DURATION_THRESHOLD = 0.03;
 
-    private HashMap<String, StaticMetadata> mAllStaticInfo;
-
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
-
-        mAllStaticInfo = new HashMap<String, StaticMetadata>();
-        for (String cameraId : mCameraIds) {
-            StaticMetadata staticMetadata = new StaticMetadata(
-                    mCameraManager.getCameraCharacteristics(cameraId),
-                    CheckLevel.ASSERT, /*collector*/null);
-            mAllStaticInfo.put(cameraId, staticMetadata);
-        }
     }
 
     /**
      * Test that passing in invalid physical camera ids in OutputConfiguragtion behaves as expected
      * for logical multi-camera and non-logical multi-camera.
      */
+    @Test
     public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                if (mAllStaticInfo.get(id).isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + id + " is legacy, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 Size yuvSize = mOrderedPreviewSizes.get(0);
                 // Create a YUV image reader.
                 ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(),
@@ -141,23 +134,25 @@
      * Test for making sure that streaming from physical streams work as expected, and
      * FPS isn't slowed down.
      */
+    @Test
     public void testBasicPhysicalStreaming() throws Exception {
 
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
-                if (!mStaticInfo.isLogicalMultiCamera()) {
+                if (!staticInfo.isLogicalMultiCamera()) {
                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 assertTrue("Logical multi-camera must be LIMITED or higher",
                         mStaticInfo.isHardwareLevelAtLeastLimited());
 
@@ -180,23 +175,25 @@
      * Test for making sure that logical/physical stream requests work when both logical stream
      * and physical stream are configured.
      */
+    @Test
     public void testBasicLogicalPhysicalStreamCombination() throws Exception {
 
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
-                if (!mStaticInfo.isLogicalMultiCamera()) {
+                if (!staticInfo.isLogicalMultiCamera()) {
                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 assertTrue("Logical multi-camera must be LIMITED or higher",
                         mStaticInfo.isHardwareLevelAtLeastLimited());
 
@@ -291,23 +288,25 @@
     /**
      * Test for making sure that multiple requests for physical cameras work as expected.
      */
+    @Test
     public void testBasicPhysicalRequests() throws Exception {
 
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
 
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
-                if (!mStaticInfo.isLogicalMultiCamera()) {
+                if (!staticInfo.isLogicalMultiCamera()) {
                     Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 assertTrue("Logical multi-camera must be LIMITED or higher",
                         mStaticInfo.isHardwareLevelAtLeastLimited());
 
@@ -411,22 +410,24 @@
     /**
      * Tests invalid/incorrect multiple physical capture request cases.
      */
+    @Test
     public void testInvalidPhysicalCameraRequests() throws Exception {
 
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
-                openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + id + " is legacy, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 Size yuvSize = mOrderedPreviewSizes.get(0);
                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
                 List<ImageReader> imageReaders = new ArrayList<>();
@@ -509,6 +510,237 @@
     }
 
     /**
+     * Test for physical camera switch based on focal length (optical zoom) and crop region
+     * (digital zoom).
+     *
+     * - Focal length and crop region change must be synchronized to not have sudden jump in field
+     *   of view.
+     * - Main physical id must be valid.
+     */
+    @Test
+    public void testLogicalCameraZoomSwitch() throws Exception {
+
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                if (!staticInfo.isLogicalMultiCamera()) {
+                    Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
+                    continue;
+                }
+
+                openDevice(id);
+                Size yuvSize = mOrderedPreviewSizes.get(0);
+                // Create a YUV image reader.
+                ImageReader imageReader = CameraTestUtils.makeImageReader(yuvSize,
+                        ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                        new ImageDropperListener(), mHandler);
+
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                OutputConfiguration config = new OutputConfiguration(imageReader.getSurface());
+                outputConfigs.add(config);
+
+                mSessionListener = new BlockingSessionCallback();
+                mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                        mSessionListener, mHandler);
+
+                final float FOV_MARGIN = 0.01f;
+                final float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
+                final int zoomSteps = focalLengths.length;
+                final float maxZoom = staticInfo.getAvailableMaxDigitalZoomChecked();
+                final Rect activeArraySize = staticInfo.getActiveArraySizeChecked();
+                final Set<String> physicalIds =
+                        staticInfo.getCharacteristics().getPhysicalCameraIds();
+
+                CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                requestBuilder.addTarget(imageReader.getSurface());
+
+                // For each adjacent focal lengths, set different crop region such that the
+                // resulting angle of view is the same. This is to make sure that no sudden FOV
+                // (field of view) jump when switching between different focal lengths.
+                for (int i = 0; i < zoomSteps-1; i++) {
+                    // Start with larger focal length + full active array crop region.
+                    requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]);
+                    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize);
+                    SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback();
+                    mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener,
+                            mHandler);
+                    waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+                    // This is an approximate, assuming that subject distance >> focal length.
+                    float zoomFactor = focalLengths[i+1]/focalLengths[i];
+                    PointF zoomCenter = new PointF(0.5f, 0.5f);
+                    Rect requestCropRegion = getCropRegionForZoom(zoomFactor,
+                            zoomCenter, maxZoom, activeArraySize);
+                    if (VERBOSE) {
+                        Log.v(TAG, "Switching from crop region " + activeArraySize + ", focal " +
+                            "length " + focalLengths[i+1] + " to crop region " + requestCropRegion +
+                            ", focal length " + focalLengths[i]);
+                    }
+
+                    // Create a burst capture to switch between different focal_length/crop_region
+                    // combination with same field of view.
+                    List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
+                    SimpleCaptureCallback listener = new SimpleCaptureCallback();
+
+                    requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]);
+                    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion);
+                    requests.add(requestBuilder.build());
+                    requests.add(requestBuilder.build());
+
+                    requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]);
+                    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize);
+                    requests.add(requestBuilder.build());
+                    requests.add(requestBuilder.build());
+
+                    requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]);
+                    requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion);
+                    requests.add(requestBuilder.build());
+                    requests.add(requestBuilder.build());
+
+                    mSession.captureBurst(requests, listener, mHandler);
+                    TotalCaptureResult[] results = listener.getTotalCaptureResultsForRequests(
+                            requests, WAIT_FOR_RESULT_TIMEOUT_MS);
+
+                    // Verify result metadata to produce similar field of view.
+                    float fov = activeArraySize.width()/(2*focalLengths[i+1]);
+                    for (int j = 0; j < results.length; j++) {
+                        TotalCaptureResult result = results[j];
+                        Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
+                        Rect resultCropRegion = result.get(CaptureResult.SCALER_CROP_REGION);
+
+                        if (VERBOSE) {
+                            Log.v(TAG, "Result crop region " + resultCropRegion + ", focal length "
+                                    + resultFocalLength + " for result " + j);
+                        }
+                        float newFov = resultCropRegion.width()/(2*resultFocalLength);
+
+                        mCollector.expectTrue("Field of view must be consistent with focal " +
+                                "length and crop region change cancelling out each other.",
+                                Math.abs(newFov - fov)/fov < FOV_MARGIN);
+
+                        if (staticInfo.isActivePhysicalCameraIdSupported()) {
+                            String activePhysicalId = result.get(
+                                    CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
+                            assertTrue(physicalIds.contains(activePhysicalId));
+
+                            StaticMetadata physicalCameraStaticInfo =
+                                    mAllStaticInfo.get(activePhysicalId);
+                            float[] physicalCameraFocalLengths =
+                                    physicalCameraStaticInfo.getAvailableFocalLengthsChecked();
+                            mCollector.expectTrue("Current focal length " + resultFocalLength
+                                    + " must be supported by active physical camera "
+                                    + activePhysicalId, Arrays.asList(CameraTestUtils.toObject(
+                                    physicalCameraFocalLengths)).contains(resultFocalLength));
+                        }
+                    }
+                }
+
+                if (mSession != null) {
+                    mSession.close();
+                }
+
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Test that for logical multi-camera, the activePhysicalId is valid, and is the same
+     * for all capture templates.
+     */
+    @Test
+    public void testActivePhysicalId() throws Exception {
+        int[] sTemplates = new int[] {
+            CameraDevice.TEMPLATE_PREVIEW,
+            CameraDevice.TEMPLATE_RECORD,
+            CameraDevice.TEMPLATE_STILL_CAPTURE,
+            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+            CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+            CameraDevice.TEMPLATE_MANUAL,
+        };
+
+        for (String id : mCameraIds) {
+            try {
+                Log.i(TAG, "Testing Camera " + id);
+
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                if (!staticInfo.isLogicalMultiCamera()) {
+                    Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping");
+                    continue;
+                }
+
+                if (!staticInfo.isActivePhysicalCameraIdSupported()) {
+                    continue;
+                }
+
+                final Set<String> physicalIds =
+                        staticInfo.getCharacteristics().getPhysicalCameraIds();
+                openDevice(id);
+                Size previewSz =
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager,
+                        getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
+
+                String storedActiveId = null;
+                for (int template : sTemplates) {
+                    try {
+                        CaptureRequest.Builder requestBuilder =
+                                mCamera.createCaptureRequest(template);
+                        SimpleCaptureCallback listener = new SimpleCaptureCallback();
+                        startPreview(requestBuilder, previewSz, listener);
+                        waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+                        CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+                        String activePhysicalId = result.get(
+                                CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
+
+                        assertNotNull("activePhysicalId must not be null", activePhysicalId);
+                        if (storedActiveId == null) {
+                            storedActiveId = activePhysicalId;
+                            assertTrue(
+                                  "Camera device reported invalid activePhysicalId: " +
+                                  activePhysicalId, physicalIds.contains(activePhysicalId));
+                        } else {
+                            assertTrue(
+                                  "Camera device reported different activePhysicalId " +
+                                  activePhysicalId + " vs " + storedActiveId +
+                                  " for different capture templates",
+                                  storedActiveId.equals(activePhysicalId));
+                        }
+                    } catch (IllegalArgumentException e) {
+                        if (template == CameraDevice.TEMPLATE_MANUAL &&
+                                !staticInfo.isCapabilitySupported(CameraCharacteristics.
+                                REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                            // OK
+                        } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG &&
+                                !staticInfo.isCapabilitySupported(CameraCharacteristics.
+                                REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) {
+                            // OK.
+                        } else {
+                            throw e; // rethrow
+                        }
+                    }
+                }
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
      * Find a common preview size that's supported by both the logical camera and
      * two of the underlying physical cameras.
      */
@@ -536,7 +768,7 @@
         }
 
         // Find display size from window service.
-        Context context = getInstrumentation().getTargetContext();
+        Context context = mActivityRule.getActivity().getApplicationContext();
         WindowManager windowManager =
                 (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = windowManager.getDefaultDisplay();
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 7e18b98..80b6f5b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -34,7 +34,6 @@
 import android.media.ImageReader;
 import android.os.SystemClock;
 import android.os.ConditionVariable;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -46,10 +45,11 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.Test;
+
 /**
  * CameraDevice test by using combination of SurfaceView, TextureView and ImageReader
  */
-@AppModeFull
 public class MultiViewTest extends Camera2MultiViewTestCase {
     private static final String TAG = "MultiViewTest";
     private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 5000; //ms
@@ -58,6 +58,7 @@
     private final static int IMG_READER_COUNT = 2;
     private final static int YUV_IMG_READER_COUNT = 3;
 
+    @Test
     public void testTextureViewPreview() throws Exception {
         for (String cameraId : mCameraIds) {
             Exception prior = null;
@@ -86,6 +87,7 @@
         }
     }
 
+    @Test
     public void testTextureViewPreviewWithImageReader() throws Exception {
         for (String cameraId : mCameraIds) {
             Exception prior = null;
@@ -133,6 +135,7 @@
         }
     }
 
+    @Test
     public void testDualTextureViewPreview() throws Exception {
         for (String cameraId : mCameraIds) {
             Exception prior = null;
@@ -165,6 +168,7 @@
         }
     }
 
+    @Test
     public void testDualTextureViewAndImageReaderPreview() throws Exception {
         for (String cameraId : mCameraIds) {
             Exception prior = null;
@@ -209,6 +213,7 @@
         }
     }
 
+    @Test
     public void testDualCameraPreview() throws Exception {
         final int NUM_CAMERAS_TESTED = 2;
         if (mCameraIds.length < NUM_CAMERAS_TESTED) {
@@ -248,6 +253,7 @@
     /*
      * Verify dynamic shared surface behavior.
      */
+    @Test
     public void testSharedSurfaceBasic() throws Exception {
         for (String cameraId : mCameraIds) {
             try {
@@ -389,6 +395,7 @@
     /*
      * Verify dynamic shared surface behavior using multiple ImageReaders.
      */
+    @Test
     public void testSharedSurfaceImageReaderSwitch() throws Exception {
         for (String cameraId : mCameraIds) {
             try {
@@ -477,6 +484,7 @@
     /*
      * Verify dynamic shared surface behavior using YUV ImageReaders.
      */
+    @Test
     public void testSharedSurfaceYUVImageReaderSwitch() throws Exception {
         int YUVFormats[] = {ImageFormat.YUV_420_888, ImageFormat.YUV_422_888,
             ImageFormat.YUV_444_888, ImageFormat.YUY2, ImageFormat.YV12,
@@ -607,6 +615,7 @@
     /*
      * Test the dynamic shared surface limit.
      */
+    @Test
     public void testSharedSurfaceLimit() throws Exception {
         for (String cameraId : mCameraIds) {
             try {
@@ -687,6 +696,8 @@
                 }
             } else {
                 surfaceSharedOutput.addSurface(surfaces[i]);
+                assertTrue("Session configuration should not fail",
+                        isSessionConfigurationSupported(cameraId, outputConfigurations));
                 updateOutputConfiguration(cameraId, surfaceSharedOutput);
                 sequenceId = updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
 
@@ -704,7 +715,8 @@
                 }
             } else {
                 surfaceSharedOutput.removeSurface(surfaces[i]);
-
+                assertTrue("Session configuration should not fail",
+                        isSessionConfigurationSupported(cameraId, outputConfigurations));
             }
         }
         //Remove all previously added shared outputs in one call
@@ -721,6 +733,7 @@
     /*
      * Test dynamic shared surface switch behavior.
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testSharedSurfaceSwitch() throws Exception {
         for (String cameraId : mCameraIds) {
             try {
@@ -845,6 +858,7 @@
     /*
      * Verify behavior of sharing surfaces within one OutputConfiguration
      */
+    @Test
     public void testSharedSurfaces() throws Exception {
         for (String cameraId : mCameraIds) {
             try {
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
index d54ab17..251ed45 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
@@ -18,15 +18,18 @@
 
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-@AppModeFull
 public class NativeCameraDeviceTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "NativeCameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -38,16 +41,19 @@
         Log.i("NativeCameraDeviceTest", "after loadlibrary");
     }
 
+    @Test
     public void testCameraDeviceOpenAndClose() {
         assertTrue("testCameraDeviceOpenAndClose fail, see log for details",
                 testCameraDeviceOpenAndCloseNative());
     }
 
+    @Test
     public void testCameraDeviceCreateCaptureRequest() {
         assertTrue("testCameraDeviceCreateCaptureRequest fail, see log for details",
                 testCameraDeviceCreateCaptureRequestNative());
     }
 
+    @Test
     public void testCameraDeviceSessionOpenAndClose() {
         // Init preview surface to a guaranteed working size
         updatePreviewSurface(new Size(640, 480));
@@ -55,6 +61,7 @@
                 testCameraDeviceSessionOpenAndCloseNative(mPreviewSurface));
     }
 
+    @Test
     public void testCameraDeviceSimplePreview() {
         // Init preview surface to a guaranteed working size
         updatePreviewSurface(new Size(640, 480));
@@ -62,6 +69,7 @@
                 testCameraDeviceSimplePreviewNative(mPreviewSurface));
     }
 
+    @Test
     public void testCameraDevicePreviewWithSessionParameters() {
         // Init preview surface to a guaranteed working size
         updatePreviewSurface(new Size(640, 480));
@@ -69,6 +77,7 @@
                 testCameraDevicePreviewWithSessionParametersNative(mPreviewSurface));
     }
 
+    @Test
     public void testCameraDeviceSharedOutputUpdate() {
         // Init preview surface to a guaranteed working size
         Size previewSize = new Size(640, 480);
@@ -76,10 +85,22 @@
         SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
         outputTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
         Surface outputSurface = new Surface(outputTexture);
-        assertTrue("testCameraDeviceSharedWindowAddRemove fail, see log for details",
+        assertTrue("testCameraDeviceSharedOutputUpdate fail, see log for details",
                 testCameraDeviceSharedOutputUpdate(mPreviewSurface, outputSurface));
     }
 
+    @Test
+    public void testCameraDeviceLogicalPhysicalStreaming() {
+        // Init preview surface to a guaranteed working size
+        Size previewSize = new Size(640, 480);
+        updatePreviewSurface(previewSize);
+        SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
+        outputTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+        Surface outputSurface = new Surface(outputTexture);
+        assertTrue("testCameraDeviceLogicalPhysicalStreamingNative fail, see log for details",
+                testCameraDeviceLogicalPhysicalStreamingNative(mPreviewSurface));
+    }
+
     private static native boolean testCameraDeviceOpenAndCloseNative();
     private static native boolean testCameraDeviceCreateCaptureRequestNative();
     private static native boolean testCameraDeviceSessionOpenAndCloseNative(Surface preview);
@@ -87,4 +108,6 @@
     private static native boolean testCameraDevicePreviewWithSessionParametersNative(
             Surface preview);
     private static native boolean testCameraDeviceSharedOutputUpdate(Surface src, Surface dst);
+    private static native boolean testCameraDeviceLogicalPhysicalStreamingNative(Surface preview);
+
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
index 864b7d1..08e0363 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
@@ -16,14 +16,12 @@
 
 package android.hardware.camera2.cts;
 
-import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-@AppModeFull
 public class NativeCameraManagerTest extends AndroidTestCase {
     private static final String TAG = "NativeCameraManagerTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
index fe01fbd..4eee734 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
@@ -17,13 +17,11 @@
 package android.hardware.camera2.cts;
 
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-@AppModeFull
 public class NativeImageReaderTest extends Camera2AndroidTestCase {
     private static final String TAG = "NativeImageReaderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -37,7 +35,12 @@
 
     public void testJpeg() {
         assertTrue("testJpeg fail, see log for details",
-                testJpegNative(DEBUG_FILE_NAME_BASE));
+                testJpegNative(mDebugFileNameBase));
+    }
+
+    public void testY8() {
+        assertTrue("testY8 fail, see log for details",
+                testY8Native(mDebugFileNameBase));
     }
 
     public void testImageReaderCloseAcquiredImages() {
@@ -46,5 +49,6 @@
     }
 
     private static native boolean testJpegNative(String filePath);
+    private static native boolean testY8Native(String filePath);
     private static native boolean testImageReaderCloseAcquiredImagesNative();
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
index 8882b40..a892970 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
@@ -17,15 +17,17 @@
 package android.hardware.camera2.cts;
 
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-@AppModeFull
 public class NativeStillCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "NativeStillCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -37,11 +39,12 @@
         Log.i("NativeStillCaptureTest", "after loadlibrary");
     }
 
+    @Test
     public void testStillCapture() {
         // Init preview surface to a guaranteed working size
         updatePreviewSurface(new Size(640, 480));
         assertTrue("testStillCapture fail, see log for details",
-                testStillCaptureNative(DEBUG_FILE_NAME_BASE, mPreviewSurface));
+                testStillCaptureNative(mDebugFileNameBase, mPreviewSurface));
     }
 
     private static native boolean testStillCaptureNative(
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 9006b09..0fa1741 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -18,7 +18,9 @@
 
 import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
 
+import android.app.Instrumentation;
 import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
@@ -31,7 +33,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.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
@@ -39,7 +41,7 @@
 import android.media.ImageWriter;
 import android.os.ConditionVariable;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
@@ -59,12 +61,16 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
 /**
  * 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.
  */
-@AppModeFull
-public class PerformanceTest extends Camera2SurfaceViewTestCase {
+public class PerformanceTest extends Camera2AndroidTestCase {
     private static final String TAG = "PerformanceTest";
     private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -79,6 +85,9 @@
     // count to maintain reasonable number of candidate image for the worse-case.
     private final int MAX_ZSL_IMAGES = MAX_REPROCESS_IMAGES * 3 / 2;
     private final double REPROCESS_STALL_MARGIN = 0.1;
+    private static final int WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+    private static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
+    private static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
 
     private DeviceReportLog mReportLog;
 
@@ -92,13 +101,19 @@
     private ImageWriter mWriter;
     private SimpleCaptureCallback mZslResultListener;
 
+    private Instrumentation mInstrumentation;
+
+    private Surface mPreviewSurface;
+    private SurfaceTexture mPreviewSurfaceTexture;
+
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
     }
 
@@ -171,7 +186,8 @@
                         cameraLaunchTimes[i] = previewStartedTimeMs - startTimeMs;
 
                         // Let preview on for a couple of frames
-                        waitForNumResults(resultListener, NUM_RESULTS_WAIT);
+                        CameraTestUtils.waitForNumResults(resultListener, NUM_RESULTS_WAIT,
+                                WAIT_FOR_RESULT_TIMEOUT_MS);
 
                         // Blocking stop preview
                         startTimeMs = SystemClock.elapsedRealtime();
@@ -181,7 +197,7 @@
                     finally {
                         // Blocking camera close
                         startTimeMs = SystemClock.elapsedRealtime();
-                        closeDevice();
+                        closeDevice(id);
                         cameraCloseTimes[i] = SystemClock.elapsedRealtime() - startTimeMs;
                     }
                 }
@@ -203,10 +219,11 @@
                         ResultType.LOWER_BETTER, ResultUnit.MS);
             }
             finally {
-                closeImageReader();
+                closeDefaultImageReader();
+                closePreviewSurface();
             }
             counter++;
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
 
             if (VERBOSE) {
                 Log.v(TAG, "Camera " + id + " device open times(ms): "
@@ -246,14 +263,14 @@
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.setSummary("camera_launch_average_time_for_all_cameras",
                     Stat.getAverage(avgCameraLaunchTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
         }
     }
 
     /**
-     * Test camera capture KPI for YUV_420_888 format: the time duration between
-     * sending out a single image capture request and receiving image data and
-     * capture result.
+     * Test camera capture KPI for YUV_420_888, PRIVATE, JPEG, RAW and RAW+JPEG
+     * formats: the time duration between sending out a single image capture request
+     * and receiving image data and capture result.
      * <p>
      * It enumerates the following metrics: capture latency, computed by
      * measuring the time between sending out the capture request and getting
@@ -264,26 +281,63 @@
      * </p>
      */
     public void testSingleCapture() throws Exception {
+        int[] YUV_FORMAT = {ImageFormat.YUV_420_888};
+        testSingleCaptureForFormat(YUV_FORMAT, null, /*addPreviewDelay*/ false);
+        int[] PRIVATE_FORMAT = {ImageFormat.PRIVATE};
+        testSingleCaptureForFormat(PRIVATE_FORMAT, "private", /*addPreviewDelay*/ true);
+        int[] JPEG_FORMAT = {ImageFormat.JPEG};
+        testSingleCaptureForFormat(JPEG_FORMAT, "jpeg", /*addPreviewDelay*/ true);
+        int[] RAW_FORMAT = {ImageFormat.RAW_SENSOR};
+        testSingleCaptureForFormat(RAW_FORMAT, "raw", /*addPreviewDelay*/ true);
+        int[] RAW_JPEG_FORMATS = {ImageFormat.RAW_SENSOR, ImageFormat.JPEG};
+        testSingleCaptureForFormat(RAW_JPEG_FORMATS, "raw_jpeg", /*addPreviewDelay*/ true);
+    }
+
+    private String appendFormatDescription(String message, String formatDescription) {
+        if (message == null) {
+            return null;
+        }
+
+        String ret = message;
+        if (formatDescription != null) {
+            ret = String.format(ret + "_%s", formatDescription);
+        }
+
+        return ret;
+    }
+
+    private void testSingleCaptureForFormat(int[] formats, String formatDescription,
+            boolean addPreviewDelay) throws Exception {
         double[] avgResultTimes = new double[mCameraIds.length];
 
         int counter = 0;
         for (String id : mCameraIds) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
-            String streamName = "test_single_capture";
+            String streamName = appendFormatDescription("test_single_capture", formatDescription);
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
             double[] captureTimes = new double[NUM_TEST_LOOPS];
             double[] getPartialTimes = new double[NUM_TEST_LOOPS];
             double[] getResultTimes = new double[NUM_TEST_LOOPS];
+            ImageReader[] readers = null;
             try {
-                openDevice(id);
-
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
+
+                StreamConfigurationMap configMap = mStaticInfo.getCharacteristics().get(
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                for (int format : formats) {
+                    if (!configMap.isOutputSupportedFor(format)) {
+                        Log.i(TAG, "Camera " + id + " does not support output format: " + format +
+                                " skipping");
+                        continue;
+                    }
+                }
 
                 boolean partialsExpected = mStaticInfo.getPartialResultCount() > 1;
                 long startTimeMs;
@@ -299,20 +353,26 @@
                             new SimpleCaptureCallback();
                     SimpleTimingResultListener captureResultListener =
                             new SimpleTimingResultListener();
-                    SimpleImageListener imageListener = new SimpleImageListener();
+                    SimpleImageListener[] imageListeners = new SimpleImageListener[formats.length];
+                    Size[] imageSizes = new Size[formats.length];
+                    for (int j = 0; j < formats.length; j++) {
+                        imageSizes[j] = CameraTestUtils.getSortedSizesForFormat(
+                                id, mCameraManager, formats[j], /*bound*/null).get(0);
+                        imageListeners[j] = new SimpleImageListener();
+                    }
 
-                    Size maxYuvSize = CameraTestUtils.getSortedSizesForFormat(
-                        id, mCameraManager, ImageFormat.YUV_420_888, /*bound*/null).get(0);
+                    readers = prepareStillCaptureAndStartPreview(previewBuilder, captureBuilder,
+                            mOrderedPreviewSizes.get(0), imageSizes, formats,
+                            previewResultListener, NUM_MAX_IMAGES, imageListeners);
 
-                    prepareCaptureAndStartPreview(previewBuilder, captureBuilder,
-                            mOrderedPreviewSizes.get(0), maxYuvSize,
-                            ImageFormat.YUV_420_888, previewResultListener,
-                            NUM_MAX_IMAGES, imageListener);
+                    if (addPreviewDelay) {
+                        Thread.sleep(500);
+                    }
 
                     // Capture an image and get image data
                     startTimeMs = SystemClock.elapsedRealtime();
                     CaptureRequest request = captureBuilder.build();
-                    mSession.capture(request, captureResultListener, mHandler);
+                    mCameraSession.capture(request, captureResultListener, mHandler);
 
                     Pair<CaptureResult, Long> partialResultNTime = null;
                     if (partialsExpected) {
@@ -327,10 +387,15 @@
                     Pair<CaptureResult, Long> captureResultNTime =
                             captureResultListener.getCaptureResultNTimeForRequest(
                                     request, NUM_RESULTS_WAIT);
-                    imageListener.waitForImageAvailable(
-                            CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
 
-                    captureTimes[i] = imageListener.getTimeReceivedImage() - startTimeMs;
+                    double [] imageTimes = new double[formats.length];
+                    for (int j = 0; j < formats.length; j++) {
+                        imageListeners[j].waitForImageAvailable(
+                                CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
+                        imageTimes[j] = imageListeners[j].getTimeReceivedImage();
+                    }
+
+                    captureTimes[i] = Stat.getAverage(imageTimes) - startTimeMs;
                     if (partialsExpected) {
                         getPartialTimes[i] = partialResultNTime.second - startTimeMs;
                         if (getPartialTimes[i] < 0) {
@@ -340,38 +405,51 @@
                     getResultTimes[i] = captureResultNTime.second - startTimeMs;
 
                     // simulate real scenario (preview runs a bit)
-                    waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
+                    CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
+                            WAIT_FOR_RESULT_TIMEOUT_MS);
 
-                    stopPreview();
+                    stopPreviewAndDrain();
 
+                    CameraTestUtils.closeImageReaders(readers);
+                    readers = null;
                 }
-                mReportLog.addValues("camera_capture_latency", captureTimes,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                String message = appendFormatDescription("camera_capture_latency",
+                        formatDescription);
+                mReportLog.addValues(message, captureTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
                 // If any of the partial results do not contain AE and AF state, then no report
                 if (isPartialTimingValid) {
-                    mReportLog.addValues("camera_partial_result_latency", getPartialTimes,
-                            ResultType.LOWER_BETTER, ResultUnit.MS);
+                    message = appendFormatDescription("camera_partial_result_latency",
+                            formatDescription);
+                    mReportLog.addValues(message, getPartialTimes, ResultType.LOWER_BETTER,
+                            ResultUnit.MS);
                 }
-                mReportLog.addValues("camera_capture_result_latency", getResultTimes,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                message = appendFormatDescription("camera_capture_result_latency",
+                        formatDescription);
+                mReportLog.addValues(message, getResultTimes, ResultType.LOWER_BETTER,
+                        ResultUnit.MS);
 
                 avgResultTimes[counter] = Stat.getAverage(getResultTimes);
             }
             finally {
-                closeImageReader();
-                closeDevice();
+                CameraTestUtils.closeImageReaders(readers);
+                readers = null;
+                closeDevice(id);
+                closePreviewSurface();
             }
             counter++;
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
         }
 
         // Result will not be reported in CTS report if no summary is printed.
         if (mCameraIds.length != 0) {
-            String streamName = "test_single_capture_average";
+            String streamName = appendFormatDescription("test_single_capture_average",
+                    formatDescription);
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
-            mReportLog.setSummary("camera_capture_result_average_latency_for_all_cameras",
-                    Stat.getAverage(avgResultTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.submit(getInstrumentation());
+            String message = appendFormatDescription(
+                    "camera_capture_result_average_latency_for_all_cameras", formatDescription);
+            mReportLog.setSummary(message, Stat.getAverage(avgResultTimes),
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            mReportLog.submit(mInstrumentation);
         }
     }
 
@@ -445,13 +523,12 @@
             double[] getResultTimes = new double[NUM_MAX_IMAGES];
             double[] frameDurationMs = new double[NUM_MAX_IMAGES-1];
             try {
-                openDevice(id);
-
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 for (int i = 0; i < NUM_TEST_LOOPS; i++) {
 
                     // setup builders and listeners
@@ -475,8 +552,9 @@
                     final long minStillFrameDuration =
                             config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, maxYuvSize);
                     if (minStillFrameDuration > 0) {
-                        Range<Integer> targetRange = getSuitableFpsRangeForDuration(id,
-                                minStillFrameDuration);
+                        Range<Integer> targetRange =
+                            CameraTestUtils.getSuitableFpsRangeForDuration(id,
+                                    minStillFrameDuration, mStaticInfo);
                         previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
                         captureBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
                     }
@@ -487,17 +565,21 @@
                             sessionListener, NUM_MAX_IMAGES, imageListener);
 
                     // Converge AE
-                    waitForAeStable(previewResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+                    CameraTestUtils.waitForAeStable(previewResultListener,
+                            NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY, mStaticInfo,
+                            WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
 
                     if (mStaticInfo.isAeLockSupported()) {
                         // Lock AE if possible to improve stability
                         previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
-                        mSession.setRepeatingRequest(previewBuilder.build(), previewResultListener,
-                                mHandler);
+                        mCameraSession.setRepeatingRequest(previewBuilder.build(),
+                                previewResultListener, mHandler);
                         if (mStaticInfo.isHardwareLevelAtLeastLimited()) {
                             // Legacy mode doesn't output AE state
-                            waitForResultValue(previewResultListener, CaptureResult.CONTROL_AE_STATE,
-                                    CaptureResult.CONTROL_AE_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT);
+                            CameraTestUtils.waitForResultValue(previewResultListener,
+                                    CaptureResult.CONTROL_AE_STATE,
+                                    CaptureResult.CONTROL_AE_STATE_LOCKED,
+                                    NUM_RESULTS_WAIT_TIMEOUT, WAIT_FOR_RESULT_TIMEOUT_MS);
                         }
                     }
 
@@ -507,7 +589,7 @@
                         // Capture an image and get image data
                         startTimes[j] = SystemClock.elapsedRealtime();
                         CaptureRequest request = captureBuilder.build();
-                        mSession.capture(request, captureResultListener, mHandler);
+                        mCameraSession.capture(request, captureResultListener, mHandler);
 
                         // Wait for capture queue empty for the current request
                         sessionListener.waitForCaptureQueueEmpty(
@@ -534,7 +616,8 @@
                     }
 
                     // simulate real scenario (preview runs a bit)
-                    waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
+                    CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
+                            WAIT_FOR_RESULT_TIMEOUT_MS);
 
                     stopPreview();
                 }
@@ -558,11 +641,12 @@
                 avgDurationMs[counter] = Stat.getAverage(frameDurationMs);
             }
             finally {
-                closeImageReader();
-                closeDevice();
+                closeDefaultImageReader();
+                closeDevice(id);
+                closePreviewSurface();
             }
             counter++;
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
         }
 
         // Result will not be reported in CTS report if no summary is printed.
@@ -571,11 +655,11 @@
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.setSummary("camera_multiple_capture_result_average_latency_for_all_cameras",
                     Stat.getAverage(avgResultTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.setSummary("camera_multiple_capture_frame_duration_average_for_all_cameras",
                     Stat.getAverage(avgDurationMs), ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.submit(getInstrumentation());
+            mReportLog.submit(mInstrumentation);
         }
     }
 
@@ -600,8 +684,9 @@
                             /*highQuality*/false);
                 } finally {
                     closeReaderWriters();
-                    closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    closeDevice(id);
+                    closePreviewSurface();
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -629,8 +714,9 @@
                             /*highQuality*/false);
                 } finally {
                     closeReaderWriters();
-                    closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    closeDevice(id);
+                    closePreviewSurface();
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -657,8 +743,9 @@
                             /*requireHighQuality*/true);
                 } finally {
                     closeReaderWriters();
-                    closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    closeDevice(id);
+                    closePreviewSurface();
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -686,8 +773,9 @@
                             /*requireHighQuality*/true);
                 } finally {
                     closeReaderWriters();
-                    closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    closeDevice(id);
+                    closePreviewSurface();
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -712,8 +800,9 @@
                     reprocessingCaptureStallTestByCamera(format);
                 } finally {
                     closeReaderWriters();
-                    closeDevice();
-                    mReportLog.submit(getInstrumentation());
+                    closeDevice(id);
+                    closePreviewSurface();
+                    mReportLog.submit(mInstrumentation);
                 }
             }
         }
@@ -753,7 +842,7 @@
         for (int i = 0; i < NUM_REPROCESS_TESTED; i++) {
             mZslResultListener.drain();
             CaptureRequest reprocessRequest = reprocessReqs[i].build();
-            mSession.capture(reprocessRequest, reprocessResultListener, mHandler);
+            mCameraSession.capture(reprocessRequest, reprocessResultListener, mHandler);
             // Wait for reprocess output jpeg and result come back.
             reprocessResultListener.getCaptureResultForRequest(reprocessRequest,
                     CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
@@ -864,7 +953,7 @@
 
             // Submit the requests
             for (int i = 0; i < MAX_REPROCESS_IMAGES; i++) {
-                mSession.capture(reprocessReqs[i].build(), null, null);
+                mCameraSession.capture(reprocessReqs[i].build(), null, null);
             }
 
             // Get images
@@ -886,7 +975,7 @@
             for (int i = 0; i < MAX_REPROCESS_IMAGES; i++) {
                 startTimeMs = SystemClock.elapsedRealtime();
                 mWriter.queueInputImage(inputImages[i]);
-                mSession.capture(reprocessReqs[i].build(), null, null);
+                mCameraSession.capture(reprocessReqs[i].build(), null, null);
                 jpegImages[i] = mJpegListener.getImage(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
                 getImageLatenciesMs[i] = SystemClock.elapsedRealtime() - startTimeMs;
             }
@@ -941,12 +1030,12 @@
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
         zslBuilder.addTarget(mPreviewSurface);
         zslBuilder.addTarget(mCameraZslReader.getSurface());
-        mSession.setRepeatingRequest(zslBuilder.build(), mZslResultListener, mHandler);
+        mCameraSession.setRepeatingRequest(zslBuilder.build(), mZslResultListener, mHandler);
     }
 
     private void stopZslStreaming() throws Exception {
-        mSession.stopRepeating();
-        mSessionListener.getStateWaiter().waitForState(
+        mCameraSession.stopRepeating();
+        mCameraSessionListener.getStateWaiter().waitForState(
             BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
     }
 
@@ -1009,19 +1098,19 @@
         outSurfaces.add(mJpegReader.getSurface());
         InputConfiguration inputConfig = new InputConfiguration(maxInputSize.getWidth(),
                 maxInputSize.getHeight(), inputFormat);
-        mSessionListener = new BlockingSessionCallback();
-        mSession = CameraTestUtils.configureReprocessableCameraSession(
-                mCamera, inputConfig, outSurfaces, mSessionListener, mHandler);
+        mCameraSessionListener = new BlockingSessionCallback();
+        mCameraSession = CameraTestUtils.configureReprocessableCameraSession(
+                mCamera, inputConfig, outSurfaces, mCameraSessionListener, mHandler);
 
         // 3. Create ImageWriter for input
         mWriter = CameraTestUtils.makeImageWriter(
-                mSession.getInputSurface(), MAX_INPUT_IMAGES, /*listener*/null, /*handler*/null);
+                mCameraSession.getInputSurface(), MAX_INPUT_IMAGES, /*listener*/null, /*handler*/null);
 
     }
 
     private void blockingStopPreview() throws Exception {
         stopPreview();
-        mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED,
+        mCameraSessionListener.getStateWaiter().waitForState(SESSION_CLOSED,
                 CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
     }
 
@@ -1037,25 +1126,210 @@
             previewBuilder.addTarget(mPreviewSurface);
         }
         previewBuilder.addTarget(mReaderSurface);
-        mSession.setRepeatingRequest(previewBuilder.build(), listener, mHandler);
+        mCameraSession.setRepeatingRequest(previewBuilder.build(), listener, mHandler);
         imageListener.waitForImageAvailable(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
     }
 
     /**
+     * Setup still capture configuration and start preview.
+     *
+     * @param previewRequest The capture request to be used for preview
+     * @param stillRequest The capture request to be used for still capture
+     * @param previewSz Preview size
+     * @param captureSizes Still capture sizes
+     * @param formats The single capture image formats
+     * @param resultListener Capture result listener
+     * @param maxNumImages The max number of images set to the image reader
+     * @param imageListeners The single capture capture image listeners
+     */
+    private ImageReader[] prepareStillCaptureAndStartPreview(
+            CaptureRequest.Builder previewRequest, CaptureRequest.Builder stillRequest,
+            Size previewSz, Size[] captureSizes, int[] formats, CaptureCallback resultListener,
+            int maxNumImages, ImageReader.OnImageAvailableListener[] imageListeners)
+            throws Exception {
+
+        if ((captureSizes == null) || (formats == null) || (imageListeners == null) &&
+                (captureSizes.length != formats.length) ||
+                (formats.length != imageListeners.length)) {
+            throw new IllegalArgumentException("Invalid capture sizes/formats or image listeners!");
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Prepare still capture and preview (%s)",
+                    previewSz.toString()));
+        }
+
+        // Update preview size.
+        updatePreviewSurface(previewSz);
+
+        ImageReader[] readers = new ImageReader[captureSizes.length];
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
+        outputSurfaces.add(mPreviewSurface);
+        for (int i = 0; i < captureSizes.length; i++) {
+            readers[i] = CameraTestUtils.makeImageReader(captureSizes[i], formats[i], maxNumImages,
+                    imageListeners[i], mHandler);
+            outputSurfaces.add(readers[i].getSurface());
+        }
+
+        mCameraSessionListener = new BlockingSessionCallback();
+        mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
+                mCameraSessionListener, mHandler);
+
+        // Configure the requests.
+        previewRequest.addTarget(mPreviewSurface);
+        stillRequest.addTarget(mPreviewSurface);
+        for (int i = 0; i < readers.length; i++) {
+            stillRequest.addTarget(readers[i].getSurface());
+        }
+
+        // Start preview.
+        mCameraSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+
+        return readers;
+    }
+
+    /**
+     * Setup single capture configuration and start preview.
+     *
+     * @param previewRequest The capture request to be used for preview
+     * @param stillRequest The capture request to be used for still capture
+     * @param previewSz Preview size
+     * @param captureSz Still capture size
+     * @param format The single capture image format
+     * @param resultListener Capture result listener
+     * @param sessionListener Session listener
+     * @param maxNumImages The max number of images set to the image reader
+     * @param imageListener The single capture capture image listener
+     */
+    private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+            CaptureRequest.Builder stillRequest, Size previewSz, Size captureSz, int format,
+            CaptureCallback resultListener, CameraCaptureSession.StateCallback sessionListener,
+            int maxNumImages, ImageReader.OnImageAvailableListener imageListener) throws Exception {
+        if ((captureSz == null) || (imageListener == null)) {
+            throw new IllegalArgumentException("Invalid capture size or image listener!");
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Prepare single capture (%s) and preview (%s)",
+                    captureSz.toString(), previewSz.toString()));
+        }
+
+        // Update preview size.
+        updatePreviewSurface(previewSz);
+
+        // Create ImageReader.
+        createDefaultImageReader(captureSz, format, maxNumImages, imageListener);
+
+        // Configure output streams with preview and jpeg streams.
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
+        outputSurfaces.add(mPreviewSurface);
+        outputSurfaces.add(mReaderSurface);
+        if (sessionListener == null) {
+            mCameraSessionListener = new BlockingSessionCallback();
+        } else {
+            mCameraSessionListener = new BlockingSessionCallback(sessionListener);
+        }
+        mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
+                mCameraSessionListener, mHandler);
+
+        // Configure the requests.
+        previewRequest.addTarget(mPreviewSurface);
+        stillRequest.addTarget(mPreviewSurface);
+        stillRequest.addTarget(mReaderSurface);
+
+        // Start preview.
+        mCameraSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+    }
+
+    /**
+     * Update the preview surface size.
+     *
+     * @param size The preview size to be updated.
+     */
+    private void updatePreviewSurface(Size size) {
+        if ((mPreviewSurfaceTexture != null ) || (mPreviewSurface != null)) {
+            closePreviewSurface();
+        }
+
+        mPreviewSurfaceTexture = new SurfaceTexture(/*random int*/ 1);
+        mPreviewSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
+        mPreviewSurface = new Surface(mPreviewSurfaceTexture);
+    }
+
+    /**
+     * Release preview surface and corresponding surface texture.
+     */
+    private void closePreviewSurface() {
+        if (mPreviewSurface != null) {
+            mPreviewSurface.release();
+            mPreviewSurface = null;
+        }
+
+        if (mPreviewSurfaceTexture != null) {
+            mPreviewSurfaceTexture.release();
+            mPreviewSurfaceTexture = null;
+        }
+    }
+
+    private boolean isReprocessSupported(String cameraId, int format)
+            throws CameraAccessException {
+        if (format != ImageFormat.YUV_420_888 && format != ImageFormat.PRIVATE) {
+            throw new IllegalArgumentException(
+                    "format " + format + " is not supported for reprocessing");
+        }
+
+        StaticMetadata info = new StaticMetadata(
+                mCameraManager.getCameraCharacteristics(cameraId), CheckLevel.ASSERT,
+                /*collector*/ null);
+        int cap = CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING;
+        if (format == ImageFormat.PRIVATE) {
+            cap = CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
+        }
+        return info.isCapabilitySupported(cap);
+    }
+
+    /**
+     * Stop preview for current camera device by closing the session.
+     * Does _not_ wait for the device to go idle
+     */
+    private void stopPreview() throws Exception {
+        // Stop repeat, wait for captures to complete, and disconnect from surfaces
+        if (mCameraSession != null) {
+            if (VERBOSE) Log.v(TAG, "Stopping preview");
+            mCameraSession.close();
+        }
+    }
+
+    /**
+     * Stop preview for current camera device by closing the session and waiting for it to close,
+     * resulting in an idle device.
+     */
+    private void stopPreviewAndDrain() throws Exception {
+        // Stop repeat, wait for captures to complete, and disconnect from surfaces
+        if (mCameraSession != null) {
+            if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+            mCameraSession.close();
+            mCameraSessionListener.getStateWaiter().waitForState(
+                    BlockingSessionCallback.SESSION_CLOSED,
+                    /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+        }
+    }
+
+    /**
      * Configure reader and preview outputs and wait until done.
      */
     private void configureReaderAndPreviewOutputs() throws Exception {
         if (mPreviewSurface == null || mReaderSurface == null) {
             throw new IllegalStateException("preview and reader surface must be initilized first");
         }
-        mSessionListener = new BlockingSessionCallback();
+        mCameraSessionListener = new BlockingSessionCallback();
         List<Surface> outputSurfaces = new ArrayList<>();
         if (mStaticInfo.isColorOutputSupported()) {
             outputSurfaces.add(mPreviewSurface);
         }
         outputSurfaces.add(mReaderSurface);
-        mSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
-                mSessionListener, mHandler);
+        mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
+                mCameraSessionListener, mHandler);
     }
 
     /**
@@ -1069,7 +1343,7 @@
                 CameraTestUtils.getPreviewSizeBound(mWindowManager,
                     CameraTestUtils.PREVIEW_SIZE_BOUND));
         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
-        createImageReader(maxPreviewSize, format, NUM_MAX_IMAGES, /*listener*/null);
+        createDefaultImageReader(maxPreviewSize, format, NUM_MAX_IMAGES, /*listener*/null);
         updatePreviewSurface(maxPreviewSize);
     }
 
@@ -1079,8 +1353,6 @@
         mCollector.setCameraId(cameraId);
         mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
                 CheckLevel.ASSERT, /*collector*/null);
-        mMinPreviewFrameDurationMap =
-                mStaticInfo.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.YUV_420_888);
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 651b8b6..35507e9 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -22,8 +22,11 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.HardwareBuffer;
 import android.util.Size;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.media.CamcorderProfile;
@@ -33,13 +36,13 @@
 import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.media.Image;
 import android.media.ImageReader;
+import android.media.ImageWriter;
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaRecorder;
 import android.os.Environment;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.util.Range;
@@ -50,6 +53,8 @@
 
 import junit.framework.AssertionFailedError;
 
+import org.junit.Test;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -61,7 +66,6 @@
  * CameraDevice video recording use case tests by using MediaRecorder and
  * MediaCodec.
  */
-@AppModeFull
 @LargeTest
 public class RecordingTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "RecordingTest";
@@ -78,7 +82,6 @@
     private static final int BIT_RATE_MIN = 64000;
     private static final int BIT_RATE_MAX = 40000000;
     private static final int VIDEO_FRAME_RATE = 30;
-    private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
     private static final int[] mCamcorderProfileList = {
             CamcorderProfile.QUALITY_HIGH,
             CamcorderProfile.QUALITY_2160P,
@@ -103,46 +106,58 @@
     private Size mVideoSize;
     private long mRecordingStartTime;
 
+    private Surface mIntermediateSurface;
+    private ImageReader mIntermediateReader;
+    private ImageWriter mIntermediateWriter;
+    private ImageWriterQueuer mQueuer;
+
     @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();
     }
 
     private void doBasicRecording(boolean useVideoStab) throws Exception {
+        doBasicRecording(useVideoStab, false);
+    }
+
+    private void doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface)
+            throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
                 // External camera doesn't support CamcorderProfile recording
-                if (mStaticInfo.isExternalCamera()) {
+                if (staticInfo.isExternalCamera()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support CamcorderProfile, skipping");
                     continue;
                 }
 
-                if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
+                if (!staticInfo.isVideoStabilizationSupported() && useVideoStab) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support video stabilization, skipping the stabilization"
                             + " test");
                     continue;
                 }
 
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIds[i]);
                 initSupportedVideoSize(mCameraIds[i]);
 
-                basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab);
+                basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab,
+                        useIntermediateSurface);
             } finally {
                 closeDevice();
                 releaseRecorder();
@@ -163,6 +178,7 @@
      * recorded video. Preview is set to the video size.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testBasicVideoStabilizationRecording() throws Exception {
         doBasicRecording(/*useVideoStab*/true);
     }
@@ -179,12 +195,28 @@
      * recorded video. Preview is set to the video size.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testBasicRecording() throws Exception {
         doBasicRecording(/*useVideoStab*/false);
     }
 
     /**
      * <p>
+     * Test camera recording with intermediate surface.
+     * </p>
+     * <p>
+     * This test is similar to testBasicRecording with a tweak where an intermediate
+     * surface is setup between camera and MediaRecorder, giving application a chance
+     * to decide whether to send a frame to recorder or not.
+     * </p>
+     */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
+    public void testIntermediateSurfaceRecording() throws Exception {
+        doBasicRecording(/*useVideoStab*/false, /*useIntermediateSurface*/true);
+    }
+
+    /**
+     * <p>
      * Test basic camera recording from a persistent input surface.
      * </p>
      * <p>
@@ -192,6 +224,7 @@
      * from a persistent input surface that's used across multiple recording sessions.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testRecordingFromPersistentSurface() throws Exception {
         if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) {
             return; // skipped
@@ -217,18 +250,20 @@
      * validated according to the recording configuration.
      * </p>
      */
+    @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++) {
             try {
                 Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIds[i]);
+
                 initSupportedVideoSize(mCameraIds[i]);
 
                 recordingSizeTestByCamera();
@@ -244,6 +279,7 @@
      *
      * <p>The recording should be working fine for any kind of start/stop orders.</p>
      */
+    @Test
     public void testCameraRecorderOrdering() {
         // TODO: need implement
     }
@@ -258,6 +294,7 @@
      * validated according to the recording configuration.
      * </p>
      */
+    @Test
     public void testMediaCodecRecording() throws Exception {
         // TODO. Need implement.
     }
@@ -274,6 +311,7 @@
      * checked to make sure no frame drop caused by video snapshot.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testVideoSnapshot() throws Exception {
         videoSnapshotHelper(/*burstTest*/false);
     }
@@ -290,6 +328,7 @@
      * configuration.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testBurstVideoSnapshot() throws Exception {
         videoSnapshotHelper(/*burstTest*/true);
     }
@@ -297,36 +336,42 @@
     /**
      * Test timelapse recording, where capture rate is slower than video (playback) frame rate.
      */
+    @Test
     public void testTimelapseRecording() throws Exception {
         // TODO. Need implement.
     }
 
+    @Test
     public void testSlowMotionRecording() throws Exception {
         slowMotionRecording();
     }
 
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testConstrainedHighSpeedRecording() throws Exception {
         constrainedHighSpeedRecording();
     }
 
+    @Test
     public void testAbandonedHighSpeedRequest() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing bad suface for createHighSpeedRequestList for camera " + id);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) {
+                if (!staticInfo.isConstrainedHighSpeedVideoSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " does not support constrained high speed video, skipping");
                     continue;
                 }
 
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(id);
+
                 StreamConfigurationMap config =
                         mStaticInfo.getValueFromKeyNonNull(
                                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -348,7 +393,7 @@
                     continue;
                 }
 
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+                mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
                 prepareRecording(size, videoFramerate, captureRate);
                 updatePreviewSurfaceWithVideo(size, captureRate);
 
@@ -428,23 +473,26 @@
      * profile of highest framerate. Make sure that the video framerate are still accurate.
      * </p>
      */
+    @Test
     public void testRecordingFramerateLowToHigh() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (mStaticInfo.isExternalCamera()) {
+                if (staticInfo.isExternalCamera()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support CamcorderProfile, skipping");
                     continue;
                 }
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIds[i]);
+
                 initSupportedVideoSize(mCameraIds[i]);
 
                 int minFpsProfileId = -1, minFps = 1000;
@@ -480,21 +528,23 @@
      * Test preview and video surfaces sharing the same camera stream.
      * </p>
      */
+    @Test
     public void testVideoPreviewSurfaceSharing() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIds[i]);
 
                 initSupportedVideoSize(mCameraIds[i]);
 
@@ -520,10 +570,10 @@
             }
 
             // Configure preview and recording surfaces.
-            mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_share.mp4";
+            mOutMediaFileName = mDebugFileNameBase + "/test_video_share.mp4";
             if (DEBUG_DUMP) {
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_share_" + mCamera.getId() + "_"
-                        + sz.toString() + ".mp4";
+                mOutMediaFileName = mDebugFileNameBase + "/test_video_share_" + mCamera.getId() +
+                    "_" + sz.toString() + ".mp4";
             }
 
             // Allow external camera to use variable fps range
@@ -601,18 +651,20 @@
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing slow motion recording for camera " + id);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isHighSpeedVideoSupported()) {
+                if (!staticInfo.isHighSpeedVideoSupported()) {
                     continue;
                 }
 
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(id);
+
                 StreamConfigurationMap config =
                         mStaticInfo.getValueFromKeyNonNull(
                                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -634,9 +686,9 @@
                         continue;
                     }
 
-                    mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video.mp4";
+                    mOutMediaFileName = mDebugFileNameBase + "/test_slowMo_video.mp4";
                     if (DEBUG_DUMP) {
-                        mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video_" + id + "_"
+                        mOutMediaFileName = mDebugFileNameBase + "/test_slowMo_video_" + id + "_"
                                 + size.toString() + ".mp4";
                     }
 
@@ -674,15 +726,16 @@
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing constrained high speed recording for camera " + id);
-                // Re-use the MediaRecorder object for the same camera device.
-                mMediaRecorder = new MediaRecorder();
-                openDevice(id);
 
-                if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) {
+                if (!mAllStaticInfo.get(id).isConstrainedHighSpeedVideoSupported()) {
                     Log.i(TAG, "Camera " + id + " doesn't support high speed recording, skipping.");
                     continue;
                 }
 
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(id);
+
                 StreamConfigurationMap config =
                         mStaticInfo.getValueFromKeyNonNull(
                                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -710,8 +763,8 @@
 
                         startConstrainedPreview(fpsRange, previewResultListener);
 
-                        mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
-                                "fps_" + id + "_" + size.toString() + ".mp4";
+                        mOutMediaFileName = mDebugFileNameBase + "/test_cslowMo_video_" +
+                            captureRate + "fps_" + id + "_" + size.toString() + ".mp4";
 
                         prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
 
@@ -816,6 +869,10 @@
         requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
         requestBuilder.addTarget(mPreviewSurface);
         CaptureRequest initialRequest = requestBuilder.build();
+        assertTrue("Constrained session configuration query failed",
+                CameraTestUtils.checkSessionConfigurationWithSurfaces(mCamera, mHandler,
+                outputSurfaces, /*inputConfig*/ null, SessionConfiguration.SESSION_HIGH_SPEED,
+                /*expectedResult*/ true));
         mSession = buildConstrainedCameraSession(mCamera, outputSurfaces, mSessionListener,
                 mHandler, initialRequest);
         slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
@@ -897,12 +954,17 @@
 
     }
 
+    private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)
+            throws Exception {
+        basicRecordingTestByCamera(camcorderProfileList, useVideoStab, false);
+    }
+
     /**
      * Test camera recording by using each available CamcorderProfile for a
      * given camera. preview size is set to the video size.
      */
-    private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)
-            throws Exception {
+    private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab,
+            boolean useIntermediateSurface) throws Exception {
         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
         List<Range<Integer> > fpsRanges = Arrays.asList(
                 mStaticInfo.getAeAvailableTargetFpsRangesChecked());
@@ -942,26 +1004,27 @@
             }
 
             // Configure preview and recording surfaces.
-            mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+            mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
             if (DEBUG_DUMP) {
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+                mOutMediaFileName = mDebugFileNameBase + "/test_video_" + cameraId + "_"
                         + videoSz.toString() + ".mp4";
             }
 
-            prepareRecordingWithProfile(profile);
+            prepareRecordingWithProfile(profile, useIntermediateSurface);
 
             // prepare preview surface by using video size.
             updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
 
             // Start recording
             SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
-            startRecording(/* useMediaRecorder */true, resultListener, useVideoStab);
+            startRecording(/* useMediaRecorder */true, resultListener, useVideoStab,
+                    useIntermediateSurface);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
-            stopRecording(/* useMediaRecorder */true);
+            stopRecording(/* useMediaRecorder */true, useIntermediateSurface);
             // Convert number of frames camera produced into the duration in unit of ms.
             float frameDurationMs = 1000.0f / profile.videoFrameRate;
             float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
@@ -996,9 +1059,9 @@
             }
 
             // Configure preview and recording surfaces.
-            mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+            mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
             if (DEBUG_DUMP) {
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + mCamera.getId() + "_"
+                mOutMediaFileName = mDebugFileNameBase + "/test_video_" + mCamera.getId() + "_"
                         + sz.toString() + ".mp4";
             }
 
@@ -1031,7 +1094,7 @@
             SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
             startRecording(
                     /* useMediaRecorder */true, resultListener,
-                    /*useVideoStab*/false, fpsRange);
+                    /*useVideoStab*/false, fpsRange, false);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
@@ -1074,23 +1137,25 @@
             for (String id : mCameraIds) {
                 try {
                     Log.i(TAG, "Testing video snapshot for camera " + id);
-                    // Re-use the MediaRecorder object for the same camera device.
-                    mMediaRecorder = new MediaRecorder();
 
-                    openDevice(id);
-
-                    if (!mStaticInfo.isColorOutputSupported()) {
+                    StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                    if (!staticInfo.isColorOutputSupported()) {
                         Log.i(TAG, "Camera " + id +
                                 " does not support color outputs, skipping");
                         continue;
                     }
 
-                    if (mStaticInfo.isExternalCamera()) {
+                    if (staticInfo.isExternalCamera()) {
                         Log.i(TAG, "Camera " + id +
                                 " does not support CamcorderProfile, skipping");
                         continue;
                     }
 
+                    // Re-use the MediaRecorder object for the same camera device.
+                    mMediaRecorder = new MediaRecorder();
+
+                    openDevice(id);
+
                     initSupportedVideoSize(id);
 
                     videoSnapshotTestByCamera(burstTest);
@@ -1258,9 +1323,9 @@
             }
 
             // Configure preview and recording surfaces.
-            mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+            mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
             if (DEBUG_DUMP) {
-                mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+                mOutMediaFileName = mDebugFileNameBase + "/test_video_" + cameraId + "_"
                         + videoSz.toString() + ".mp4";
             }
 
@@ -1431,12 +1496,16 @@
         updatePreviewSurface(previewSize);
     }
 
+    private void prepareRecordingWithProfile(CamcorderProfile profile) throws Exception {
+        prepareRecordingWithProfile(profile, false);
+    }
+
     /**
      * Configure MediaRecorder recording session with CamcorderProfile, prepare
      * the recording surface.
      */
-    private void prepareRecordingWithProfile(CamcorderProfile profile)
-            throws Exception {
+    private void prepareRecordingWithProfile(CamcorderProfile profile,
+            boolean useIntermediateSurface) throws Exception {
         // Prepare MediaRecorder.
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
@@ -1453,6 +1522,19 @@
         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
         mVideoFrameRate = profile.videoFrameRate;
         mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+
+        if (useIntermediateSurface) {
+            mIntermediateReader = ImageReader.newInstance(
+                    profile.videoFrameWidth, profile.videoFrameHeight,
+                    ImageFormat.PRIVATE, /*maxImages*/3, HardwareBuffer.USAGE_VIDEO_ENCODE);
+
+            mIntermediateSurface = mIntermediateReader.getSurface();
+            mIntermediateWriter = ImageWriter.newInstance(mRecordingSurface, /*maxImages*/3,
+                    ImageFormat.PRIVATE);
+            mQueuer = new ImageWriterQueuer(mIntermediateWriter);
+
+            mIntermediateReader.setOnImageAvailableListener(mQueuer, mHandler);
+        }
     }
 
     /**
@@ -1488,12 +1570,20 @@
 
     private void startRecording(boolean useMediaRecorder,
             CameraCaptureSession.CaptureCallback listener, boolean useVideoStab) throws Exception {
-        startRecording(useMediaRecorder, listener, useVideoStab, /*variableFpsRange*/null);
+        startRecording(useMediaRecorder, listener, useVideoStab, /*variableFpsRange*/null,
+                /*useIntermediateSurface*/false);
     }
 
     private void startRecording(boolean useMediaRecorder,
             CameraCaptureSession.CaptureCallback listener, boolean useVideoStab,
-            Range<Integer> variableFpsRange) throws Exception {
+            boolean useIntermediateSurface) throws Exception {
+        startRecording(useMediaRecorder, listener, useVideoStab, /*variableFpsRange*/null,
+                useIntermediateSurface);
+    }
+
+    private void startRecording(boolean useMediaRecorder,
+            CameraCaptureSession.CaptureCallback listener, boolean useVideoStab,
+            Range<Integer> variableFpsRange, boolean useIntermediateSurface) throws Exception {
         if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
             throw new IllegalArgumentException("Video stabilization is not supported");
         }
@@ -1502,7 +1592,12 @@
         assertTrue("Both preview and recording surfaces should be valid",
                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
         outputSurfaces.add(mPreviewSurface);
-        outputSurfaces.add(mRecordingSurface);
+        if (useIntermediateSurface) {
+            outputSurfaces.add(mIntermediateSurface);
+        } else {
+            outputSurfaces.add(mRecordingSurface);
+        }
+
         // Video snapshot surface
         if (mReaderSurface != null) {
             outputSurfaces.add(mReaderSurface);
@@ -1520,7 +1615,11 @@
             recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
                     CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
         }
-        recordingRequestBuilder.addTarget(mRecordingSurface);
+        if (useIntermediateSurface) {
+            recordingRequestBuilder.addTarget(mIntermediateSurface);
+        } else {
+            recordingRequestBuilder.addTarget(mRecordingSurface);
+        }
         recordingRequestBuilder.addTarget(mPreviewSurface);
         CaptureRequest recordingRequest = recordingRequestBuilder.build();
         mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, mSessionListener,
@@ -1600,8 +1699,13 @@
         mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
     }
 
-    // Stop recording and return the estimated video duration in milliseconds.
     private int stopRecording(boolean useMediaRecorder) throws Exception {
+        return stopRecording(useMediaRecorder, false);
+    }
+
+    // Stop recording and return the estimated video duration in milliseconds.
+    private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface)
+            throws Exception {
         long stopRecordingTime = SystemClock.elapsedRealtime();
         if (useMediaRecorder) {
             stopCameraStreaming();
@@ -1616,6 +1720,16 @@
             mRecordingSurface.release();
             mRecordingSurface = null;
         }
+        if (useIntermediateSurface) {
+            mIntermediateSurface.release();
+            mIntermediateReader.close();
+            mIntermediateWriter.close();
+            mQueuer.close();
+            mIntermediateSurface = null;
+            mIntermediateReader = null;
+            mIntermediateWriter = null;
+            mQueuer = null;
+        }
         return (int) (stopRecordingTime - mRecordingStartTime);
     }
 
@@ -1924,4 +2038,28 @@
         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         return mcl.findEncoderForFormat(format) != null;
     }
+
+    private static class ImageWriterQueuer implements ImageReader.OnImageAvailableListener {
+        public ImageWriterQueuer(ImageWriter writer) {
+            mWriter = writer;
+        }
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image image = null;
+            try {
+                image = reader.acquireNextImage();
+            } finally {
+                if (image != null && mWriter != null) {
+                    mWriter.queueInputImage(image);
+                }
+            }
+        }
+
+        public void close() {
+            mWriter = null;
+        }
+
+        private ImageWriter mWriter = null;
+    }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
index f30ca92..8fa1077 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
@@ -32,7 +32,6 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.InputConfiguration;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -44,10 +43,11 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.junit.Test;
+
 /**
  * <p>Tests for Reprocess API.</p>
  */
-@AppModeFull
 public class ReprocessCaptureTest extends Camera2SurfaceViewTestCase  {
     private static final String TAG = "ReprocessCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -88,6 +88,7 @@
     /**
      * Test YUV_420_888 -> YUV_420_888 with maximal supported sizes
      */
+    @Test
     public void testBasicYuvToYuvReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id)) {
@@ -102,6 +103,7 @@
     /**
      * Test YUV_420_888 -> JPEG with maximal supported sizes
      */
+    @Test
     public void testBasicYuvToJpegReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id)) {
@@ -116,6 +118,7 @@
     /**
      * Test OPAQUE -> YUV_420_888 with maximal supported sizes
      */
+    @Test
     public void testBasicOpaqueToYuvReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isOpaqueReprocessSupported(id)) {
@@ -130,6 +133,7 @@
     /**
      * Test OPAQUE -> JPEG with maximal supported sizes
      */
+    @Test
     public void testBasicOpaqueToJpegReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isOpaqueReprocessSupported(id)) {
@@ -144,6 +148,7 @@
     /**
      * Test all supported size and format combinations.
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testReprocessingSizeFormat() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -165,6 +170,7 @@
     /**
      * Test all supported size and format combinations with preview.
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testReprocessingSizeFormatWithPreview() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -185,6 +191,7 @@
     /**
      * Test recreating reprocessing sessions.
      */
+    @Test
     public void testRecreateReprocessingSessions() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -222,6 +229,7 @@
     /**
      * Verify issuing cross session capture requests is invalid.
      */
+    @Test
     public void testCrossSessionCaptureException() throws Exception {
         for (String id : mCameraIds) {
             // Test one supported input format -> JPEG
@@ -291,6 +299,7 @@
     /**
      * Test burst reprocessing captures with and without preview.
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testBurstReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -314,6 +323,7 @@
     /**
      * Test burst captures mixed with regular and reprocess captures with and without preview.
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testMixedBurstReprocessing() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -339,6 +349,7 @@
      * Test aborting reprocess capture requests of the largest input and output sizes for each
      * supported format.
      */
+    @Test
     public void testReprocessAbort() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -368,6 +379,7 @@
     /**
      * Test reprocess timestamps for the largest input and output sizes for each supported format.
      */
+    @Test
     public void testReprocessTimestamps() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -398,6 +410,7 @@
      * Test reprocess jpeg output's exif data for the largest input and output sizes for each
      * supported format.
      */
+    @Test
     public void testReprocessJpegExif() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -428,6 +441,7 @@
         }
     }
 
+    @Test
     public void testReprocessRequestKeys() throws Exception {
         for (String id : mCameraIds) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
@@ -963,7 +977,8 @@
                 // Verify output image's and result's JPEG EXIF data.
                 Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
                 verifyJpegKeys(image, reprocessResults[i], reprocessOutputSize,
-                        testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mCollector);
+                        testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mCollector,
+                        mDebugFileNameBase);
                 image.close();
 
             }
@@ -1371,7 +1386,7 @@
     }
 
     private void dumpImage(Image image, String name) {
-        String filename = DEBUG_FILE_NAME_BASE + name;
+        String filename = mDebugFileNameBase + name;
         switch(image.getFormat()) {
             case ImageFormat.JPEG:
                 filename += ".jpg";
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index 47020ed..995a2c7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -35,12 +35,14 @@
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OisSample;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.MandatoryStreamCombination;
+import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Size;
 import android.view.Display;
@@ -62,7 +64,6 @@
 /**
  * Tests exercising edge cases in camera setup, configuration, and usage.
  */
-@AppModeFull
 public class RobustnessTest extends Camera2AndroidTestCase {
     private static final String TAG = "RobustnessTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -161,10 +162,10 @@
     }
 
     /**
-     * Test for making sure the required output combinations for each hardware level and capability
-     * work as expected.
+     * Test for making sure the required logical output combinations for each hardware level and
+     * capability work as expected.
      */
-    public void testMandatoryOutputCombinations() throws Exception {
+    public void testMandatoryLogicalOutputCombinations() throws Exception {
         /**
          * Tables for maximum sizes to try for each hardware level and capability.
          *
@@ -174,59 +175,6 @@
          * Each row of the table is a set of (format, max resolution) pairs, using the below consts
          */
 
-        // Enum values are defined in MaxStreamSizes
-        final int[][] LEGACY_COMBINATIONS = {
-            // Simple preview, GPU video processing, or no-preview video recording
-            {PRIV, MAXIMUM},
-            // No-viewfinder still image capture
-            {JPEG, MAXIMUM},
-            // In-application video/image processing
-            {YUV,  MAXIMUM},
-            // Standard still imaging.
-            {PRIV, PREVIEW,  JPEG, MAXIMUM},
-            // In-app processing plus still capture.
-            {YUV,  PREVIEW,  JPEG, MAXIMUM},
-            // Standard recording.
-            {PRIV, PREVIEW,  PRIV, PREVIEW},
-            // Preview plus in-app processing.
-            {PRIV, PREVIEW,  YUV,  PREVIEW},
-            // Still capture plus in-app processing.
-            {PRIV, PREVIEW,  YUV,  PREVIEW,  JPEG, MAXIMUM}
-        };
-
-        final int[][] LIMITED_COMBINATIONS = {
-            // High-resolution video recording with preview.
-            {PRIV, PREVIEW,  PRIV, RECORD },
-            // High-resolution in-app video processing with preview.
-            {PRIV, PREVIEW,  YUV , RECORD },
-            // Two-input in-app video processing.
-            {YUV , PREVIEW,  YUV , RECORD },
-            // High-resolution recording with video snapshot.
-            {PRIV, PREVIEW,  PRIV, RECORD,   JPEG, RECORD  },
-            // High-resolution in-app processing with video snapshot.
-            {PRIV, PREVIEW,  YUV,  RECORD,   JPEG, RECORD  },
-            // Two-input in-app processing with still capture.
-            {YUV , PREVIEW,  YUV,  PREVIEW,  JPEG, MAXIMUM }
-        };
-
-        final int[][] BURST_COMBINATIONS = {
-            // Maximum-resolution GPU processing with preview.
-            {PRIV, PREVIEW,  PRIV, MAXIMUM },
-            // Maximum-resolution in-app processing with preview.
-            {PRIV, PREVIEW,  YUV,  MAXIMUM },
-            // Maximum-resolution two-input in-app processsing.
-            {YUV,  PREVIEW,  YUV,  MAXIMUM },
-        };
-
-        final int[][] FULL_COMBINATIONS = {
-            // Video recording with maximum-size video snapshot.
-            {PRIV, PREVIEW,  PRIV, PREVIEW,  JPEG, MAXIMUM },
-            // Standard video recording plus maximum-resolution in-app processing.
-            {YUV,  VGA,      PRIV, PREVIEW,  YUV,  MAXIMUM },
-            // Preview plus two-input maximum-resolution in-app processing.
-            {YUV,  VGA,      YUV,  PREVIEW,  YUV,  MAXIMUM }
-        };
-
         final int[][] RAW_COMBINATIONS = {
             // No-preview DNG capture.
             {RAW,  MAXIMUM },
@@ -246,16 +194,7 @@
             {YUV,  PREVIEW,  JPEG, MAXIMUM,  RAW, MAXIMUM}
         };
 
-        final int[][] LEVEL_3_COMBINATIONS = {
-            // In-app viewfinder analysis with dynamic selection of output format
-            {PRIV, PREVIEW, PRIV, VGA, YUV, MAXIMUM, RAW, MAXIMUM},
-            // In-app viewfinder analysis with dynamic selection of output format
-            {PRIV, PREVIEW, PRIV, VGA, JPEG, MAXIMUM, RAW, MAXIMUM}
-        };
-
-        final int[][][] TABLES =
-                { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS,
-                  RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
+        final int[][][] TABLES = { RAW_COMBINATIONS };
 
         sanityCheckConfigurationTables(TABLES);
 
@@ -272,58 +211,13 @@
                 Log.v(TAG, "StreamConfigurationMap: " + streamConfigurationMapString);
             }
 
-            // Always run legacy-level tests for color-supporting devices
-
-            if (mStaticInfo.isColorOutputSupported()) {
-                for (int[] config : LEGACY_COMBINATIONS) {
-                    testOutputCombination(id, config, maxSizes);
-                }
-            }
-
             // Then run higher-level tests if applicable
-
             if (!mStaticInfo.isHardwareLevelLegacy()) {
-
-                // If not legacy, at least limited, so run limited-level tests
-
-                if (mStaticInfo.isColorOutputSupported()) {
-                    for (int[] config : LIMITED_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                // Check for BURST_CAPTURE, FULL and RAW and run those if appropriate
-
-                if (mStaticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
-                    for (int[] config : BURST_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                if (mStaticInfo.isHardwareLevelAtLeastFull()) {
-                    for (int[] config : FULL_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
-
-                if (mStaticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    for (int[] config : RAW_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                } else if (mStaticInfo.isLogicalMultiCamera()) {
+                if (mStaticInfo.isLogicalMultiCamera()) {
                     for (int[] config : RAW_COMBINATIONS) {
                         testMultiCameraOutputCombination(id, config, maxSizes);
                     }
                 }
-
-                if (mStaticInfo.isHardwareLevelAtLeast(
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
-                    for (int[] config: LEVEL_3_COMBINATIONS) {
-                        testOutputCombination(id, config, maxSizes);
-                    }
-                }
             }
 
             closeDevice(id);
@@ -331,105 +225,24 @@
     }
 
     /**
-     * Test for making sure the required reprocess input/output combinations for each hardware
-     * level and capability work as expected.
+     * Test for making sure the mandatory stream combinations work as expected.
      */
-    public void testMandatoryReprocessConfigurations() throws Exception {
-
-        /**
-         * For each stream combination, verify that
-         *    1. A reprocessable session can be created using the stream combination.
-         *    2. Reprocess capture requests targeting YUV and JPEG outputs are successful.
-         */
-        final int[][] LIMITED_COMBINATIONS = {
-            // Input           Outputs
-            {PRIV, MAXIMUM,    JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV,  MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-        };
-
-        final int[][] FULL_COMBINATIONS = {
-            // Input           Outputs
-            {YUV , MAXIMUM,    PRIV, PREVIEW},
-            {YUV , MAXIMUM,    YUV , PREVIEW},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , RECORD},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , RECORD},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
-        };
-
-        final int[][] RAW_COMBINATIONS = {
-            // Input           Outputs
-            {PRIV, MAXIMUM,    YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {PRIV, MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-            {YUV , MAXIMUM,    YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
-        };
-
-        final int[][] LEVEL_3_COMBINATIONS = {
-            // Input          Outputs
-            // In-app viewfinder analysis with YUV->YUV ZSL and RAW
-            {YUV , MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM},
-            // In-app viewfinder analysis with PRIV->JPEG ZSL and RAW
-            {PRIV, MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
-            // In-app viewfinder analysis with YUV->JPEG ZSL and RAW
-            {YUV , MAXIMUM,   PRIV, PREVIEW, PRIV, VGA, RAW, MAXIMUM, JPEG, MAXIMUM},
-        };
-
-        final int[][][] TABLES =
-                { LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
-
-        sanityCheckConfigurationTables(TABLES);
-
+    public void testMandatoryOutputCombinations() throws Exception {
         for (String id : mCameraIds) {
-            CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(id);
-            StaticMetadata staticInfo = new StaticMetadata(cc);
-            MaxStreamSizes maxSizes = new MaxStreamSizes(staticInfo, id, getContext());
-
-            // Skip the test for legacy devices.
-            if (staticInfo.isHardwareLevelLegacy()) {
+            openDevice(id);
+            MandatoryStreamCombination[] combinations =
+                    mStaticInfo.getCharacteristics().get(
+                            CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+            if (combinations == null) {
+                Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+                closeDevice(id);
                 continue;
             }
 
-            openDevice(id);
-
             try {
-                for (int[] config : LIMITED_COMBINATIONS) {
-                    testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                }
-
-                // Check FULL devices
-                if (staticInfo.isHardwareLevelAtLeastFull()) {
-                    for (int[] config : FULL_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                    }
-                }
-
-                // Check devices with RAW capability.
-                if (staticInfo.isCapabilitySupported(
-                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    for (int[] config : RAW_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
-                    }
-                }
-
-                if (mStaticInfo.isHardwareLevelAtLeast(
-                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
-                    for (int[] config: LEVEL_3_COMBINATIONS) {
-                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
+                for (MandatoryStreamCombination combination : combinations) {
+                    if (!combination.isReprocessable()) {
+                        testMandatoryStreamCombination(id, combination);
                     }
                 }
             } finally {
@@ -438,24 +251,587 @@
         }
     }
 
+    private void setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo,
+            List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
+            List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
+            List<ImageReader> rawTargets, List<OutputConfiguration> outputConfigs,
+            int numBuffers, boolean substituteY8, MandatoryStreamInformation overrideStreamInfo,
+            List<String> overridePhysicalCameraIds, List<Size> overridePhysicalCameraSizes) {
+
+        ImageDropperListener imageDropperListener = new ImageDropperListener();
+
+        for (MandatoryStreamInformation streamInfo : streamsInfo) {
+            if (streamInfo.isInput()) {
+                continue;
+            }
+            int format = streamInfo.getFormat();
+            if (substituteY8 && (format == ImageFormat.YUV_420_888)) {
+                format = ImageFormat.Y8;
+            }
+            Surface newSurface;
+            Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
+            availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
+            Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
+
+            int numConfigs = 1;
+            if ((overrideStreamInfo == streamInfo) && overridePhysicalCameraIds != null &&
+                    overridePhysicalCameraIds.size() > 1) {
+                numConfigs = overridePhysicalCameraIds.size();
+            }
+            for (int j = 0; j < numConfigs; j++) {
+                targetSize = (numConfigs == 1) ? targetSize : overridePhysicalCameraSizes.get(j);
+                switch (format) {
+                    case ImageFormat.PRIVATE: {
+                        SurfaceTexture target = new SurfaceTexture(/*random int*/1);
+                        target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
+                        OutputConfiguration config = new OutputConfiguration(new Surface(target));
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        privTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.JPEG: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        jpegTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.YUV_420_888: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        yuvTargets.add(target);
+                        break;
+                    }
+                    case ImageFormat.Y8: {
+                        ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                targetSize.getHeight(), format, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        y8Targets.add(target);
+                        break;
+                    }
+                    case ImageFormat.RAW_SENSOR: {
+                        // targetSize could be null in the logical camera case where only
+                        // physical camera supports RAW stream.
+                        if (targetSize != null) {
+                            ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                                    targetSize.getHeight(), format, numBuffers);
+                            target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                            OutputConfiguration config =
+                                    new OutputConfiguration(target.getSurface());
+                            if (numConfigs > 1) {
+                                config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                            }
+                            outputConfigs.add(config);
+                            rawTargets.add(target);
+                        }
+                        break;
+                    }
+                    default:
+                        fail("Unknown output format " + format);
+                }
+            }
+        }
+    }
+
+    private void testMandatoryStreamCombination(String cameraId,
+            MandatoryStreamCombination combination) throws Exception {
+        // Check whether substituting YUV_888 format with Y8 format
+        boolean substituteY8 = false;
+        if (mStaticInfo.isMonochromeWithY8()) {
+            List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+            for (MandatoryStreamInformation streamInfo : streamsInfo) {
+                if (streamInfo.getFormat() == ImageFormat.YUV_420_888) {
+                    substituteY8 = true;
+                    break;
+                }
+            }
+        }
+
+        // Test camera output combination
+        Log.i(TAG, "Testing mandatory stream combination: " + combination.getDescription() +
+                " on camera: " + cameraId);
+        testMandatoryStreamCombination(cameraId, combination, /*substituteY8*/false);
+
+        if (substituteY8) {
+            testMandatoryStreamCombination(cameraId, combination, substituteY8);
+        }
+
+        // Test substituting YUV_888/RAW with physical streams for logical camera
+        if (mStaticInfo.isLogicalMultiCamera()) {
+            Log.i(TAG, String.format("Testing logical Camera %s, combination: %s",
+                    cameraId, combination.getDescription()));
+
+            testMultiCameraOutputCombination(cameraId, combination, /*substituteY8*/false);
+
+            if (substituteY8) {
+                testMultiCameraOutputCombination(cameraId, combination, substituteY8);
+            }
+        }
+    }
+
+    private void testMultiCameraOutputCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) throws Exception {
+
+        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
+        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
+        final int MIN_RESULT_COUNT = 3;
+        Set<String> physicalCameraIds = mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+
+        List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+        for (MandatoryStreamInformation streamInfo : streamsInfo) {
+            int format = streamInfo.getFormat();
+            if (substituteY8 && (format == ImageFormat.YUV_420_888)) {
+                format = ImageFormat.Y8;
+            }
+            if (format != ImageFormat.YUV_420_888 && format != ImageFormat.Y8 &&
+                    format != ImageFormat.RAW_SENSOR) {
+                continue;
+            }
+
+            // Find physical cameras with matching size.
+            Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
+            availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
+            Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
+
+            List<String> physicalCamerasForSize = new ArrayList<String>();
+            List<Size> physicalCameraSizes = new ArrayList<Size>();
+            for (String physicalId : physicalCameraIds) {
+                Size[] sizes = mAllStaticInfo.get(physicalId).getAvailableSizesForFormatChecked(
+                        format, StaticMetadata.StreamDirection.Output);
+                if (targetSize != null) {
+                    if (Arrays.asList(sizes).contains(targetSize)) {
+                        physicalCameraSizes.add(targetSize);
+                        physicalCamerasForSize.add(physicalId);
+                    }
+                } else if (format == ImageFormat.RAW_SENSOR && sizes.length > 0) {
+                    physicalCamerasForSize.add(physicalId);
+                    physicalCameraSizes.add(CameraTestUtils.getMaxSize(sizes));
+                }
+                if (physicalCamerasForSize.size() == 2) {
+                    break;
+                }
+            }
+            if (physicalCamerasForSize.size() < 2) {
+                continue;
+            }
+
+            // Set up outputs
+            List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+            List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
+            List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
+            List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
+            List<ImageReader> y8Targets = new ArrayList<ImageReader>();
+            List<ImageReader> rawTargets = new ArrayList<ImageReader>();
+
+            setupConfigurationTargets(streamsInfo, privTargets, jpegTargets, yuvTargets,
+                    y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, substituteY8,
+                    streamInfo, physicalCamerasForSize, physicalCameraSizes);
+
+            boolean haveSession = false;
+            try {
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+                for (OutputConfiguration c : outputConfigs) {
+                    requestBuilder.addTarget(c.getSurface());
+                }
+
+                CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                        mock(CameraCaptureSession.CaptureCallback.class);
+
+                assertTrue(String.format("Session configuration query %s failed",
+                        combination.getDescription()),
+                        checkSessionConfiguration(mCamera, mHandler, outputConfigs,
+                        /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                        /*expectedResult*/ true));
+
+                createSessionByConfigs(outputConfigs);
+                haveSession = true;
+                CaptureRequest request = requestBuilder.build();
+                mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
+
+                verify(mockCaptureCallback,
+                        timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
+                        .onCaptureCompleted(
+                            eq(mCameraSession),
+                            eq(request),
+                            isA(TotalCaptureResult.class));
+                verify(mockCaptureCallback, never()).
+                        onCaptureFailed(
+                            eq(mCameraSession),
+                            eq(request),
+                            isA(CaptureFailure.class));
+
+            } catch (Throwable e) {
+                mCollector.addMessage(String.format("Output combination: %s failed due to: %s",
+                        combination.getDescription(), e.getMessage()));
+            }
+            if (haveSession) {
+                try {
+                    Log.i(TAG, String.format("Done camera %s, combination: %s, closing session",
+                                    cameraId, combination.getDescription()));
+                    stopCapture(/*fast*/false);
+                } catch (Throwable e) {
+                    mCollector.addMessage(
+                        String.format("Closing down for output combination: %s failed due to: %s",
+                                combination.getDescription(), e.getMessage()));
+                }
+            }
+
+            for (SurfaceTexture target : privTargets) {
+                target.release();
+            }
+            for (ImageReader target : jpegTargets) {
+                target.close();
+            }
+            for (ImageReader target : yuvTargets) {
+                target.close();
+            }
+            for (ImageReader target : y8Targets) {
+                target.close();
+            }
+            for (ImageReader target : rawTargets) {
+                target.close();
+            }
+        }
+    }
+
+    private void testMandatoryStreamCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) throws Exception {
+
+        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
+        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
+        final int MIN_RESULT_COUNT = 3;
+
+        // Set up outputs
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
+        List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
+        List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
+        List<ImageReader> y8Targets = new ArrayList<ImageReader>();
+        List<ImageReader> rawTargets = new ArrayList<ImageReader>();
+
+        setupConfigurationTargets(combination.getStreamsInformation(), privTargets, jpegTargets,
+                yuvTargets, y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, substituteY8,
+                null /*overrideStreamInfo*/, null /*overridePhysicalCameraIds*/,
+                null /* overridePhysicalCameraSizes) */);
+
+        boolean haveSession = false;
+        try {
+            CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+            for (OutputConfiguration c : outputConfigs) {
+                requestBuilder.addTarget(c.getSurface());
+            }
+
+            CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                    mock(CameraCaptureSession.CaptureCallback.class);
+
+            assertTrue(String.format("Session configuration query fro combination: %s failed",
+                    combination.getDescription()), checkSessionConfiguration(mCamera,
+                    mHandler, outputConfigs, /*inputConfig*/ null,
+                    SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
+
+            createSessionByConfigs(outputConfigs);
+            haveSession = true;
+            CaptureRequest request = requestBuilder.build();
+            mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
+
+            verify(mockCaptureCallback,
+                    timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
+                    .onCaptureCompleted(
+                        eq(mCameraSession),
+                        eq(request),
+                        isA(TotalCaptureResult.class));
+            verify(mockCaptureCallback, never()).
+                    onCaptureFailed(
+                        eq(mCameraSession),
+                        eq(request),
+                        isA(CaptureFailure.class));
+
+        } catch (Throwable e) {
+            mCollector.addMessage(String.format("Mandatory stream combination: %s failed due: %s",
+                    combination.getDescription(), e.getMessage()));
+        }
+        if (haveSession) {
+            try {
+                Log.i(TAG, String.format("Done with camera %s, combination: %s, closing session",
+                                cameraId, combination.getDescription()));
+                stopCapture(/*fast*/false);
+            } catch (Throwable e) {
+                mCollector.addMessage(
+                    String.format("Closing down for combination: %s failed due to: %s",
+                            combination.getDescription(), e.getMessage()));
+            }
+        }
+
+        for (SurfaceTexture target : privTargets) {
+            target.release();
+        }
+        for (ImageReader target : jpegTargets) {
+            target.close();
+        }
+        for (ImageReader target : yuvTargets) {
+            target.close();
+        }
+        for (ImageReader target : y8Targets) {
+            target.close();
+        }
+        for (ImageReader target : rawTargets) {
+            target.close();
+        }
+    }
+
+    /**
+     * Test for making sure the required reprocess input/output combinations for each hardware
+     * level and capability work as expected.
+     */
+    public void testMandatoryReprocessConfigurations() throws Exception {
+        for (String id : mCameraIds) {
+            openDevice(id);
+            MandatoryStreamCombination[] combinations =
+                    mStaticInfo.getCharacteristics().get(
+                            CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
+            if (combinations == null) {
+                Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test");
+                closeDevice(id);
+                continue;
+            }
+
+            try {
+                for (MandatoryStreamCombination combination : combinations) {
+                    if (combination.isReprocessable()) {
+                        Log.i(TAG, "Testing mandatory reprocessable stream combination: " +
+                                combination.getDescription() + " on camera: " + id);
+                        testMandatoryReprocessableStreamCombination(id, combination);
+                    }
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    private void testMandatoryReprocessableStreamCombination(String cameraId,
+            MandatoryStreamCombination combination) {
+        // Test reprocess stream combination
+        testMandatoryReprocessableStreamCombination(cameraId, combination, /*substituteY8*/false);
+
+        // Test substituting YUV_888 format with Y8 format in reprocess stream combination.
+        if (mStaticInfo.isMonochromeWithY8()) {
+            List<MandatoryStreamInformation> streamsInfo = combination.getStreamsInformation();
+            boolean hasY8 = false;
+            for (MandatoryStreamInformation streamInfo : streamsInfo) {
+                if (streamInfo.getFormat() == ImageFormat.YUV_420_888) {
+                    hasY8 = true;
+                    break;
+                }
+            }
+            if (hasY8) {
+                testMandatoryReprocessableStreamCombination(cameraId, combination, hasY8);
+            }
+        }
+    }
+
+    private void testMandatoryReprocessableStreamCombination(String cameraId,
+            MandatoryStreamCombination combination, boolean substituteY8) {
+
+        final int TIMEOUT_FOR_RESULT_MS = 3000;
+        final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
+
+        List<SurfaceTexture> privTargets = new ArrayList<>();
+        List<ImageReader> jpegTargets = new ArrayList<>();
+        List<ImageReader> yuvTargets = new ArrayList<>();
+        List<ImageReader> y8Targets = new ArrayList<>();
+        List<ImageReader> rawTargets = new ArrayList<>();
+        ArrayList<Surface> outputSurfaces = new ArrayList<>();
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        ImageReader inputReader = null;
+        ImageWriter inputWriter = null;
+        SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
+        SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
+        SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
+
+        List<MandatoryStreamInformation> streamInfo = combination.getStreamsInformation();
+        assertTrue("Reprocessable stream combinations should have at least 3 or more streams",
+                    (streamInfo != null) && (streamInfo.size() >= 3));
+
+        assertTrue("The first mandatory stream information in a reprocessable combination must " +
+                "always be input", streamInfo.get(0).isInput());
+
+        List<Size> inputSizes = streamInfo.get(0).getAvailableSizes();
+        int inputFormat = streamInfo.get(0).getFormat();
+        if (substituteY8 && (inputFormat == ImageFormat.YUV_420_888)) {
+            inputFormat = ImageFormat.Y8;
+        }
+
+        try {
+            // The second stream information entry is the ZSL stream, which is configured
+            // separately.
+            setupConfigurationTargets(streamInfo.subList(2, streamInfo.size()), privTargets,
+                    jpegTargets, yuvTargets, y8Targets, rawTargets, outputConfigs,
+                    NUM_REPROCESS_CAPTURES_PER_CONFIG, substituteY8,  null /*overrideStreamInfo*/,
+                    null /*overridePhysicalCameraIds*/, null /* overridePhysicalCameraSizes) */);
+
+            outputSurfaces.ensureCapacity(outputConfigs.size());
+            for (OutputConfiguration config : outputConfigs) {
+                outputSurfaces.add(config.getSurface());
+            }
+
+            InputConfiguration inputConfig = new InputConfiguration(inputSizes.get(0).getWidth(),
+                    inputSizes.get(0).getHeight(), inputFormat);
+
+            // For each config, YUV and JPEG outputs will be tested. (For YUV/Y8 reprocessing,
+            // the YUV/Y8 ImageReader for input is also used for output.)
+            final boolean inputIsYuv = inputConfig.getFormat() == ImageFormat.YUV_420_888;
+            final boolean inputIsY8 = inputConfig.getFormat() == ImageFormat.Y8;
+            final boolean useYuv = inputIsYuv || yuvTargets.size() > 0;
+            final boolean useY8 = inputIsY8 || y8Targets.size() > 0;
+            final int totalNumReprocessCaptures =  NUM_REPROCESS_CAPTURES_PER_CONFIG * (
+                    ((inputIsYuv || inputIsY8) ? 1 : 0) +
+                    jpegTargets.size() + (useYuv ? yuvTargets.size() : y8Targets.size()));
+
+            // It needs 1 input buffer for each reprocess capture + the number of buffers
+            // that will be used as outputs.
+            inputReader = ImageReader.newInstance(inputConfig.getWidth(), inputConfig.getHeight(),
+                    inputConfig.getFormat(),
+                    totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG);
+            inputReader.setOnImageAvailableListener(inputReaderListener, mHandler);
+            outputSurfaces.add(inputReader.getSurface());
+
+            assertTrue(String.format("Session configuration query %s failed",
+                    combination.getDescription()),
+                    checkSessionConfigurationWithSurfaces(mCamera, mHandler, outputSurfaces,
+                    inputConfig, SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true));
+
+            // Verify we can create a reprocessable session with the input and all outputs.
+            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+            CameraCaptureSession session = configureReprocessableCameraSession(mCamera,
+                    inputConfig, outputSurfaces, sessionListener, mHandler);
+            inputWriter = ImageWriter.newInstance(session.getInputSurface(),
+                    totalNumReprocessCaptures);
+
+            // Prepare a request for reprocess input
+            CaptureRequest.Builder builder = mCamera.createCaptureRequest(
+                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+            builder.addTarget(inputReader.getSurface());
+
+            for (int i = 0; i < totalNumReprocessCaptures; i++) {
+                session.capture(builder.build(), inputCaptureListener, mHandler);
+            }
+
+            List<CaptureRequest> reprocessRequests = new ArrayList<>();
+            List<Surface> reprocessOutputs = new ArrayList<>();
+            if (inputIsYuv || inputIsY8) {
+                reprocessOutputs.add(inputReader.getSurface());
+            }
+
+            for (ImageReader reader : jpegTargets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (ImageReader reader : yuvTargets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (ImageReader reader : y8Targets) {
+                reprocessOutputs.add(reader.getSurface());
+            }
+
+            for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
+                for (Surface output : reprocessOutputs) {
+                    TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
+                            TIMEOUT_FOR_RESULT_MS);
+                    builder =  mCamera.createReprocessCaptureRequest(result);
+                    inputWriter.queueInputImage(
+                            inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
+                    builder.addTarget(output);
+                    reprocessRequests.add(builder.build());
+                }
+            }
+
+            session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
+
+            for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
+                TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
+                        TIMEOUT_FOR_RESULT_MS);
+            }
+        } catch (Throwable e) {
+            mCollector.addMessage(String.format("Reprocess stream combination %s failed due to: %s",
+                    combination.getDescription(), e.getMessage()));
+        } finally {
+            inputReaderListener.drain();
+            reprocessOutputCaptureListener.drain();
+
+            for (SurfaceTexture target : privTargets) {
+                target.release();
+            }
+
+            for (ImageReader target : jpegTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : yuvTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : y8Targets) {
+                target.close();
+            }
+
+            for (ImageReader target : rawTargets) {
+                target.close();
+            }
+
+            if (inputReader != null) {
+                inputReader.close();
+            }
+
+            if (inputWriter != null) {
+                inputWriter.close();
+            }
+        }
+    }
+
     public void testBasicTriggerSequence() throws Exception {
 
         for (String id : mCameraIds) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
-            openDevice(id);
             try {
                 // Legacy devices do not support precapture trigger; don't test devices that
                 // can't focus
-                if (mStaticInfo.isHardwareLevelLegacy() || !mStaticInfo.hasFocuser()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy() || !staticInfo.hasFocuser()) {
                     continue;
                 }
                 // Depth-only devices won't support AE
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
                 int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
 
@@ -581,19 +957,20 @@
         for (String id : mCameraIds) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
-            openDevice(id);
             try {
                 // Legacy devices do not support precapture trigger; don't test devices that
                 // can't focus
-                if (mStaticInfo.isHardwareLevelLegacy() || !mStaticInfo.hasFocuser()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy() || !staticInfo.hasFocuser()) {
                     continue;
                 }
                 // Depth-only devices won't support AE
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
                 int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
 
@@ -682,19 +1059,20 @@
         for (String id : mCameraIds) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
-            openDevice(id);
             try {
                 // Legacy devices do not support precapture trigger; don't test devices that
                 // can't focus
-                if (mStaticInfo.isHardwareLevelLegacy() || !mStaticInfo.hasFocuser()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy() || !staticInfo.hasFocuser()) {
                     continue;
                 }
                 // Depth-only devices won't support AE
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
                 int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
 
@@ -797,19 +1175,20 @@
         for (String id : mCameraIds) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
-            openDevice(id);
             try {
                 // Legacy devices do not support precapture trigger; don't test devices that
                 // can't focus
-                if (mStaticInfo.isHardwareLevelLegacy() || !mStaticInfo.hasFocuser()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy() || !staticInfo.hasFocuser()) {
                     continue;
                 }
                 // Depth-only devices won't support AE
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(id);
                 int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
                 int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
 
@@ -908,6 +1287,188 @@
         }
     }
 
+    public void testAeAndAfCausality() throws Exception {
+
+        for (String id : mCameraIds) {
+            Log.i(TAG, String.format("Testing Camera %s", id));
+
+            try {
+                // Legacy devices do not support precapture trigger; don't test devices that
+                // can't focus
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy() || !staticInfo.hasFocuser()) {
+                    continue;
+                }
+                // Depth-only devices won't support AE
+                if (!staticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+
+                openDevice(id);
+                int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+                int[] availableAeModes = mStaticInfo.getAeAvailableModesChecked();
+                final int maxPipelineDepth = mStaticInfo.getCharacteristics().get(
+                        CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH);
+
+                for (int afMode : availableAfModes) {
+                    if (afMode == CameraCharacteristics.CONTROL_AF_MODE_OFF ||
+                            afMode == CameraCharacteristics.CONTROL_AF_MODE_EDOF) {
+                        // Only test AF modes that have meaningful trigger behavior
+                        continue;
+                    }
+                    for (int aeMode : availableAeModes) {
+                        if (aeMode ==  CameraCharacteristics.CONTROL_AE_MODE_OFF) {
+                            // Only test AE modes that have meaningful trigger behavior
+                            continue;
+                        }
+
+                        SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1);
+
+                        CaptureRequest.Builder previewRequest =
+                                prepareTriggerTestSession(preview, aeMode, afMode);
+
+                        SimpleCaptureCallback captureListener =
+                                new CameraTestUtils.SimpleCaptureCallback();
+
+                        mCameraSession.setRepeatingRequest(previewRequest.build(), captureListener,
+                                mHandler);
+
+                        List<CaptureRequest> triggerRequests =
+                                new ArrayList<CaptureRequest>(maxPipelineDepth+1);
+                        for (int i = 0; i < maxPipelineDepth; i++) {
+                            triggerRequests.add(previewRequest.build());
+                        }
+
+                        // Cancel triggers
+                        cancelTriggersAndWait(previewRequest, captureListener, afMode);
+
+                        //
+                        // Standard sequence - Part 1 AF trigger
+
+                        if (VERBOSE) {
+                            Log.v(TAG, String.format("Triggering AF"));
+                        }
+
+                        previewRequest.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                                CaptureRequest.CONTROL_AF_TRIGGER_START);
+                        previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
+                        triggerRequests.add(previewRequest.build());
+
+                        mCameraSession.captureBurst(triggerRequests, captureListener, mHandler);
+
+                        TotalCaptureResult[] triggerResults =
+                                captureListener.getTotalCaptureResultsForRequests(
+                                triggerRequests, MAX_RESULT_STATE_CHANGE_WAIT_FRAMES);
+                        for (int i = 0; i < maxPipelineDepth; i++) {
+                            TotalCaptureResult triggerResult = triggerResults[i];
+                            int afState = triggerResult.get(CaptureResult.CONTROL_AF_STATE);
+                            int afTrigger = triggerResult.get(CaptureResult.CONTROL_AF_TRIGGER);
+
+                            verifyStartingAfState(afMode, afState);
+                            assertTrue(String.format("In AF mode %s, previous AF_TRIGGER must not "
+                                    + "be START before TRIGGER_START",
+                                    StaticMetadata.getAfModeName(afMode)),
+                                    afTrigger != CaptureResult.CONTROL_AF_TRIGGER_START);
+                        }
+
+                        int afState =
+                                triggerResults[maxPipelineDepth].get(CaptureResult.CONTROL_AF_STATE);
+                        boolean focusComplete = false;
+                        for (int i = 0;
+                             i < MAX_TRIGGER_SEQUENCE_FRAMES && !focusComplete;
+                             i++) {
+
+                            focusComplete = verifyAfSequence(afMode, afState, focusComplete);
+
+                            CaptureResult focusResult = captureListener.getCaptureResult(
+                                    CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
+                            afState = focusResult.get(CaptureResult.CONTROL_AF_STATE);
+                        }
+
+                        assertTrue("Focusing never completed!", focusComplete);
+
+                        // Standard sequence - Part 2 AE trigger
+
+                        if (VERBOSE) {
+                            Log.v(TAG, String.format("Triggering AE"));
+                        }
+                        // Remove AF trigger request
+                        triggerRequests.remove(maxPipelineDepth);
+
+                        previewRequest.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+                        previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+                        triggerRequests.add(previewRequest.build());
+
+                        mCameraSession.captureBurst(triggerRequests, captureListener, mHandler);
+
+                        triggerResults = captureListener.getTotalCaptureResultsForRequests(
+                                triggerRequests, MAX_RESULT_STATE_CHANGE_WAIT_FRAMES);
+
+                        for (int i = 0; i < maxPipelineDepth; i++) {
+                            TotalCaptureResult triggerResult = triggerResults[i];
+                            int aeState = triggerResult.get(CaptureResult.CONTROL_AE_STATE);
+                            int aeTrigger = triggerResult.get(
+                                    CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER);
+
+                            assertTrue(String.format("In AE mode %s, previous AE_TRIGGER must not "
+                                    + "be START before TRIGGER_START",
+                                    StaticMetadata.getAeModeName(aeMode)),
+                                    aeTrigger != CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+                            assertTrue(String.format("In AE mode %s, previous AE_STATE must not be"
+                                    + " PRECAPTURE_TRIGGER before TRIGGER_START",
+                                    StaticMetadata.getAeModeName(aeMode)),
+                                    aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE);
+                        }
+
+                        // Stand sequence - Part 3 Cancel AF trigger
+                        if (VERBOSE) {
+                            Log.v(TAG, String.format("Cancel AF trigger"));
+                        }
+                        // Remove AE trigger request
+                        triggerRequests.remove(maxPipelineDepth);
+                        previewRequest.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                                CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+                        triggerRequests.add(previewRequest.build());
+
+                        mCameraSession.captureBurst(triggerRequests, captureListener, mHandler);
+                        triggerResults = captureListener.getTotalCaptureResultsForRequests(
+                                triggerRequests, MAX_RESULT_STATE_CHANGE_WAIT_FRAMES);
+                        for (int i = 0; i < maxPipelineDepth; i++) {
+                            TotalCaptureResult triggerResult = triggerResults[i];
+                            afState = triggerResult.get(CaptureResult.CONTROL_AF_STATE);
+                            int afTrigger = triggerResult.get(CaptureResult.CONTROL_AF_TRIGGER);
+
+                            assertTrue(
+                                    String.format("In AF mode %s, previous AF_TRIGGER must not " +
+                                    "be CANCEL before TRIGGER_CANCEL",
+                                    StaticMetadata.getAfModeName(afMode)),
+                                    afTrigger != CaptureResult.CONTROL_AF_TRIGGER_CANCEL);
+                            assertTrue(
+                                    String.format("In AF mode %s, previous AF_STATE must be LOCKED"
+                                    + " before CANCEL, but is %s",
+                                    StaticMetadata.getAfModeName(afMode),
+                                    StaticMetadata.AF_STATE_NAMES[afState]),
+                                    afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
+                                    afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
+                        }
+
+                        stopCapture(/*fast*/ false);
+                        preview.release();
+                    }
+
+                }
+
+            } finally {
+                closeDevice(id);
+            }
+        }
+
+    }
+
     public void testAbandonRepeatingRequestSurface() throws Exception {
         for (String id : mCameraIds) {
             Log.i(TAG, String.format(
@@ -1207,6 +1768,17 @@
         afState = previewResult.get(CaptureResult.CONTROL_AF_STATE);
         aeState = previewResult.get(CaptureResult.CONTROL_AE_STATE);
 
+        verifyStartingAfState(afMode, afState);
+
+        // After several frames, AE must no longer be in INACTIVE state
+        assertTrue(String.format("AE state must be SEARCHING, CONVERGED, " +
+                        "or FLASH_REQUIRED, is %s", StaticMetadata.AE_STATE_NAMES[aeState]),
+                aeState == CaptureResult.CONTROL_AE_STATE_SEARCHING ||
+                aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
+                aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED);
+    }
+
+    private void verifyStartingAfState(int afMode, int afState) {
         switch (afMode) {
             case CaptureResult.CONTROL_AF_MODE_AUTO:
             case CaptureResult.CONTROL_AF_MODE_MACRO:
@@ -1228,13 +1800,6 @@
             default:
                 fail("unexpected af mode");
         }
-
-        // After several frames, AE must no longer be in INACTIVE state
-        assertTrue(String.format("AE state must be SEARCHING, CONVERGED, " +
-                        "or FLASH_REQUIRED, is %s", StaticMetadata.AE_STATE_NAMES[aeState]),
-                aeState == CaptureResult.CONTROL_AE_STATE_SEARCHING ||
-                aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
-                aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED);
     }
 
     private boolean verifyAfSequence(int afMode, int afState, boolean focusComplete) {
@@ -1350,23 +1915,22 @@
         static final int JPEG = ImageFormat.JPEG;
         static final int YUV  = ImageFormat.YUV_420_888;
         static final int RAW  = ImageFormat.RAW_SENSOR;
+        static final int Y8   = ImageFormat.Y8;
 
         // Max resolution indices
         static final int PREVIEW = 0;
         static final int RECORD  = 1;
         static final int MAXIMUM = 2;
         static final int VGA = 3;
-        static final int VGA_FULL_FOV = 4;
-        static final int MAX_30FPS = 5;
-        static final int RESOLUTION_COUNT = 6;
-
-        static final long FRAME_DURATION_30FPS_NSEC = (long) 1e9 / 30;
+        static final int RESOLUTION_COUNT = 4;
 
         public MaxStreamSizes(StaticMetadata sm, String cameraId, Context context) {
             Size[] privSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
                     StaticMetadata.StreamDirection.Output);
             Size[] yuvSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.YUV_420_888,
                     StaticMetadata.StreamDirection.Output);
+            Size[] y8Sizes = sm.getAvailableSizesForFormatChecked(ImageFormat.Y8,
+                    StaticMetadata.StreamDirection.Output);
             Size[] jpegSizes = sm.getJpegOutputSizesChecked();
             Size[] rawSizes = sm.getRawOutputSizesChecked();
 
@@ -1400,72 +1964,17 @@
                 maxPrivSizes[VGA] = vgaSize;
                 maxYuvSizes[VGA] = vgaSize;
                 maxJpegSizes[VGA] = vgaSize;
-            }
 
-            if (sm.isColorOutputSupported() && !sm.isHardwareLevelLegacy()) {
-                // VGA resolution, but with aspect ratio matching full res FOV
-                float fullFovAspect = maxYuvSizes[MAXIMUM].getWidth() / (float) maxYuvSizes[MAXIMUM].getHeight();
-                Size vgaFullFovSize = new Size(640, (int) (640 / fullFovAspect));
-
-                maxPrivSizes[VGA_FULL_FOV] = vgaFullFovSize;
-                maxYuvSizes[VGA_FULL_FOV] = vgaFullFovSize;
-                maxJpegSizes[VGA_FULL_FOV] = vgaFullFovSize;
-
-                // Max resolution that runs at 30fps
-
-                Size maxPriv30fpsSize = null;
-                Size maxYuv30fpsSize = null;
-                Size maxJpeg30fpsSize = null;
-                Comparator<Size> comparator = new SizeComparator();
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("Priv Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxPriv30fpsSize == null ||
-                                comparator.compare(maxPriv30fpsSize, s) < 0) {
-                            maxPriv30fpsSize = s;
-                        }
+                if (sm.isMonochromeWithY8()) {
+                    maxY8Sizes[PREVIEW]  = getMaxSize(y8Sizes, maxPreviewSize);
+                    if (sm.isExternalCamera()) {
+                        maxY8Sizes[RECORD]  = getMaxExternalRecordingSize(cameraId, configs);
+                    } else {
+                        maxY8Sizes[RECORD]  = getMaxRecordingSize(cameraId);
                     }
+                    maxY8Sizes[MAXIMUM] = CameraTestUtils.getMaxSize(y8Sizes);
+                    maxY8Sizes[VGA] = vgaSize;
                 }
-                assertTrue("No PRIVATE resolution available at 30fps!", maxPriv30fpsSize != null);
-
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(
-                                     ImageFormat.YUV_420_888).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("YUV Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxYuv30fpsSize == null ||
-                                comparator.compare(maxYuv30fpsSize, s) < 0) {
-                            maxYuv30fpsSize = s;
-                        }
-                    }
-                }
-                assertTrue("No YUV_420_888 resolution available at 30fps!", maxYuv30fpsSize != null);
-
-                for (Map.Entry<Size, Long> e :
-                             sm.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG).
-                             entrySet()) {
-                    Size s = e.getKey();
-                    Long minDuration = e.getValue();
-                    Log.d(TAG, String.format("JPEG Size: %s, duration %d limit %d", s, minDuration, FRAME_DURATION_30FPS_NSEC));
-                    if (minDuration <= FRAME_DURATION_30FPS_NSEC) {
-                        if (maxJpeg30fpsSize == null ||
-                                comparator.compare(maxJpeg30fpsSize, s) < 0) {
-                            maxJpeg30fpsSize = s;
-                        }
-                    }
-                }
-                assertTrue("No JPEG resolution available at 30fps!", maxJpeg30fpsSize != null);
-
-                maxPrivSizes[MAX_30FPS] = maxPriv30fpsSize;
-                maxYuvSizes[MAX_30FPS] = maxYuv30fpsSize;
-                maxJpegSizes[MAX_30FPS] = maxJpeg30fpsSize;
             }
 
             Size[] privInputSizes = configs.getInputSizes(ImageFormat.PRIVATE);
@@ -1474,16 +1983,20 @@
             Size[] yuvInputSizes = configs.getInputSizes(ImageFormat.YUV_420_888);
             maxInputYuvSize = yuvInputSizes != null ?
                     CameraTestUtils.getMaxSize(yuvInputSizes) : null;
-
+            Size[] y8InputSizes = configs.getInputSizes(ImageFormat.Y8);
+            maxInputY8Size = y8InputSizes != null ?
+                    CameraTestUtils.getMaxSize(y8InputSizes) : null;
         }
 
         public final Size[] maxPrivSizes = new Size[RESOLUTION_COUNT];
         public final Size[] maxJpegSizes = new Size[RESOLUTION_COUNT];
         public final Size[] maxYuvSizes = new Size[RESOLUTION_COUNT];
+        public final Size[] maxY8Sizes = new Size[RESOLUTION_COUNT];
         public final Size maxRawSize;
         // TODO: support non maximum reprocess input.
         public final Size maxInputPrivSize;
         public final Size maxInputYuvSize;
+        public final Size maxInputY8Size;
 
         static public String configToString(int[] config) {
             StringBuilder b = new StringBuilder("{ ");
@@ -1527,6 +2040,9 @@
                 case YUV:
                     b.append("[YUV, ");
                     break;
+                case Y8:
+                    b.append("[Y8, ");
+                    break;
                 case RAW:
                     b.append("[RAW, ");
                     break;
@@ -1548,12 +2064,6 @@
                 case VGA:
                     b.append("VGA]");
                     break;
-                case VGA_FULL_FOV:
-                    b.append("VGA_FULL_FOV]");
-                    break;
-                case MAX_30FPS:
-                    b.append("MAX_30FPS]");
-                    break;
                 default:
                     b.append("UNK]");
                     break;
@@ -1561,264 +2071,6 @@
         }
     }
 
-    /**
-     * Return an InputConfiguration for a given reprocess configuration.
-     */
-    private InputConfiguration getInputConfig(int[] reprocessConfig, MaxStreamSizes maxSizes) {
-        int format;
-        Size size;
-
-        if (reprocessConfig[1] != MAXIMUM) {
-            throw new IllegalArgumentException("Test only supports MAXIMUM input");
-        }
-
-        switch (reprocessConfig[0]) {
-            case PRIV:
-                format = ImageFormat.PRIVATE;
-                size = maxSizes.maxInputPrivSize;
-                break;
-            case YUV:
-                format = ImageFormat.YUV_420_888;
-                size = maxSizes.maxInputYuvSize;
-                break;
-            default:
-                throw new IllegalArgumentException("Input format not supported: " +
-                        reprocessConfig[0]);
-        }
-
-        return new InputConfiguration(size.getWidth(), size.getHeight(), format);
-    }
-
-    private void testReprocessStreamCombination(String cameraId, int[] reprocessConfig,
-            MaxStreamSizes maxSizes, StaticMetadata staticInfo) throws Exception {
-
-        Log.i(TAG, String.format("Testing Camera %s, reprocess config: %s", cameraId,
-                MaxStreamSizes.reprocessConfigToString(reprocessConfig)));
-
-        final int TIMEOUT_FOR_RESULT_MS = 3000;
-        final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
-
-        List<SurfaceTexture> privTargets = new ArrayList<>();
-        List<ImageReader> jpegTargets = new ArrayList<>();
-        List<ImageReader> yuvTargets = new ArrayList<>();
-        List<ImageReader> rawTargets = new ArrayList<>();
-        List<Surface> outputSurfaces = new ArrayList<>();
-        ImageReader inputReader = null;
-        ImageWriter inputWriter = null;
-        SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
-        SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
-        SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
-
-        boolean supportYuvReprocess = staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
-        boolean supportOpaqueReprocess = staticInfo.isCapabilitySupported(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
-
-        // Skip the configuration if the format is not supported for reprocessing.
-        if ((reprocessConfig[0] == YUV && !supportYuvReprocess) ||
-                (reprocessConfig[0] == PRIV && !supportOpaqueReprocess)) {
-            return;
-        }
-
-        try {
-            // reprocessConfig[2..] are additional outputs
-            setupConfigurationTargets(
-                    Arrays.copyOfRange(reprocessConfig, 2, reprocessConfig.length),
-                    maxSizes, privTargets, jpegTargets, yuvTargets, rawTargets, outputSurfaces,
-                    NUM_REPROCESS_CAPTURES_PER_CONFIG);
-
-            // reprocessConfig[0:1] is input
-            InputConfiguration inputConfig = getInputConfig(
-                    Arrays.copyOfRange(reprocessConfig, 0, 2), maxSizes);
-
-            // For each config, YUV and JPEG outputs will be tested. (For YUV reprocessing,
-            // the YUV ImageReader for input is also used for output.)
-            final int totalNumReprocessCaptures =  NUM_REPROCESS_CAPTURES_PER_CONFIG * (
-                    (inputConfig.getFormat() == ImageFormat.YUV_420_888 ? 1 : 0) +
-                    jpegTargets.size() + yuvTargets.size());
-
-            // It needs 1 input buffer for each reprocess capture + the number of buffers
-            // that will be used as outputs.
-            inputReader = ImageReader.newInstance(inputConfig.getWidth(), inputConfig.getHeight(),
-                    inputConfig.getFormat(),
-                    totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG);
-            inputReader.setOnImageAvailableListener(inputReaderListener, mHandler);
-            outputSurfaces.add(inputReader.getSurface());
-
-            // Verify we can create a reprocessable session with the input and all outputs.
-            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
-            CameraCaptureSession session = configureReprocessableCameraSession(mCamera,
-                    inputConfig, outputSurfaces, sessionListener, mHandler);
-            inputWriter = ImageWriter.newInstance(session.getInputSurface(),
-                    totalNumReprocessCaptures);
-
-            // Prepare a request for reprocess input
-            CaptureRequest.Builder builder = mCamera.createCaptureRequest(
-                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
-            builder.addTarget(inputReader.getSurface());
-
-            for (int i = 0; i < totalNumReprocessCaptures; i++) {
-                session.capture(builder.build(), inputCaptureListener, mHandler);
-            }
-
-            List<CaptureRequest> reprocessRequests = new ArrayList<>();
-            List<Surface> reprocessOutputs = new ArrayList<>();
-            if (inputConfig.getFormat() == ImageFormat.YUV_420_888) {
-                reprocessOutputs.add(inputReader.getSurface());
-            }
-
-            for (ImageReader reader : jpegTargets) {
-                reprocessOutputs.add(reader.getSurface());
-            }
-
-            for (ImageReader reader : yuvTargets) {
-                reprocessOutputs.add(reader.getSurface());
-            }
-
-            for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
-                for (Surface output : reprocessOutputs) {
-                    TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
-                            TIMEOUT_FOR_RESULT_MS);
-                    builder =  mCamera.createReprocessCaptureRequest(result);
-                    inputWriter.queueInputImage(
-                            inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
-                    builder.addTarget(output);
-                    reprocessRequests.add(builder.build());
-                }
-            }
-
-            session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
-
-            for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) {
-                TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
-                        TIMEOUT_FOR_RESULT_MS);
-            }
-        } catch (Throwable e) {
-            mCollector.addMessage(String.format("Reprocess stream combination %s failed due to: %s",
-                    MaxStreamSizes.reprocessConfigToString(reprocessConfig), e.getMessage()));
-        } finally {
-            inputReaderListener.drain();
-            reprocessOutputCaptureListener.drain();
-
-            for (SurfaceTexture target : privTargets) {
-                target.release();
-            }
-
-            for (ImageReader target : jpegTargets) {
-                target.close();
-            }
-
-            for (ImageReader target : yuvTargets) {
-                target.close();
-            }
-
-            for (ImageReader target : rawTargets) {
-                target.close();
-            }
-
-            if (inputReader != null) {
-                inputReader.close();
-            }
-
-            if (inputWriter != null) {
-                inputWriter.close();
-            }
-        }
-    }
-
-    private void testOutputCombination(String cameraId, int[] config, MaxStreamSizes maxSizes)
-            throws Exception {
-
-        Log.i(TAG, String.format("Testing single Camera %s, config %s",
-                        cameraId, MaxStreamSizes.configToString(config)));
-
-        testSingleCameraOutputCombination(cameraId, config, maxSizes);
-
-        if (mStaticInfo.isLogicalMultiCamera()) {
-            Log.i(TAG, String.format("Testing logical Camera %s, config %s",
-                    cameraId, MaxStreamSizes.configToString(config)));
-
-            testMultiCameraOutputCombination(cameraId, config, maxSizes);
-        }
-    }
-
-    private void testSingleCameraOutputCombination(String cameraId, int[] config,
-        MaxStreamSizes maxSizes) throws Exception {
-
-        // Timeout is relaxed by 1 second for LEGACY devices to reduce false positive rate in CTS
-        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 2000 : 1000;
-        final int MIN_RESULT_COUNT = 3;
-
-        // Set up outputs
-        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
-        List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
-        List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
-        List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
-        List<ImageReader> rawTargets = new ArrayList<ImageReader>();
-
-        setupConfigurationTargets(config, maxSizes, privTargets, jpegTargets, yuvTargets,
-                rawTargets, outputConfigs, MIN_RESULT_COUNT, -1 /*overrideStreamIndex*/,
-                null /*overridePhysicalCameraIds*/, null /*overridePhysicalCameraSizes*/);
-
-        boolean haveSession = false;
-        try {
-            CaptureRequest.Builder requestBuilder =
-                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-
-            for (OutputConfiguration c : outputConfigs) {
-                requestBuilder.addTarget(c.getSurface());
-            }
-
-            CameraCaptureSession.CaptureCallback mockCaptureCallback =
-                    mock(CameraCaptureSession.CaptureCallback.class);
-
-            createSessionByConfigs(outputConfigs);
-            haveSession = true;
-            CaptureRequest request = requestBuilder.build();
-            mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
-
-            verify(mockCaptureCallback,
-                    timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
-                    .onCaptureCompleted(
-                        eq(mCameraSession),
-                        eq(request),
-                        isA(TotalCaptureResult.class));
-            verify(mockCaptureCallback, never()).
-                    onCaptureFailed(
-                        eq(mCameraSession),
-                        eq(request),
-                        isA(CaptureFailure.class));
-
-        } catch (Throwable e) {
-            mCollector.addMessage(String.format("Output combination %s failed due to: %s",
-                    MaxStreamSizes.configToString(config), e.getMessage()));
-        }
-        if (haveSession) {
-            try {
-                Log.i(TAG, String.format("Done with camera %s, config %s, closing session",
-                                cameraId, MaxStreamSizes.configToString(config)));
-                stopCapture(/*fast*/false);
-            } catch (Throwable e) {
-                mCollector.addMessage(
-                    String.format("Closing down for output combination %s failed due to: %s",
-                            MaxStreamSizes.configToString(config), e.getMessage()));
-            }
-        }
-
-        for (SurfaceTexture target : privTargets) {
-            target.release();
-        }
-        for (ImageReader target : jpegTargets) {
-            target.close();
-        }
-        for (ImageReader target : yuvTargets) {
-            target.close();
-        }
-        for (ImageReader target : rawTargets) {
-            target.close();
-        }
-    }
-
     private void testMultiCameraOutputCombination(String cameraId, int[] config,
         MaxStreamSizes maxSizes) throws Exception {
 
@@ -1830,12 +2082,13 @@
         for (int i = 0; i < config.length; i += 2) {
             int format = config[i];
             int sizeLimit = config[i+1];
-            if (format != YUV && format != RAW) {
+            if (format != YUV && format != Y8 && format != RAW) {
                 continue;
             }
 
             // Find physical cameras with matching size.
             Size targetSize = (format == YUV) ? maxSizes.maxYuvSizes[sizeLimit] :
+                    (format == Y8) ? maxSizes.maxY8Sizes[sizeLimit] :
                     maxSizes.maxRawSize;
             List<String> physicalCamerasForSize = new ArrayList<String>();
             List<Size> physicalCameraSizes = new ArrayList<Size>();
@@ -1864,11 +2117,12 @@
             List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
             List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
             List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
+            List<ImageReader> y8Targets = new ArrayList<ImageReader>();
             List<ImageReader> rawTargets = new ArrayList<ImageReader>();
 
             setupConfigurationTargets(config, maxSizes, privTargets, jpegTargets, yuvTargets,
-                    rawTargets, outputConfigs, MIN_RESULT_COUNT, i, physicalCamerasForSize,
-                    physicalCameraSizes);
+                    y8Targets, rawTargets, outputConfigs, MIN_RESULT_COUNT, i,
+                    physicalCamerasForSize, physicalCameraSizes);
 
             boolean haveSession = false;
             try {
@@ -1882,6 +2136,12 @@
                 CameraCaptureSession.CaptureCallback mockCaptureCallback =
                         mock(CameraCaptureSession.CaptureCallback.class);
 
+                assertTrue(String.format("Session configuration query %s failed",
+                        MaxStreamSizes.configToString(config)),
+                        checkSessionConfiguration(mCamera, mHandler, outputConfigs,
+                        /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                        /*expectedResult*/ true));
+
                 createSessionByConfigs(outputConfigs);
                 haveSession = true;
                 CaptureRequest request = requestBuilder.build();
@@ -1924,6 +2184,9 @@
             for (ImageReader target : yuvTargets) {
                 target.close();
             }
+            for (ImageReader target : y8Targets) {
+                target.close();
+            }
             for (ImageReader target : rawTargets) {
                 target.close();
             }
@@ -1932,23 +2195,8 @@
 
     private void setupConfigurationTargets(int[] configs, MaxStreamSizes maxSizes,
             List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
-            List<ImageReader> yuvTargets, List<ImageReader> rawTargets,
-            List<Surface> outputSurfaces, int numBuffers) {
-        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration> ();
-
-        setupConfigurationTargets(configs, maxSizes, privTargets, jpegTargets, yuvTargets,
-                rawTargets, outputConfigs, numBuffers, -1 /*overrideStreamIndex*/,
-                null /*overridePhysicalCameraIds*/, null /* overridePhysicalCameraSizes) */);
-
-        for (OutputConfiguration outputConfig : outputConfigs) {
-            outputSurfaces.add(outputConfig.getSurface());
-        }
-    }
-
-    private void setupConfigurationTargets(int[] configs, MaxStreamSizes maxSizes,
-            List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
-            List<ImageReader> yuvTargets, List<ImageReader> rawTargets,
-            List<OutputConfiguration> outputConfigs, int numBuffers,
+            List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
+            List<ImageReader> rawTargets, List<OutputConfiguration> outputConfigs, int numBuffers,
             int overrideStreamIndex, List<String> overridePhysicalCameraIds,
             List<Size> overridePhysicalCameraSizes) {
 
@@ -2007,6 +2255,20 @@
                         yuvTargets.add(target);
                         break;
                     }
+                    case Y8: {
+                        Size targetSize = (numConfigs == 1) ? maxSizes.maxY8Sizes[sizeLimit] :
+                                overridePhysicalCameraSizes.get(j);
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), Y8, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        y8Targets.add(target);
+                        break;
+                    }
                     case RAW: {
                         Size targetSize = (numConfigs == 1) ? maxSizes.maxRawSize :
                                 overridePhysicalCameraSizes.get(j);
diff --git a/tests/camera/src/android/hardware/camera2/cts/SimpleObjectsTest.java b/tests/camera/src/android/hardware/camera2/cts/SimpleObjectsTest.java
new file mode 100644
index 0000000..5c1624b
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/SimpleObjectsTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import android.app.Activity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CameraCharacteristics;
+
+/**
+ * Test CaptureRequest/Result/CameraCharacteristics.Key objects.
+ */
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("EqualsIncompatibleType")
+public class SimpleObjectsTest {
+
+    @Test
+    public void CameraKeysTest() throws Exception {
+        String keyName = "android.testing.Key";
+        String keyName2 = "android.testing.Key2";
+
+        CaptureRequest.Key<Integer> testRequestKey =
+                new CaptureRequest.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Request key name not correct",
+                testRequestKey.getName(), keyName);
+
+        CaptureResult.Key<Integer> testResultKey =
+                new CaptureResult.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Result key name not correct",
+                testResultKey.getName(), keyName);
+
+        CameraCharacteristics.Key<Integer> testCharacteristicsKey =
+                new CameraCharacteristics.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Characteristics key name not correct",
+                testCharacteristicsKey.getName(), keyName);
+
+        CaptureRequest.Key<Integer> testRequestKey2 =
+                new CaptureRequest.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Two request keys with same name/type should be equal",
+                testRequestKey, testRequestKey2);
+        CaptureRequest.Key<Byte> testRequestKey3 =
+                new CaptureRequest.Key<>(keyName, Byte.class);
+        Assert.assertTrue("Two request keys with different types should not be equal",
+                !testRequestKey.equals(testRequestKey3));
+        CaptureRequest.Key<Integer> testRequestKey4 =
+                new CaptureRequest.Key<>(keyName2, Integer.class);
+        Assert.assertTrue("Two request keys with different names should not be equal",
+                !testRequestKey.equals(testRequestKey4));
+        CaptureRequest.Key<Byte> testRequestKey5 =
+                new CaptureRequest.Key<>(keyName2, Byte.class);
+        Assert.assertTrue("Two request keys with different types and names should not be equal",
+                !testRequestKey.equals(testRequestKey5));
+
+        CaptureResult.Key<Integer> testResultKey2 =
+                new CaptureResult.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Two result keys with same name/type should be equal",
+                testResultKey, testResultKey2);
+        CaptureResult.Key<Byte> testResultKey3 =
+                new CaptureResult.Key<>(keyName, Byte.class);
+        Assert.assertTrue("Two result keys with different types should not be equal",
+                !testResultKey.equals(testResultKey3));
+        CaptureResult.Key<Integer> testResultKey4 =
+                new CaptureResult.Key<>(keyName2, Integer.class);
+        Assert.assertTrue("Two result keys with different names should not be equal",
+                !testResultKey.equals(testResultKey4));
+        CaptureResult.Key<Byte> testResultKey5 =
+                new CaptureResult.Key<>(keyName2, Byte.class);
+        Assert.assertTrue("Two result keys with different types and names should not be equal",
+                !testResultKey.equals(testResultKey5));
+
+        CameraCharacteristics.Key<Integer> testCharacteristicsKey2 =
+                new CameraCharacteristics.Key<>(keyName, Integer.class);
+        Assert.assertEquals("Two characteristics keys with same name/type should be equal",
+                testCharacteristicsKey, testCharacteristicsKey2);
+        CameraCharacteristics.Key<Byte> testCharacteristicsKey3 =
+                new CameraCharacteristics.Key<>(keyName, Byte.class);
+        Assert.assertTrue("Two characteristics keys with different types should not be equal",
+                !testCharacteristicsKey.equals(testCharacteristicsKey3));
+        CameraCharacteristics.Key<Integer> testCharacteristicsKey4 =
+                new CameraCharacteristics.Key<>(keyName2, Integer.class);
+        Assert.assertTrue("Two characteristics keys with different names should not be equal",
+                !testCharacteristicsKey.equals(testCharacteristicsKey4));
+        CameraCharacteristics.Key<Byte> testCharacteristicsKey5 =
+                new CameraCharacteristics.Key<>(keyName2, Byte.class);
+        Assert.assertTrue(
+                "Two characteristics keys with different types and names should not be equal",
+                !testCharacteristicsKey.equals(testCharacteristicsKey5));
+
+    }
+
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index 00ed180..4846b68 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -29,7 +29,6 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.hardware.camera2.params.StreamConfigurationMap;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
@@ -51,7 +50,6 @@
  * Note that most of the tests in this class don't require camera open.
  * </p>
  */
-@AppModeFull
 public class StaticMetadataTest extends Camera2AndroidTestCase {
     private static final String TAG = "StaticMetadataTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -488,19 +486,23 @@
         Set<CameraCharacteristics.Key<?>> characteristicsKeys = new HashSet<>();
         characteristicsKeys.add(HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES);
         characteristicsKeys.add(SENSOR_BLACK_LEVEL_PATTERN);
-        characteristicsKeys.add(SENSOR_CALIBRATION_TRANSFORM1);
-        characteristicsKeys.add(SENSOR_COLOR_TRANSFORM1);
-        characteristicsKeys.add(SENSOR_FORWARD_MATRIX1);
         characteristicsKeys.add(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
         characteristicsKeys.add(SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
         characteristicsKeys.add(SENSOR_INFO_WHITE_LEVEL);
-        characteristicsKeys.add(SENSOR_REFERENCE_ILLUMINANT1);
         characteristicsKeys.add(STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES);
+        if (!mStaticInfo.isMonochromeCamera()) {
+            characteristicsKeys.add(SENSOR_CALIBRATION_TRANSFORM1);
+            characteristicsKeys.add(SENSOR_COLOR_TRANSFORM1);
+            characteristicsKeys.add(SENSOR_FORWARD_MATRIX1);
+            characteristicsKeys.add(SENSOR_REFERENCE_ILLUMINANT1);
+        }
 
         Set<CaptureResult.Key<?>> resultKeys = new HashSet<>();
-        resultKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
-        resultKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
         resultKeys.add(CaptureResult.SENSOR_NOISE_PROFILE);
+        if (!mStaticInfo.isMonochromeCamera()) {
+            resultKeys.add(CaptureResult.SENSOR_GREEN_SPLIT);
+            resultKeys.add(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+        }
 
         boolean rawOutputSupported = mStaticInfo.getRawOutputSizesChecked().length > 0;
         boolean requestKeysPresent = mStaticInfo.areRequestKeysAvailable(requestKeys);
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index e77ad79..a169fee 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -37,11 +37,11 @@
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
 import android.hardware.camera2.cts.helpers.Camera2Focuser;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.media.Image;
 import android.os.ConditionVariable;
-import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
 import android.util.Rational;
@@ -55,7 +55,10 @@
 import java.util.Arrays;
 import java.util.List;
 
-@AppModeFull
+import junit.framework.Assert;
+
+import org.junit.Test;
+
 public class StillCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "StillCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -74,28 +77,29 @@
     private static final int MAX_ALLOCATED_BITMAPS = 3;
 
     @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 JPEG capture exif fields for each camera.
      */
+    @Test
     public void testJpegExif() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 jpegExifTestByCamera();
             } finally {
                 closeDevice();
@@ -114,15 +118,16 @@
      * is CONTINUOUS_PICTURE.
      * </p>
      */
+    @Test
     public void testTakePicture() throws Exception{
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing basic take picture for Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null);
             } finally {
                 closeDevice();
@@ -141,15 +146,16 @@
      * is CONTINUOUS_PICTURE. Same as testTakePicture, but with enableZSL set.
      * </p>
      */
+    @Test
     public void testTakePictureZsl() throws Exception{
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing basic ZSL take picture for Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 CaptureRequest.Builder stillRequest =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                 stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -166,19 +172,20 @@
     /**
      * Test basic Raw capture. Raw buffer avaiablility is checked, but raw buffer data is not.
      */
+    @Test
     public void testBasicRawCapture()  throws Exception {
        for (int i = 0; i < mCameraIds.length; i++) {
            try {
                Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
-               openDevice(mCameraIds[i]);
 
-               if (!mStaticInfo.isCapabilitySupported(
+               if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                            ". Skip the test.");
                    continue;
                }
 
+               openDevice(mCameraIds[i]);
                rawCaptureTestByCamera(/*stillRequest*/null);
            } finally {
                closeDevice();
@@ -190,18 +197,19 @@
     /**
      * Test basic Raw ZSL capture. Raw buffer avaiablility is checked, but raw buffer data is not.
      */
+    @Test
     public void testBasicRawZslCapture()  throws Exception {
        for (int i = 0; i < mCameraIds.length; i++) {
            try {
                Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIds[i]);
-               openDevice(mCameraIds[i]);
 
-               if (!mStaticInfo.isCapabilitySupported(
+               if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                            ". Skip the test.");
                    continue;
                }
+               openDevice(mCameraIds[i]);
                CaptureRequest.Builder stillRequest =
                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -222,18 +230,19 @@
      * - Running preview until AE/AF can settle.
      * - Capturing with a request targeting all three output streams.
      */
+    @Test
     public void testFullRawCapture() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                     Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                             ". Skip the test.");
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 fullRawCaptureTestByCamera(/*stillRequest*/null);
             } finally {
                 closeDevice();
@@ -250,17 +259,18 @@
      * - Running preview until AE/AF can settle.
      * - Capturing with a request targeting all three output streams.
      */
+    @Test
     public void testFullRawZSLCapture() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                     Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
                             ". Skip the test.");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 CaptureRequest.Builder stillRequest =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                 stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -280,20 +290,22 @@
      * converges in reasonable time.
      * </p>
      */
+    @Test
     public void testTouchForFocus() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing touch for focus for Camera " + id);
-                openDevice(id);
-                int maxAfRegions = mStaticInfo.getAfMaxRegionsChecked();
-                if (!(mStaticInfo.hasFocuser() && maxAfRegions > 0)) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                int maxAfRegions = staticInfo.getAfMaxRegionsChecked();
+                if (!(staticInfo.hasFocuser() && maxAfRegions > 0)) {
                     continue;
                 }
                 // TODO: Relax test to use non-SurfaceView output for depth cases
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 touchForFocusTestByCamera();
             } finally {
                 closeDevice();
@@ -309,15 +321,16 @@
      * result validation is covered by {@link #jpegExifTestByCamera} test.
      * </p>
      */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testStillPreviewCombination() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing Still preview capture combination for Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 previewStillCombinationTestByCamera();
             } finally {
                 closeDevice();
@@ -336,20 +349,22 @@
      * compensation settings is changed, even when AE lock is ON.
      * </p>
      */
+    @Test
     public void testAeCompensation() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing AE compensation for Camera " + id);
-                openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Skipping test on legacy devices");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 aeCompensationTestByCamera();
             } finally {
                 closeDevice();
@@ -361,6 +376,7 @@
     /**
      * Test Ae region for still capture.
      */
+    @Test
     public void testAeRegions() throws Exception {
         for (String id : mCameraIds) {
             try {
@@ -386,6 +402,7 @@
     /**
      * Test AWB region for still capture.
      */
+    @Test
     public void testAwbRegions() throws Exception {
         for (String id : mCameraIds) {
             try {
@@ -411,6 +428,7 @@
     /**
      * Test Af region for still capture.
      */
+    @Test
     public void testAfRegions() throws Exception {
         for (String id : mCameraIds) {
             try {
@@ -436,15 +454,16 @@
     /**
      * Test preview is still running after a still request
      */
+    @Test
     public void testPreviewPersistence() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing preview persistence for Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 previewPersistenceTestByCamera();
             } finally {
                 closeDevice();
@@ -453,21 +472,23 @@
         }
     }
 
+    @Test
     public void testAePrecaptureTriggerCancelJpegCapture() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing AE precapture cancel for jpeg capture for Camera " + id);
-                openDevice(id);
 
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
                 // Legacy device doesn't support AE precapture trigger
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Skipping AE precapture trigger cancel test on legacy devices");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null,
                         /*addAeTriggerCancel*/true, /*allocateBitmap*/false,
                         /*previewRequest*/null, /*stillRequest*/null);
@@ -486,15 +507,16 @@
      * the devices.
      * </p>
      */
+    @Test
     public void testAllocateBitmap() throws Exception {
         for (String id : mCameraIds) {
             try {
                 Log.i(TAG, "Testing bitmap allocations for Camera " + id);
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null,
                         /*addAeTriggerCancel*/false, /*allocateBitmap*/true,
                         /*previewRequest*/null, /*stillRequest*/null);
@@ -509,22 +531,24 @@
     /**
      * Test focal length controls.
      */
+    @Test
     public void testFocalLengths() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(id);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + id + " is legacy, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
-                if (mStaticInfo.isExternalCamera()) {
+                if (staticInfo.isExternalCamera()) {
                     Log.i(TAG, "Camera " + id + " is external, skipping");
                     continue;
                 }
+                openDevice(id);
                 focalLengthTestByCamera();
             } finally {
                 closeDevice();
@@ -589,7 +613,7 @@
 
             validateJpegCapture(image, maxStillSz);
             verifyJpegKeys(image, result, maxStillSz, thumbnailSize, exifTestData,
-                    mStaticInfo, mCollector);
+                    mStaticInfo, mCollector, mDebugFileNameBase);
         }
     }
 
@@ -950,7 +974,7 @@
         validateRaw16Image(image, size);
         if (DEBUG) {
             byte[] rawBuffer = getDataFromImage(image);
-            String rawFileName = DEBUG_FILE_NAME_BASE + "/test" + "_" + size.toString() + "_cam" +
+            String rawFileName = mDebugFileNameBase + "/test" + "_" + size.toString() + "_cam" +
                     mCamera.getId() + ".raw16";
             Log.d(TAG, "Dump raw file into " + rawFileName);
             dumpFile(rawFileName, rawBuffer);
@@ -1038,13 +1062,13 @@
 
             if (DEBUG) {
                 byte[] rawBuffer = outputStream.toByteArray();
-                String rawFileName = DEBUG_FILE_NAME_BASE + "/raw16_" + TAG + size.toString() +
+                String rawFileName = mDebugFileNameBase + "/raw16_" + TAG + size.toString() +
                         "_cam_" + mCamera.getId() + ".dng";
                 Log.d(TAG, "Dump raw file into " + rawFileName);
                 dumpFile(rawFileName, rawBuffer);
 
                 byte[] jpegBuffer = getDataFromImage(jpegImage);
-                String jpegFileName = DEBUG_FILE_NAME_BASE + "/jpeg_" + TAG + size.toString() +
+                String jpegFileName = mDebugFileNameBase + "/jpeg_" + TAG + size.toString() +
                         "_cam_" + mCamera.getId() + ".jpg";
                 Log.d(TAG, "Dump jpeg file into " + rawFileName);
                 dumpFile(jpegFileName, jpegBuffer);
@@ -1069,25 +1093,44 @@
         assertNotNull(rawRequest);
         assertNotNull(rawResult);
 
-        Rational[] empty = new Rational[] { Rational.ZERO, Rational.ZERO, Rational.ZERO};
-        Rational[] neutralColorPoint = mCollector.expectKeyValueNotNull("NeutralColorPoint",
-                rawResult, CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
-        if (neutralColorPoint != null) {
-            mCollector.expectEquals("NeutralColorPoint length", empty.length,
-                    neutralColorPoint.length);
-            mCollector.expectNotEquals("NeutralColorPoint cannot be all zeroes, ", empty,
-                    neutralColorPoint);
-            mCollector.expectValuesGreaterOrEqual("NeutralColorPoint", neutralColorPoint,
-                    Rational.ZERO);
-        }
+        if (!mStaticInfo.isMonochromeCamera()) {
+            Rational[] empty = new Rational[] { Rational.ZERO, Rational.ZERO, Rational.ZERO};
+            Rational[] neutralColorPoint = mCollector.expectKeyValueNotNull("NeutralColorPoint",
+                    rawResult, CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+            if (neutralColorPoint != null) {
+                mCollector.expectEquals("NeutralColorPoint length", empty.length,
+                        neutralColorPoint.length);
+                mCollector.expectNotEquals("NeutralColorPoint cannot be all zeroes, ", empty,
+                        neutralColorPoint);
+                mCollector.expectValuesGreaterOrEqual("NeutralColorPoint", neutralColorPoint,
+                        Rational.ZERO);
+            }
 
-        mCollector.expectKeyValueGreaterOrEqual(rawResult, CaptureResult.SENSOR_GREEN_SPLIT, 0.0f);
+            mCollector.expectKeyValueGreaterOrEqual(rawResult,
+                    CaptureResult.SENSOR_GREEN_SPLIT, 0.0f);
+        }
 
         Pair<Double, Double>[] noiseProfile = mCollector.expectKeyValueNotNull("NoiseProfile",
                 rawResult, CaptureResult.SENSOR_NOISE_PROFILE);
         if (noiseProfile != null) {
-            mCollector.expectEquals("NoiseProfile length", noiseProfile.length,
-                /*Num CFA channels*/4);
+            int cfa = mStaticInfo.getCFAChecked();
+            int numCfaChannels = 0;
+            switch (cfa) {
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB:
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG:
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG:
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR:
+                    numCfaChannels = 4;
+                    break;
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO:
+                case CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR:
+                    numCfaChannels = 1;
+                    break;
+                default:
+                    Assert.fail("Invalid color filter arrangement " + cfa);
+                    break;
+            }
+            mCollector.expectEquals("NoiseProfile length", noiseProfile.length, numCfaChannels);
             for (Pair<Double, Double> p : noiseProfile) {
                 mCollector.expectTrue("NoiseProfile coefficients " + p +
                         " must have: S > 0, O >= 0", p.first > 0 && p.second >= 0);
@@ -1188,7 +1231,7 @@
             Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
 
             verifyJpegKeys(image, stillResult, maxStillSz, testThumbnailSizes[i], EXIF_TEST_DATA[i],
-                    mStaticInfo, mCollector);
+                    mStaticInfo, mCollector, mDebugFileNameBase);
 
             // Free image resources
             image.close();
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index a1a7e02..d59cf7d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -33,9 +33,10 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.util.Size;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.OutputConfiguration;
-import android.platform.test.annotations.AppModeFull;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
@@ -51,10 +52,11 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.Test;
+
 /**
  * CameraDevice preview test by using SurfaceView.
  */
-@AppModeFull
 public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "SurfaceViewPreviewTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -65,12 +67,12 @@
     private static final int PREPARE_TIMEOUT_MS = 10000; // 10 s
 
     @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();
     }
 
@@ -82,16 +84,17 @@
      * (monotonically increasing) ordering are verified.
      * </p>
      */
+    @Test
     public void testCameraPreview() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 previewTestByCamera();
             } finally {
                 closeDevice();
@@ -106,16 +109,17 @@
      * is not validated.
      * </p>
      */
+    @Test
     public void testBasicTestPatternPreview() throws Exception{
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
                 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 previewTestPatternTestByCamera();
             } finally {
                 closeDevice();
@@ -127,14 +131,15 @@
      * Test {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE} for preview, validate the preview
      * frame duration and exposure time.
      */
+    @Test
     public void testPreviewFpsRange() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 previewFpsRangeTestByCamera();
             } finally {
                 closeDevice();
@@ -152,14 +157,15 @@
      * also exercises the prepare API.
      * </p>
      */
+    @Test
     public void testSurfaceSet() throws Exception {
         for (String id : mCameraIds) {
             try {
-                openDevice(id);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(id);
                 surfaceSetTestByCamera(id);
             } finally {
                 closeDevice();
@@ -176,15 +182,16 @@
      * - Ensure that starting to use a newly-prepared output does not cause additional
      *   preview glitches to occur
      */
+    @Test
     public void testPreparePerformance() throws Throwable {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 preparePerformanceTestByCamera(mCameraIds[i]);
             }
             finally {
@@ -335,15 +342,16 @@
      * Test to verify correct behavior with the same Surface object being used repeatedly with
      * different native internals, and multiple Surfaces pointing to the same actual consumer object
      */
+    @Test
     public void testSurfaceEquality() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                openDevice(mCameraIds[i]);
                 surfaceEqualityTestByCamera(mCameraIds[i]);
             }
             finally {
@@ -425,20 +433,22 @@
     /*
      * Verify creation of deferred surface capture sessions
      */
+    @Test
     public void testDeferredSurfaces() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                if (staticInfo.isHardwareLevelLegacy()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
                     continue;
                 }
-                if (!mStaticInfo.isColorOutputSupported()) {
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIds[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
+                openDevice(mCameraIds[i]);
                 testDeferredSurfacesByCamera(mCameraIds[i]);
             }
             finally {
@@ -516,6 +526,12 @@
             }
         }
 
+        // Check whether session configuration is supported
+        assertTrue("Deferred session configuration query failed",
+                CameraTestUtils.checkSessionConfiguration(mCamera, mHandler, outputSurfaces,
+                /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                /*expectedResult*/ true));
+
         // Create session
 
         BlockingSessionCallback sessionListener =
diff --git a/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java b/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
index fa05199..93e5392 100644
--- a/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
+++ b/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
@@ -119,6 +119,96 @@
     }
 
     /**
+     * Utility class wrapping Bayer specific DNG metadata.
+     */
+    static class DngBayerMetadata {
+        public final int referenceIlluminant1;
+        public final int referenceIlluminant2;
+        public final float[] calibrationTransform1;
+        public final float[] calibrationTransform2;
+        public final float[] colorMatrix1;
+        public final float[] colorMatrix2;
+        public final float[] forwardTransform1;
+        public final float[] forwardTransform2;
+        public final Rational[/*3*/] neutralColorPoint;
+
+        /**
+         * Convert a 9x9 {@link ColorSpaceTransform} to a matrix and write the matrix into the
+         * output.
+         *
+         * @param xform a {@link ColorSpaceTransform} to transform.
+         * @param output the 3x3 matrix to overwrite.
+         */
+        private static void convertColorspaceTransform(ColorSpaceTransform xform,
+                /*out*/float[] output) {
+            for (int i = 0; i < 3; i++) {
+                for (int j = 0; j < 3; j++) {
+                    output[i * 3 + j] = xform.getElement(j, i).floatValue();
+                }
+            }
+        }
+
+        /**
+         * Constructor to parse static and dynamic metadata into DNG metadata.
+         */
+        public DngBayerMetadata(CameraCharacteristics staticMetadata,
+                CaptureResult dynamicMetadata) {
+            referenceIlluminant1 =
+                    staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1);
+            if (staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
+                referenceIlluminant2 =
+                        staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2);
+            } else {
+                referenceIlluminant2 = referenceIlluminant1;
+            }
+            calibrationTransform1 = new float[9];
+            calibrationTransform2 = new float[9];
+            convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1),
+                    calibrationTransform1);
+            if (staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2) != null) {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2),
+                    calibrationTransform2);
+            } else {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1),
+                    calibrationTransform2);
+            }
+            colorMatrix1 = new float[9];
+            colorMatrix2 = new float[9];
+            convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1),
+                    colorMatrix1);
+            if (staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2) != null) {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2),
+                    colorMatrix2);
+            } else {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1),
+                    colorMatrix2);
+            }
+            forwardTransform1 = new float[9];
+            forwardTransform2 = new float[9];
+            convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1),
+                    forwardTransform1);
+            if (staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2) != null) {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2),
+                    forwardTransform2);
+            } else {
+                convertColorspaceTransform(
+                    staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1),
+                    forwardTransform2);
+            }
+
+            neutralColorPoint = dynamicMetadata.get(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+        }
+    }
+
+    /**
      * Convert a RAW16 buffer into an sRGB buffer, and write the result into a bitmap.
      *
      * <p> This function applies the operations roughly outlined in the Adobe DNG specification
@@ -191,62 +281,23 @@
             CaptureResult dynamicMetadata, int outputOffsetX, int outputOffsetY,
             /*out*/Bitmap argbOutput) {
         int cfa = staticMetadata.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
+        boolean isMono = (cfa == CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO ||
+                cfa == CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR);
         int[] blackLevelPattern = new int[4];
         staticMetadata.get(CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN).
                 copyTo(blackLevelPattern, /*offset*/0);
         int whiteLevel = staticMetadata.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
-        int ref1 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1);
-        int ref2;
-        if (staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
-            ref2 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2);
-        }
-        else {
-            ref2 = ref1;
-        }
-        float[] calib1 = new float[9];
-        float[] calib2 = new float[9];
-        convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1), calib1);
-        if (staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2) != null) {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2), calib2);
-        }
-        else {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1), calib2);
-        }
-        float[] color1 = new float[9];
-        float[] color2 = new float[9];
-        convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1), color1);
-        if (staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2) != null) {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2), color2);
-        }
-        else {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1), color2);
-        }
-        float[] forward1 = new float[9];
-        float[] forward2 = new float[9];
-        convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1), forward1);
-        if (staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2) != null) {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2), forward2);
-        }
-        else {
-            convertColorspaceTransform(
-                staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1), forward2);
-        }
 
-        Rational[] neutral = dynamicMetadata.get(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+        LensShadingMap shadingMap = dynamicMetadata.get(
+                CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
 
-        LensShadingMap shadingMap = dynamicMetadata.get(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
-
-        convertToSRGB(rs, inputWidth, inputHeight, inputStride, cfa, blackLevelPattern, whiteLevel,
-                rawImageInput, ref1, ref2, calib1, calib2, color1, color2,
-                forward1, forward2, neutral, shadingMap, outputOffsetX, outputOffsetY, argbOutput);
+        DngBayerMetadata dngBayerMetadata = null;
+        if (!isMono) {
+            dngBayerMetadata = new DngBayerMetadata(staticMetadata, dynamicMetadata);
+        }
+        convertToSRGB(rs, inputWidth, inputHeight, inputStride, cfa, blackLevelPattern,
+                whiteLevel, rawImageInput, dngBayerMetadata,
+                shadingMap, outputOffsetX, outputOffsetY, argbOutput);
     }
 
     /**
@@ -256,11 +307,8 @@
      */
     private static void convertToSRGB(RenderScript rs, int inputWidth, int inputHeight,
             int inputStride, int cfa, int[] blackLevelPattern, int whiteLevel, byte[] rawImageInput,
-            int referenceIlluminant1, int referenceIlluminant2, float[] calibrationTransform1,
-            float[] calibrationTransform2, float[] colorMatrix1, float[] colorMatrix2,
-            float[] forwardTransform1, float[] forwardTransform2, Rational[/*3*/] neutralColorPoint,
-            LensShadingMap lensShadingMap, int outputOffsetX, int outputOffsetY,
-            /*out*/Bitmap argbOutput) {
+            DngBayerMetadata dngBayerMetadata, LensShadingMap lensShadingMap,
+            int outputOffsetX, int outputOffsetY, /*out*/Bitmap argbOutput) {
 
         // Validate arguments
         if (argbOutput == null || rs == null || rawImageInput == null) {
@@ -286,7 +334,7 @@
                     ", h=" + inputHeight + "), cannot converted into sRGB image with dimensions (w="
                     + outWidth + ", h=" + outHeight + ").");
         }
-        if (cfa < 0 || cfa > 3) {
+        if (cfa < 0 || cfa > 5) {
             throw new IllegalArgumentException("Unsupported cfa pattern " + cfa + " used.");
         }
         if (DEBUG) {
@@ -297,15 +345,6 @@
             Log.d(TAG, "CFA: " + cfa);
             Log.d(TAG, "BlackLevelPattern: " + Arrays.toString(blackLevelPattern));
             Log.d(TAG, "WhiteLevel: " + whiteLevel);
-            Log.d(TAG, "ReferenceIlluminant1: " + referenceIlluminant1);
-            Log.d(TAG, "ReferenceIlluminant2: " + referenceIlluminant2);
-            Log.d(TAG, "CalibrationTransform1: " + Arrays.toString(calibrationTransform1));
-            Log.d(TAG, "CalibrationTransform2: " + Arrays.toString(calibrationTransform2));
-            Log.d(TAG, "ColorMatrix1: " + Arrays.toString(colorMatrix1));
-            Log.d(TAG, "ColorMatrix2: " + Arrays.toString(colorMatrix2));
-            Log.d(TAG, "ForwardTransform1: " + Arrays.toString(forwardTransform1));
-            Log.d(TAG, "ForwardTransform2: " + Arrays.toString(forwardTransform2));
-            Log.d(TAG, "NeutralColorPoint: " + Arrays.toString(neutralColorPoint));
         }
 
         Allocation gainMap = null;
@@ -316,43 +355,74 @@
                     lensShadingMap.getRowCount());
         }
 
-        float[] normalizedForwardTransform1 = Arrays.copyOf(forwardTransform1,
-                forwardTransform1.length);
-        normalizeFM(normalizedForwardTransform1);
-        float[] normalizedForwardTransform2 = Arrays.copyOf(forwardTransform2,
-                forwardTransform2.length);
-        normalizeFM(normalizedForwardTransform2);
+        float[] sensorToProPhoto = new float[9];
+        float[] proPhotoToSRGB = new float[9];
+        if (dngBayerMetadata != null) {
+            float[] normalizedForwardTransform1 = Arrays.copyOf(dngBayerMetadata.forwardTransform1,
+                    dngBayerMetadata.forwardTransform1.length);
+            normalizeFM(normalizedForwardTransform1);
+            float[] normalizedForwardTransform2 = Arrays.copyOf(dngBayerMetadata.forwardTransform2,
+                    dngBayerMetadata.forwardTransform2.length);
+            normalizeFM(normalizedForwardTransform2);
 
-        float[] normalizedColorMatrix1 = Arrays.copyOf(colorMatrix1, colorMatrix1.length);
-        normalizeCM(normalizedColorMatrix1);
-        float[] normalizedColorMatrix2 = Arrays.copyOf(colorMatrix2, colorMatrix2.length);
-        normalizeCM(normalizedColorMatrix2);
+            float[] normalizedColorMatrix1 = Arrays.copyOf(dngBayerMetadata.colorMatrix1,
+                    dngBayerMetadata.colorMatrix1.length);
+            normalizeCM(normalizedColorMatrix1);
+            float[] normalizedColorMatrix2 = Arrays.copyOf(dngBayerMetadata.colorMatrix2,
+                    dngBayerMetadata.colorMatrix2.length);
+            normalizeCM(normalizedColorMatrix2);
 
-        if (DEBUG) {
-            Log.d(TAG, "Normalized ForwardTransform1: " + Arrays.toString(normalizedForwardTransform1));
-            Log.d(TAG, "Normalized ForwardTransform2: " + Arrays.toString(normalizedForwardTransform2));
-            Log.d(TAG, "Normalized ColorMatrix1: " + Arrays.toString(normalizedColorMatrix1));
-            Log.d(TAG, "Normalized ColorMatrix2: " + Arrays.toString(normalizedColorMatrix2));
+            if (DEBUG) {
+                Log.d(TAG, "ReferenceIlluminant1: " + dngBayerMetadata.referenceIlluminant1);
+                Log.d(TAG, "ReferenceIlluminant2: " + dngBayerMetadata.referenceIlluminant2);
+                Log.d(TAG, "CalibrationTransform1: "
+                        + Arrays.toString(dngBayerMetadata.calibrationTransform1));
+                Log.d(TAG, "CalibrationTransform2: "
+                        + Arrays.toString(dngBayerMetadata.calibrationTransform2));
+                Log.d(TAG, "ColorMatrix1: "
+                        + Arrays.toString(dngBayerMetadata.colorMatrix1));
+                Log.d(TAG, "ColorMatrix2: "
+                        + Arrays.toString(dngBayerMetadata.colorMatrix2));
+                Log.d(TAG, "ForwardTransform1: "
+                        + Arrays.toString(dngBayerMetadata.forwardTransform1));
+                Log.d(TAG, "ForwardTransform2: "
+                        + Arrays.toString(dngBayerMetadata.forwardTransform2));
+                Log.d(TAG, "NeutralColorPoint: "
+                        + Arrays.toString(dngBayerMetadata.neutralColorPoint));
+
+                Log.d(TAG, "Normalized ForwardTransform1: "
+                        + Arrays.toString(normalizedForwardTransform1));
+                Log.d(TAG, "Normalized ForwardTransform2: "
+                        + Arrays.toString(normalizedForwardTransform2));
+                Log.d(TAG, "Normalized ColorMatrix1: "
+                        + Arrays.toString(normalizedColorMatrix1));
+                Log.d(TAG, "Normalized ColorMatrix2: "
+                        + Arrays.toString(normalizedColorMatrix2));
+            }
+
+            // Calculate full sensor colorspace to sRGB colorspace transform.
+            double interpolationFactor = findDngInterpolationFactor(
+                    dngBayerMetadata.referenceIlluminant1, dngBayerMetadata.referenceIlluminant2,
+                    dngBayerMetadata.calibrationTransform1, dngBayerMetadata.calibrationTransform2,
+                    normalizedColorMatrix1, normalizedColorMatrix2,
+                    dngBayerMetadata.neutralColorPoint);
+            if (DEBUG) Log.d(TAG, "Interpolation factor used: " + interpolationFactor);
+            float[] sensorToXYZ = new float[9];
+            calculateCameraToXYZD50Transform(normalizedForwardTransform1,
+                    normalizedForwardTransform2,
+                    dngBayerMetadata.calibrationTransform1, dngBayerMetadata.calibrationTransform2,
+                    dngBayerMetadata.neutralColorPoint,
+                    interpolationFactor, /*out*/sensorToXYZ);
+            if (DEBUG) Log.d(TAG, "CameraToXYZ xform used: " + Arrays.toString(sensorToXYZ));
+            multiply(sXYZtoProPhoto, sensorToXYZ, /*out*/sensorToProPhoto);
+            if (DEBUG) {
+                Log.d(TAG, "CameraToIntemediate xform used: " + Arrays.toString(sensorToProPhoto));
+            }
+            multiply(sXYZtoRGBBradford, sProPhotoToXYZ, /*out*/proPhotoToSRGB);
         }
 
-        // Calculate full sensor colorspace to sRGB colorspace transform.
-        double interpolationFactor = findDngInterpolationFactor(referenceIlluminant1,
-                referenceIlluminant2, calibrationTransform1, calibrationTransform2,
-                normalizedColorMatrix1, normalizedColorMatrix2, neutralColorPoint);
-        if (DEBUG) Log.d(TAG, "Interpolation factor used: " + interpolationFactor);
-        float[] sensorToXYZ = new float[9];
-        calculateCameraToXYZD50Transform(normalizedForwardTransform1, normalizedForwardTransform2,
-                calibrationTransform1, calibrationTransform2, neutralColorPoint,
-                interpolationFactor, /*out*/sensorToXYZ);
-        if (DEBUG) Log.d(TAG, "CameraToXYZ xform used: " + Arrays.toString(sensorToXYZ));
-        float[] sensorToProPhoto = new float[9];
-        multiply(sXYZtoProPhoto, sensorToXYZ, /*out*/sensorToProPhoto);
-        if (DEBUG) Log.d(TAG, "CameraToIntemediate xform used: " + Arrays.toString(sensorToProPhoto));
         Allocation output = Allocation.createFromBitmap(rs, argbOutput);
 
-        float[] proPhotoToSRGB = new float[9];
-        multiply(sXYZtoRGBBradford, sProPhotoToXYZ, /*out*/proPhotoToSRGB);
-
         // Setup input allocation (16-bit raw pixels)
         Type.Builder typeBuilder = new Type.Builder(rs, Element.U16(rs));
         typeBuilder.setX((inputStride / 2));
@@ -365,14 +435,10 @@
         ScriptC_raw_converter converterKernel = new ScriptC_raw_converter(rs);
         converterKernel.set_inputRawBuffer(input);
         converterKernel.set_whiteLevel(whiteLevel);
-        converterKernel.set_sensorToIntermediate(new Matrix3f(transpose(sensorToProPhoto)));
-        converterKernel.set_intermediateToSRGB(new Matrix3f(transpose(proPhotoToSRGB)));
         converterKernel.set_offsetX(outputOffsetX);
         converterKernel.set_offsetY(outputOffsetY);
         converterKernel.set_rawHeight(inputHeight);
         converterKernel.set_rawWidth(inputWidth);
-        converterKernel.set_neutralPoint(new Float3(neutralColorPoint[0].floatValue(),
-                neutralColorPoint[1].floatValue(), neutralColorPoint[2].floatValue()));
         converterKernel.set_toneMapCoeffs(new Float4(DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[0],
                 DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[1], DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[2],
                 DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[3]));
@@ -383,6 +449,16 @@
             converterKernel.set_gainMapHeight(lensShadingMap.getRowCount());
         }
 
+        converterKernel.set_isMonochrome(dngBayerMetadata == null);
+        if (dngBayerMetadata != null) {
+            converterKernel.set_sensorToIntermediate(new Matrix3f(transpose(sensorToProPhoto)));
+            converterKernel.set_intermediateToSRGB(new Matrix3f(transpose(proPhotoToSRGB)));
+            converterKernel.set_neutralPoint(
+                    new Float3(dngBayerMetadata.neutralColorPoint[0].floatValue(),
+                    dngBayerMetadata.neutralColorPoint[1].floatValue(),
+                    dngBayerMetadata.neutralColorPoint[2].floatValue()));
+        }
+
         converterKernel.set_cfaPattern(cfa);
         converterKernel.set_blackLevelPattern(new Int4(blackLevelPattern[0],
                 blackLevelPattern[1], blackLevelPattern[2], blackLevelPattern[3]));
@@ -477,21 +553,6 @@
     }
 
     /**
-     * Convert a 9x9 {@link ColorSpaceTransform} to a matrix and write the matrix into the
-     * output.
-     *
-     * @param xform a {@link ColorSpaceTransform} to transform.
-     * @param output the 3x3 matrix to overwrite.
-     */
-    private static void convertColorspaceTransform(ColorSpaceTransform xform, /*out*/float[] output) {
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 3; j++) {
-                output[i * 3 + j] = xform.getElement(j, i).floatValue();
-            }
-        }
-    }
-
-    /**
      * Find the interpolation factor to use with the RAW matrices given a neutral color point.
      *
      * @param referenceIlluminant1 first reference illuminant.
diff --git a/tests/camera/src/android/hardware/camera2/cts/rs/raw_converter.rs b/tests/camera/src/android/hardware/camera2/cts/rs/raw_converter.rs
index fb467bb..e1146fc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/rs/raw_converter.rs
+++ b/tests/camera/src/android/hardware/camera2/cts/rs/raw_converter.rs
@@ -29,6 +29,7 @@
 uint gainMapWidth;  // The width of the gain map
 uint gainMapHeight;  // The height of the gain map
 bool hasGainMap; // Does gainmap exist?
+bool isMonochrome;  // Is monochrome camera?
 rs_matrix3x3 sensorToIntermediate; // Color transform from sensor to a wide-gamut colorspace
 rs_matrix3x3 intermediateToSRGB; // Color transform from wide-gamut colorspace to sRGB
 ushort4 blackLevelPattern; // Blacklevel to subtract for each channel, given in CFA order
@@ -360,13 +361,34 @@
     if (xP == rawWidth - 1) xP = rawWidth - 2;
     if (yP == rawHeight - 1) yP = rawHeight  - 2;
 
-    float patch[9];
-    // TODO: Once ScriptGroup and RS kernels have been updated to allow for iteration over 3x3 pixel
-    // patches, this can be optimized to avoid re-applying the pre-demosaic steps for each pixel,
-    // potentially achieving a 9x speedup here.
-    load3x3(xP, yP, inputRawBuffer, /*out*/ patch);
-    linearizeAndGainmap(xP, yP, blackLevelPattern, whiteLevel, cfaPattern, /*inout*/patch);
-    pRGB = demosaic(xP, yP, cfaPattern, patch);
+    if (isMonochrome) {
+        float pixel = *((ushort *) rsGetElementAt(inputRawBuffer, x, y));
 
-    return rsPackColorTo8888(applyColorspace(pRGB));
+        // Apply linearization and gain map
+        float4 gains = 1.f;
+        if (hasGainMap) {
+            gains = getGain(xP, yP);
+        }
+        float bl = blackLevelPattern.x;
+        float g = gains.x;
+        pixel = clamp(g * (pixel - bl) / (whiteLevel - bl), 0.f, 1.f);
+
+        // Use same Y value for R, G, and B.
+        pRGB.x = pRGB.y = pRGB.z = pixel;
+
+        // apply tonemap and gamma correction
+        pRGB = tonemap(pRGB);
+        pRGB = gammaCorrectPixel(pRGB);
+    } else {
+        float patch[9];
+        // TODO: Once ScriptGroup and RS kernels have been updated to allow for iteration over 3x3 pixel
+        // patches, this can be optimized to avoid re-applying the pre-demosaic steps for each pixel,
+        // potentially achieving a 9x speedup here.
+        load3x3(xP, yP, inputRawBuffer, /*out*/ patch);
+        linearizeAndGainmap(xP, yP, blackLevelPattern, whiteLevel, cfaPattern, /*inout*/patch);
+        pRGB = demosaic(xP, yP, cfaPattern, patch);
+        pRGB = applyColorspace(pRGB);
+    }
+
+    return rsPackColorTo8888(pRGB);
 }
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 4513c5c..55a4399 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -24,10 +24,12 @@
 import android.graphics.Rect;
 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.CameraTestUtils;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -36,7 +38,6 @@
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
@@ -57,8 +58,6 @@
     private static final String TAG = "Camera2AndroidTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    protected static final String DEBUG_FILE_NAME_BASE =
-            Environment.getExternalStorageDirectory().getPath();
     // 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;
@@ -79,6 +78,7 @@
     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;
 
@@ -113,13 +113,27 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateCallback();
         mCollector = new CameraErrorCollector();
+        mDebugFileNameBase = getContext().getExternalFilesDir(null).getPath();
 
         mAllStaticInfo = new HashMap<String, StaticMetadata>();
+        List<String> hiddenPhysicalIds = new ArrayList<>();
         for (String cameraId : mCameraIds) {
-            StaticMetadata staticMetadata = new StaticMetadata(
-                    mCameraManager.getCameraCharacteristics(cameraId),
+            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) &&
+                        !hiddenPhysicalIds.contains(physicalId)) {
+                    hiddenPhysicalIds.add(physicalId);
+                    props = mCameraManager.getCameraCharacteristics(physicalId);
+                    staticMetadata = new StaticMetadata(
+                            mCameraManager.getCameraCharacteristics(physicalId),
+                            CheckLevel.ASSERT, /*collector*/null);
+                    mAllStaticInfo.put(physicalId, staticMetadata);
+                }
+            }
         }
     }
 
@@ -210,8 +224,7 @@
         mCamera = CameraTestUtils.openCamera(
                 mCameraManager, cameraId, listener, mHandler);
         mCollector.setCameraId(cameraId);
-        mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
-                CheckLevel.ASSERT, /*collector*/null);
+        mStaticInfo = mAllStaticInfo.get(cameraId);
         if (mStaticInfo.isColorOutputSupported()) {
             mOrderedPreviewSizes = getSupportedPreviewSizes(
                     cameraId, mCameraManager,
@@ -319,6 +332,31 @@
     }
 
     /**
+     * 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>
@@ -342,6 +380,29 @@
     }
 
     /**
+     * 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() {
@@ -370,6 +431,14 @@
         }
     }
 
+    protected boolean checkImageReaderSessionConfiguration() throws Exception {
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        outputConfigs.add(new OutputConfiguration(mReaderSurface));
+
+        return checkSessionConfiguration(mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
+                SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true);
+    }
+
     protected CaptureRequest prepareCaptureRequest() throws Exception {
         List<Surface> outputSurfaces = new ArrayList<Surface>();
         Surface surface = mReader.getSurface();
@@ -394,6 +463,22 @@
         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}.
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 ae06f3d..4eb1543 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -20,6 +20,7 @@
 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
@@ -34,12 +35,13 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.rule.ActivityTestRule;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -56,11 +58,14 @@
 import java.util.List;
 import java.util.HashMap;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
 /**
  * Camera2 test case base class by using mixed SurfaceView and TextureView as rendering target.
  */
-public class Camera2MultiViewTestCase extends
-        ActivityInstrumentationTestCase2<Camera2MultiViewCtsActivity> {
+public class Camera2MultiViewTestCase {
     private static final String TAG = "MultiViewTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -73,6 +78,7 @@
 
     private CameraManager mCameraManager;
     private HandlerThread mHandlerThread;
+    private Activity mActivity;
     private Context mContext;
 
     private CameraHolder[] mCameraHolders;
@@ -80,14 +86,14 @@
 
     protected WindowManager mWindowManager;
 
-    public Camera2MultiViewTestCase() {
-        super(Camera2MultiViewCtsActivity.class);
-    }
+    @Rule
+    public ActivityTestRule<Camera2MultiViewCtsActivity> mActivityRule =
+            new ActivityTestRule<>(Camera2MultiViewCtsActivity.class);
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getActivity();
+    @Before
+    public void setUp() throws Exception {
+        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);
@@ -96,7 +102,7 @@
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
-        Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
+        Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mActivity;
         for (int i = 0; i < Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS; i++) {
             mTextureView[i] = activity.getTextureView(i);
         }
@@ -111,8 +117,8 @@
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @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));
@@ -129,7 +135,6 @@
                 camera = null;
             }
         }
-        super.tearDown();
     }
 
     /**
@@ -146,7 +151,7 @@
      */
     protected void updatePreviewDisplayRotation(Size previewSize, TextureView textureView) {
         int rotationDegrees = 0;
-        Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
+        Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mActivity;
         int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
         Configuration config = activity.getResources().getConfiguration();
 
@@ -288,6 +293,13 @@
         camera.updateOutputConfiguration(config);
     }
 
+    protected boolean isSessionConfigurationSupported(String cameraId,
+            List<OutputConfiguration> configs) {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+        return camera.isSessionConfigurationSupported(configs);
+    }
+
     protected void capture(String cameraId, CaptureRequest request, CaptureCallback listener)
             throws Exception {
         CameraHolder camera = getCameraHolder(cameraId);
@@ -525,6 +537,10 @@
         public int startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
                 CaptureCallback listener)
                 throws Exception {
+            assertTrue("Session configuration query should not fail",
+                    checkSessionConfiguration(mCamera, mHandler, outputConfigs,
+                    /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                    /*expectedResult*/ true));
             createSessionWithConfigs(outputConfigs);
 
             CaptureRequest.Builder captureBuilder =
@@ -555,6 +571,12 @@
             mSession.updateOutputConfiguration(config);
         }
 
+        public boolean isSessionConfigurationSupported(List<OutputConfiguration> configs) {
+            return checkSessionConfiguration(mCamera, mHandler, configs,
+                    /*inputConig*/ null, SessionConfiguration.SESSION_REGULAR,
+                    /*expectedResult*/ true);
+        }
+
         public void capture(CaptureRequest request, CaptureCallback listener)
                 throws Exception {
             mSession.capture(request, listener, mHandler);
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 6697b75..8c245bd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -21,11 +21,9 @@
 
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.ImageReader;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
@@ -50,6 +48,7 @@
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.support.test.rule.ActivityTestRule;
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
@@ -60,6 +59,10 @@
 import java.util.HashMap;
 import java.util.List;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
 /**
  * Camera2 Preview test case base class by using SurfaceView as rendering target.
  *
@@ -70,15 +73,11 @@
  * </p>
  */
 
-public class Camera2SurfaceViewTestCase extends
-        ActivityInstrumentationTestCase2<Camera2SurfaceViewCtsActivity> {
+public class Camera2SurfaceViewTestCase {
     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;
 
-    // TODO: Use internal storage for this to make sure the file is only visible to test.
-    protected static final String DEBUG_FILE_NAME_BASE =
-            Environment.getExternalStorageDirectory().getPath();
     protected static final int WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
     protected static final float FRAME_DURATION_ERROR_MARGIN = 0.01f; // 1 percent error margin.
     protected static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
@@ -93,6 +92,7 @@
     protected BlockingStateCallback mCameraListener;
     protected BlockingSessionCallback mSessionListener;
     protected CameraErrorCollector mCollector;
+    protected HashMap<String, StaticMetadata> mAllStaticInfo;
     // Per device fields:
     protected StaticMetadata mStaticInfo;
     protected CameraDevice mCamera;
@@ -107,21 +107,17 @@
     protected List<Size> mOrderedVideoSizes; // In descending order.
     protected List<Size> mOrderedStillSizes; // In descending order.
     protected HashMap<Size, Long> mMinPreviewFrameDurationMap;
+    protected String mDebugFileNameBase;
 
     protected WindowManager mWindowManager;
 
-    public Camera2SurfaceViewTestCase() {
-        super(Camera2SurfaceViewCtsActivity.class);
-    }
+    @Rule
+    public ActivityTestRule<Camera2SurfaceViewCtsActivity> mActivityRule =
+            new ActivityTestRule<>(Camera2SurfaceViewCtsActivity.class);
 
-    @Override
-    protected void setUp() throws Exception {
-        /**
-         * Set up the camera preview required environments, including activity,
-         * CameraManager, HandlerThread, Camera IDs, and CameraStateCallback.
-         */
-        super.setUp();
-        mContext = getActivity();
+    @Before
+    public void setUp() throws Exception {
+        mContext = mActivityRule.getActivity().getApplicationContext();
         /**
          * Workaround for mockito and JB-MR2 incompatibility
          *
@@ -138,12 +134,34 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateCallback();
         mCollector = new CameraErrorCollector();
+        mDebugFileNameBase = mContext.getExternalFilesDir(null).getPath();
+
+        mAllStaticInfo = new HashMap<String, StaticMetadata>();
+        List<String> hiddenPhysicalIds = new ArrayList<>();
+        for (String cameraId : mCameraIds) {
+            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) &&
+                        !hiddenPhysicalIds.contains(physicalId)) {
+                    hiddenPhysicalIds.add(physicalId);
+                    props = mCameraManager.getCameraCharacteristics(physicalId);
+                    staticMetadata = new StaticMetadata(
+                            mCameraManager.getCameraCharacteristics(physicalId),
+                            CheckLevel.ASSERT, /*collector*/null);
+                    mAllStaticInfo.put(physicalId, staticMetadata);
+                }
+            }
+        }
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @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));
@@ -162,8 +180,6 @@
         } catch (Throwable e) {
             // When new Exception(e) is used, exception info will be printed twice.
             throw new Exception(e.getMessage());
-        } finally {
-            super.tearDown();
         }
     }
 
@@ -333,9 +349,8 @@
     protected static <T> void waitForResultValue(SimpleCaptureCallback listener,
             CaptureResult.Key<T> resultKey,
             T expectedValue, int numResultsWait) {
-        List<T> expectedValues = new ArrayList<T>();
-        expectedValues.add(expectedValue);
-        waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait);
+        CameraTestUtils.waitForResultValue(listener, resultKey, expectedValue,
+                numResultsWait, WAIT_FOR_RESULT_TIMEOUT_MS);
     }
 
     /**
@@ -357,31 +372,8 @@
     protected static <T> void waitForAnyResultValue(SimpleCaptureCallback listener,
             CaptureResult.Key<T> resultKey,
             List<T> expectedValues, int numResultsWait) {
-        if (numResultsWait < 0 || listener == null || expectedValues == null) {
-            throw new IllegalArgumentException(
-                    "Input must be non-negative number and listener/expectedValues "
-                    + "must be non-null");
-        }
-
-        int i = 0;
-        CaptureResult result;
-        do {
-            result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
-            T value = result.get(resultKey);
-            for ( T expectedValue : expectedValues) {
-                if (VERBOSE) {
-                    Log.v(TAG, "Current result value for key " + resultKey.getName() + " is: "
-                            + value.toString());
-                }
-                if (value.equals(expectedValue)) {
-                    return;
-                }
-            }
-        } while (i++ < numResultsWait);
-
-        throw new TimeoutRuntimeException(
-                "Unable to get the expected result value " + expectedValues + " for key " +
-                        resultKey.getName() + " after waiting for " + numResultsWait + " results");
+        CameraTestUtils.waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait,
+                WAIT_FOR_RESULT_TIMEOUT_MS);
     }
 
     /**
@@ -465,17 +457,8 @@
      */
     protected static CaptureResult waitForNumResults(SimpleCaptureCallback resultListener,
             int numResultsWait) {
-        if (numResultsWait < 0 || resultListener == null) {
-            throw new IllegalArgumentException(
-                    "Input must be positive number and listener must be non-null");
-        }
-
-        CaptureResult result = null;
-        for (int i = 0; i < numResultsWait; i++) {
-            result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
-        }
-
-        return result;
+        return CameraTestUtils.waitForNumResults(resultListener, numResultsWait,
+                WAIT_FOR_RESULT_TIMEOUT_MS);
     }
 
     /**
@@ -495,7 +478,6 @@
         waitForNumResults(resultListener, maxLatency);
     }
 
-
     /**
      * Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
      *
@@ -512,17 +494,8 @@
      */
     protected void waitForAeStable(SimpleCaptureCallback resultListener,
             int numResultWaitForUnknownLatency) {
-        waitForSettingsApplied(resultListener, numResultWaitForUnknownLatency);
-
-        if (!mStaticInfo.isHardwareLevelAtLeastLimited()) {
-            // No-op for metadata
-            return;
-        }
-        List<Integer> expectedAeStates = new ArrayList<Integer>();
-        expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_CONVERGED));
-        expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED));
-        waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
-                NUM_RESULTS_WAIT_TIMEOUT);
+        CameraTestUtils.waitForAeStable(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY,
+                mStaticInfo, WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
     }
 
     /**
@@ -551,8 +524,8 @@
 
         List<Integer> expectedAeStates = new ArrayList<Integer>();
         expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_LOCKED));
-        waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
-                NUM_RESULTS_WAIT_TIMEOUT);
+        CameraTestUtils.waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE,
+                expectedAeStates, WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
     }
 
     /**
@@ -583,6 +556,70 @@
     }
 
     /**
+     * Close the pending images then close current active {@link ImageReader} objects.
+     */
+    protected void closeImageReaders(ImageReader[] readers) {
+        CameraTestUtils.closeImageReaders(readers);
+    }
+
+    /**
+     * Setup still capture configuration and start preview.
+     *
+     * @param previewRequest The capture request to be used for preview
+     * @param stillRequest The capture request to be used for still capture
+     * @param previewSz Preview size
+     * @param captureSizes Still capture sizes
+     * @param formats The single capture image formats
+     * @param resultListener Capture result listener
+     * @param maxNumImages The max number of images set to the image reader
+     * @param imageListeners The single capture capture image listeners
+     */
+    protected ImageReader[] prepareStillCaptureAndStartPreview(
+            CaptureRequest.Builder previewRequest, CaptureRequest.Builder stillRequest,
+            Size previewSz, Size[] captureSizes, int[] formats, CaptureCallback resultListener,
+            int maxNumImages, ImageReader.OnImageAvailableListener[] imageListeners)
+            throws Exception {
+
+        if ((captureSizes == null) || (formats == null) || (imageListeners == null) &&
+                (captureSizes.length != formats.length) ||
+                (formats.length != imageListeners.length)) {
+            throw new IllegalArgumentException("Invalid capture sizes/formats or image listeners!");
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Prepare still capture and preview (%s)",
+                    previewSz.toString()));
+        }
+
+        // Update preview size.
+        updatePreviewSurface(previewSz);
+
+        ImageReader[] readers = new ImageReader[captureSizes.length];
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
+        outputSurfaces.add(mPreviewSurface);
+        for (int i = 0; i < captureSizes.length; i++) {
+            readers[i] = makeImageReader(captureSizes[i], formats[i], maxNumImages,
+                    imageListeners[i], mHandler);
+            outputSurfaces.add(readers[i].getSurface());
+        }
+
+        mSessionListener = new BlockingSessionCallback();
+        mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+
+        // Configure the requests.
+        previewRequest.addTarget(mPreviewSurface);
+        stillRequest.addTarget(mPreviewSurface);
+        for (int i = 0; i < readers.length; i++) {
+            stillRequest.addTarget(readers[i].getSurface());
+        }
+
+        // Start preview.
+        mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+
+        return readers;
+    }
+
+    /**
      * Open a camera device and get the StaticMetadata for a given camera id.
      *
      * @param cameraId The id of the camera device to be opened.
@@ -636,7 +673,7 @@
         }
 
         mPreviewSize = size;
-        Camera2SurfaceViewCtsActivity ctsActivity = getActivity();
+        Camera2SurfaceViewCtsActivity ctsActivity = mActivityRule.getActivity();
         final SurfaceHolder holder = ctsActivity.getSurfaceView().getHolder();
         Handler handler = new Handler(Looper.getMainLooper());
         handler.post(new Runnable() {
@@ -663,7 +700,7 @@
      * recreated
      */
     protected void recreatePreviewSurface() {
-        Camera2SurfaceViewCtsActivity ctsActivity = getActivity();
+        Camera2SurfaceViewCtsActivity ctsActivity = mActivityRule.getActivity();
         setPreviewVisibility(View.GONE);
         boolean res = ctsActivity.waitForSurfaceState(
             WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, /*valid*/ false);
@@ -681,7 +718,7 @@
      * @param visibility the new new visibility to set, one of View.VISIBLE / INVISIBLE / GONE
      */
     protected void setPreviewVisibility(int visibility) {
-        final Camera2SurfaceViewCtsActivity ctsActivity = getActivity();
+        final Camera2SurfaceViewCtsActivity ctsActivity = mActivityRule.getActivity();
         Handler handler = new Handler(Looper.getMainLooper());
         handler.post(new Runnable() {
             @Override
@@ -829,37 +866,6 @@
     }
 
     protected Range<Integer> getSuitableFpsRangeForDuration(String cameraId, long frameDuration) {
-        // Add 0.05 here so Fps like 29.99 evaluated to 30
-        int minBurstFps = (int) Math.floor(1e9 / frameDuration + 0.05f);
-        boolean foundConstantMaxYUVRange = false;
-        boolean foundYUVStreamingRange = false;
-        boolean isExternalCamera = mStaticInfo.isExternalCamera();
-
-        // Find suitable target FPS range - as high as possible that covers the max YUV rate
-        // Also verify that there's a good preview rate as well
-        List<Range<Integer> > fpsRanges = Arrays.asList(
-                mStaticInfo.getAeAvailableTargetFpsRangesChecked());
-        Range<Integer> targetRange = null;
-        for (Range<Integer> fpsRange : fpsRanges) {
-            if (fpsRange.getLower() == minBurstFps && fpsRange.getUpper() == minBurstFps) {
-                foundConstantMaxYUVRange = true;
-                targetRange = fpsRange;
-            } else if (isExternalCamera && fpsRange.getUpper() == minBurstFps) {
-                targetRange = fpsRange;
-            }
-            if (fpsRange.getLower() <= 15 && fpsRange.getUpper() == minBurstFps) {
-                foundYUVStreamingRange = true;
-            }
-
-        }
-
-        if (!isExternalCamera) {
-            assertTrue(String.format("Cam %s: Target FPS range of (%d, %d) must be supported",
-                    cameraId, minBurstFps, minBurstFps), foundConstantMaxYUVRange);
-        }
-        assertTrue(String.format(
-                "Cam %s: Target FPS range of (x, %d) where x <= 15 must be supported",
-                cameraId, minBurstFps), foundYUVStreamingRange);
-        return targetRange;
+        return CameraTestUtils.getSuitableFpsRangeForDuration(cameraId, frameDuration, mStaticInfo);
     }
 }
diff --git a/tests/camera/src/android/hardware/cts/CameraGLTest.java b/tests/camera/src/android/hardware/cts/CameraGLTest.java
index 738c3b7..2cd95d6 100644
--- a/tests/camera/src/android/hardware/cts/CameraGLTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraGLTest.java
@@ -32,9 +32,8 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
-import android.platform.test.annotations.AppModeFull;
 
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.rule.ActivityTestRule;
 import android.test.MoreAsserts;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -56,12 +55,22 @@
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.opengles.GL10;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
 /**
  * This test case must run with hardware. It can't be tested in emulator
  */
-@AppModeFull
 @LargeTest
-public class CameraGLTest extends ActivityInstrumentationTestCase2<GLSurfaceViewCtsActivity> {
+public class CameraGLTest {
     private static final String TAG = "CameraGLTest";
     private static final String PACKAGE = "android.hardware.cts";
     private static final boolean LOGV = false;
@@ -89,27 +98,29 @@
     Renderer mRenderer;
     GLSurfaceView mGLView;
 
-    public CameraGLTest() {
-        super(PACKAGE, GLSurfaceViewCtsActivity.class);
-        if (LOGV) Log.v(TAG, "CameraGLTest Constructor");
-    }
+    @Rule
+    public ActivityTestRule<GLSurfaceViewCtsActivity> mActivityRule =
+            new ActivityTestRule<GLSurfaceViewCtsActivity>(GLSurfaceViewCtsActivity.class) {
+                @Override
+                protected void beforeActivityLaunched() {
+                    // Set up renderer instance
+                    mRenderer = new Renderer();
+                    GLSurfaceViewCtsActivity.setRenderer(mRenderer);
+                    GLSurfaceViewCtsActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+                    GLSurfaceViewCtsActivity.setGlVersion(2);
+                }
+            };
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // Set up renderer instance
-        mRenderer = this.new Renderer();
-        GLSurfaceViewCtsActivity.setRenderer(mRenderer);
-        GLSurfaceViewCtsActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
-        GLSurfaceViewCtsActivity.setGlVersion(2);
+    @Before
+    public void setUp() throws Exception {
         // Start CameraCtsActivity.
-        GLSurfaceViewCtsActivity ctsActivity = getActivity();
+        GLSurfaceViewCtsActivity ctsActivity = mActivityRule.getActivity();
         // Store a link to the view so we can redraw it when needed
         mGLView = ctsActivity.getView();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mCamera != null) {
             terminateMessageLooper();
         }
@@ -117,8 +128,6 @@
         GLSurfaceViewCtsActivity.resetRenderMode();
         GLSurfaceViewCtsActivity.resetRenderer();
         GLSurfaceViewCtsActivity.resetGlVersion();
-
-        super.tearDown();
     }
 
     /**
@@ -141,7 +150,7 @@
                 mCamera = Camera.open(cameraId);
                 try {
                     mIsExternalCamera = CameraUtils.isExternal(
-                            getInstrumentation().getContext(), cameraId);
+                            mActivityRule.getActivity().getApplicationContext(), cameraId);
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to query external camera!" + e);
                 }
@@ -321,7 +330,8 @@
         }
 
         /* Make sure the screen stays on while testing - otherwise the OpenGL context may disappear */
-        PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager) mActivityRule.getActivity().getSystemService(
+                Context.POWER_SERVICE);
         PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "CameraGLTest");
         wl.acquire();
         try {
@@ -342,6 +352,7 @@
 
     /** Test Camera.setPreviewTexture in conjunction with the standard Camera preview callback */
     @UiThreadTest
+    @Test
     public void testSetPreviewTexturePreviewCallback() throws Exception {
         runForAllCameras(testSetPreviewTexturePreviewCallbackByCamera);
     }
@@ -385,6 +396,7 @@
     /** Test Camera.setPreviewTexture in conjunction with both the standard Camera preview callback,
         and the SurfaceTexture onFrameAvailable callback */
     @UiThreadTest
+    @Test
     public void testSetPreviewTextureBothCallbacks() throws Exception {
         runForAllCameras(testSetPreviewTextureBothCallbacksByCamera);
     }
@@ -443,6 +455,7 @@
 
     /** Test Camera.setPreviewTexture in conjunction with just the SurfaceTexture onFrameAvailable callback */
     @UiThreadTest
+    @Test
     public void testSetPreviewTextureTextureCallback() throws Exception {
         runForAllCameras(testSetPreviewTextureTextureCallbackByCamera);
     }
@@ -510,6 +523,7 @@
      * TODO: This should be made stricter once SurfaceTexture timestamps are generated by the drivers.
      */
     @UiThreadTest
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testCameraToSurfaceTextureMetadata() throws Exception {
         runForAllCameras(testCameraToSurfaceTextureMetadataByCamera);
     }
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index 81b42ef..2d73b37 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -16,6 +16,7 @@
 
 package android.hardware.cts;
 
+import android.app.Activity;
 import android.content.pm.PackageManager;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageFormat;
@@ -36,11 +37,9 @@
 import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.ConditionVariable;
-import android.os.Environment;
 import android.os.Looper;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.test.rule.ActivityTestRule;
 import android.test.MoreAsserts;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -60,19 +59,24 @@
 import java.util.List;
 import java.util.TimeZone;
 
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Rule;
+
 import junit.framework.AssertionFailedError;
 
 /**
  * This test case must run with hardware. It can't be tested in emulator
  */
-@AppModeFull
 @LargeTest
-public class CameraTest extends ActivityInstrumentationTestCase2<CameraCtsActivity> {
+public class CameraTest extends Assert {
     private static final String TAG = "CameraTest";
     private static final String PACKAGE = "android.hardware.cts";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-    private final String JPEG_PATH = Environment.getExternalStorageDirectory().getPath() +
-            "/test.jpg";
+    private String mJpegPath = null;
     private byte[] mJpegData;
 
     private static final int PREVIEW_CALLBACK_NOT_RECEIVED = 0;
@@ -125,25 +129,20 @@
     Camera mCamera;
     boolean mIsExternalCamera;
 
-    public CameraTest() {
-        super(PACKAGE, CameraCtsActivity.class);
-        if (VERBOSE) Log.v(TAG, "Camera Constructor");
+    @Rule
+    public ActivityTestRule<CameraCtsActivity> mActivityRule =
+            new ActivityTestRule<>(CameraCtsActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // to starCtsActivity.
-        getActivity();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mCamera != null) {
             mCamera.release();
             mCamera = null;
         }
-        super.tearDown();
     }
 
     /*
@@ -163,7 +162,7 @@
                 mLooper = Looper.myLooper();
                 try {
                     mIsExternalCamera = CameraUtils.isExternal(
-                            getInstrumentation().getContext(), cameraId);
+                            mActivityRule.getActivity().getApplicationContext(), cameraId);
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to query external camera!" + e);
                 }
@@ -187,7 +186,8 @@
             fail("initializeMessageLooper: start timeout");
         }
         assertNotNull("Fail to open camera.", mCamera);
-        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        mCamera.setPreviewDisplay(mActivityRule.getActivity().getSurfaceView().getHolder());
+        mJpegPath = mActivityRule.getActivity().getExternalFilesDir(null).getPath() + "/test.jpg";
     }
 
     /*
@@ -306,7 +306,7 @@
                 mJpegData = rawData;
                 if (rawData != null) {
                     // try to store the picture on the SD card
-                    File rawoutput = new File(JPEG_PATH);
+                    File rawoutput = new File(mJpegPath);
                     FileOutputStream outStream = new FileOutputStream(rawoutput);
                     outStream.write(rawData);
                     outStream.close();
@@ -401,6 +401,7 @@
      * functions are called properly.
      */
     @UiThreadTest
+    @Test
     public void testTakePicture() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -446,6 +447,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -490,6 +492,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testStabilizationOneShotPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -514,6 +517,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSetOneShotPreviewCallback() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -537,6 +541,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSetPreviewDisplay() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -546,7 +551,7 @@
     }
 
     private void testSetPreviewDisplayByCamera(int cameraId) throws Exception {
-        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+        SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
         initializeMessageLooper(cameraId);
 
         // Check the order: startPreview->setPreviewDisplay.
@@ -580,6 +585,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testDisplayOrientation() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -618,6 +624,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testParameters() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -841,6 +848,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testJpegThumbnailSize() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -868,7 +876,7 @@
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
         assertTrue(mJpegPictureCallbackResult);
-        ExifInterface exif = new ExifInterface(JPEG_PATH);
+        ExifInterface exif = new ExifInterface(mJpegPath);
         assertTrue(exif.hasThumbnail());
         byte[] thumb = exif.getThumbnail();
         BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
@@ -894,10 +902,10 @@
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
         assertTrue(mJpegPictureCallbackResult);
-        exif = new ExifInterface(JPEG_PATH);
+        exif = new ExifInterface(mJpegPath);
         assertFalse(exif.hasThumbnail());
         // Primary image should still be valid for no thumbnail capture.
-        BitmapFactory.decodeFile(JPEG_PATH, bmpOptions);
+        BitmapFactory.decodeFile(mJpegPath, bmpOptions);
         if (!recording) {
             assertTrue("Jpeg primary image size should match requested size",
                     bmpOptions.outWidth == pictureSize.width &&
@@ -910,10 +918,11 @@
         }
 
         assertNotNull("Jpeg primary image data should be decodable",
-                BitmapFactory.decodeFile(JPEG_PATH));
+                BitmapFactory.decodeFile(mJpegPath));
     }
 
     @UiThreadTest
+    @Test
     public void testJpegExif() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -935,7 +944,7 @@
         double focalLength = parameters.getFocalLength();
 
         // Test various exif tags.
-        ExifInterface exif = new ExifInterface(JPEG_PATH);
+        ExifInterface exif = new ExifInterface(mJpegPath);
         StringBuffer failedCause = new StringBuffer("Jpeg exif test failed:\n");
         boolean extraExiftestPassed = checkExtraExifTagsSucceeds(failedCause, exif);
 
@@ -977,7 +986,7 @@
         mCamera.setParameters(parameters);
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
-        exif = new ExifInterface(JPEG_PATH);
+        exif = new ExifInterface(mJpegPath);
         checkGpsDataNull(exif);
         assertBitmapAndJpegSizeEqual(mJpegData, exif);
         // Reset the rotation to prevent from affecting other tests.
@@ -1160,7 +1169,7 @@
         mCamera.setParameters(parameters);
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
-        ExifInterface exif = new ExifInterface(JPEG_PATH);
+        ExifInterface exif = new ExifInterface(mJpegPath);
         assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
         assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
         assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
@@ -1206,6 +1215,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testLockUnlock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1218,15 +1228,15 @@
         initializeMessageLooper(cameraId);
         Camera.Parameters parameters = mCamera.getParameters();
         SurfaceHolder surfaceHolder;
-        surfaceHolder = getActivity().getSurfaceView().getHolder();
-        CamcorderProfile profile = CamcorderProfile.get(cameraId,
-                CamcorderProfile.QUALITY_LOW);
+        surfaceHolder = mActivityRule.getActivity().getSurfaceView().getHolder();
+        CamcorderProfile profile = null; // Used for built-in camera
         Camera.Size videoSize = null; // Used for external camera
 
         // Set the preview size.
         if (mIsExternalCamera) {
             videoSize = setupExternalCameraRecord(parameters);
         } else {
+            profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
             setPreviewSizeByProfile(parameters, profile);
         }
 
@@ -1398,6 +1408,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewCallbackWithBuffer() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1409,7 +1420,7 @@
     private void testPreviewCallbackWithBufferByCamera(int cameraId) throws Exception {
         initializeMessageLooper(cameraId);
         SurfaceHolder surfaceHolder;
-        surfaceHolder = getActivity().getSurfaceView().getHolder();
+        surfaceHolder = mActivityRule.getActivity().getSurfaceView().getHolder();
         mCamera.setPreviewDisplay(surfaceHolder);
         Parameters parameters = mCamera.getParameters();
         PreviewCallbackWithBuffer callback = new PreviewCallbackWithBuffer();
@@ -1486,6 +1497,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testImmediateZoom() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1548,6 +1560,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSmoothZoom() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1673,6 +1686,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFocusDistances() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1781,6 +1795,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testCancelAutofocus() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -1837,7 +1852,7 @@
 
         // Ensure the camera can be opened if release is called right after AF.
         mCamera = Camera.open(cameraId);
-        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        mCamera.setPreviewDisplay(mActivityRule.getActivity().getSurfaceView().getHolder());
         mCamera.startPreview();
         mCamera.autoFocus(mAutoFocusCallback);
         mCamera.release();
@@ -1862,6 +1877,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testMultipleCameras() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         Log.v(TAG, "total " + nCameras + " cameras");
@@ -1924,6 +1940,7 @@
     }
 
     @UiThreadTest
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testPreviewPictureSizesCombination() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2003,6 +2020,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewFpsRange() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2231,6 +2249,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testSceneMode() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2331,6 +2350,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testInvalidParameters() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2385,6 +2405,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testGetParameterDuringFocus() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2420,6 +2441,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testPreviewFormats() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2444,6 +2466,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testMultiCameraRelease() throws Exception {
         // Verify that multiple cameras exist, and that they can be opened at the same time
         if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
@@ -2532,6 +2555,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFocusAreas() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2551,6 +2575,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testMeteringAreas() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2687,6 +2712,7 @@
 
     // Apps should be able to call startPreview in jpeg callback.
     @UiThreadTest
+    @Test
     public void testJpegCallbackStartPreview() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2716,6 +2742,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testRecordingHint() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2728,14 +2755,14 @@
         initializeMessageLooper(cameraId);
         Parameters parameters = mCamera.getParameters();
 
-        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
-        CamcorderProfile profile = CamcorderProfile.get(cameraId,
-                CamcorderProfile.QUALITY_LOW);
+        SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
+        CamcorderProfile profile = null; // for built-in camera
         Camera.Size videoSize = null; // for external camera
 
         if (mIsExternalCamera) {
             videoSize = setupExternalCameraRecord(parameters);
         } else {
+            profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
             setPreviewSizeByProfile(parameters, profile);
         }
 
@@ -2811,6 +2838,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testAutoExposureLock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2827,6 +2855,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testAutoWhiteBalanceLock() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -2843,6 +2872,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void test3ALockInteraction() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3067,6 +3097,7 @@
     }
 
     @UiThreadTest
+    @Test
     public void testFaceDetection() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3193,6 +3224,7 @@
     }
 
     @UiThreadTest
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testVideoSnapshot() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3221,7 +3253,7 @@
             return;
         }
 
-        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+        SurfaceHolder holder = mActivityRule.getActivity().getSurfaceView().getHolder();
 
         for (int profileId: mCamcorderProfileList) {
             if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
@@ -3271,6 +3303,7 @@
         }
     }
 
+    @Test
     public void testPreviewCallbackWithPicture() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3330,6 +3363,7 @@
         terminateMessageLooper();
     }
 
+    @Test
     public void testEnableShutterSound() throws Exception {
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
@@ -3356,8 +3390,9 @@
         terminateMessageLooper();
     }
 
+    @Test
     public void testCameraExternalConnected() {
-        if (getActivity().getPackageManager().
+        if (mActivityRule.getActivity().getPackageManager().
                 hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL) ) {
             int nCameras = Camera.getNumberOfCameras();
             assertTrue("Devices with external camera support must have a camera connected for " +
diff --git a/tests/camera/src/android/hardware/cts/CameraTestCase.java b/tests/camera/src/android/hardware/cts/CameraTestCase.java
new file mode 100644
index 0000000..1b4193b
--- /dev/null
+++ b/tests/camera/src/android/hardware/cts/CameraTestCase.java
@@ -0,0 +1,198 @@
+/*
+ * 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.hardware.cts;
+
+import android.hardware.Camera;
+import android.hardware.Camera.AutoFocusCallback;
+import android.hardware.Camera.ErrorCallback;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.PreviewCallback;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import junit.framework.TestCase;
+
+public class CameraTestCase extends TestCase {
+    private static final String TAG = "CameraTestCase";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    protected static final int NO_ERROR = -1;
+    protected static final long WAIT_FOR_COMMAND_TO_COMPLETE_NS = 5000000000L;
+    protected static final long WAIT_FOR_FOCUS_TO_COMPLETE_NS = 5000000000L;
+    protected static final long WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS = 5000000000L;
+    protected Looper mLooper = null;
+
+    protected int mCameraErrorCode;
+    protected Camera mCamera;
+
+    /**
+     * Initializes the message looper so that the Camera object can
+     * receive the callback messages.
+     */
+    protected void initializeMessageLooper(final int cameraId) throws InterruptedException {
+        Lock startLock = new ReentrantLock();
+        Condition startDone = startLock.newCondition();
+        mCameraErrorCode = NO_ERROR;
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by camera.
+                Looper.prepare();
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+                try {
+                    mCamera = Camera.open(cameraId);
+                    mCamera.setErrorCallback(new ErrorCallback() {
+                        @Override
+                        public void onError(int error, Camera camera) {
+                            mCameraErrorCode = error;
+                        }
+                    });
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "Fail to open camera." + e);
+                }
+                startLock.lock();
+                startDone.signal();
+                startLock.unlock();
+                Looper.loop(); // Blocks forever until Looper.quit() is called.
+                if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
+            }
+        }.start();
+
+        startLock.lock();
+        try {
+            if (startDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) <= 0L) {
+                fail("initializeMessageLooper: start timeout");
+            }
+        } finally {
+            startLock.unlock();
+        }
+
+        assertNotNull("Fail to open camera.", mCamera);
+    }
+
+    /**
+     * Terminates the message looper thread, optionally allowing evict error
+     */
+    protected void terminateMessageLooper() throws Exception {
+        mLooper.quit();
+        // Looper.quit() is asynchronous. The looper may still has some
+        // preview callbacks in the queue after quit is called. The preview
+        // callback still uses the camera object (setHasPreviewCallback).
+        // After camera is released, RuntimeException will be thrown from
+        // the method. So we need to join the looper thread here.
+        mLooper.getThread().join();
+        mCamera.release();
+        mCamera = null;
+        assertEquals("Got camera error callback.", NO_ERROR, mCameraErrorCode);
+    }
+
+    /**
+     * Start preview and wait for the first preview callback, which indicates the
+     * preview becomes active.
+     */
+    protected void startPreview() throws InterruptedException {
+        Lock previewLock = new ReentrantLock();
+        Condition previewDone = previewLock.newCondition();
+
+        mCamera.setPreviewCallback(new PreviewCallback() {
+            @Override
+            public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
+                previewLock.lock();
+                previewDone.signal();
+                previewLock.unlock();
+            }
+        });
+        mCamera.startPreview();
+
+        previewLock.lock();
+        try {
+            if (previewDone.awaitNanos(WAIT_FOR_COMMAND_TO_COMPLETE_NS) <= 0L) {
+                fail("Preview done timeout");
+            }
+        } finally {
+            previewLock.unlock();
+        }
+
+        mCamera.setPreviewCallback(null);
+    }
+
+    /**
+     * Trigger and wait for autofocus to complete.
+     */
+    protected void autoFocus() throws InterruptedException {
+        Lock focusLock = new ReentrantLock();
+        Condition focusDone = focusLock.newCondition();
+
+        mCamera.autoFocus(new AutoFocusCallback() {
+            @Override
+            public void onAutoFocus(boolean success, Camera camera) {
+                focusLock.lock();
+                focusDone.signal();
+                focusLock.unlock();
+            }
+        });
+
+        focusLock.lock();
+        try {
+            if (focusDone.awaitNanos(WAIT_FOR_FOCUS_TO_COMPLETE_NS) <= 0L) {
+                fail("Autofocus timeout");
+            }
+        } finally {
+            focusLock.unlock();
+        }
+    }
+
+    /**
+     * Trigger and wait for snapshot to finish.
+     */
+    protected void takePicture() throws InterruptedException {
+        Lock snapshotLock = new ReentrantLock();
+        Condition snapshotDone = snapshotLock.newCondition();
+
+        mCamera.takePicture(/*shutterCallback*/ null, /*rawPictureCallback*/ null,
+                new PictureCallback() {
+            @Override
+            public void onPictureTaken(byte[] rawData, Camera camera) {
+                snapshotLock.lock();
+                try {
+                    if (rawData == null) {
+                        fail("Empty jpeg data");
+                    }
+                    snapshotDone.signal();
+                } finally {
+                    snapshotLock.unlock();
+                }
+            }
+        });
+
+        snapshotLock.lock();
+        try {
+            if (snapshotDone.awaitNanos(WAIT_FOR_SNAPSHOT_TO_COMPLETE_NS) <= 0L) {
+                fail("TakePicture timeout");
+            }
+        } finally {
+            snapshotLock.unlock();
+        }
+    }
+
+}
diff --git a/tests/camera/src/android/hardware/cts/Camera_ParametersTest.java b/tests/camera/src/android/hardware/cts/Camera_ParametersTest.java
index a614982..b37959e 100644
--- a/tests/camera/src/android/hardware/cts/Camera_ParametersTest.java
+++ b/tests/camera/src/android/hardware/cts/Camera_ParametersTest.java
@@ -18,9 +18,7 @@
 
 import junit.framework.TestCase;
 import android.hardware.Camera.Parameters;
-import android.platform.test.annotations.AppModeFull;
 
-@AppModeFull
 public class Camera_ParametersTest extends TestCase {
 
     public void testAccessMethods() {
diff --git a/tests/camera/src/android/hardware/cts/Camera_SizeTest.java b/tests/camera/src/android/hardware/cts/Camera_SizeTest.java
index ace3cf7..77e75dd 100644
--- a/tests/camera/src/android/hardware/cts/Camera_SizeTest.java
+++ b/tests/camera/src/android/hardware/cts/Camera_SizeTest.java
@@ -19,7 +19,6 @@
 import android.hardware.Camera;
 import android.hardware.Camera.Parameters;
 import android.hardware.cts.helpers.CameraUtils;
-import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
@@ -31,7 +30,6 @@
 import java.util.Collections;
 import java.util.List;
 
-@AppModeFull
 @LargeTest
 public class Camera_SizeTest extends CtsAndroidTestCase {
 
diff --git a/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java b/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java
new file mode 100644
index 0000000..e5d9dfc
--- /dev/null
+++ b/tests/camera/src/android/hardware/cts/LegacyCameraPerformanceTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.hardware.cts;
+
+import android.app.Instrumentation;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.Stat;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+/**
+ * Measure and report legacy camera device performance.
+ */
+public class LegacyCameraPerformanceTest extends CameraTestCase {
+    private static final String TAG = "CameraPerformanceTest";
+    private static final String REPORT_LOG_NAME = "CtsCamera1TestCases";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private Instrumentation mInstrumentation;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mCamera != null) {
+            mCamera.release();
+            mCamera = null;
+        }
+    }
+
+    public void testLegacyApiPerformance() throws Exception {
+        final int NUM_TEST_LOOPS = 10;
+
+        int nCameras = Camera.getNumberOfCameras();
+        double[] avgCameraTakePictureTimes = new double[nCameras];
+
+        for (int id = 0; id < nCameras; id++) {
+            DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME,
+                    "test_camera_takePicture");
+            reportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+            double[] cameraOpenTimes = new double[NUM_TEST_LOOPS];
+            double[] startPreviewTimes = new double[NUM_TEST_LOOPS];
+            double[] stopPreviewTimes = new double[NUM_TEST_LOOPS];
+            double[] cameraCloseTimes = new double[NUM_TEST_LOOPS];
+            double[] cameraTakePictureTimes = new double[NUM_TEST_LOOPS];
+            double[] cameraAutoFocusTimes = new double[NUM_TEST_LOOPS];
+            boolean afSupported = false;
+            long openTimeMs, startPreviewTimeMs, stopPreviewTimeMs, closeTimeMs, takePictureTimeMs,
+                 autofocusTimeMs;
+
+            for (int i = 0; i < NUM_TEST_LOOPS; i++) {
+                openTimeMs = SystemClock.elapsedRealtime();
+                initializeMessageLooper(id);
+                cameraOpenTimes[i] = SystemClock.elapsedRealtime() - openTimeMs;
+
+                Parameters parameters = mCamera.getParameters();
+                if (i == 0) {
+                    for (String focusMode: parameters.getSupportedFocusModes()) {
+                        if (Parameters.FOCUS_MODE_AUTO.equals(focusMode)) {
+                            afSupported = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (afSupported) {
+                    parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
+                    mCamera.setParameters(parameters);
+                }
+
+                SurfaceTexture previewTexture = new SurfaceTexture(/*random int*/ 1);
+                mCamera.setPreviewTexture(previewTexture);
+                startPreviewTimeMs = SystemClock.elapsedRealtime();
+                startPreview();
+                startPreviewTimes[i] = SystemClock.elapsedRealtime() - startPreviewTimeMs;
+
+                if (afSupported) {
+                    autofocusTimeMs = SystemClock.elapsedRealtime();
+                    autoFocus();
+                    cameraAutoFocusTimes[i] = SystemClock.elapsedRealtime() - autofocusTimeMs;
+                }
+
+                //Let preview run for a while
+                Thread.sleep(1000);
+
+                takePictureTimeMs = SystemClock.elapsedRealtime();
+                takePicture();
+                cameraTakePictureTimes[i] = SystemClock.elapsedRealtime() - takePictureTimeMs;
+
+                //Resume preview after image capture
+                startPreview();
+
+                stopPreviewTimeMs = SystemClock.elapsedRealtime();
+                mCamera.stopPreview();
+                closeTimeMs = SystemClock.elapsedRealtime();
+                stopPreviewTimes[i] = closeTimeMs - stopPreviewTimeMs;
+
+                terminateMessageLooper();
+                cameraCloseTimes[i] = SystemClock.elapsedRealtime() - closeTimeMs;
+                previewTexture.release();
+            }
+
+            if (VERBOSE) {
+                Log.v(TAG, "Camera " + id + " device open times(ms): "
+                        + Arrays.toString(cameraOpenTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraOpenTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraOpenTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraOpenTimes));
+                Log.v(TAG, "Camera " + id + " start preview times(ms): "
+                        + Arrays.toString(startPreviewTimes)
+                        + ". Average(ms): " + Stat.getAverage(startPreviewTimes)
+                        + ". Min(ms): " + Stat.getMin(startPreviewTimes)
+                        + ". Max(ms): " + Stat.getMax(startPreviewTimes));
+                if (afSupported) {
+                    Log.v(TAG, "Camera " + id + " autofocus times(ms): "
+                            + Arrays.toString(cameraAutoFocusTimes)
+                            + ". Average(ms): " + Stat.getAverage(cameraAutoFocusTimes)
+                            + ". Min(ms): " + Stat.getMin(cameraAutoFocusTimes)
+                            + ". Max(ms): " + Stat.getMax(cameraAutoFocusTimes));
+                }
+                Log.v(TAG, "Camera " + id + " stop preview times(ms): "
+                        + Arrays.toString(stopPreviewTimes)
+                        + ". Average(ms): " + Stat.getAverage(stopPreviewTimes)
+                        + ". Min(ms): " + Stat.getMin(stopPreviewTimes)
+                        + ". Max(ms): " + Stat.getMax(stopPreviewTimes));
+                Log.v(TAG, "Camera " + id + " device close times(ms): "
+                        + Arrays.toString(cameraCloseTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraCloseTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraCloseTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraCloseTimes));
+                Log.v(TAG, "Camera " + id + " camera takepicture times(ms): "
+                        + Arrays.toString(cameraTakePictureTimes)
+                        + ". Average(ms): " + Stat.getAverage(cameraTakePictureTimes)
+                        + ". Min(ms): " + Stat.getMin(cameraTakePictureTimes)
+                        + ". Max(ms): " + Stat.getMax(cameraTakePictureTimes));
+            }
+
+            avgCameraTakePictureTimes[id] = Stat.getAverage(cameraTakePictureTimes);
+            reportLog.addValues("camera_open_time", cameraOpenTimes, ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.addValues("camera_start_preview_time", startPreviewTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            if (afSupported) {
+                reportLog.addValues("camera_autofocus_time", cameraAutoFocusTimes,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+            }
+            reportLog.addValues("camera_stop_preview", stopPreviewTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            reportLog.addValues("camera_close_time", cameraCloseTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+            reportLog.addValues("camera_takepicture_time", cameraTakePictureTimes,
+                    ResultType.LOWER_BETTER, ResultUnit.MS);
+
+            reportLog.submit(mInstrumentation);
+        }
+
+        if (nCameras != 0) {
+            DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME,
+                    "test_camera_takepicture_average");
+            reportLog.setSummary("camera_takepicture_average_time_for_all_cameras",
+                    Stat.getAverage(avgCameraTakePictureTimes), ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.submit(mInstrumentation);
+        }
+    }
+}
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 6ad8b2e..0f48364 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -26,7 +26,6 @@
 import android.hardware.camera2.CameraManager;
 import android.hardware.cts.CameraCtsActivity;
 import android.os.Handler;
-import android.platform.test.annotations.AppModeFull;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
@@ -41,7 +40,6 @@
 /**
  * Tests for multi-process camera usage behavior.
  */
-@AppModeFull
 public class CameraEvictionTest extends ActivityInstrumentationTestCase2<CameraCtsActivity> {
 
     public static final String TAG = "CameraEvictionTest";
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/MediaRecorderCameraActivity.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/MediaRecorderCameraActivity.java
index 0558800..1f12cea 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/MediaRecorderCameraActivity.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/MediaRecorderCameraActivity.java
@@ -20,7 +20,6 @@
 import android.camera.cts.R;
 import android.media.MediaRecorder;
 import android.os.Bundle;
-import android.os.Environment;
 import android.util.Log;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -42,9 +41,7 @@
     private static final int LAYOUT_WIDTH = VIDEO_WIDTH;
     private static final int LAYOUT_HEIGHT = VIDEO_HEIGHT;
 
-    private final String OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
-                "record.out").getAbsolutePath();
-
+    private String mOutputPath;
     private File mOutFile;
     private SurfaceView mSurfaceView;
     private ErrorLoggingService.ErrorServiceConnection mErrorServiceConnection;
@@ -61,6 +58,7 @@
         mErrorServiceConnection.start();
 
         mMediaRecorder = new MediaRecorder();
+        mOutputPath = new File(getExternalFilesDir(null), "record.out").getAbsolutePath();
     }
 
     @Override
@@ -118,13 +116,13 @@
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         try {
-            mOutFile = new File(OUTPUT_PATH);
+            mOutFile = new File(mOutputPath);
             mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
             mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
             mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
             mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
             mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
-            mMediaRecorder.setOutputFile(OUTPUT_PATH);
+            mMediaRecorder.setOutputFile(mOutputPath);
             mMediaRecorder.prepare();
             mMediaRecorder.start();
 
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 d473975..4fd4b39 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -47,7 +48,6 @@
 import android.media.ImageWriter;
 import android.media.Image.Plane;
 import android.os.Build;
-import android.os.Environment;
 import android.os.Handler;
 import android.util.Log;
 import android.util.Pair;
@@ -126,9 +126,6 @@
     private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
 
-    protected static final String DEBUG_FILE_NAME_BASE =
-            Environment.getExternalStorageDirectory().getPath();
-
     static {
         sTestLocation0.setTime(1199145600000L);
         sTestLocation0.setLatitude(37.736071);
@@ -212,6 +209,17 @@
     }
 
     /**
+     * Close the pending images then close current active {@link ImageReader} objects.
+     */
+    public static void closeImageReaders(ImageReader[] readers) {
+        if ((readers != null) && (readers.length > 0)) {
+            for (ImageReader reader : readers) {
+                CameraTestUtils.closeImageReader(reader);
+            }
+        }
+    }
+
+    /**
      * Close pending images and clean up an {@link android.media.ImageWriter} object.
      * @param writer an {@link android.media.ImageWriter} to close.
      */
@@ -1027,9 +1035,9 @@
 
         ByteBuffer buffer = null;
         // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
-        // Same goes for DEPTH_POINT_CLOUD
+        // Same goes for DEPTH_POINT_CLOUD and DEPTH_JPEG
         if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD ||
-                format == ImageFormat.RAW_PRIVATE) {
+                format == ImageFormat.RAW_PRIVATE || format == ImageFormat.DEPTH_JPEG) {
             buffer = planes[0].getBuffer();
             assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer);
             data = new byte[buffer.remaining()];
@@ -1112,7 +1120,9 @@
             case ImageFormat.RAW_PRIVATE:
             case ImageFormat.DEPTH16:
             case ImageFormat.DEPTH_POINT_CLOUD:
-                assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length);
+            case ImageFormat.DEPTH_JPEG:
+            case ImageFormat.Y8:
+                assertEquals("JPEG/RAW/depth/Y8 Images should have one plane", 1, planes.length);
                 break;
             default:
                 fail("Unsupported Image Format: " + format);
@@ -1516,6 +1526,9 @@
         assertTrue("Invalid image data", data != null && data.length > 0);
 
         switch (format) {
+            // Clients must be able to process and handle depth jpeg images like any other
+            // regular jpeg.
+            case ImageFormat.DEPTH_JPEG:
             case ImageFormat.JPEG:
                 validateJpegData(data, width, height, filePath);
                 break;
@@ -1535,6 +1548,9 @@
             case ImageFormat.RAW_PRIVATE:
                 validateRawPrivateData(data, width, height, image.getTimestamp(), filePath);
                 break;
+            case ImageFormat.Y8:
+                validateY8Data(data, width, height, format, image.getTimestamp(), filePath);
+                break;
             default:
                 throw new UnsupportedOperationException("Unsupported format for validation: "
                         + format);
@@ -1645,6 +1661,23 @@
         return;
     }
 
+    private static void validateY8Data(byte[] rawData, int width, int height, int format,
+            long ts, String filePath) {
+        if (VERBOSE) Log.v(TAG, "Validating Y8 data");
+        int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+        assertEquals("Y8 data doesn't match", expectedSize, rawData.length);
+
+        // TODO: Can add data validation for test pattern.
+
+        if (DEBUG && filePath != null) {
+            String fileName =
+                    filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".y8";
+            dumpFile(fileName, rawData);
+        }
+
+        return;
+    }
+
     private static void validateRawPrivateData(byte[] rawData, int width, int height,
             long ts, String filePath) {
         if (VERBOSE) Log.v(TAG, "Validating private raw data");
@@ -2048,13 +2081,13 @@
      */
     public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize,
             Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo,
-            CameraErrorCollector collector) throws Exception {
+            CameraErrorCollector collector, String debugFileNameBase) throws Exception {
 
         basicValidateJpegImage(image, expectedSize);
 
         byte[] jpegBuffer = getDataFromImage(image);
         // Have to dump into a file to be able to use ExifInterface
-        String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg";
+        String jpegFilename = debugFileNameBase + "/verifyJpegKeys.jpeg";
         dumpFile(jpegFilename, jpegBuffer);
         ExifInterface exif = new ExifInterface(jpegFilename);
 
@@ -2486,4 +2519,232 @@
             return false;
         }
     }
+
+    /**
+     * Query whether a particular stream combination is supported.
+     */
+    public static boolean checkSessionConfigurationWithSurfaces(CameraDevice camera,
+            Handler handler, List<Surface> outputSurfaces, InputConfiguration inputConfig,
+            int operatingMode, boolean expectedResult) {
+        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+        for (Surface surface : outputSurfaces) {
+            outConfigurations.add(new OutputConfiguration(surface));
+        }
+
+        return checkSessionConfiguration(camera, handler, outConfigurations, inputConfig,
+                operatingMode, expectedResult);
+    }
+
+    /**
+     * Query whether a particular stream combination is supported.
+     */
+    public static boolean checkSessionConfiguration(CameraDevice camera, Handler handler,
+            List<OutputConfiguration> outputConfigs, InputConfiguration inputConfig,
+            int operatingMode, boolean expectedResult) {
+        boolean ret;
+        BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+
+        SessionConfiguration sessionConfig = new SessionConfiguration(operatingMode, outputConfigs,
+                new HandlerExecutor(handler), sessionListener);
+        if (inputConfig != null) {
+            sessionConfig.setInputConfiguration(inputConfig);
+        }
+
+        try {
+            ret = camera.isSessionConfigurationSupported(sessionConfig);
+        } catch (UnsupportedOperationException e) {
+            // Camera doesn't support session configuration query, return expected result
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            return false;
+        }
+
+        return !(expectedResult ^ ret);
+    }
+
+    /**
+     * Wait for numResultWait frames
+     *
+     * @param resultListener The capture listener to get capture result back.
+     * @param numResultsWait Number of frame to wait
+     * @param timeout Wait timeout in ms.
+     *
+     * @return the last result, or {@code null} if there was none
+     */
+    public static CaptureResult waitForNumResults(SimpleCaptureCallback resultListener,
+            int numResultsWait, int timeout) {
+        if (numResultsWait < 0 || resultListener == null) {
+            throw new IllegalArgumentException(
+                    "Input must be positive number and listener must be non-null");
+        }
+
+        CaptureResult result = null;
+        for (int i = 0; i < numResultsWait; i++) {
+            result = resultListener.getCaptureResult(timeout);
+        }
+
+        return result;
+    }
+
+    /**
+     * Wait for any expected result key values available in a certain number of results.
+     *
+     * <p>
+     * Check the result immediately if numFramesWait is 0.
+     * </p>
+     *
+     * @param listener The capture listener to get capture result.
+     * @param resultKey The capture result key associated with the result value.
+     * @param expectedValues The list of result value need to be waited for,
+     * return immediately if the list is empty.
+     * @param numResultsWait Number of frame to wait before times out.
+     * @param timeout result wait time out in ms.
+     * @throws TimeoutRuntimeException If more than numResultsWait results are.
+     * seen before the result matching myRequest arrives, or each individual wait
+     * for result times out after 'timeout' ms.
+     */
+    public static <T> void waitForAnyResultValue(SimpleCaptureCallback listener,
+            CaptureResult.Key<T> resultKey, List<T> expectedValues, int numResultsWait,
+            int timeout) {
+        if (numResultsWait < 0 || listener == null || expectedValues == null) {
+            throw new IllegalArgumentException(
+                    "Input must be non-negative number and listener/expectedValues "
+                    + "must be non-null");
+        }
+
+        int i = 0;
+        CaptureResult result;
+        do {
+            result = listener.getCaptureResult(timeout);
+            T value = result.get(resultKey);
+            for ( T expectedValue : expectedValues) {
+                if (VERBOSE) {
+                    Log.v(TAG, "Current result value for key " + resultKey.getName() + " is: "
+                            + value.toString());
+                }
+                if (value.equals(expectedValue)) {
+                    return;
+                }
+            }
+        } while (i++ < numResultsWait);
+
+        throw new TimeoutRuntimeException(
+                "Unable to get the expected result value " + expectedValues + " for key " +
+                        resultKey.getName() + " after waiting for " + numResultsWait + " results");
+    }
+
+    /**
+     * Wait for expected result key value available in a certain number of results.
+     *
+     * <p>
+     * Check the result immediately if numFramesWait is 0.
+     * </p>
+     *
+     * @param listener The capture listener to get capture result
+     * @param resultKey The capture result key associated with the result value
+     * @param expectedValue The result value need to be waited for
+     * @param numResultsWait Number of frame to wait before times out
+     * @param timeout Wait time out.
+     * @throws TimeoutRuntimeException If more than numResultsWait results are
+     * seen before the result matching myRequest arrives, or each individual wait
+     * for result times out after 'timeout' ms.
+     */
+    public static <T> void waitForResultValue(SimpleCaptureCallback listener,
+            CaptureResult.Key<T> resultKey, T expectedValue, int numResultsWait, int timeout) {
+        List<T> expectedValues = new ArrayList<T>();
+        expectedValues.add(expectedValue);
+        waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait, timeout);
+    }
+
+    /**
+     * Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
+     *
+     * <p>Waits for {@code android.sync.maxLatency} number of results first, to make sure
+     * that the result is synchronized (or {@code numResultWaitForUnknownLatency} if the latency
+     * is unknown.</p>
+     *
+     * <p>This is a no-op for {@code LEGACY} devices since they don't report
+     * the {@code aeState} result.</p>
+     *
+     * @param resultListener The capture listener to get capture result back.
+     * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
+     *                                       unknown.
+     * @param staticInfo corresponding camera device static metadata.
+     * @param settingsTimeout wait timeout for settings application in ms.
+     * @param resultTimeout wait timeout for result in ms.
+     * @param numResultsWait Number of frame to wait before times out.
+     */
+    public static void waitForAeStable(SimpleCaptureCallback resultListener,
+            int numResultWaitForUnknownLatency, StaticMetadata staticInfo,
+            int settingsTimeout, int numResultWait) {
+        waitForSettingsApplied(resultListener, numResultWaitForUnknownLatency, staticInfo,
+                settingsTimeout);
+
+        if (!staticInfo.isHardwareLevelAtLeastLimited()) {
+            // No-op for metadata
+            return;
+        }
+        List<Integer> expectedAeStates = new ArrayList<Integer>();
+        expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_CONVERGED));
+        expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED));
+        waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
+                numResultWait, settingsTimeout);
+    }
+
+    /**
+     * Wait for enough results for settings to be applied
+     *
+     * @param resultListener The capture listener to get capture result back.
+     * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
+     *                                       unknown.
+     * @param staticInfo corresponding camera device static metadata.
+     * @param timeout wait timeout in ms.
+     */
+    public static void waitForSettingsApplied(SimpleCaptureCallback resultListener,
+            int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int timeout) {
+        int maxLatency = staticInfo.getSyncMaxLatency();
+        if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
+            maxLatency = numResultWaitForUnknownLatency;
+        }
+        // Wait for settings to take effect
+        waitForNumResults(resultListener, maxLatency, timeout);
+    }
+
+    public static Range<Integer> getSuitableFpsRangeForDuration(String cameraId,
+            long frameDuration, StaticMetadata staticInfo) {
+        // Add 0.05 here so Fps like 29.99 evaluated to 30
+        int minBurstFps = (int) Math.floor(1e9 / frameDuration + 0.05f);
+        boolean foundConstantMaxYUVRange = false;
+        boolean foundYUVStreamingRange = false;
+        boolean isExternalCamera = staticInfo.isExternalCamera();
+
+        // Find suitable target FPS range - as high as possible that covers the max YUV rate
+        // Also verify that there's a good preview rate as well
+        List<Range<Integer> > fpsRanges = Arrays.asList(
+                staticInfo.getAeAvailableTargetFpsRangesChecked());
+        Range<Integer> targetRange = null;
+        for (Range<Integer> fpsRange : fpsRanges) {
+            if (fpsRange.getLower() == minBurstFps && fpsRange.getUpper() == minBurstFps) {
+                foundConstantMaxYUVRange = true;
+                targetRange = fpsRange;
+            } else if (isExternalCamera && fpsRange.getUpper() == minBurstFps) {
+                targetRange = fpsRange;
+            }
+            if (fpsRange.getLower() <= 15 && fpsRange.getUpper() == minBurstFps) {
+                foundYUVStreamingRange = true;
+            }
+
+        }
+
+        if (!isExternalCamera) {
+            assertTrue(String.format("Cam %s: Target FPS range of (%d, %d) must be supported",
+                    cameraId, minBurstFps, minBurstFps), foundConstantMaxYUVRange);
+        }
+        assertTrue(String.format(
+                "Cam %s: Target FPS range of (x, %d) where x <= 15 must be supported",
+                cameraId, minBurstFps), foundYUVStreamingRange);
+        return targetRange;
+    }
 }
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 646dfe7..15701f1 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
@@ -113,6 +113,20 @@
         "AF_STATE_PASSIVE_UNFOCUSED"
     };
 
+    // Index with android.control.aePrecaptureTrigger
+    public static final String[] AE_TRIGGER_NAMES = new String[] {
+        "AE_TRIGGER_IDLE",
+        "AE_TRIGGER_START",
+        "AE_TRIGGER_CANCEL"
+    };
+
+    // Index with android.control.afTrigger
+    public static final String[] AF_TRIGGER_NAMES = new String[] {
+        "AF_TRIGGER_IDLE",
+        "AF_TRIGGER_START",
+        "AF_TRIGGER_CANCEL"
+    };
+
     public enum CheckLevel {
         /** Only log warnings for metadata check failures. Execution continues. */
         WARN,
@@ -299,6 +313,20 @@
     }
 
     /**
+     * Get the color filter arrangement for this camera device.
+     *
+     * @return Color Filter arrangement of this camera device
+     */
+    public int getCFAChecked() {
+        Integer cfa = getValueFromKeyNonNull(
+                CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
+        if (cfa == null) {
+            Assert.fail("No color filter array (CFA) reported.");
+        }
+        return cfa;
+    }
+
+    /**
      * Whether or not the hardware level reported by android.info.supportedHardwareLevel
      * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED}.
      *
@@ -2254,6 +2282,20 @@
     }
 
     /**
+     * Check if this camera device is a monochrome camera with Y8 support.
+     *
+     * @return true if this is a monochrome camera with Y8 support.
+     */
+    public boolean isMonochromeWithY8() {
+        int[] supportedFormats = getAvailableFormats(
+                StaticMetadata.StreamDirection.Output);
+        return (isColorOutputSupported()
+                && isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME)
+                && CameraTestUtils.contains(supportedFormats, ImageFormat.Y8));
+    }
+
+    /**
      * Check if high speed video is supported (HIGH_SPEED_VIDEO scene mode is
      * supported, supported high speed fps ranges and sizes are valid).
      *
@@ -2304,6 +2346,14 @@
     }
 
     /**
+     * Check if this camera is a MONOCHROME camera.
+     */
+    public boolean isMonochromeCamera() {
+        return isCapabilitySupported(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME);
+    }
+
+    /**
      * Check if optical black regions key is supported.
      */
     public boolean isOpticalBlackRegionSupported() {
@@ -2377,6 +2427,13 @@
     }
 
     /**
+     * Check if active physical camera Id metadata is supported.
+     */
+    public boolean isActivePhysicalCameraIdSupported() {
+        return areKeysAvailable(CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID);
+    }
+
+    /**
      * Get the value in index for a fixed-size array from a given key.
      *
      * <p>If the camera device is incorrectly reporting values, log a warning and return
diff --git a/tests/contentcaptureservice/Android.mk b/tests/contentcaptureservice/Android.mk
new file mode 100644
index 0000000..e6f42ac
--- /dev/null
+++ b/tests/contentcaptureservice/Android.mk
@@ -0,0 +1,41 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    androidx.annotation_annotation \
+    compatibility-device-util \
+    ctstestrunner \
+    truth-prebuilt \
+    testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsContentCaptureServiceTestCases
+
+LOCAL_SDK_VERSION := system_current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/contentcaptureservice/AndroidManifest.xml b/tests/contentcaptureservice/AndroidManifest.xml
new file mode 100644
index 0000000..11dc7ff
--- /dev/null
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -0,0 +1,97 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.contentcaptureservice.cts"
+    android:targetSandboxVersion="2">
+
+    <application>
+
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".BlankActivity"
+                  android:label="Blank"
+                  android:taskAffinity=".BlankActivity"
+                  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>
+        <activity android:name=".BlankWithTitleActivity"
+                  android:label="Blanka"
+                  android:taskAffinity=".BlankWithTitleActivity">
+            <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>
+        <activity android:name=".LoginActivity"
+                  android:label="Login"
+                  android:taskAffinity=".LoginActivity"
+                  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>
+        <activity android:name=".ChildlessActivity"
+                  android:label="Childless"
+                  android:taskAffinity=".ChildlessActivity"
+                  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>
+        <activity android:name=".CustomViewActivity"
+                  android:label="CustomView"
+                  android:taskAffinity=".CustomViewActivity"
+                  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>
+
+        <service
+            android:name=".CtsContentCaptureService"
+            android:label="CtsContentCaptureService"
+            android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.contentcapture.ContentCaptureService" />
+            </intent-filter>
+        </service>
+
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for the AutoFill Framework APIs."
+        android:targetPackage="android.contentcaptureservice.cts" >
+    </instrumentation>
+
+</manifest>
diff --git a/tests/contentcaptureservice/AndroidTest.xml b/tests/contentcaptureservice/AndroidTest.xml
new file mode 100644
index 0000000..941df89
--- /dev/null
+++ b/tests/contentcaptureservice/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<configuration description="Config for ContentCapture CTS tests.">
+  <option name="test-suite-tag" value="cts" />
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsContentCaptureServiceTestCases.apk" />
+  </target_preparer>
+
+  <!--  TODO(b/122605818): add preparer for instant-apps tests  -->
+
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.contentcaptureservice.cts" />
+    <!-- 20x default timeout of 600sec -->
+    <option name="shell-timeout" value="12000000"/>
+  </test>
+
+</configuration>
diff --git a/tests/contentcaptureservice/res/layout/childless_activity.xml b/tests/contentcaptureservice/res/layout/childless_activity.xml
new file mode 100644
index 0000000..8cc67d5
--- /dev/null
+++ b/tests/contentcaptureservice/res/layout/childless_activity.xml
@@ -0,0 +1,26 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/contentcaptureservice/res/layout/custom_view_activity.xml b/tests/contentcaptureservice/res/layout/custom_view_activity.xml
new file mode 100644
index 0000000..0c91965
--- /dev/null
+++ b/tests/contentcaptureservice/res/layout/custom_view_activity.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.
+-->
+
+<android.contentcaptureservice.cts.CustomView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/custom_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" />
diff --git a/tests/contentcaptureservice/res/layout/login_activity.xml b/tests/contentcaptureservice/res/layout/login_activity.xml
new file mode 100644
index 0000000..1164b4f
--- /dev/null
+++ b/tests/contentcaptureservice/res/layout/login_activity.xml
@@ -0,0 +1,50 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+      <TextView
+          android:id="@+id/username_label"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="Username" />
+
+      <EditText
+          android:id="@+id/username"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content" />
+
+      <TextView
+          android:id="@+id/password_label"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="Password" />
+
+      <EditText
+          android:id="@+id/password"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:inputType="textPassword" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
new file mode 100644
index 0000000..7a1db5b
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
@@ -0,0 +1,157 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.app.Activity;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for all activities.
+ */
+abstract class AbstractContentCaptureActivity extends Activity {
+
+    private final String mTag = getClass().getSimpleName();
+
+    private int mRealTaskId;
+
+    @Nullable
+    public ContentCaptureManager getContentCaptureManager() {
+        return getSystemService(ContentCaptureManager.class);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mRealTaskId = getTaskId();
+
+        Log.i(mTag, "onCreate(): taskId= " + mRealTaskId);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onStart() {
+        Log.i(mTag, "onStart()");
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.i(mTag, "onResume()");
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.i(mTag, "onPause()");
+        super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(mTag, "onStop()");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(mTag, "onDestroy()");
+        super.onDestroy();
+    }
+
+    /**
+     * Asserts the events generated when this session was launched and finished,
+     * without any custom / dynamic operations in between.
+     */
+    public abstract void assertDefaultEvents(@NonNull Session session);
+
+    /**
+     * Gets the real task id associated with the activity, as {@link #getTaskId()} returns
+     * {@code -1} after it's gone.
+     */
+    public final int getRealTaskId() {
+        return mRealTaskId;
+    }
+
+    /**
+     * Runs an action in the UI thread, and blocks caller until the action is finished.
+     */
+    public final void syncRunOnUiThread(@NonNull Runnable action) {
+        syncRunOnUiThread(action, Helper.GENERIC_TIMEOUT_MS);
+    }
+
+    /**
+     * Calls an action in the UI thread, and blocks caller until the action is finished.
+     */
+    public final <T> T syncCallOnUiThread(@NonNull Callable<T> action) throws Exception {
+        final AtomicReference<T> result = new AtomicReference<>();
+        final AtomicReference<Exception> exception  = new AtomicReference<>();
+        syncRunOnUiThread(() -> {
+            try {
+                result.set(action.call());
+            } catch (Exception e) {
+                exception.set(e);
+            }
+        });
+        final Exception e = exception.get();
+        if (e != null) {
+            throw e;
+        }
+        return result.get();
+    }
+
+    /**
+     * Run an action in the UI thread, and blocks caller until the action is finished or it times
+     * out.
+     */
+    public final void syncRunOnUiThread(@NonNull Runnable action, long timeoutMs) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        runOnUiThread(() -> {
+            action.run();
+            latch.countDown();
+        });
+        try {
+            if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                // TODO(b/120665995): throw RetryableException (once moved from Autofill to common)
+                throw new IllegalStateException(
+                        String.format("action on UI thread timed out after %d ms", timeoutMs));
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted", e);
+        }
+    }
+
+    /**
+     * Dumps the {@link ContentCaptureManager} state of the activity on logcat.
+     */
+    public void dumpIt() {
+        final String dump = runShellCommand(
+                "dumpsys activity %s --contentcapture",  getComponentName().flattenToString());
+        Log.v(mTag, "dump it: " + dump);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
new file mode 100644
index 0000000..ba2b788
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.GENERIC_TIMEOUT_MS;
+import static android.contentcaptureservice.cts.Helper.resetService;
+import static android.contentcaptureservice.cts.Helper.setService;
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.contentcaptureservice.cts.CtsContentCaptureService.ServiceWatcher;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.Visitor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Base class for all (or most :-) integration tests in this CTS suite.
+ */
+@RunWith(AndroidJUnit4.class)
+public abstract class AbstractContentCaptureIntegrationTest
+        <A extends AbstractContentCaptureActivity> {
+
+    private final String mTag = getClass().getSimpleName();
+
+    protected static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+    protected ActivitiesWatcher mActivitiesWatcher;
+
+    private final Class<A> mActivityClass;
+
+    private final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule("content_capture");
+    private final ContentCaptureLoggingTestRule mLoggingRule =
+            new ContentCaptureLoggingTestRule(mTag);
+
+
+    protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+            .setDumper(mLoggingRule)
+            .add(() -> {
+                return CtsContentCaptureService.getExceptions();
+            });
+
+    @Rule
+    public final RuleChain mLookAllTheseRules = RuleChain
+            //
+            // mRequiredServiceRule should be first so the test can be skipped right away
+            .outerRule(mRequiredServiceRule)
+            //
+            // mLoggingRule wraps the test but doesn't interfere with it
+            .around(mLoggingRule)
+            //
+            // mSafeCleanerRule will catch errors
+            .around(mSafeCleanerRule)
+            //
+            // Finally, let subclasses set their ActivityTestRule
+            .around(getActivityTestRule());
+
+    /**
+     * Watcher set on {@link #enableService()} and used to wait until it's gone after the test
+     * finishes.
+     */
+    private ServiceWatcher mServiceWatcher;
+
+    protected AbstractContentCaptureIntegrationTest(@NonNull Class<A> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    @Before
+    public void prepareDevice() throws Exception {
+        Log.v(mTag, "@Before: prepareDevice()");
+
+        // Unlock screen.
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+        // Dismiss keyguard, in case it's set as "Swipe to unlock".
+        runShellCommand("wm dismiss-keyguard");
+
+        // Collapse notifications.
+        runShellCommand("cmd statusbar collapse");
+    }
+
+    @Before
+    public void clearState() {
+        Log.v(mTag, "@Before: clearState()");
+        CtsContentCaptureService.resetStaticState();
+    }
+
+    @Before
+    public void registerLifecycleCallback() {
+        Log.v(mTag, "@Before: Registering lifecycle callback");
+        final Application app = (Application) sContext.getApplicationContext();
+        mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS);
+        app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+    }
+
+    @After
+    public void unregisterLifecycleCallback() {
+        Log.d(mTag, "@After: Unregistering lifecycle callback: " + mActivitiesWatcher);
+        if (mActivitiesWatcher != null) {
+            final Application app = (Application) sContext.getApplicationContext();
+            app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+        }
+    }
+
+    // TODO(b/123539404): this method should be called from the SafeCleaner, but we'll need to
+    // add a run() method that takes an object that can throw an exception
+    @After
+    public void restoreDefaultService() throws InterruptedException {
+        Log.v(mTag, "@After: restoreDefaultService()");
+        resetService();
+
+        if (mServiceWatcher != null) {
+            mServiceWatcher.waitOnDestroy();
+        }
+    }
+
+    // TODO(b/123429736): temporary method until Autofill's StateChangerRule is moved to common
+    @Nullable
+    public static void setFeatureEnabled(@Nullable String enabled) {
+        final String property = Settings.Secure.CONTENT_CAPTURE_ENABLED;
+        if (enabled == null) {
+            runShellCommand("settings delete secure %s", property);
+        } else {
+            runShellCommand("settings put secure %s %s", property, enabled);
+        }
+        SystemClock.sleep(1000); // We need to sleep as we're not waiting for the listener callback
+    }
+
+    /**
+     * Sets {@link CtsContentCaptureService} as the service for the current user and waits until
+     * its created.
+     */
+    public CtsContentCaptureService enableService() throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+        mServiceWatcher = CtsContentCaptureService.setServiceWatcher();
+        setService(CtsContentCaptureService.SERVICE_NAME);
+
+        return mServiceWatcher.waitOnCreate();
+    }
+
+    /**
+     * Gets the {@link ActivityTestRule} use to launch this activity.
+     *
+     * <p><b>NOTE: </b>implementation must return a static singleton, otherwise it might be
+     * {@code null} when used it in this class' {@code @Rule}
+     */
+    protected abstract ActivityTestRule<A> getActivityTestRule();
+
+    protected A launchActivity() {
+        Log.d(mTag, "Launching " + mActivityClass.getSimpleName());
+
+        return getActivityTestRule().launchActivity(new Intent(sContext, mActivityClass));
+    }
+
+    protected A launchActivity(@Nullable Visitor<Intent> visitor) {
+        Log.d(mTag, "Launching " + mActivityClass.getSimpleName());
+
+        final Intent intent = new Intent(sContext, mActivityClass);
+        if (visitor != null) {
+            visitor.visit(intent);
+        }
+        return getActivityTestRule().launchActivity(intent);
+    }
+
+    @NonNull
+    protected ActivityWatcher startWatcher() {
+        return mActivitiesWatcher.watch(mActivityClass);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java
new file mode 100644
index 0000000..0a49f35
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java
@@ -0,0 +1,128 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import android.app.Activity;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base class for classes that have a {@code root_view} root view.
+ */
+abstract class AbstractRootViewActivity extends AbstractContentCaptureActivity {
+
+    private static final String TAG = AbstractRootViewActivity.class.getSimpleName();
+
+    private static DoubleVisitor<AbstractRootViewActivity, LinearLayout> sRootViewVisitor;
+    private static DoubleVisitor<AbstractRootViewActivity, LinearLayout> sOnAnimationVisitor;
+
+    private LinearLayout mRootView;
+
+    /**
+     * Sets a visitor called when the activity is created.
+     */
+    static void onRootView(@NonNull DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor) {
+        sRootViewVisitor = visitor;
+    }
+
+    /**
+     * Sets a visitor to be called on {@link Activity#onEnterAnimationComplete()}.
+     */
+    static void onAnimationComplete(
+            @NonNull DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor) {
+        sOnAnimationVisitor = visitor;
+    }
+
+    @Override
+    protected final void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentViewOnCreate(savedInstanceState);
+
+        mRootView = findViewById(R.id.root_view);
+
+        Log.d(TAG, "Parents for " + getClass() + ": rootView=" + mRootView
+                + "\ngrandParent=" + getGrandParent()
+                + "\ngrandGrandParent=" + getGrandGrandParent()
+                + "\ndecorView=" + getDecorView());
+
+        if (sRootViewVisitor != null) {
+            Log.d(TAG, "Applying visitor to " + this + "/" + mRootView);
+            try {
+                sRootViewVisitor.visit(this, mRootView);
+            } finally {
+                sRootViewVisitor = null;
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.d(TAG, "AutofillIds for " + getClass() + ": "
+                + " rootView=" + getRootView().getAutofillId()
+                + ", grandParent=" + getGrandParent().getAutofillId()
+                + ", grandGrandParent=" + getGrandGrandParent().getAutofillId()
+                + ", decorView=" + getDecorView().getAutofillId());
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        if (sOnAnimationVisitor != null) {
+            Log.i(TAG, "onEnterAnimationComplete(): applying visitor on " + this);
+            try {
+                sOnAnimationVisitor.visit(this, mRootView);
+            } finally {
+                sOnAnimationVisitor = null;
+            }
+        } else {
+            Log.i(TAG, "onEnterAnimationComplete(): no visitor on " + this);
+        }
+    }
+
+    public LinearLayout getRootView() {
+        return mRootView;
+    }
+
+    // TODO(b/122315042): remove this method when not needed anymore
+    @NonNull
+    public ViewGroup getGrandParent() {
+        return (ViewGroup) mRootView.getParent();
+    }
+
+    // TODO(b/122315042): remove this method when not needed anymore
+    @NonNull
+    public ViewGroup getGrandGrandParent() {
+        return (ViewGroup) getGrandParent().getParent();
+    }
+
+    // TODO(b/122315042): remove this method when not needed anymore
+    @NonNull
+    public ViewGroup getDecorView() {
+        return (ViewGroup) getGrandGrandParent().getParent();
+    }
+
+    /**
+     * The real "onCreate" method that should be extended by subclasses.
+     *
+     */
+    protected abstract void setContentViewOnCreate(Bundle savedInstanceState);
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
new file mode 100644
index 0000000..f48a62d
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -0,0 +1,406 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.MY_EPOCH;
+import static android.contentcaptureservice.cts.Helper.TAG;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.ViewNode;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Helper for common assertions.
+ */
+final class Assertions {
+
+    /**
+     * Asserts a session belongs to the right activity.
+     */
+    public static void assertRightActivity(@NonNull Session session,
+            @NonNull ContentCaptureSessionId expectedSessionId,
+            @NonNull AbstractContentCaptureActivity activity) {
+        assertWithMessage("wrong activity for %s", session)
+                .that(session.context.getActivityComponent())
+                .isEqualTo(activity.getComponentName());
+        // TODO(b/123540602): merge both or replace check above by:
+        //  assertMainSessionContext(session, activity);
+        assertThat(session.id).isEqualTo(expectedSessionId);
+    }
+
+    /**
+     * Asserts the context of a main session.
+     */
+    public static void assertMainSessionContext(@NonNull Session session,
+            @NonNull AbstractContentCaptureActivity activity) {
+        assertMainSessionContext(session, activity, /* expectedFlags= */ 0);
+    }
+
+    /**
+     * Asserts the context of a main session.
+     */
+    public static void assertMainSessionContext(@NonNull Session session,
+            @NonNull AbstractContentCaptureActivity activity, int expectedFlags) {
+        assertWithMessage("no context on %s", session).that(session.context).isNotNull();
+        assertWithMessage("wrong activity for %s", session)
+                .that(session.context.getActivityComponent())
+                .isEqualTo(activity.getComponentName());
+        // TODO(b/121260224): add this assertion when it's set
+        // assertWithMessage("context for session %s should have displayId", session)
+        //        .that(session.context.getDisplayId()).isNotEqualTo(0);
+        assertWithMessage("wrong task id for session %s", session)
+                .that(session.context.getTaskId()).isEqualTo(activity.getRealTaskId());
+        assertWithMessage("wrong flags on context for session %s", session)
+                .that(session.context.getFlags()).isEqualTo(expectedFlags);
+        assertWithMessage("context for session %s should not have URI", session)
+                .that(session.context.getUri()).isNull();
+        assertWithMessage("context for session %s should not have extras", session)
+                .that(session.context.getExtras()).isNull();
+    }
+
+    /**
+     * Asserts the invariants of a child session.
+     */
+    public static void assertChildSessionContext(@NonNull Session session) {
+        assertWithMessage("no context on %s", session).that(session.context).isNotNull();
+        assertWithMessage("context for session %s should not have component", session)
+                .that(session.context.getActivityComponent()).isNull();
+        assertWithMessage("context for session %s should not have displayId", session)
+                .that(session.context.getDisplayId()).isEqualTo(0);
+        assertWithMessage("context for session %s should not have taskId", session)
+                .that(session.context.getTaskId()).isEqualTo(0);
+        assertWithMessage("context for session %s should not have flags", session)
+                .that(session.context.getFlags()).isEqualTo(0);
+    }
+
+    /**
+     * Asserts the invariants of a child session.
+     */
+    public static void assertChildSessionContext(@NonNull Session session,
+            @NonNull String expectedUri) {
+        assertChildSessionContext(session);
+        assertThat(session.context.getUri()).isEqualTo(Uri.parse(expectedUri));
+    }
+
+    /**
+     * Asserts a session belongs to the right parent
+     */
+    public static void assertRightRelationship(@NonNull Session parent, @NonNull Session child) {
+        final ContentCaptureSessionId expectedParentId = parent.id;
+        assertWithMessage("No id on parent session %s", parent).that(expectedParentId).isNotNull();
+        assertWithMessage("No context on child session %s", child).that(child.context).isNotNull();
+        final ContentCaptureSessionId actualParentId = child.context.getParentSessionId();
+        assertWithMessage("No parent id on context %s of child session %s", child.context, child)
+                .that(actualParentId).isNotNull();
+        assertWithMessage("id of parent session doesn't match child").that(actualParentId)
+                .isEqualTo(expectedParentId);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
+     */
+    public static ViewNode assertViewWithUnknownParentAppeared(
+            @NonNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView) {
+        return assertViewWithUnknownParentAppeared(events, index, expectedView,
+                /* expectedText= */ null);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
+     */
+    public static ViewNode assertViewWithUnknownParentAppeared(
+            @NonNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView,
+            @Nullable String expectedText) {
+        final ViewNode node = assertViewAppeared(events, index);
+        final ContentCaptureEvent event = events.get(index);
+
+        assertWithMessage("invalid time on %s (%s)", event, index).that(event.getEventTime())
+                .isAtLeast(MY_EPOCH);
+        assertWithMessage("wrong class on %s (%s)", event, index).that(node.getClassName())
+                .isEqualTo(expectedView.getClass().getName());
+        assertWithMessage("wrong autofill id on %s (%s)", event, index).that(node.getAutofillId())
+                .isEqualTo(expectedView.getAutofillId());
+
+        if (expectedText != null) {
+            assertWithMessage("wrong text on %s (%s)", event, index).that(node.getText().toString())
+                    .isEqualTo(expectedText);
+        }
+        // TODO(b/123540602): test more fields, like resource id
+        return node;
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
+     */
+    public static ViewNode assertViewAppeared(@NonNull List<ContentCaptureEvent> events,
+            int index) {
+        final ContentCaptureEvent event = getEvent(events, index);
+        assertWithMessage("wrong event type at index %s: %s", index, event).that(event.getType())
+                .isEqualTo(TYPE_VIEW_APPEARED);
+        final ViewNode node = event.getViewNode();
+        assertThat(node).isNotNull();
+        return node;
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
+     */
+    public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull View expectedView, @Nullable AutofillId expectedParentId,
+            @Nullable String expectedText) {
+        final ViewNode node = assertViewWithUnknownParentAppeared(events, index, expectedView,
+                expectedText);
+        assertWithMessage("wrong parent autofill id on %s (%s)", events.get(index), index)
+            .that(node.getParentAutofillId()).isEqualTo(expectedParentId);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
+     */
+    public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull View expectedView, @Nullable AutofillId expectedParentId) {
+        assertViewAppeared(events, index, expectedView, expectedParentId, /* expectedText= */ null);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
+     */
+    public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull ContentCaptureSessionId expectedSessionId,
+            @NonNull View expectedView, @Nullable AutofillId expectedParentId) {
+        assertViewAppeared(events, index, expectedView, expectedParentId);
+        assertSessionId(expectedSessionId, expectedView);
+    }
+
+    /**
+     * Asserts that a session for the given activity has no events.
+     */
+    public static void assertNoEvents(@NonNull Session session,
+            @NonNull AbstractContentCaptureActivity activity) {
+        assertRightActivity(session, session.id, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events on " + activity + ": " + events);
+        assertThat(events).isEmpty();
+    }
+
+    /**
+     * Asserts that the events received by the service optionally contains the
+     * {@code TYPE_VIEW_DISAPPEARED} events, as they might have not been generated if the views
+     * disappeared after the activity stopped.
+     *
+     * @param events events received by the service.
+     * @param minimumSize size of events received if activity stopped before views disappeared
+     * @param expectedIds ids of views that might have disappeared.
+     */
+    // TODO(b/123540067): remove this method if we could make it deterministic
+    public static void assertViewsOptionallyDisappeared(@NonNull List<ContentCaptureEvent> events,
+            int minimumSize, @NonNull AutofillId... expectedIds) {
+        final int actualSize = events.size();
+        if (actualSize == minimumSize) {
+            // Activity stopped before TYPE_VIEW_DISAPPEARED were sent.
+            return;
+        }
+        assertThat(events).hasSize(minimumSize + 1);
+        final ContentCaptureEvent batchDisappearEvent = events.get(minimumSize);
+        final List<AutofillId> actualIds = batchDisappearEvent.getIds();
+        assertThat(actualIds).containsExactly((Object[]) expectedIds);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent
+     */
+    public static void assertViewWithUnknownParentAppeared(
+            @NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView) {
+        assertViewWithUnknownParentAppeared(events, index, expectedView);
+        assertSessionId(expectedSessionId, expectedView);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a single view.
+     */
+    public static void assertViewDisappeared(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull AutofillId expectedId) {
+        final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index);
+        assertWithMessage("wrong autofillId on event %s (index %s)", event, index)
+            .that(event.getId()).isEqualTo(expectedId);
+        assertWithMessage("event %s (index %s) should not have autofillIds", event, index)
+            .that(event.getIds()).isNull();
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for multiple views.
+     */
+    public static void assertViewsDisappeared(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull AutofillId... expectedIds) {
+        final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index);
+        final List<AutofillId> ids = event.getIds();
+        assertWithMessage("no autofillIds on event %s (index %s)", event, index).that(ids)
+                .isNotNull();
+        assertWithMessage("wrong autofillId on event %s (index %s)", event, index)
+            .that(ids).containsExactly((Object[]) expectedIds).inOrder();
+        assertWithMessage("event %s (index %s) should not have autofillId", event, index)
+            .that(event.getId()).isNull();
+    }
+
+    private static ContentCaptureEvent assertCommonViewDisappearedProperties(
+            @NonNull List<ContentCaptureEvent> events, int index) {
+        final ContentCaptureEvent event = getEvent(events, index);
+        assertWithMessage("wrong event type at index %s: %s", index, event).that(event.getType())
+                .isEqualTo(TYPE_VIEW_DISAPPEARED);
+        assertWithMessage("invalid time on %s (index %s)", event, index).that(event.getEventTime())
+            .isAtLeast(MY_EPOCH);
+        assertWithMessage("event %s (index %s) should not have a ViewNode", event, index)
+                .that(event.getViewNode()).isNull();
+        assertWithMessage("event %s (index %s) should not have text", event, index)
+            .that(event.getText()).isNull();
+        assertWithMessage("event %s (index %s) should not have a ViewNode", event, index)
+            .that(event.getViewNode()).isNull();
+        return event;
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event for a virtual node.
+     */
+    public static void assertVirtualViewAppeared(@NonNull List<ContentCaptureEvent> events,
+            int index, @NonNull ContentCaptureSession session, @NonNull AutofillId parentId,
+            int childId, @Nullable String expectedText) {
+        final ContentCaptureEvent event = getEvent(events, index);
+        assertWithMessage("wrong event type at index %s: %s", index, event).that(event.getType())
+            .isEqualTo(TYPE_VIEW_APPEARED);
+        final ViewNode node = event.getViewNode();
+        assertThat(node).isNotNull();
+        assertWithMessage("invalid time on %s (index %s)", event, index).that(event.getEventTime())
+            .isAtLeast(MY_EPOCH);
+        final AutofillId expectedId = session.newAutofillId(parentId, childId);
+        assertWithMessage("wrong autofill id on %s (index %s)", event, index)
+            .that(node.getAutofillId()).isEqualTo(expectedId);
+        if (expectedText != null) {
+            assertWithMessage("wrong text on %s(index %s) ", event, index)
+                .that(node.getText().toString()).isEqualTo(expectedText);
+        } else {
+            assertWithMessage("%s (index %s) should not have text", node, index)
+                .that(node.getText()).isNull();
+        }
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a virtual node.
+     */
+    public static void assertVirtualViewDisappeared(@NonNull List<ContentCaptureEvent> events,
+            int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session,
+            long childId) {
+        assertViewDisappeared(events, index, session.newAutofillId(parentId, childId));
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for many virtual nodes.
+     */
+    public static void assertVirtualViewsDisappeared(@NonNull List<ContentCaptureEvent> events,
+            int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session,
+            long... childrenIds) {
+        final int size = childrenIds.length;
+        final AutofillId[] expectedIds = new AutofillId[size];
+        for (int i = 0; i < childrenIds.length; i++) {
+            expectedIds[i] = session.newAutofillId(parentId, childrenIds[i]);
+        }
+        assertViewsDisappeared(events, index, expectedIds);
+    }
+
+    /**
+     * Asserts a view has the given session id.
+     */
+    public static void assertSessionId(@NonNull ContentCaptureSessionId expectedSessionId,
+            @NonNull View view) {
+        assertThat(expectedSessionId).isNotNull();
+        final ContentCaptureSession session = view.getContentCaptureSession();
+        assertWithMessage("no session for view %s", view).that(session).isNotNull();
+        assertWithMessage("wrong session id for for view %s", view)
+                .that(session.getContentCaptureSessionId()).isEqualTo(expectedSessionId);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_TEXT_CHANGED} event.
+     */
+    public static void assertViewTextChanged(@NonNull List<ContentCaptureEvent> events, int index,
+            @NonNull AutofillId expectedId, @NonNull String expectedText) {
+        final ContentCaptureEvent event = getEvent(events, index);
+        assertWithMessage("wrong event at index %s: %s", index, event).that(event.getType())
+                .isEqualTo(TYPE_VIEW_TEXT_CHANGED);
+        assertWithMessage("Wrong id on %s (%s)", event, index).that(event.getId())
+                .isEqualTo(expectedId);
+        assertWithMessage("Wrong text on %s (%s)", event, index).that(event.getText().toString())
+                .isEqualTo(expectedText);
+    }
+
+    /**
+     * Asserts the order a session was created or destroyed.
+     */
+    public static void assertLifecycleOrder(int expectedOrder, @NonNull Session session,
+            @NonNull LifecycleOrder type) {
+        switch(type) {
+            case CREATION:
+                assertWithMessage("Wrong order of creation for session %s", session)
+                    .that(session.creationOrder).isEqualTo(expectedOrder);
+                break;
+            case DESTRUCTION:
+                assertWithMessage("Wrong order of destruction for session %s", session)
+                    .that(session.destructionOrder).isEqualTo(expectedOrder);
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid type: " + type);
+        }
+    }
+
+    /**
+     * Gets the event at the given index, failing with the user-friendly message if necessary...
+     */
+    @NonNull
+    public static ContentCaptureEvent getEvent(@NonNull List<ContentCaptureEvent> events,
+            int index) {
+        assertWithMessage("events is null").that(events).isNotNull();
+        final ContentCaptureEvent event = events.get(index);
+        assertWithMessage("no event at index %s (size %s): %s", index, events.size(), events)
+                .that(event).isNotNull();
+        return event;
+    }
+
+    private Assertions() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    public enum LifecycleOrder {
+        CREATION, DESTRUCTION
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java
new file mode 100644
index 0000000..6ccbae9
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertNoEvents;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+
+import androidx.annotation.NonNull;
+
+public class BlankActivity extends AbstractContentCaptureActivity {
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        assertNoEvents(session, this);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
new file mode 100644
index 0000000..5a78cb6
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.CtsContentCaptureService.CONTENT_CAPTURE_SERVICE_COMPONENT_NAME;
+import static android.contentcaptureservice.cts.Helper.resetService;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class BlankActivityTest extends AbstractContentCaptureIntegrationTest<BlankActivity> {
+
+    private static final String TAG = BlankActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<BlankActivity> sActivityRule = new ActivityTestRule<>(
+            BlankActivity.class, false, false);
+
+    public BlankActivityTest() {
+        super(BlankActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<BlankActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Test
+    public void testSimpleSessionLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @Test
+    public void testGetServiceComponentName() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        try {
+            assertThat(activity.getContentCaptureManager().getServiceComponentName())
+                    .isEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+
+            resetService();
+            service.waitUntilDisconnected();
+
+            assertThat(activity.getContentCaptureManager().getServiceComponentName())
+                    .isNotEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+        } finally {
+            activity.finish();
+            watcher.waitFor(DESTROYED);
+        }
+    }
+
+    @Test
+    public void testGetServiceComponentName_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final AtomicReference<ComponentName> ref = new AtomicReference<>();
+        activity.syncRunOnUiThread(
+                () -> ref.set(activity.getContentCaptureManager().getServiceComponentName()));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(ref.get()).isEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+    }
+
+    @Test
+    public void testIsContentCaptureFeatureEnabled_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final AtomicBoolean ref = new AtomicBoolean();
+        activity.syncRunOnUiThread(() -> ref
+                .set(activity.getContentCaptureManager().isContentCaptureFeatureEnabled()));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(ref.get()).isTrue();
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final AtomicReference<Exception> ref = new AtomicReference<>();
+        activity.syncRunOnUiThread(() -> {
+            try {
+                activity.getContentCaptureManager().setContentCaptureFeatureEnabled(true);
+            } catch (Exception e) {
+                ref.set(e);
+            }
+        });
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Exception e = ref.get();
+        if (e != null) throw e;
+    }
+
+    @Test
+    public void testOnConnectionEvents() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        resetService();
+        service.waitUntilDisconnected();
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.java
new file mode 100644
index 0000000..d6d8ba4
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.ViewNode;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+public class BlankWithTitleActivity extends AbstractContentCaptureActivity {
+
+    private static final String TAG = BlankWithTitleActivity.class.getSimpleName();
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, this);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+
+        final int minEvents = 1;
+        // TODO(b/119638528): somehow asset the grandparents...
+        assertThat(events.size()).isAtLeast(minEvents);
+
+        final ViewNode title = assertViewAppeared(events, 0);
+        assertThat(title.getText()).isEqualTo("Blanka");
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java
new file mode 100644
index 0000000..e08371c
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+
+import org.junit.Test;
+
+public class BlankWithTitleActivityTest
+        extends AbstractContentCaptureIntegrationTest<BlankWithTitleActivity> {
+
+    private static final String TAG = BlankWithTitleActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<BlankWithTitleActivity> sActivityRule =
+            new ActivityTestRule<>(BlankWithTitleActivity.class, false, false);
+
+    public BlankWithTitleActivityTest() {
+        super(BlankWithTitleActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<BlankWithTitleActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Test
+    public void testSimpleSessionLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankWithTitleActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java
new file mode 100644
index 0000000..153c4c0
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertNoEvents;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+public class ChildlessActivity extends AbstractRootViewActivity {
+
+    @Override
+    protected void setContentViewOnCreate(Bundle savedInstanceState) {
+        setContentView(R.layout.childless_activity);
+    }
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        // Should be empty because the root view is not important for content capture without a
+        // child that is important.
+        assertNoEvents(session, this);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
new file mode 100644
index 0000000..c3f5470
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -0,0 +1,1099 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.LifecycleOrder.CREATION;
+import static android.contentcaptureservice.cts.Assertions.LifecycleOrder.DESTRUCTION;
+import static android.contentcaptureservice.cts.Assertions.assertChildSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertLifecycleOrder;
+import static android.contentcaptureservice.cts.Assertions.assertMainSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewDisappeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewWithUnknownParentAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewsDisappeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+import static android.contentcaptureservice.cts.Helper.componentNameFor;
+import static android.contentcaptureservice.cts.Helper.newImportantView;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.DisconnectListener;
+import android.contentcaptureservice.cts.CtsContentCaptureService.ServiceWatcher;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.ActivityLauncher;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ChildlessActivityTest
+        extends AbstractContentCaptureIntegrationTest<ChildlessActivity> {
+
+    private static final String TAG = ChildlessActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<ChildlessActivity> sActivityRule = new ActivityTestRule<>(
+            ChildlessActivity.class, false, false);
+
+    public ChildlessActivityTest() {
+        super(ChildlessActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<ChildlessActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Before
+    @After
+    public void resetActivityStaticState() {
+        ChildlessActivity.onRootView(null);
+    }
+
+    @Test
+    public void testDefaultLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @Ignore("not implemented yet, pending on b/123658889")
+    @Test
+    public void testGetContentCapture_disabledWhenNoService() throws Exception {
+
+        // TODO(b/123658889): must call a cmd that always disable the service, even if the OEM
+        // provides an implementation
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+    }
+
+    @Test
+    public void testGetContentCapture_enabledWhenNoService() throws Exception {
+        enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isTrue();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+    }
+
+    @Test
+    public void testLaunchAnotherActivity() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher1 = startWatcher();
+
+        // Launch and finish 1st activity
+        final ChildlessActivity activity1 = launchActivity();
+        watcher1.waitFor(RESUMED);
+        activity1.finish();
+        watcher1.waitFor(DESTROYED);
+
+        // Launch and finish 2nd activity
+        final ActivityLauncher<LoginActivity> anotherActivityLauncher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher2 = anotherActivityLauncher.getWatcher();
+        final LoginActivity activity2 = anotherActivityLauncher.launchActivity();
+        watcher2.waitFor(RESUMED);
+        activity2.finish();
+        watcher2.waitFor(DESTROYED);
+
+        // Assert the sessions
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).hasSize(2);
+        final ContentCaptureSessionId sessionId1 = sessionIds.get(0);
+        Log.v(TAG, "session id1: " + sessionId1);
+        final ContentCaptureSessionId sessionId2 = sessionIds.get(1);
+        Log.v(TAG, "session id2: " + sessionId2);
+
+        final Session session1 = service.getFinishedSession(sessionId1);
+        activity1.assertDefaultEvents(session1);
+
+        final Session session2 = service.getFinishedSession(sessionId2);
+        activity2.assertDefaultEvents(session2);
+    }
+
+    @Ignore("not implemented yet, pending on b/122595322")
+    @Test
+    public void testLaunchAnotherActivity_serviceDisabledActivity() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher1 = startWatcher();
+
+        // Disable activity 2
+        service.setActivityContentCaptureEnabled(componentNameFor(LoginActivity.class), false);
+
+        // Launch and finish 1st activity
+        final ChildlessActivity activity1 = launchActivity();
+        watcher1.waitFor(RESUMED);
+        activity1.finish();
+        watcher1.waitFor(DESTROYED);
+
+        // Launch and finish 2nd activity
+        final ActivityLauncher<LoginActivity> anotherActivityLauncher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher2 = anotherActivityLauncher.getWatcher();
+        final LoginActivity activity2 = anotherActivityLauncher.launchActivity();
+        watcher2.waitFor(RESUMED);
+        activity2.finish();
+        watcher2.waitFor(DESTROYED);
+
+        // Assert the sessions
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).hasSize(1);
+        final ContentCaptureSessionId sessionId1 = sessionIds.get(0);
+        Log.v(TAG, "session id1: " + sessionId1);
+
+        final Session session1 = service.getFinishedSession(sessionId1);
+        activity1.assertDefaultEvents(session1);
+
+        // TODO(b/122595322): should also test events after re-enabling it
+    }
+
+    // TODO(b/122595322): same tests for disabled by package, explicity whitelisted, etc...
+
+    @Test
+    public void testAddAndRemoveNoImportantChild() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        // Child must be created inside the lambda because it needs to use the Activity context.
+        final AtomicReference<TextView> childRef = new AtomicReference<>();
+
+        ChildlessActivity.onRootView((activity, rootView) -> {
+            final TextView child = new TextView(activity);
+            child.setText("VIEW, Y U NO IMPORTANT?");
+            child.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO);
+
+            rootView.addView(child);
+        });
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        // Remove view
+        final TextView child = childRef.get();
+        activity.syncRunOnUiThread(() -> activity.getRootView().removeView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        // Should be empty because the root view is not important for content capture without a
+        // child that is important.
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testAddAndRemoveImportantChild() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        // TODO(b/120494182): Child must be created inside the lambda because it needs to use the
+        // Activity context.
+        final AtomicReference<TextView> childRef = new AtomicReference<>();
+
+        ChildlessActivity.onRootView((activity, rootView) -> {
+            final TextView text = newImportantView(activity, "Important I am");
+            rootView.addView(text);
+            childRef.set(text);
+        });
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        // Remove view
+        final TextView child = childRef.get();
+        activity.syncRunOnUiThread(() -> activity.getRootView().removeView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): ideally it should be 3 so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+        assertThat(events.size()).isAtLeast(5);
+
+        // Assert just the relevant events
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewAppeared(events, 0, sessionId, child, rootId);
+        assertViewWithUnknownParentAppeared(events, 1, sessionId, activity.getRootView());
+        // Ignore events 2 and 3 (intermediate parents appeared)
+        assertViewDisappeared(events, 4, child.getAutofillId());
+    }
+
+    @Test
+    public void testAddImportantChildAfterSessionStarted() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final TextView child = newImportantView(activity, "Important I am");
+        activity.runOnUiThread(() -> activity.getRootView().addView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/123540067): ideally it should be 3 so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+        assertThat(events.size()).isAtLeast(4);
+
+        // Assert just the relevant events
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewAppeared(events, 0, sessionId, child, rootId);
+    }
+
+    @Test
+    public void testAddAndRemoveImportantChildOnDifferentSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(new ContentCaptureContext.Builder()
+                        .setUri(Uri.parse("http://child")).build());
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + childSessionId);
+
+        final TextView child = newImportantView(activity, "Important I am");
+        child.setContentCaptureSession(childSession);
+        activity.runOnUiThread(() -> activity.getRootView().addView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).containsExactly(mainSessionId, childSessionId).inOrder();
+
+        // Assert sessions
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        final List<ContentCaptureEvent> mainEvents = mainTestSession.getEvents();
+        // TODO(b/123540067): ideally it should have only one event for the root view ,
+        // right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+        assertThat(mainEvents.size()).isAtLeast(3);
+        assertViewWithUnknownParentAppeared(mainEvents, 0, mainSessionId, activity.getRootView());
+
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "http://child");
+        final List<ContentCaptureEvent> childEvents = childTestSession.getEvents();
+        final int minEvents = 1;
+        assertThat(mainEvents.size()).isAtLeast(minEvents);
+        assertViewAppeared(childEvents, 0, childSessionId, child,
+                activity.getRootView().getAutofillId());
+        assertViewsOptionallyDisappeared(childEvents, minEvents, child.getAutofillId());
+    }
+
+    /**
+     * Tests scenario where new sessions are added from the main session, but they're not nested
+     * neither have views attached to them.
+     */
+    @Test
+    public void testDinamicallyManageChildlessSiblingSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session1")).build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session2")).build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        // Close 1st session before opening 3rd
+        childSession1.close();
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session3")).build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        // ...and close it right away
+        childSession3.close();
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session4")).build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        assertThat(mainTestSession.getEvents()).isEmpty();
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "http://session1");
+        assertThat(childTestSession1.getEvents()).isEmpty();
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        assertChildSessionContext(childTestSession2, "http://session2");
+        assertThat(childTestSession2.getEvents()).isEmpty();
+
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "http://session3");
+        assertThat(childTestSession3.getEvents()).isEmpty();
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "http://session4");
+        assertThat(childTestSession4.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession3, CREATION);
+        assertLifecycleOrder(6, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(7, childTestSession4, CREATION);
+        assertLifecycleOrder(8, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    @Test
+    public void testDinamicallyAddOneChildOnAnotherSession_manuallyCloseSession() throws Exception {
+        dinamicallyAddOneChildOnAnotherSessionTest(/* manuallyCloseSession= */ true);
+    }
+
+    @Test
+    public void testDinamicallyAddOneChildOnAnotherSession_autoCloseSession() throws Exception {
+        dinamicallyAddOneChildOnAnotherSessionTest(/* manuallyCloseSession= */ false);
+    }
+
+    /**
+     * Tests scenario where just 1 session with 1 dinamically added view is created.
+     */
+    private void dinamicallyAddOneChildOnAnotherSessionTest(boolean manuallyCloseSession)
+            throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create session
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(new ContentCaptureContext.Builder()
+                        .setUri(Uri.parse("http://child_session")).build());
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session: " + childSessionId);
+
+        final TextView child = addChild(activity, childSession, "Sweet O'Mine");
+        if (manuallyCloseSession) {
+            waitAndClose(childSession);
+        }
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(mainSessionId, childSessionId).inOrder();
+
+        // Assert main session
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        // TODO(b/123540067): ideally it should be empty, but has intermediate parents stuff...
+        // assertThat(mainTestSession.getEvents()).isEmpty();
+
+        // Assert child session
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "http://child_session");
+        final List<ContentCaptureEvent> childEvents = childTestSession.getEvents();
+        assertThat(childEvents.size()).isAtLeast(1);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewAppeared(childEvents, 0, child, rootId);
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,  CREATION);
+        assertLifecycleOrder(2, childTestSession, CREATION);
+        assertLifecycleOrder(3, childTestSession, DESTRUCTION);
+        assertLifecycleOrder(4, mainTestSession, DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where new sessions with children are added from the main session.
+     */
+    @Test
+    public void testDinamicallyManageSiblingSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session1")).build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Session 1, child 1
+        final TextView s1c1 = addChild(activity, childSession1, "s1c1");
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session2")).build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        final TextView s2c1 = addChild(activity, childSession2, "s2c1");
+        final TextView s2c2 = addChild(activity, childSession2, "s2c2");
+
+        // Close 1st session before opening 3rd
+        waitAndClose(childSession1);
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session3")).build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        final TextView s3c1 = addChild(activity, childSession3, "s3c1");
+        final TextView s3c2 = addChild(activity, childSession3, "s3c2");
+        waitAndRemoveViews(activity, s3c1);
+        final TextView s3c3 = addChild(activity, childSession3, "s3c3");
+
+        // ...and close it right away
+        waitAndClose(childSession3);
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session4")).build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "http://session1");
+        final List<ContentCaptureEvent> events1 = childTestSession1.getEvents();
+        Log.v(TAG, "events1: " + events1);
+        assertThat(events1.size()).isAtLeast(1);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewAppeared(events1, 0, s1c1, rootId);
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        final List<ContentCaptureEvent> events2 = childTestSession2.getEvents();
+        assertChildSessionContext(childTestSession2, "http://session2");
+        Log.v(TAG, "events2: " + events2);
+        assertThat(events2.size()).isAtLeast(2);
+        assertViewAppeared(events2, 0, s2c1, rootId);
+        assertViewAppeared(events2, 1, s2c2, rootId);
+
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "http://session3");
+        List<ContentCaptureEvent> events3 = childTestSession3.getEvents();
+        Log.v(TAG, "events3: " + events3);
+        assertThat(events3.size()).isAtLeast(4);
+        assertViewAppeared(events3, 0, s3c1, rootId);
+        assertViewAppeared(events3, 1, s3c2, rootId);
+        assertViewDisappeared(events3, 2, s3c1.getAutofillId());
+        assertViewAppeared(events3, 3, s3c3, rootId);
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "http://session4");
+        assertThat(childTestSession4.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession3, CREATION);
+        assertLifecycleOrder(6, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(7, childTestSession4, CREATION);
+        assertLifecycleOrder(8, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    @Test
+    public void testNestedSessions_simplestScenario() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create child session
+        final ContentCaptureContext childContext = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://child")).build();
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(childContext);
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + childSessionId);
+
+        // Create grand child session
+        final ContentCaptureContext grandChild = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://grandChild")).build();
+        final ContentCaptureSession grandChildSession = childSession
+                .createContentCaptureSession(grandChild);
+        final ContentCaptureSessionId grandChildSessionId = grandChildSession
+                .getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + grandChildSessionId);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId,
+                grandChildSessionId)
+            .inOrder();
+
+        // Assert sessions
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        assertThat(mainTestSession.getEvents()).isEmpty();
+
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "http://child");
+        assertThat(childTestSession.getEvents()).isEmpty();
+
+        final Session grandChildTestSession = service.getFinishedSession(grandChildSessionId);
+        assertChildSessionContext(grandChildTestSession, "http://grandChild");
+        assertThat(grandChildTestSession.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession, CREATION);
+        assertLifecycleOrder(2, childTestSession, CREATION);
+        assertLifecycleOrder(3, grandChildTestSession, CREATION);
+        assertLifecycleOrder(4, grandChildTestSession, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession, DESTRUCTION);
+        assertLifecycleOrder(6, mainTestSession,  DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where new sessions are added from each other session, but they're not nested
+     * neither have views attached to them.
+     *
+     * <p>This test actions are exactly the same as
+     * {@link #testDinamicallyManageChildlessSiblingSessions()}, except for session nesting (and
+     * order of lifecycle events).
+     */
+    @Test
+    public void testDinamicallyManageChildlessNestedSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session1")).build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session2")).build();
+        final ContentCaptureSession childSession2 = childSession1
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        // Close 1st session before opening 3rd
+        childSession1.close();
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session3")).build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        // ...and close it right away
+        childSession3.close();
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session4")).build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        assertThat(mainTestSession.getEvents()).isEmpty();
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "http://session1");
+        assertThat(childTestSession1.getEvents()).isEmpty();
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        assertChildSessionContext(childTestSession2, "http://session2");
+        assertThat(childTestSession2.getEvents()).isEmpty();
+
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "http://session3");
+        assertThat(childTestSession3.getEvents()).isEmpty();
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "http://session4");
+        assertThat(childTestSession4.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(6, childTestSession3, CREATION);
+        assertLifecycleOrder(7, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(8, childTestSession4, CREATION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where views from different session are removed in sequence - they should not
+     * have been batched.
+     */
+    @Test
+    public void testRemoveChildrenFromDifferentSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session1")).build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Session 1, child 1
+        final TextView s1c1 = addChild(activity, childSession1, "s1c1");
+        final AutofillId s1c1Id = s1c1.getAutofillId();
+        Log.v(TAG, "childrens from session1: " + s1c1Id);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = new ContentCaptureContext.Builder()
+                .setUri(Uri.parse("http://session2")).build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        final TextView s2c1 = addChild(activity, childSession2, "s2c1");
+        final AutofillId s2c1Id = s2c1.getAutofillId();
+        final TextView s2c2 = addChild(activity, childSession2, "s2c2");
+        final AutofillId s2c2Id = s2c2.getAutofillId();
+        Log.v(TAG, "childrens from session2: " + s2c1Id + ", " + s2c2Id);
+
+        // Remove views - should generate one batch event for s2 and one single event for s1
+        waitAndRemoveViews(activity, s2c1, s2c2, s1c1);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "http://session1");
+        final List<ContentCaptureEvent> events1 = childTestSession1.getEvents();
+        Log.v(TAG, "events1: " + events1);
+        assertThat(events1.size()).isAtLeast(2);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewAppeared(events1, 0, s1c1, rootId);
+        assertViewDisappeared(events1, 1, s1c1Id);
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        final List<ContentCaptureEvent> events2 = childTestSession2.getEvents();
+        assertChildSessionContext(childTestSession2, "http://session2");
+        Log.v(TAG, "events2: " + events2);
+        assertThat(events2.size()).isAtLeast(3);
+        assertViewAppeared(events2, 0, s2c1, rootId);
+        assertViewAppeared(events2, 1, s2c2, rootId);
+        assertViewsDisappeared(events2, 2, s2c1Id, s2c2Id);
+    }
+
+    /* TODO(b/119638528): add more scenarios for nested sessions, such as:
+     * - add views to the children sessions
+     * - s1 -> s2 -> s3 and main -> s4; close(s1) then generate events on view from s3
+     * - s1 -> s2 -> s3 and main -> s4; close(s2) then generate events on view from s3
+     * - s1 -> s2 and s3->s4 -> s4
+     * - etc
+     */
+
+    @Test
+    public void testIsContentCaptureFeatureEnabled_notService() throws Exception {
+        final ContentCaptureManager mgr = getContentCaptureManagerHack();
+        assertThrows(SecurityException.class,  () -> mgr.isContentCaptureFeatureEnabled());
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledBySettings() throws Exception {
+        setContentCaptureFeatureEnabledTest_disabled(/* bySettings= */ true);
+    }
+
+    private void setContentCaptureFeatureEnabledTest_disabled(boolean bySettings) throws Exception {
+        // TODO(b/123429736): remove try/finally once we use a StateChangerRule
+        try {
+            final ContentCaptureManager mgr = getContentCaptureManagerHack();
+
+            final CtsContentCaptureService service = enableService();
+            assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+            final DisconnectListener disconnectedListener = service.setOnDisconnectListener();
+
+            if (bySettings) {
+                setFeatureEnabled("false");
+            } else {
+                mgr.setContentCaptureFeatureEnabled(false);
+            }
+
+            disconnectedListener.waitForOnDisconnected();
+            assertThat(mgr.isContentCaptureFeatureEnabled()).isFalse();
+            assertThat(mgr.isContentCaptureEnabled()).isFalse();
+
+            final ActivityWatcher watcher = startWatcher();
+            final ChildlessActivity activity = launchActivity();
+
+            watcher.waitFor(RESUMED);
+            activity.finish();
+            watcher.waitFor(DESTROYED);
+
+            assertThat(service.getAllSessionIds()).isEmpty();
+        } finally {
+            try {
+                Helper.resetService();
+            } finally {
+                setFeatureEnabled("true");
+            }
+        }
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledThenReEnabledBySettings()
+            throws Exception {
+        setContentCaptureFeatureEnabledTest_disabledThenReEnabled(/* bySettings= */ true);
+    }
+
+    private void setContentCaptureFeatureEnabledTest_disabledThenReEnabled(boolean bySettings)
+            throws Exception {
+        // TODO(b/123429736): remove try/finally once we use a StateChangerRule
+        try {
+            final ContentCaptureManager mgr = getContentCaptureManagerHack();
+
+            final CtsContentCaptureService service1 = enableService();
+            assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+            final DisconnectListener disconnectedListener = service1.setOnDisconnectListener();
+
+            if (bySettings) {
+                setFeatureEnabled("false");
+            } else {
+                mgr.setContentCaptureFeatureEnabled(false);
+            }
+            disconnectedListener.waitForOnDisconnected();
+
+            assertThat(mgr.isContentCaptureFeatureEnabled()).isFalse();
+            assertThat(mgr.isContentCaptureEnabled()).isFalse();
+
+            // Launch and finish 1st activity while it's disabled
+            final ActivityWatcher watcher1 = startWatcher();
+            final ChildlessActivity activity1 = launchActivity();
+            watcher1.waitFor(RESUMED);
+            activity1.finish();
+            watcher1.waitFor(DESTROYED);
+
+            // Re-enable feature
+            final ServiceWatcher reconnectionWatcher = CtsContentCaptureService.setServiceWatcher();
+            if (bySettings) {
+                setFeatureEnabled("true");
+            } else {
+                mgr.setContentCaptureFeatureEnabled(true);
+            }
+            final CtsContentCaptureService service2 = reconnectionWatcher.waitOnCreate();
+            assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+
+            // Launch and finish 2nd activity while it's enabled
+            final ActivityLauncher<CustomViewActivity> launcher2 = new ActivityLauncher<>(
+                    sContext, mActivitiesWatcher, CustomViewActivity.class);
+            final ActivityWatcher watcher2 = launcher2.getWatcher();
+            final CustomViewActivity activity2 = launcher2.launchActivity();
+            watcher2.waitFor(RESUMED);
+            activity2.finish();
+            watcher2.waitFor(DESTROYED);
+
+            assertThat(service1.getAllSessionIds()).isEmpty();
+            final Session session = service2.getOnlyFinishedSession();
+            activity2.assertDefaultEvents(session);
+        } finally {
+            try {
+                Helper.resetService();
+            } finally {
+                setFeatureEnabled("true");
+            }
+        }
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_notService() throws Exception {
+        final ContentCaptureManager mgr = getContentCaptureManagerHack();
+        assertThrows(SecurityException.class,  () -> mgr.setContentCaptureFeatureEnabled(true));
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledByApi() throws Exception {
+        setContentCaptureFeatureEnabledTest_disabled(/* bySettings= */ false);
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledThenReEnabledByApi()
+            throws Exception {
+        setContentCaptureFeatureEnabledTest_disabledThenReEnabled(/* bySettings= */ false);
+    }
+
+    // TODO(b/123406031): add tests that mix feature_enabled with user_restriction_enabled (and
+    // make sure mgr.isContentCaptureFeatureEnabled() returns only the state of the 1st)
+
+    private TextView addChild(@NonNull ChildlessActivity activity,
+            @NonNull ContentCaptureSession session, @NonNull String text) {
+        final TextView child = newImportantView(activity, text);
+        child.setContentCaptureSession(session);
+        Log.i(TAG, "adding " + child.getAutofillId() + " on session "
+                + session.getContentCaptureSessionId());
+        activity.runOnUiThread(() -> activity.getRootView().addView(child));
+        return child;
+    }
+
+    // TODO(b/123024698): these method are used in cases where we cannot close a session because we
+    // would miss intermediate events, so we need to sleep. This is a hack (it's slow and flaky):
+    // ideally we should block and wait until the service receives the event, but right now
+    // we don't get the service events until after the activity is finished, so we cannot do that...
+    private void waitAndClose(@NonNull ContentCaptureSession session) {
+        Log.d(TAG, "sleeping for 1s before closing " + session.getContentCaptureSessionId());
+        SystemClock.sleep(1_000);
+        session.close();
+    }
+
+    private void waitAndRemoveViews(@NonNull ChildlessActivity activity, @NonNull View... views) {
+        Log.d(TAG, "sleeping for 1s before removing " + Arrays.toString(views));
+        SystemClock.sleep(1_000);
+        activity.syncRunOnUiThread(() -> {
+            for (View view : views) {
+                activity.getRootView().removeView(view);
+            }
+        });
+    }
+
+    // TODO(b/120494182): temporary hack to get the manager, which currently is only available on
+    // Activity contexts (and would be null from sContext)
+    @NonNull
+    private ContentCaptureManager getContentCaptureManagerHack() throws InterruptedException {
+        final AtomicReference<ContentCaptureManager> ref = new AtomicReference<>();
+        LoginActivity.onRootView(
+                (activity, rootView) -> ref.set(activity.getContentCaptureManager()));
+
+        final ActivityLauncher<LoginActivity> launcher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher = launcher.getWatcher();
+        final LoginActivity activity = launcher.launchActivity();
+        watcher.waitFor(RESUMED);
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final ContentCaptureManager mgr = ref.get();
+        assertThat(mgr).isNotNull();
+
+        return mgr;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureContextTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureContextTest.java
new file mode 100644
index 0000000..c19f0f7
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureContextTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.platform.test.annotations.AppModeFull;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureContext.Builder;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@AppModeFull // Unit test
+public class ContentCaptureContextTest {
+
+    private static final Uri URI = Uri.parse("file:/dev/null");
+    private static final String ACTION = "Jackson";
+
+    private final ContentCaptureContext.Builder mBuilder = new ContentCaptureContext.Builder();
+
+    private final Bundle mExtras = new Bundle();
+
+    @Before
+    public void setExtras() {
+        mExtras.putString("DUDE", "SWEET");
+    }
+
+    @Test
+    public void testBuilder_invalidUri() {
+        assertThrows(NullPointerException.class, () -> mBuilder.setUri(null));
+    }
+
+    @Test
+    public void testBuilder_invalidAction() {
+        assertThrows(NullPointerException.class, () -> mBuilder.setAction(null));
+    }
+
+    @Test
+    public void testBuilder_invalidExtras() {
+        assertThrows(NullPointerException.class, () -> mBuilder.setExtras(null));
+    }
+
+    @Test
+    public void testAfterBuild_setExtras() {
+        assertThat(mBuilder.setUri(URI).build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.setExtras(mExtras));
+    }
+
+    @Test
+    public void testAfterBuild_setAction() {
+        assertThat(mBuilder.setUri(URI).build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.setAction(ACTION));
+    }
+
+    @Test
+    public void testAfterBuild_setUri() {
+        assertThat(mBuilder.setExtras(mExtras).build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.setUri(URI));
+    }
+
+    @Test
+    public void testAfterBuild_build() {
+        assertThat(mBuilder.setExtras(mExtras).build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testBuild_empty() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testSetGetUri() {
+        final Builder builder = mBuilder.setUri(URI);
+        assertThat(builder).isSameAs(mBuilder);
+        final ContentCaptureContext context = builder.build();
+        assertThat(context).isNotNull();
+        assertThat(context.getUri()).isEqualTo(URI);
+    }
+
+    @Test
+    public void testSetGetAction() {
+        final Builder builder = mBuilder.setAction(ACTION);
+        assertThat(builder).isSameAs(mBuilder);
+        final ContentCaptureContext context = builder.build();
+        assertThat(context).isNotNull();
+        assertThat(context.getAction()).isEqualTo(ACTION);
+    }
+
+    @Test
+    public void testGetSetBundle() {
+        final Builder builder = mBuilder.setExtras(mExtras);
+        assertThat(builder).isSameAs(mBuilder);
+        final ContentCaptureContext context = builder.build();
+        assertThat(context).isNotNull();
+        assertExtras(context.getExtras());
+    }
+
+    @Test
+    public void testParcel() {
+        final Builder builder = mBuilder
+                .setUri(URI)
+                .setAction(ACTION)
+                .setExtras(mExtras);
+        assertThat(builder).isSameAs(mBuilder);
+        final ContentCaptureContext context = builder.build();
+        assertEverything(context);
+
+        final ContentCaptureContext clone = cloneThroughParcel(context);
+        assertEverything(clone);
+    }
+
+    private void assertEverything(@NonNull ContentCaptureContext context) {
+        assertThat(context).isNotNull();
+        assertThat(context.getUri()).isEqualTo(URI);
+        assertThat(context.getAction()).isEqualTo(ACTION);
+        assertExtras(context.getExtras());
+    }
+
+    private void assertExtras(@NonNull Bundle bundle) {
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.keySet()).hasSize(1);
+        assertThat(bundle.getString("DUDE")).isEqualTo("SWEET");
+    }
+
+    private ContentCaptureContext cloneThroughParcel(@NonNull ContentCaptureContext context) {
+        final Parcel parcel = Parcel.obtain();
+
+        try {
+            // Write to parcel
+            parcel.setDataPosition(0); // Sanity / paranoid check
+            context.writeToParcel(parcel, 0);
+
+            // Read from parcel
+            parcel.setDataPosition(0);
+            final ContentCaptureContext clone = ContentCaptureContext.CREATOR
+                    .createFromParcel(parcel);
+            assertThat(clone).isNotNull();
+            return clone;
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java
new file mode 100644
index 0000000..51aaa4b
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ContentCaptureLoggingTestRule.java
@@ -0,0 +1,93 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.MY_PACKAGE;
+import static android.contentcaptureservice.cts.Helper.TAG;
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves ContentCapture-related logging by:
+ *
+ * <ol>
+ *   <li>Setting logging level to verbose before test start.
+ *   <li>Call {@code dumpsys} in case of failure.
+ * </ol>
+ */
+public class ContentCaptureLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
+
+    private final String mTag;
+    private boolean mDumped;
+
+    public ContentCaptureLoggingTestRule(String tag) {
+        mTag = tag;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                // TODO(b/121044306): set verbose logging once ContentCapture supports it
+                final String testName = description.getDisplayName();
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    dump(testName, t);
+                    throw t;
+                } finally {
+                    // TODO(b/121044306): recover logging level
+                }
+            }
+        };
+    }
+
+    @Override
+    public void dump(@NonNull String testName, @NonNull Throwable t) {
+        if (mDumped) {
+            Log.e(mTag, "dump(" + testName + "): already dumped");
+            return;
+        }
+        if ((t instanceof AssumptionViolatedException)) {
+            // This exception is used to indicate a test should be skipped and is
+            // ignored by JUnit runners - we don't need to dump it...
+            Log.w(TAG, "ignoring exception: " + t);
+            return;
+        }
+        // TODO(b/123540602, b/120784831): should dump to a file (and integrate with tradefed)
+        // instead of outputting to log directly...
+        Log.e(mTag, "Dumping after exception on " + testName, t);
+        final String serviceDump = runShellCommand("dumpsys content_capture");
+        Log.e(mTag, "content_capture dump: \n" + serviceDump);
+        final String activityDump = runShellCommand("dumpsys activity %s --contentcapture",
+                MY_PACKAGE);
+        Log.e(mTag, "activity dump: \n" + activityDump);
+        mDumped = true;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
new file mode 100644
index 0000000..e869fc1
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -0,0 +1,468 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Helper.MY_PACKAGE;
+import static android.contentcaptureservice.cts.Helper.await;
+import static android.contentcaptureservice.cts.Helper.componentNameFor;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.service.contentcapture.ContentCaptureService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.UserDataRemovalRequest;
+import android.view.contentcapture.ViewNode;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+// TODO(b/123540602): if we don't move this service to a separate package, we need to handle the
+// onXXXX methods in a separate thread
+// Either way, we need to make sure its methods are thread safe
+
+public class CtsContentCaptureService extends ContentCaptureService {
+
+    private static final String TAG = CtsContentCaptureService.class.getSimpleName();
+
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + CtsContentCaptureService.class.getSimpleName();
+    public static final ComponentName CONTENT_CAPTURE_SERVICE_COMPONENT_NAME =
+            componentNameFor(CtsContentCaptureService.class);
+
+    private static int sIdCounter;
+
+    private static ServiceWatcher sServiceWatcher;
+
+    // TODO(b/123421324): reuse with allSessions
+    /** Used by {@link #getOnlyFinishedSession()}. */
+    private static ContentCaptureSessionId sFirstSessionId;
+
+
+    private final int mId = ++sIdCounter;
+
+    private static final ArrayList<Throwable> sExceptions = new ArrayList<>();
+
+    private final CountDownLatch mConnectedLatch = new CountDownLatch(1);
+    private final CountDownLatch mDisconnectedLatch = new CountDownLatch(1);
+
+    /**
+     * List of all sessions started - never reset.
+     */
+    private final ArrayList<ContentCaptureSessionId> mAllSessions = new ArrayList<>();
+
+    /**
+     * Map of all sessions started but not finished yet - sessions are removed as they're finished.
+     */
+    private final ArrayMap<ContentCaptureSessionId, Session> mOpenSessions = new ArrayMap<>();
+
+    /**
+     * Map of all sessions finished.
+     */
+    private final ArrayMap<ContentCaptureSessionId, Session> mFinishedSessions = new ArrayMap<>();
+
+    /**
+     * Map of latches for sessions that started but haven't finished yet.
+     */
+    private final ArrayMap<ContentCaptureSessionId, CountDownLatch> mUnfinishedSessionLatches =
+            new ArrayMap<>();
+
+    /**
+     * Counter of onCreate() / onDestroy() events.
+     */
+    private int mLifecycleEventsCounter;
+
+    /**
+     * Used for testing onUserDataRemovalRequest.
+     */
+    private UserDataRemovalRequest mRemovalRequest;
+
+    /**
+     * Optional listener for {@code onDisconnect()}.
+     */
+    @Nullable
+    private DisconnectListener mOnDisconnectListener;
+
+    @NonNull
+    public static ServiceWatcher setServiceWatcher() {
+        if (sServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+        sServiceWatcher = new ServiceWatcher();
+        return sServiceWatcher;
+    }
+
+
+    public static void resetStaticState() {
+        sFirstSessionId = null;
+        sExceptions.clear();
+        // TODO(b/123540602): should probably set sInstance to null as well, but first we would need
+        // to make sure each test unbinds the service.
+
+        // TODO(b/123540602): each test should use a different service instance, but we need
+        // to provide onConnected() / onDisconnected() methods first and then change the infra so
+        // we can wait for those
+
+        if (sServiceWatcher != null) {
+            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
+            sServiceWatcher = null;
+        }
+    }
+
+    @Override
+    public void onConnected() {
+        Log.i(TAG, "onConnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+
+        if (sServiceWatcher == null) {
+            addException("onConnected() without a watcher");
+            return;
+        }
+
+        if (sServiceWatcher.mService != null) {
+            addException("onConnected(): already created: %s", sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mService = this;
+        sServiceWatcher.mCreated.countDown();
+
+        if (mConnectedLatch.getCount() == 0) {
+            addException("already connected: %s", mConnectedLatch);
+        }
+        mConnectedLatch.countDown();
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.i(TAG, "onDisconnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+
+        if (mDisconnectedLatch.getCount() == 0) {
+            addException("already disconnected: %s", mConnectedLatch);
+        }
+        mDisconnectedLatch.countDown();
+
+        if (sServiceWatcher == null) {
+            addException("onDisconnected() without a watcher");
+            return;
+        }
+        if (sServiceWatcher.mService == null) {
+            addException("onDisconnected(): no service on %s", sServiceWatcher);
+            return;
+        }
+        // Notify test case as well
+        if (mOnDisconnectListener != null) {
+            final CountDownLatch latch = mOnDisconnectListener.mLatch;
+            mOnDisconnectListener = null;
+            latch.countDown();
+        }
+        sServiceWatcher.mDestroyed.countDown();
+        sServiceWatcher.mService = null;
+        sServiceWatcher = null;
+    }
+
+    /**
+     * Waits until the system calls {@link #onConnected()}.
+     */
+    public void waitUntilConnected() throws InterruptedException {
+        await(mConnectedLatch, "not connected");
+    }
+
+    /**
+     * Waits until the system calls {@link #onDisconnected()}.
+     */
+    public void waitUntilDisconnected() throws InterruptedException {
+        await(mDisconnectedLatch, "not disconnected");
+    }
+
+    @Override
+    public void onCreateContentCaptureSession(ContentCaptureContext context,
+            ContentCaptureSessionId sessionId) {
+        Log.i(TAG, "onCreateContentCaptureSession(id=" + mId + ", ctx=" + context
+                + ", session=" + sessionId + ", firstId=" + sFirstSessionId + ")");
+        mAllSessions.add(sessionId);
+        if (sFirstSessionId == null) {
+            sFirstSessionId = sessionId;
+        }
+
+        safeRun(() -> {
+            final Session session = mOpenSessions.get(sessionId);
+            if (session != null) {
+                throw new IllegalStateException("Already contains session for " + sessionId
+                        + ": " + session);
+            }
+            mUnfinishedSessionLatches.put(sessionId, new CountDownLatch(1));
+            mOpenSessions.put(sessionId, new Session(sessionId, context));
+        });
+    }
+
+    @Override
+    public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) {
+        Log.i(TAG, "onDestroyContentCaptureSession(id=" + mId + ", session=" + sessionId + ")");
+        safeRun(() -> {
+            final Session session = getExistingSession(sessionId);
+            session.finish();
+            mOpenSessions.remove(sessionId);
+            if (mFinishedSessions.containsKey(sessionId)) {
+                throw new IllegalStateException("Already destroyed " + sessionId);
+            } else {
+                mFinishedSessions.put(sessionId, session);
+                final CountDownLatch latch = getUnfinishedSessionLatch(sessionId);
+                latch.countDown();
+            }
+        });
+    }
+
+    @Override
+    public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
+            ContentCaptureEvent event) {
+        Log.i(TAG, "onContentCaptureEventsRequest(id=" + mId + ", session=" + sessionId + "): "
+                + event);
+        final ViewNode node = event.getViewNode();
+        if (node != null) {
+            Log.v(TAG, "onContentCaptureEvent(): parentId=" + node.getParentAutofillId());
+        }
+        safeRun(() -> {
+            final Session session = getExistingSession(sessionId);
+            session.mEvents.add(event);
+        });
+    }
+
+    @Override
+    public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+        Log.i(TAG, "onUserDataRemovalRequest(id=" + mId + ",req=" + request + ")");
+        mRemovalRequest = request;
+    }
+
+    /**
+     * Gets the cached UserDataRemovalRequest for testing.
+     */
+    public UserDataRemovalRequest getRemovalRequest() {
+        return mRemovalRequest;
+    }
+
+    /**
+     * Gets the finished session for the given session id.
+     *
+     * @throws IllegalStateException if the session didn't finish yet.
+     */
+    @NonNull
+    public Session getFinishedSession(@NonNull ContentCaptureSessionId sessionId)
+            throws InterruptedException {
+        final CountDownLatch latch = getUnfinishedSessionLatch(sessionId);
+        await(latch, "session %s not finished yet", sessionId);
+
+        final Session session = mFinishedSessions.get(sessionId);
+        if (session == null) {
+            throwIllegalSessionStateException("No finished session for id %s", sessionId);
+        }
+        return session;
+    }
+
+    /**
+     * Gets the finished session when only one session is expected.
+     *
+     * <p>Should be used when the test case doesn't known in advance the id of the session.
+     */
+    @NonNull
+    public Session getOnlyFinishedSession() throws InterruptedException {
+        // TODO(b/123421324): add some assertions to make sure There Can Be Only One!
+        assertWithMessage("No session yet").that(sFirstSessionId).isNotNull();
+        return getFinishedSession(sFirstSessionId);
+    }
+
+    /**
+     * Gets all sessions that have been created so far.
+     */
+    @NonNull
+    public List<ContentCaptureSessionId> getAllSessionIds() {
+        return Collections.unmodifiableList(mAllSessions);
+    }
+
+    /**
+     * Sets a listener to wait until the service disconnects.
+     */
+    @NonNull
+    public DisconnectListener setOnDisconnectListener() {
+        if (mOnDisconnectListener != null) {
+            throw new IllegalStateException("already set");
+        }
+        mOnDisconnectListener = new DisconnectListener();
+        return mOnDisconnectListener;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+
+        pw.print("sServiceWatcher: "); pw.println(sServiceWatcher);
+        pw.print("sFirstSessionId: "); pw.println(sFirstSessionId);
+        pw.print("sExceptions: "); pw.println(sExceptions);
+        pw.print("sIdCounter: "); pw.println(sIdCounter);
+        pw.print("mId: "); pw.println(mId);
+        pw.print("mConnectedLatch: "); pw.println(mConnectedLatch);
+        pw.print("mDisconnectedLatch: "); pw.println(mDisconnectedLatch);
+        pw.print("mAllSessions: "); pw.println(mAllSessions);
+        pw.print("mOpenSessions: "); pw.println(mOpenSessions);
+        pw.print("mFinishedSessions: "); pw.println(mFinishedSessions);
+        pw.print("mUnfinishedSessionLatches: "); pw.println(mUnfinishedSessionLatches);
+        pw.print("mLifecycleEventsCounter: "); pw.println(mLifecycleEventsCounter);
+    }
+
+    @NonNull
+    private CountDownLatch getUnfinishedSessionLatch(final ContentCaptureSessionId sessionId) {
+        final CountDownLatch latch = mUnfinishedSessionLatches.get(sessionId);
+        if (latch == null) {
+            throwIllegalSessionStateException("no latch for %s", sessionId);
+        }
+        return latch;
+    }
+
+    /**
+     * Gets the exceptions that were thrown while the service handlded requests.
+     */
+    public static List<Throwable> getExceptions() throws Exception {
+        return Collections.unmodifiableList(sExceptions);
+    }
+
+    private void throwIllegalSessionStateException(@NonNull String fmt, @Nullable Object...args) {
+        throw new IllegalStateException(String.format(fmt, args)
+                + ".\nID=" + mId
+                + ".\nAll=" + mAllSessions
+                + ".\nOpen=" + mOpenSessions
+                + ".\nLatches=" + mUnfinishedSessionLatches
+                + ".\nFinished=" + mFinishedSessions);
+    }
+
+    private Session getExistingSession(@NonNull ContentCaptureSessionId sessionId) {
+        final Session session = mOpenSessions.get(sessionId);
+        if (session == null) {
+            throwIllegalSessionStateException("No open session with id %s", sessionId);
+        }
+        if (session.finished) {
+            throw new IllegalStateException("session already finished: " + session);
+        }
+
+        return session;
+    }
+
+    private void safeRun(@NonNull Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            Log.e(TAG, "Exception handling service callback: " + t);
+            sExceptions.add(t);
+        }
+    }
+
+    private static void addException(@NonNull String fmt, @Nullable Object...args) {
+        final String msg = String.format(fmt, args);
+        Log.e(TAG, msg);
+        sExceptions.add(new IllegalStateException(msg));
+    }
+
+    public final class Session {
+        public final ContentCaptureSessionId id;
+        public final ContentCaptureContext context;
+        public final int creationOrder;
+        private final List<ContentCaptureEvent> mEvents = new ArrayList<>();
+        public boolean finished;
+        public int destructionOrder;
+
+        private Session(ContentCaptureSessionId id, ContentCaptureContext context) {
+            this.id = id;
+            this.context = context;
+            creationOrder = ++mLifecycleEventsCounter;
+            Log.d(TAG, "create(" + id  + "): order=" + creationOrder);
+        }
+
+        private void finish() {
+            finished = true;
+            destructionOrder = ++mLifecycleEventsCounter;
+            Log.d(TAG, "finish(" + id  + "): order=" + destructionOrder);
+        }
+
+        // TODO(b/123540602): currently we're only interested on all events, but eventually we
+        // should track individual requests as well to make sure they're probably batch (it will
+        // require adding a Settings to tune the buffer parameters.
+        public List<ContentCaptureEvent> getEvents() {
+            return Collections.unmodifiableList(mEvents);
+        }
+
+        @Override
+        public String toString() {
+            return "[id=" + id + ", context=" + context + ", events=" + mEvents.size()
+                    + ", finished=" + finished + "]";
+        }
+    }
+
+    public static final class ServiceWatcher {
+
+        private final CountDownLatch mCreated = new CountDownLatch(1);
+        private final CountDownLatch mDestroyed = new CountDownLatch(1);
+
+        private CtsContentCaptureService mService;
+
+        @NonNull
+        public CtsContentCaptureService waitOnCreate() throws InterruptedException {
+            await(mCreated, "not created");
+
+            if (mService == null) {
+                throw new IllegalStateException("not created");
+            }
+
+            return mService;
+        }
+
+        public void waitOnDestroy() throws InterruptedException {
+            await(mDestroyed, "not destroyed");
+        }
+
+        @Override
+        public String toString() {
+            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+                    + " destroyed: " + (mDestroyed.getCount() == 0);
+        }
+    }
+
+    /**
+     * Listener used to block until the service is disconnected.
+     */
+    public class DisconnectListener {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        /**
+         * Wait or die!
+         */
+        public void waitForOnDisconnected() {
+            try {
+                await(mLatch, "not disconnected");
+            } catch (Exception e) {
+                addException("DisconnectListener: onDisconnected() not called: " + e);
+            }
+        }
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
new file mode 100644
index 0000000..754cef0
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.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.contentcaptureservice.cts;
+
+import android.content.Context;
+import android.contentcaptureservice.cts.common.Visitor;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewStructure;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A view that can be used to emulate custom behavior (like virtual children) on
+ * {@link #onProvideContentCaptureStructure(ViewStructure, int)}.
+ */
+public class CustomView extends View {
+
+    private static final String TAG = CustomView.class.getSimpleName();
+
+    private Visitor<ViewStructure> mDelegate;
+
+    public CustomView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES);
+    }
+
+    @Override
+    public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
+        if (mDelegate != null) {
+            Log.d(TAG, "onProvideContentCaptureStructure(): delegating");
+            structure.setClassName(getAccessibilityClassName().toString());
+            mDelegate.visit(structure);
+            Log.d(TAG, "onProvideContentCaptureStructure(): delegated");
+        } else {
+            superOnProvideContentCaptureStructure(structure, flags);
+        }
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return CustomView.class.getName();
+    }
+
+    void superOnProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
+        Log.d(TAG, "calling super.onProvideContentCaptureStructure()");
+        super.onProvideContentCaptureStructure(structure, flags);
+    }
+
+    void setContentCaptureDelegate(@NonNull Visitor<ViewStructure> delegate) {
+        mDelegate = delegate;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java
new file mode 100644
index 0000000..5fb5fbf
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertViewWithUnknownParentAppeared;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ViewStructure;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+public class CustomViewActivity extends AbstractContentCaptureActivity {
+
+    private static final String TAG = CustomViewActivity.class.getSimpleName();
+
+    private static DoubleVisitor<CustomView, ViewStructure> sCustomViewDelegate;
+
+    CustomView mCustomView;
+
+    /**
+     * Sets a delegate that provides the behavior of
+     * {@link CustomView#onProvideContentCaptureStructure(ViewStructure, int)}.
+     */
+    static void setCustomViewDelegate(@NonNull DoubleVisitor<CustomView, ViewStructure> delegate) {
+        sCustomViewDelegate = delegate;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.custom_view_activity);
+        mCustomView = findViewById(R.id.custom_view);
+        Log.d(TAG, "onCreate(): custom view id is " + mCustomView.getAutofillId());
+        if (sCustomViewDelegate != null) {
+            Log.d(TAG, "Setting delegate on " + mCustomView);
+            mCustomView.setContentCaptureDelegate(
+                    (structure) -> sCustomViewDelegate.visit(mCustomView, structure));
+        }
+    }
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        assertRightActivity(session, session.id, this);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): check right number once we get rid of grandparent
+        assertThat(events.size()).isAtLeast(1);
+
+        // Assert just the relevant events
+        assertViewWithUnknownParentAppeared(events, 0, session.id, mCustomView);
+
+        // TODO(b/122315042): assert views disappeared
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
new file mode 100644
index 0000000..c7afd2a
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertViewWithUnknownParentAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertVirtualViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertVirtualViewDisappeared;
+import static android.contentcaptureservice.cts.Assertions.assertVirtualViewsDisappeared;
+import static android.contentcaptureservice.cts.Helper.await;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSession;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+public class CustomViewActivityTest extends
+        AbstractContentCaptureIntegrationTest<CustomViewActivity> {
+
+    private static final String TAG = CustomViewActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<CustomViewActivity> sActivityRule =
+            new ActivityTestRule<>(CustomViewActivity.class, false, false);
+
+    public CustomViewActivityTest() {
+        super(CustomViewActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<CustomViewActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    /**
+     * Baseline / sanity check test for the other tests.
+     */
+    @Test
+    public void testLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final CustomViewActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    /**
+     * 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...
+     */
+    @Test
+    public void testVirtualView_wrongWay() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        CustomViewActivity.setCustomViewDelegate((customView, structure) -> {
+            Log.d(TAG, "delegate running on " + Thread.currentThread());
+            final AutofillId customViewId = customView.getAutofillId();
+            Log.d(TAG, "customViewId: " + customViewId);
+            final ContentCaptureSession session = customView.getContentCaptureSession();
+
+            final ViewStructure child = session.newVirtualViewStructure(customViewId, 1);
+            child.setText("child");
+            final AutofillId childId = child.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 1)).isEqualTo(childId);
+            Log.d(TAG, "nofifying child appeared: " + childId);
+            session.notifyViewAppeared(child);
+
+            Log.d(TAG, "nofifying child disappeared: " + childId);
+            session.notifyViewDisappeared(childId);
+        });
+
+        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 List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): check right number once we get rid of grandparent
+        assertThat(events.size()).isAtLeast(3);
+
+        // Assert just the relevant events
+        final AutofillId customViewId = activity.mCustomView.getAutofillId();
+        final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
+
+        assertVirtualViewAppeared(events, 0, mainSession, customViewId, 1, "child");
+        assertVirtualViewDisappeared(events, 1, customViewId, mainSession, 1);
+
+        // This is the "wrong" part - the parent is notified last
+        assertViewWithUnknownParentAppeared(events, 2, session.id, activity.mCustomView);
+
+        // TODO(b/122315042): assert views disappeared
+    }
+
+    /**
+     * Tests when the view has virtual children, but those children are leaf nodes.
+     */
+    @Test
+    public void testVirtualView_oneLevel() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final CountDownLatch asyncLatch = setAsyncDelegate((customView, structure) -> {
+            Log.d(TAG, "delegate running on " + Thread.currentThread());
+            final AutofillId customViewId = customView.getAutofillId();
+            Log.d(TAG, "customViewId: " + customViewId);
+            final ContentCaptureSession session = customView.getContentCaptureSession();
+
+            final ViewStructure child1 = session.newVirtualViewStructure(customViewId, 1);
+            child1.setText("child1");
+            final AutofillId child1Id = child1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 1)).isEqualTo(child1Id);
+            Log.d(TAG, "nofifying child1 appeared: " + child1Id);
+            session.notifyViewAppeared(child1);
+            final ViewStructure child2 = session.newVirtualViewStructure(customViewId, 2);
+            child2.setText("child2");
+            final AutofillId child2Id = child2.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 2)).isEqualTo(child2Id);
+            Log.d(TAG, "nofifying child2 appeared: " + child2Id);
+            session.notifyViewAppeared(child2);
+            Log.d(TAG, "nofifying child2 disappeared: " + child2Id);
+            session.notifyViewDisappeared(child2Id);
+            Log.d(TAG, "nofifying child1 disappeared: " + child1Id);
+            session.notifyViewDisappeared(child1Id);
+        });
+
+        final CustomViewActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        await(asyncLatch, "async onProvide");
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        assertRightActivity(session, session.id, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): check right number once we get rid of grandparent
+        assertThat(events.size()).isAtLeast(6);
+
+        // Assert just the relevant events
+        final AutofillId customViewId = activity.mCustomView.getAutofillId();
+        final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
+
+        assertViewWithUnknownParentAppeared(events, 0, session.id, activity.mCustomView);
+        // TODO(b/119638528): next 2 events are the grandparents
+        assertVirtualViewAppeared(events, 3, mainSession, customViewId, 1, "child1");
+        assertVirtualViewAppeared(events, 4, mainSession, customViewId, 2, "child2");
+        assertVirtualViewsDisappeared(events, 5, customViewId, mainSession, 2, 1);
+
+        // TODO(b/122315042): assert views disappeared
+    }
+
+    /**
+     * Tests when the view has virtual children, and some of those children have children too.
+     */
+    @Test
+    public void testVirtualView_multipleLevels() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final CountDownLatch asyncLatch = setAsyncDelegate((customView, structure) -> {
+            Log.d(TAG, "delegate running on " + Thread.currentThread());
+            final AutofillId customViewId = customView.getAutofillId();
+            Log.d(TAG, "customViewId: " + customViewId);
+            final ContentCaptureSession session = customView.getContentCaptureSession();
+
+            // Child 1
+            final ViewStructure c1 = session.newVirtualViewStructure(customViewId, 1);
+            c1.setText("c1");
+            final AutofillId c1Id = c1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 1)).isEqualTo(c1Id);
+            Log.d(TAG, "nofifying c1 appeared: " + c1Id);
+            session.notifyViewAppeared(c1);
+
+            // Child 1, grandchildren 1
+            final ViewStructure c1g1 = session.newVirtualViewStructure(c1Id, 11);
+            c1g1.setText("c1g1");
+            final AutofillId c1g1Id = c1g1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 11)).isEqualTo(c1g1Id);
+            Log.d(TAG, "nofifying c1g1 appeared: " + c1g1Id);
+            session.notifyViewAppeared(c1g1);
+
+            // Child 1, grandchildren 2
+            final ViewStructure c1g2 = session.newVirtualViewStructure(c1Id, 12);
+            c1g2.setText("c1g2");
+            final AutofillId c1g2Id = c1g2.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 12)).isEqualTo(c1g2Id);
+            Log.d(TAG, "nofifying c1g2 appeared: " + c1g2Id);
+            session.notifyViewAppeared(c1g2);
+
+            final ViewStructure c2 = session.newVirtualViewStructure(customViewId, 2);
+            c2.setText("c2");
+            final AutofillId c2Id = c2.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 2)).isEqualTo(c2Id);
+            Log.d(TAG, "nofifying c2 appeared: " + c2Id);
+            session.notifyViewAppeared(c2);
+
+            // Child 2, grandchildren 1 - not removed
+            final ViewStructure c2g1 = session.newVirtualViewStructure(c2Id, 21);
+            c2g1.setText("c2g1");
+            final AutofillId c2g1Id = c2g1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 21)).isEqualTo(c2g1Id);
+            Log.d(TAG, "nofifying c2g1 appeared: " + c2g1Id);
+            session.notifyViewAppeared(c2g1);
+
+            // Child 2, grandchildren 1, grandgrandchild1 (on purpose)
+            final ViewStructure c2g1gg1 = session.newVirtualViewStructure(c2g1Id,
+                    211);
+            c2g1gg1.setText("c2g1gg1");
+            final AutofillId c2g1ggt1Id = c2g1gg1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 211)).isEqualTo(c2g1ggt1Id);
+            Log.d(TAG, "nofifying c2g1gg1 appeared: " + c2g1ggt1Id);
+            session.notifyViewAppeared(c2g1gg1);
+
+            // Child 3 - not removed (on purpose)
+            final ViewStructure c3 = session.newVirtualViewStructure(customViewId, 3);
+            c3.setText("c3");
+            final AutofillId c3Id = c3.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 3)).isEqualTo(c3Id);
+            Log.d(TAG, "nofifying c3 appeared: " + c3Id);
+            session.notifyViewAppeared(c3);
+
+            // Remove children (although not all of them, on purpose)
+            Log.d(TAG, "nofifying c2g1 disappeared: " + c2g1Id);
+            session.notifyViewDisappeared(c2g1Id);
+            Log.d(TAG, "nofifying c2 disappeared: " + c2Id);
+            session.notifyViewDisappeared(c2Id);
+            // Remove child1 grandchildren before and after
+            Log.d(TAG, "nofifying c1g1 disappeared: " + c1g1Id);
+            session.notifyViewDisappeared(c1g1Id);
+            Log.d(TAG, "nofifying c1 disappeared: " + c1Id);
+            session.notifyViewDisappeared(c1Id);
+            Log.d(TAG, "nofifying c1g2 disappeared: " + c1g2Id);
+            session.notifyViewDisappeared(c1g2Id);
+
+        });
+
+        final CustomViewActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        await(asyncLatch, "async onProvide");
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        assertRightActivity(session, session.id, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): check right number once we get rid of grandparents
+        assertThat(events.size()).isAtLeast(11);
+
+        // Assert just the relevant events
+        final AutofillId customViewId = activity.mCustomView.getAutofillId();
+        final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
+
+        assertViewWithUnknownParentAppeared(events, 0, session.id, activity.mCustomView);
+        // TODO(b/119638528): next 2 events are the grandparents
+        assertVirtualViewAppeared(events, 3, mainSession, customViewId, 1, "c1");
+        assertVirtualViewAppeared(events, 4, mainSession, customViewId, 11, "c1g1");
+        assertVirtualViewAppeared(events, 5, mainSession, customViewId, 12, "c1g2");
+        assertVirtualViewAppeared(events, 6, mainSession, customViewId, 2, "c2");
+        assertVirtualViewAppeared(events, 7, mainSession, customViewId, 21, "c2g1");
+        assertVirtualViewAppeared(events, 8, mainSession, customViewId, 211, "c2g1gg1");
+        assertVirtualViewAppeared(events, 9, mainSession, customViewId, 3, "c3");
+        assertVirtualViewsDisappeared(events, 10, customViewId, mainSession, 21, 2, 11, 1, 12);
+
+        // TODO(b/122315042): assert other views disappeared
+    }
+
+    // TODO(b/119638528): add tests for multiple sessions
+
+    @Test
+    public void testVirtualView_batchDisappear() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final CountDownLatch asyncLatch = setAsyncDelegate((customView, structure) -> {
+            Log.d(TAG, "delegate running on " + Thread.currentThread());
+            final AutofillId customViewId = customView.getAutofillId();
+            Log.d(TAG, "customViewId: " + customViewId);
+            final ContentCaptureSession session = customView.getContentCaptureSession();
+
+            final ViewStructure child1 = session.newVirtualViewStructure(customViewId, 1);
+            child1.setText("child1");
+            final AutofillId child1Id = child1.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 1)).isEqualTo(child1Id);
+            Log.d(TAG, "nofifying child1 appeared: " + child1Id);
+            session.notifyViewAppeared(child1);
+
+            final ViewStructure child2 = session.newVirtualViewStructure(customViewId, 2);
+            child2.setText("child2");
+            final AutofillId child2Id = child2.getAutofillId();
+            assertThat(session.newAutofillId(customViewId, 2)).isEqualTo(child2Id);
+            Log.d(TAG, "nofifying child2 appeared: " + child2Id);
+            session.notifyViewAppeared(child2);
+
+            final long[] childrenIds = {2, 1};
+            Log.d(TAG, "nofifying both children disappeared: " + Arrays.toString(childrenIds));
+            session.notifyViewsDisappeared(customViewId, childrenIds);
+        });
+
+        final CustomViewActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        await(asyncLatch, "async onProvide");
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        assertRightActivity(session, session.id, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): check right number once we get rid of grandparent (should be 5)
+        assertThat(events.size()).isAtLeast(6);
+
+        // Assert just the relevant events
+        final AutofillId customViewId = activity.mCustomView.getAutofillId();
+        final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
+
+        assertViewWithUnknownParentAppeared(events, 0, session.id, activity.mCustomView);
+        // TODO(b/119638528): next 2 events are the grandparents
+        assertVirtualViewAppeared(events, 3, mainSession, customViewId, 1, "child1");
+        assertVirtualViewAppeared(events, 4, mainSession, customViewId, 2, "child2");
+        assertVirtualViewsDisappeared(events, 5, customViewId, mainSession, 2, 1);
+
+        // TODO(b/122315042): assert other views disappeared
+    }
+
+    /**
+     * Sets a delegate that will generate the events asynchronously,
+     * after {@code onProvideContentCaptureStructure()} returns.
+     */
+    private CountDownLatch setAsyncDelegate(
+            @NonNull DoubleVisitor<CustomView, ViewStructure> delegate) {
+        final CountDownLatch asyncLatch = new CountDownLatch(1);
+        CustomViewActivity.setCustomViewDelegate(
+                (customView, structure) -> new Handler(Looper.getMainLooper())
+                        .post(() -> {
+                            delegate.visit(customView, structure);
+                            asyncLatch.countDown();
+                        }));
+        return asyncLatch;
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
new file mode 100644
index 0000000..9784213
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
@@ -0,0 +1,96 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.common.ShellHelper.runShellCommand;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper for common funcionalities.
+ */
+final class Helper {
+
+    public static final String TAG = "ContentCaptureTest";
+
+    public static final long GENERIC_TIMEOUT_MS = 10_000;
+
+    public static final String MY_PACKAGE = "android.contentcaptureservice.cts";
+
+    public static final long MY_EPOCH = SystemClock.uptimeMillis();
+
+    /**
+     * Awaits for a latch to be counted down.
+     */
+    public static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+            @Nullable Object... args)
+            throws InterruptedException {
+        final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        if (!called) {
+            throw new IllegalStateException(String.format(fmt, args)
+                    + " in " + GENERIC_TIMEOUT_MS + "ms");
+        }
+    }
+
+    /**
+     * Sets the content capture service.
+     */
+    public static void setService(@NonNull String service) {
+        Log.d(TAG, "Setting service to " + service);
+        // TODO(b/123540602): use @TestingAPI to get max duration constant
+        runShellCommand("cmd content_capture set temporary-service 0 " + service + " 12000");
+    }
+
+    /**
+     * Resets the content capture service.
+     */
+    public static void resetService() {
+        Log.d(TAG, "Resetting back to default service");
+        runShellCommand("cmd content_capture set temporary-service 0");
+    }
+
+    /**
+     * Gets the component name for a given class.
+     */
+    public static ComponentName componentNameFor(@NonNull Class<?> clazz) {
+        return new ComponentName(MY_PACKAGE, clazz.getName());
+    }
+
+    /**
+     * Creates a view that can be added to a parent and is important for content capture
+     */
+    public static TextView newImportantView(@NonNull Context context, @NonNull String text) {
+        final TextView child = new TextView(context);
+        child.setText(text);
+        child.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES);
+        return child;
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
new file mode 100644
index 0000000..1148875
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertSessionId;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+public class LoginActivity extends AbstractRootViewActivity {
+
+    private static final String TAG = LoginActivity.class.getSimpleName();
+
+    TextView mUsernameLabel;
+    EditText mUsername;
+    TextView mPasswordLabel;
+    EditText mPassword;
+
+    @Override
+    protected void setContentViewOnCreate(Bundle savedInstanceState) {
+        setContentView(R.layout.login_activity);
+
+        mUsernameLabel = findViewById(R.id.username_label);
+        mUsername = findViewById(R.id.username);
+        mPasswordLabel = findViewById(R.id.password_label);
+        mPassword = findViewById(R.id.password);
+    }
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        final LoginActivity activity = this;
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        // Sanity check
+        assertSessionId(sessionId, activity.mUsernameLabel);
+        assertSessionId(sessionId, activity.mUsername);
+        assertSessionId(sessionId, activity.mPassword);
+        assertSessionId(sessionId, activity.mPasswordLabel);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+        // TODO(b/119638528): ideally it should be 5 so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        final int minEvents = 7;
+        assertThat(events.size()).isAtLeast(minEvents);
+        assertViewAppeared(events, 0, sessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 1, sessionId, activity.mUsername, rootId);
+        assertViewAppeared(events, 2, sessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 3, sessionId, activity.mPassword, rootId);
+
+        // TODO(b/119638528): get rid of those intermediated parents
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+
+        assertViewAppeared(events, 4, sessionId, activity.getRootView(),
+                grandpa1.getAutofillId());
+        assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 6, grandpa2, decorView.getAutofillId());
+
+        assertViewsOptionallyDisappeared(events, minEvents,
+                rootId,
+                grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                // decorView.getAutofillId(), // TODO(b/122315042): figure out why it's not
+                // generated
+                activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId()
+        );
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.d(TAG, "AutofillIds: " + "usernameLabel=" + mUsernameLabel.getAutofillId()
+                + ", username=" + mUsername.getAutofillId()
+                + ", passwordLabel=" + mPasswordLabel.getAutofillId()
+                + ", password=" + mPassword.getAutofillId());
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
new file mode 100644
index 0000000..c189883
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -0,0 +1,671 @@
+/*
+ * 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.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertChildSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertMainSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertRightRelationship;
+import static android.contentcaptureservice.cts.Assertions.assertSessionId;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+import static android.contentcaptureservice.cts.Helper.MY_PACKAGE;
+import static android.contentcaptureservice.cts.Helper.componentNameFor;
+import static android.contentcaptureservice.cts.Helper.newImportantView;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.UserDataRemovalRequest;
+import android.view.contentcapture.UserDataRemovalRequest.UriRequest;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class LoginActivityTest extends AbstractContentCaptureIntegrationTest<LoginActivity> {
+
+    private static final String TAG = LoginActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<LoginActivity> sActivityRule = new ActivityTestRule<>(
+            LoginActivity.class, false, false);
+
+    public LoginActivityTest() {
+        super(LoginActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<LoginActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Before
+    @After
+    public void resetActivityStaticState() {
+        LoginActivity.onRootView(null);
+    }
+
+    @Test
+    public void testSimpleLifecycle_defaultSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @Test
+    public void testSimpleLifecycle_rootViewSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final Uri uri = Uri.parse("file://dev/null");
+        final Bundle bundle = new Bundle();
+        bundle.putString("DUDE", "SWEET");
+        final ContentCaptureContext clientContext = new ContentCaptureContext.Builder()
+                .setUri(uri).setExtras(bundle).build();
+
+        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);
+            Log.i(TAG, "Setting root view (" + rootView + ") session to " + childSession);
+            rootView.setContentCaptureSession(childSession);
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final ContentCaptureSessionId mainSessionId =
+                mainSessionRef.get().getContentCaptureSessionId();
+        final ContentCaptureSessionId childSessionId =
+                childSessionRef.get().getContentCaptureSessionId();
+        Log.v(TAG, "session ids: main=" + mainSessionId + ", child=" + childSessionId);
+
+        // Sanity checks
+        assertSessionId(childSessionId, activity.getRootView());
+        assertSessionId(childSessionId, activity.mUsernameLabel);
+        assertSessionId(childSessionId, activity.mUsername);
+        assertSessionId(childSessionId, activity.mPassword);
+        assertSessionId(childSessionId, activity.mPasswordLabel);
+
+        // Get the sessions
+        final Session mainSession = service.getFinishedSession(mainSessionId);
+        final Session childSession = service.getFinishedSession(childSessionId);
+
+        assertRightActivity(mainSession, mainSessionId, activity);
+        assertRightRelationship(mainSession, childSession);
+
+        // Sanity check
+        final List<ContentCaptureSessionId> allSessionIds = service.getAllSessionIds();
+        assertThat(allSessionIds).containsExactly(mainSessionId, childSessionId);
+
+        /*
+         *  Asserts main session
+         */
+
+        // Checks context
+        assertMainSessionContext(mainSession, activity);
+
+        // Check events
+        final List<ContentCaptureEvent> mainEvents = mainSession.getEvents();
+        Log.v(TAG, "events for main session: " + mainEvents);
+
+        // TODO(b/119638528): ideally it should be empty - right now it's generating events for 2
+        // intermediate parents (android:action_mode_bar_stub and android:content), we should try to
+        // create an activity without them
+        final int minMainEvents = 2;
+        assertThat(mainEvents.size()).isAtLeast(minMainEvents);
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+        assertViewAppeared(mainEvents, 0, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(mainEvents, 1, grandpa2, decorView.getAutofillId());
+        assertViewsOptionallyDisappeared(mainEvents, minMainEvents,
+                grandpa1.getAutofillId(), grandpa2.getAutofillId()
+                // decorView.getAutofillId(), // TODO(b/122315042): figure out why it's not
+                // generated
+        );
+
+        /*
+         *  Asserts child session
+         */
+
+        // Checks context
+        assertChildSessionContext(childSession, "file://dev/null");
+
+        final Bundle extras = childSession.context.getExtras();
+        assertThat(extras.keySet()).containsExactly("DUDE");
+        assertThat(extras.getString("DUDE")).isEqualTo("SWEET");
+
+        // Check events
+        final List<ContentCaptureEvent> childEvents = childSession.getEvents();
+        Log.v(TAG, "events for child session: " + childEvents);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        final int minChildEvents = 5;
+        assertThat(childEvents.size()).isAtLeast(minChildEvents);
+        assertViewAppeared(childEvents, 0, childSessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(childEvents, 1, childSessionId, activity.mUsername, rootId);
+        assertViewAppeared(childEvents, 2, childSessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(childEvents, 3, childSessionId, activity.mPassword, rootId);
+        assertViewAppeared(childEvents, 4, childSessionId, activity.getRootView(),
+                grandpa1.getAutofillId());
+        assertViewsOptionallyDisappeared(childEvents, minChildEvents,
+                rootId,
+                activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId()
+        );
+    }
+
+    @Ignore("not implemented yet, pending on b/122595322")
+    @Test
+    public void testSimpleLifecycle_serviceDisabledActivity() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        // Disable activity
+        service.setActivityContentCaptureEnabled(componentNameFor(LoginActivity.class), false);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).isEmpty();
+
+        // TODO(b/122595322): should also test events after re-enabling it
+    }
+
+    // TODO(b/122595322): same tests for disabled by package, explicity whitelisted, etc...
+
+    @Test
+    public void testTextChanged() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername
+                .setText("user"));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.syncRunOnUiThread(() -> {
+            activity.mUsername.setText("USER");
+            activity.mPassword.setText("PASS");
+        });
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        final int minEvents = 9;
+        assertThat(events.size()).isAtLeast(minEvents);
+
+        assertViewAppeared(events, 0, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 1, activity.mUsername, rootId, "user");
+        assertViewAppeared(events, 2, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 3, activity.mPassword, rootId, "");
+        // TODO(b/119638528): get rid of those intermediated parents
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+
+        assertViewAppeared(events, 4, activity.getRootView(), grandpa1.getAutofillId());
+        assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 6, grandpa2, decorView.getAutofillId());
+
+        assertViewTextChanged(events, 7, activity.mUsername.getAutofillId(), "USER");
+        assertViewTextChanged(events, 8, activity.mPassword.getAutofillId(), "PASS");
+
+        assertViewsOptionallyDisappeared(events, minEvents,
+                rootId,
+                grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                // decorView.getAutofillId(), // TODO(b/122315042): figure out why it's not
+                // generated
+                activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId()
+        );
+    }
+
+    @Test
+    public void testTextChangeBuffer() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername
+                .setText(""));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.syncRunOnUiThread(() -> {
+            activity.mUsername.setText("a");
+            activity.mUsername.setText("ab");
+
+            activity.mPassword.setText("d");
+            activity.mPassword.setText("de");
+
+            activity.mUsername.setText("abc");
+        });
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        final int minEvents = 10;
+        assertThat(events.size()).isAtLeast(minEvents);
+
+        assertViewAppeared(events, 0, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 1, activity.mUsername, rootId, "");
+        assertViewAppeared(events, 2, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 3, activity.mPassword, rootId, "");
+        // TODO(b/119638528): get rid of those intermediated parents
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+
+        assertViewAppeared(events, 4, activity.getRootView(), grandpa1.getAutofillId());
+        assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 6, grandpa2, decorView.getAutofillId());
+
+        assertViewTextChanged(events, 7, activity.mUsername.getAutofillId(), "ab");
+        assertViewTextChanged(events, 8, activity.mPassword.getAutofillId(), "de");
+        assertViewTextChanged(events, 9, activity.mUsername.getAutofillId(), "abc");
+
+        assertViewsOptionallyDisappeared(events, minEvents,
+                rootId,
+                grandpa1.getAutofillId(),
+                grandpa2.getAutofillId(),
+                activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId()
+        );
+    }
+
+    @Test
+    public void testDisabledByFlagSecure() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getWindow()
+                .addFlags(WindowManager.LayoutParams.FLAG_SECURE));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testDisabledByApp() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .setContentCaptureEnabled(false));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+
+        activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH"));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testDisabledFlagSecureAndByApp() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> {
+            activity.getContentCaptureManager().setContentCaptureEnabled(false);
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+        activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH"));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Ignore("not implemented yet, pending on b/122595322")
+    @Test
+    public void testDisabledByFlagSecureAndService() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getWindow()
+                .addFlags(WindowManager.LayoutParams.FLAG_SECURE));
+
+        // Disable activity
+        service.setActivityContentCaptureEnabled(componentNameFor(LoginActivity.class), false);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).isEmpty();
+    }
+
+    @Ignore("not implemented yet, pending on b/122595322")
+    @Test
+    public void testDisabledByAppAndAndService() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .setContentCaptureEnabled(false));
+
+        // Disable activity
+        service.setActivityContentCaptureEnabled(componentNameFor(LoginActivity.class), false);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_forEverything() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder().forEverything()
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isTrue();
+        assertThat(request.getUriRequests()).isNull();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_oneUri() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final Uri uri = Uri.parse("com.example");
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder()
+                        .addUri(uri, false)
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isFalse();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+
+        final List<UserDataRemovalRequest.UriRequest> requests = request.getUriRequests();
+        assertThat(requests.size()).isEqualTo(1);
+
+        final UriRequest actualRequest = requests.get(0);
+        assertThat(actualRequest.getUri()).isEqualTo(uri);
+        assertThat(actualRequest.isRecursive()).isFalse();
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_manyUris() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final Uri uri1 = Uri.parse("com.example");
+        final Uri uri2 = Uri.parse("com.example2");
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder()
+                        .addUri(uri1, false)
+                        .addUri(uri2, true)
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isFalse();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+
+        // felipeal: change it here so it checks URI getters
+        final List<UserDataRemovalRequest.UriRequest> requests = request.getUriRequests();
+        assertThat(requests.size()).isEqualTo(2);
+
+        final UriRequest actualRequest1 = requests.get(0);
+        assertThat(actualRequest1.getUri()).isEqualTo(uri1);
+        assertThat(actualRequest1.isRecursive()).isFalse();
+
+        final UriRequest actualRequest2 = requests.get(1);
+        assertThat(actualRequest2.getUri()).isEqualTo(uri2);
+        assertThat(actualRequest2.isRecursive()).isTrue();
+    }
+
+    @Test
+    public void testAddChildren_rightAway() throws Exception {
+        addChildrenTest(/* afterAnimation= */ false);
+    }
+
+    @Test
+    public void testAddChildren_afterAnimation() throws Exception {
+        addChildrenTest(/* afterAnimation= */ true);
+    }
+
+    private void addChildrenTest(boolean afterAnimation) throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+        final View[] children = new View[2];
+
+        final DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor = (activity,
+                rootView) -> {
+            final TextView child1 = newImportantView(activity, "c1");
+            children[0] = child1;
+            Log.v(TAG, "Adding child1(" + child1.getAutofillId() + "): " + child1);
+            rootView.addView(child1);
+            final TextView child2 = newImportantView(activity, "c1");
+            children[1] = child2;
+            Log.v(TAG, "Adding child2(" + child2.getAutofillId() + "): " + child2);
+            rootView.addView(child2);
+        };
+        if (afterAnimation) {
+            LoginActivity.onAnimationComplete(visitor);
+        } else {
+            LoginActivity.onRootView(visitor);
+        }
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        // Sanity check
+        assertSessionId(sessionId, activity.mUsernameLabel);
+        assertSessionId(sessionId, activity.mUsername);
+        assertSessionId(sessionId, activity.mPassword);
+        assertSessionId(sessionId, activity.mPasswordLabel);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events: " + events);
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        final int minEvents = 9; // TODO(b/123540067): get rid of those intermediated parents
+        assertThat(events.size()).isAtLeast(minEvents);
+        assertViewAppeared(events, 0, sessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 1, sessionId, activity.mUsername, rootId);
+        assertViewAppeared(events, 2, sessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 3, sessionId, activity.mPassword, rootId);
+        if (afterAnimation) {
+            // TODO(b/123540067): get rid of those intermediated parents
+            final View grandpa1 = activity.getGrandParent();
+            final View grandpa2 = activity.getGrandGrandParent();
+            final View decorView = activity.getDecorView();
+
+            assertViewAppeared(events, 4, sessionId, activity.getRootView(),
+                    grandpa1.getAutofillId());
+            assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId());
+            assertViewAppeared(events, 6, grandpa2, decorView.getAutofillId());
+            assertViewAppeared(events, 7, sessionId, children[0], rootId);
+            assertViewAppeared(events, 8, sessionId, children[1], rootId);
+        } else {
+            assertViewAppeared(events, 4, sessionId, children[0], rootId);
+            assertViewAppeared(events, 5, sessionId, children[1], rootId);
+        }
+    }
+
+    // TODO(b/119638528): add moar test cases for different sessions:
+    // - session1 on rootView, session2 on children
+    // - session1 on rootView, session2 on child1, session3 on child2
+    // - combination above where the CTS test explicitly finishes a session
+
+    // TODO(b/119638528): add moar test cases for different scenarios, like:
+    // - dynamically adding /
+    // - removing views
+    // - pausing / resuming activity
+    // - changing text
+    // - secure flag with child sessions
+    // - making sure events are flushed when activity pause / resume
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivitiesWatcher.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivitiesWatcher.java
new file mode 100644
index 0000000..38a6509
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivitiesWatcher.java
@@ -0,0 +1,206 @@
+/*
+ * 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.contentcaptureservice.cts.common;
+
+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/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivityLauncher.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivityLauncher.java
new file mode 100644
index 0000000..d844894
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ActivityLauncher.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.contentcaptureservice.cts.common;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helper used to launch an activity and watch for its lifecycle events.
+ */
+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);
+    }
+
+    @NonNull
+    public ActivityWatcher getWatcher() {
+        return mWatcher;
+    }
+
+    @NonNull
+    public A launchActivity() {
+        return mActivityTestRule.launchActivity(mLaunchIntent);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/DoubleVisitor.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/DoubleVisitor.java
new file mode 100644
index 0000000..467ac2a
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/DoubleVisitor.java
@@ -0,0 +1,33 @@
+/*
+ * 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.contentcaptureservice.cts.common;
+
+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/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/README.txt b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/README.txt
new file mode 100644
index 0000000..2cdbf75
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/README.txt
@@ -0,0 +1,2 @@
+This package contains utilities that are not tied to Content Capture and might eventually move to
+a common CTS package.
\ No newline at end of file
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java
new file mode 100644
index 0000000..a9b1481
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/ShellHelper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.contentcaptureservice.cts.common;
+
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Provides Shell-based utilities such as running a command.
+ */
+public final class ShellHelper {
+
+    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);
+        }
+    }
+
+    private ShellHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/Visitor.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/Visitor.java
new file mode 100644
index 0000000..2b5865f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/common/Visitor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.contentcaptureservice.cts.common;
+
+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/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index fed22df..bb1aa98 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS File System 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="instant_app" />
     <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 f352ef2..ed97400 100644
--- a/tests/fragment/AndroidTest.xml
+++ b/tests/fragment/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Configuration for app.usage Tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsFragmentTestCases.apk" />
diff --git a/tests/fragment/sdk26/AndroidTest.xml b/tests/fragment/sdk26/AndroidTest.xml
index 84fab15..16ffd27 100644
--- a/tests/fragment/sdk26/AndroidTest.xml
+++ b/tests/fragment/sdk26/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Configuration for app.usage Tests">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsFragmentTestCasesSdk26.apk" />
diff --git a/tests/framework/base/OWNERS b/tests/framework/base/OWNERS
new file mode 100644
index 0000000..a4fd3a5
--- /dev/null
+++ b/tests/framework/base/OWNERS
@@ -0,0 +1,9 @@
+# Windows & Activities
+ogunwale@google.com
+jjaggi@google.com
+racarr@google.com
+chaviw@google.com
+vishnun@google.com
+akulian@google.com
+roosa@google.com
+takaoka@google.com
diff --git a/tests/framework/base/activitymanager/Android.mk b/tests/framework/base/activitymanager/Android.mk
index 19cd50c..28f6753 100644
--- a/tests/framework/base/activitymanager/Android.mk
+++ b/tests/framework/base/activitymanager/Android.mk
@@ -34,15 +34,21 @@
     $(call all-named-files-under,Components.java, appThirdUid) \
     $(call all-named-files-under,Components.java, translucentapp) \
     $(call all-named-files-under,Components.java, translucentappsdk26) \
+    ../../../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    cts-amwm-util
+    cts-amwm-util \
+    CtsMockInputMethodLib \
+    metrics-helper-lib
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/intent_tests
 
 LOCAL_CTS_TEST_PACKAGE := android.server
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_CERTIFICATE := platform
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
old mode 100755
new mode 100644
index f442cdb6..707e77d
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -16,14 +16,14 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     package="android.server.cts.am">
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
-    <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.INJECT_EVENTS" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"/>
 
     <application android:label="CtsActivityManagerDeviceTestCases">
         <uses-library android:name="android.test.runner" />
@@ -54,6 +54,25 @@
             android:label="MaxAspectRatioUnsetActivity"
             android:resizeableActivity="false" />
 
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioActivity"
+            android:label="MinAspectRatioActivity"
+            android:minWidth="1dp"
+            android:minAspectRatio="3.0"
+            android:resizeableActivity="false" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioResizeableActivity"
+            android:label="MinAspectRatioResizeableActivity"
+            android:minWidth="1dp"
+            android:minAspectRatio="3.0"
+            android:resizeableActivity="true" />
+
+        <activity
+            android:name="android.server.am.AspectRatioTests$MinAspectRatioUnsetActivity"
+            android:label="MinAspectRatioUnsetActivity"
+            android:resizeableActivity="false" />
+
         <activity android:name="android.server.am.ActivityManagerTestBase$SideActivity"
                   android:resizeableActivity="true"
                   android:taskAffinity="nobody.but.SideActivity"/>
@@ -62,6 +81,8 @@
 
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SecondActivity"/>
 
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ThirdActivity"/>
+
         <activity
                 android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
                 android:theme="@android:style/Theme.Translucent.NoTitleBar" />
@@ -72,6 +93,11 @@
 
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$CallbackTrackingActivity"/>
 
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$TranslucentCallbackTrackingActivity"
+                  android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity" />
+
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$LaunchForResultActivity"/>
 
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ResultActivity"/>
@@ -82,8 +108,125 @@
         <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ConfigChangeHandlingActivity"
                   android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
 
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$PipActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:supportsPictureInPicture="true"/>
+
+        <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$AlwaysFocusablePipActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:resizeableActivity="false"
+                  android:supportsPictureInPicture="true"
+                  androidprv:alwaysFocusable="true"
+                  android:exported="true"/>
+
         <activity android:name="android.server.am.StartActivityTests$TestActivity2" />
 
+        <activity android:name="android.server.am.ActivityManagerMultiDisplayTests$ImeTestActivity" />
+        <activity android:name="android.server.am.ActivityManagerMultiDisplayTests$ImeTestActivity2" />
+        <activity android:name="android.server.am.ActivityManagerMultiDisplayTests$ImeTestActivityWithBrokenContextWrapper" />
+
+        <activity android:name="android.server.am.KeyguardLockedTests$ShowWhenLockedImeActivity" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$StandardActivity"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$SecondStandardActivity"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$StandardWithSingleTopActivity"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$SingleTopActivity"
+                  android:launchMode="singleTop"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$SingleInstanceActivity"
+                  android:launchMode="singleInstance"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$SingleTaskActivity"
+                  android:launchMode="singleTask"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.lifecycle.ActivityStarterTests$TestLaunchingActivity"
+                  android:taskAffinity="nobody.but.LaunchingActivity"
+                  android:exported="true" />
+
+        <activity android:name="android.server.am.ActivityViewTest$ActivityViewTestActivity"
+                  android:exported="true"/>
+
+        <provider
+            android:name="android.server.am.TestJournalProvider"
+            android:authorities="android.server.am.testjournalprovider"
+            android:exported="true" />
+
+        <!--intent tests-->
+        <activity android:name="android.server.am.intent.Activities$RegularActivity"/>
+        <activity
+            android:name="android.server.am.intent.Activities$SingleTopActivity"
+            android:launchMode="singleTop"/>
+        <activity
+            android:name="android.server.am.intent.Activities$SingleInstanceActivity"
+            android:launchMode="singleInstance"/>
+        <activity
+            android:name="android.server.am.intent.Activities$SingleInstanceActivity2"
+            android:launchMode="singleInstance"
+            android:taskAffinity=".t1"
+        />
+        <activity
+            android:name="android.server.am.intent.Activities$SingleTaskActivity"
+            android:launchMode="singleTask"
+        />
+        <activity
+            android:name="android.server.am.intent.Activities$SingleTaskActivity2"
+            android:launchMode="singleTask"
+            android:taskAffinity=".t1"
+        />
+        <activity
+            android:name="android.server.am.intent.Activities$TaskAffinity1Activity"
+            android:allowTaskReparenting="true"
+            android:launchMode="standard"
+            android:taskAffinity=".t1"/>
+        <activity
+            android:name="android.server.am.intent.Activities$TaskAffinity1Activity2"
+            android:allowTaskReparenting="true"
+            android:launchMode="standard"
+            android:taskAffinity=".t1"/>
+        <activity
+            android:name="android.server.am.intent.Activities$TaskAffinity2Activity"
+            android:allowTaskReparenting="true"
+            android:launchMode="standard"
+            android:taskAffinity=".t2"/>
+        <activity
+            android:name="android.server.am.intent.Activities$TaskAffinity3Activity"
+            android:allowTaskReparenting="true"
+            android:launchMode="standard"
+            android:taskAffinity=".t3"/>
+        <activity
+            android:name="android.server.am.intent.Activities$ClearTaskOnLaunchActivity"
+            android:allowTaskReparenting="true"
+            android:clearTaskOnLaunch="true"
+            android:launchMode="standard"
+            android:taskAffinity=".t2"/>
+        <activity
+            android:name="android.server.am.intent.Activities$DocumentLaunchIntoActivity"
+            android:documentLaunchMode="intoExisting"/>
+        <activity
+            android:name="android.server.am.intent.Activities$DocumentLaunchAlwaysActivity"
+            android:documentLaunchMode="always"/>
+        <activity
+            android:name="android.server.am.intent.Activities$DocumentLaunchNeverActivity"
+            android:documentLaunchMode="never"/>
+        <activity
+            android:name="android.server.am.intent.Activities$NoHistoryActivity"
+            android:noHistory="true"/>
+        <activity
+            android:name="android.server.am.intent.Activities$LauncherActivity"
+            android:documentLaunchMode="always"
+            android:launchMode="singleInstance">
+        </activity>
+
+
     </application>
 
     <instrumentation
diff --git a/tests/framework/base/activitymanager/AndroidTest.xml b/tests/framework/base/activitymanager/AndroidTest.xml
index 8a6c34f..9013061 100644
--- a/tests/framework/base/activitymanager/AndroidTest.xml
+++ b/tests/framework/base/activitymanager/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS ActivityManager test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- This module needs a permission not available to instant apps -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <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" />
@@ -30,6 +32,7 @@
         <option name="test-file-name" value="CtsDevicePrereleaseSdkApp.apk" />
         <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
         <option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- 120 seconds screen off timeout to prevent device sleep while long running test. -->
diff --git a/tests/framework/base/activitymanager/app/AndroidManifest.xml b/tests/framework/base/activitymanager/app/AndroidManifest.xml
index 2b91190..be557e0 100755
--- a/tests/framework/base/activitymanager/app/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/app/AndroidManifest.xml
@@ -47,6 +47,10 @@
                 android:resizeableActivity="true"
                 android:exported="true"
         />
+        <activity-alias android:name=".AliasTestActivity"
+                  android:exported="true"
+                  android:targetActivity=".TestActivity"
+        />
         <activity android:name=".ResumeWhilePausingActivity"
                 android:allowEmbedded="true"
                 android:resumeWhilePausing="true"
@@ -259,7 +263,7 @@
                   android:resizeableActivity="true"
                   android:exported="true"
                   android:taskAffinity="nobody.but.VirtualDisplayActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden"
         />
         <activity android:name=".ShowWhenLockedActivity"
                   android:exported="true"
@@ -323,6 +327,7 @@
             android:exported="true" >
             <intent-filter>
                 <action android:name="android.server.am.LAUNCH_BROADCAST_ACTION"/>
+                <action android:name="android.server.am.ACTION_TEST_ACTIVITY_START"/>
             </intent-filter>
         </receiver>
 
@@ -378,6 +383,27 @@
                   android:showWhenLocked="true"
                   android:exported="true" />
 
+        <activity android:name=".InheritShowWhenLockedAddActivity"
+            android:exported="true" />
+
+        <activity android:name=".InheritShowWhenLockedAttrActivity"
+                  android:inheritShowWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".InheritShowWhenLockedRemoveActivity"
+                  android:inheritShowWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".NoInheritShowWhenLockedAttrActivity"
+                  android:exported="true" />
+
+        <activity android:name=".ShowWhenLockedAttrImeActivity"
+                  android:showWhenLocked="true"
+                  android:exported="true" />
+
+        <activity android:name=".ToastActivity"
+                  android:exported="true"/>
+
         <activity android:name=".TurnScreenOnAttrActivity"
                   android:turnScreenOn="true"
                   android:exported="true" />
@@ -408,6 +434,51 @@
         <activity android:name=".RecursiveActivity"
                   android:exported="true"/>
 
+        <activity android:name=".LaunchTestOnDestroyActivity"
+                  android:exported="true"/>
+
+        <activity android:name=".ReportFullyDrawnActivity"
+                  android:exported="true"/>
+
+        <activity android:name=".NoDisplayActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.NoDisplay"/>
+
+        <activity android:name=".SingleTaskInstanceDisplayActivity"
+                  android:exported="true" />
+
+        <activity android:name=".SingleTaskInstanceDisplayActivity2"
+                  android:exported="true" />
+
+        <activity android:name=".SingleTaskInstanceDisplayActivity3"
+                  android:exported="true"
+                  android:launchMode="singleInstance" />
+
+        <!-- Disable home activities by default or it may disturb other tests by
+             showing ResolverActivity when start home activity -->
+        <activity-alias android:name=".HomeActivity"
+                        android:targetActivity=".TestActivity"
+                        android:enabled="false"
+                        android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name=".SingleHomeActivity"
+                        android:targetActivity=".SingleInstanceActivity"
+                        android:launchMode="singleInstance"
+                        android:enabled="false"
+                        android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity-alias>
+
         <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
                  android:exported="true"
                  android:enabled="true"
diff --git a/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml b/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
index d34b40a..cf39278 100644
--- a/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
+++ b/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
@@ -14,7 +14,9 @@
   ~ limitations under the License
   -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@+id/display_content"
+                android:orientation="vertical"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent">
-</FrameLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
index a148b32..cca7f29 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
@@ -16,8 +16,10 @@
 
 package android.server.am;
 
+import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
@@ -63,7 +65,10 @@
         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         view.setOnApplyWindowInsetsListener((v, insets) -> {
-            Log.i(getClass().getSimpleName(), "cutout=" + (insets.getDisplayCutout() != null));
+            final boolean cutoutExists = (insets.getDisplayCutout() != null);
+            Log.i(TAG, "cutout=" + cutoutExists);
+            TestJournalProvider.putExtras(BroadcastReceiverActivity.this,
+                    bundle -> bundle.putBoolean(EXTRA_CUTOUT_EXISTS, cutoutExists));
             return insets;
         });
         setContentView(view);
@@ -99,7 +104,8 @@
             }
             if (extras.getBoolean(EXTRA_DISMISS_KEYGUARD_METHOD)) {
                 getSystemService(KeyguardManager.class).requestDismissKeyguard(
-                        BroadcastReceiverActivity.this, new KeyguardDismissLoggerCallback(context));
+                        BroadcastReceiverActivity.this,
+                        new KeyguardDismissLoggerCallback(context, BROADCAST_RECEIVER_ACTIVITY));
             }
 
             ActivityLauncher.launchActivityFromExtras(BroadcastReceiverActivity.this, extras);
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/Components.java b/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
index a02fe85..e504766 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/Components.java
@@ -65,6 +65,7 @@
     public static final ComponentName MOVE_TASK_TO_BACK_ACTIVITY =
             component("MoveTaskToBackActivity");
     public static final ComponentName NIGHT_MODE_ACTIVITY = component("NightModeActivity");
+    public static final ComponentName NO_DISPLAY_ACTIVITY = component("NoDisplayActivity");
     public static final ComponentName NO_HISTORY_ACTIVITY = component("NoHistoryActivity");
     public static final ComponentName NO_RELAUNCH_ACTIVITY = component("NoRelaunchActivity");
     public static final ComponentName NON_RESIZEABLE_ACTIVITY = component("NonResizeableActivity");
@@ -76,15 +77,27 @@
     public static final ComponentName PORTRAIT_ORIENTATION_ACTIVITY =
             component("PortraitOrientationActivity");
     public static final ComponentName RECURSIVE_ACTIVITY = component("RecursiveActivity");
+    public static final ComponentName REPORT_FULLY_DRAWN_ACTIVITY =
+            component("ReportFullyDrawnActivity");
     public static final ComponentName RESIZEABLE_ACTIVITY = component("ResizeableActivity");
     public static final ComponentName RESUME_WHILE_PAUSING_ACTIVITY =
             component("ResumeWhilePausingActivity");
     public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY =
             component("ShowWhenLockedActivity");
+    public static final ComponentName SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY =
+            component("ShowWhenLockedAttrImeActivity");
     public static final ComponentName SHOW_WHEN_LOCKED_ATTR_ACTIVITY =
             component("ShowWhenLockedAttrActivity");
     public static final ComponentName SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY =
             component("ShowWhenLockedAttrRemoveAttrActivity");
+    public static final ComponentName INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY =
+            component("InheritShowWhenLockedAddActivity");
+    public static final ComponentName INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY =
+            component("InheritShowWhenLockedAttrActivity");
+    public static final ComponentName INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY =
+            component("InheritShowWhenLockedRemoveActivity");
+    public static final ComponentName NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY =
+            component("NoInheritShowWhenLockedAttrActivity");
     public static final ComponentName SHOW_WHEN_LOCKED_DIALOG_ACTIVITY =
             component("ShowWhenLockedDialogActivity");
     public static final ComponentName SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY =
@@ -93,11 +106,14 @@
             component("ShowWhenLockedWithDialogActivity");
     public static final ComponentName SINGLE_INSTANCE_ACTIVITY =
             component("SingleInstanceActivity");
+    public static final ComponentName HOME_ACTIVITY = component("HomeActivity");
+    public static final ComponentName SINGLE_HOME_ACTIVITY = component("SingleHomeActivity");
     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");
     public static final ComponentName TEST_ACTIVITY_WITH_SAME_AFFINITY =
             component("TestActivityWithSameAffinity");
@@ -130,14 +146,32 @@
             component("VirtualDisplayActivity");
     public static final ComponentName VR_TEST_ACTIVITY = component("VrTestActivity");
     public static final ComponentName WALLPAPAER_ACTIVITY = component("WallpaperActivity");
+    public static final ComponentName LAUNCH_TEST_ON_DESTROY_ACTIVITY = component(
+            "LaunchTestOnDestroyActivity");
+    public static final ComponentName ALIAS_TEST_ACTIVITY = component("AliasTestActivity");
 
     public static final ComponentName ASSISTANT_VOICE_INTERACTION_SERVICE =
             component("AssistantVoiceInteractionService");
 
     public static final ComponentName LAUNCH_BROADCAST_RECEIVER =
             component("LaunchBroadcastReceiver");
-    public static final String LAUNCH_BROADCAST_ACTION =
-            getPackageName() + ".LAUNCH_BROADCAST_ACTION";
+
+    public static class LaunchBroadcastReceiver {
+        public static final String LAUNCH_BROADCAST_ACTION =
+                getPackageName() + ".LAUNCH_BROADCAST_ACTION";
+
+        public static final String ACTION_TEST_ACTIVITY_START =
+                getPackageName() + ".ACTION_TEST_ACTIVITY_START";
+        public static final String EXTRA_COMPONENT_NAME = "component_name";
+        public static final String EXTRA_TARGET_DISPLAY = "target_display";
+    }
+
+    public static final ComponentName SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY =
+            component("SingleTaskInstanceDisplayActivity");
+    public static final ComponentName SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 =
+            component("SingleTaskInstanceDisplayActivity2");
+    public static final ComponentName SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 =
+            component("SingleTaskInstanceDisplayActivity3");
 
     /**
      * Action and extra key constants for {@link #TEST_ACTIVITY}.
@@ -151,6 +185,8 @@
                 "android.server.am.TestActivity.finish_self";
         // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
         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";
     }
 
     /**
@@ -195,6 +231,13 @@
         public static final String EXTRA_FINISH_BROADCAST = "finish";
         public static final String EXTRA_MOVE_BROADCAST_TO_BACK = "moveToBack";
         public static final String EXTRA_BROADCAST_ORIENTATION = "orientation";
+        public static final String EXTRA_CUTOUT_EXISTS = "cutoutExists";
+    }
+
+    /** Extra key constants for {@link android.server.am.FontScaleActivity}. */
+    public static class FontScaleActivity {
+        public static final String EXTRA_FONT_PIXEL_SIZE = "fontPixelSize";
+        public static final String EXTRA_FONT_ACTIVITY_DPI = "fontActivityDpi";
     }
 
     /**
@@ -277,8 +320,6 @@
         // The amount to delay to artificially introduce in onPause()
         // (before EXTRA_ENTER_PIP_ON_PAUSE is processed)
         public static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
-        // Calls enterPictureInPicture() again after onPictureInPictureModeChanged(false) is called
-        public static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
         // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
         public static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR =
                 "set_aspect_ratio_denominator";
@@ -328,6 +369,8 @@
         public static final String KEY_LAUNCH_TARGET_COMPONENT = "launch_target_component";
         public static final String KEY_PUBLIC_DISPLAY = "public_display";
         public static final String KEY_RESIZE_DISPLAY = "resize_display";
+        public static final String KEY_SHOW_SYSTEM_DECORATIONS = "show_system_decorations";
+        public static final String KEY_PRESENTATION_DISPLAY = "presentation_display";
         // Value constants of {@link #KEY_COMMAND}.
         public static final String COMMAND_CREATE_DISPLAY = "create_display";
         public static final String COMMAND_DESTROY_DISPLAY = "destroy_display";
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
index e19c4a1..e32bce2 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
@@ -16,6 +16,8 @@
 
 package android.server.am;
 
+import static android.server.am.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
+
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.os.Bundle;
@@ -26,6 +28,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
-                new KeyguardDismissLoggerCallback(this));
+                new KeyguardDismissLoggerCallback(this, DISMISS_KEYGUARD_METHOD_ACTIVITY));
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
index 7777d46..8fddca3 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
@@ -15,6 +15,9 @@
  */
 package android.server.am;
 
+import static android.server.am.Components.FontScaleActivity.EXTRA_FONT_ACTIVITY_DPI;
+import static android.server.am.Components.FontScaleActivity.EXTRA_FONT_PIXEL_SIZE;
+
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Bundle;
@@ -53,6 +56,8 @@
                 }
 
                 Log.i(getTag(), "fontPixelSize=" + fontPixelSize);
+                TestJournalProvider.putExtras(this,
+                        bundle -> bundle.putInt(EXTRA_FONT_PIXEL_SIZE, fontPixelSize));
             } finally {
                 ta.recycle();
             }
@@ -64,5 +69,7 @@
     protected void dumpActivityDpi() {
         final int fontActivityDpi = getResources().getDisplayMetrics().densityDpi;
         Log.i(getTag(), "fontActivityDpi=" + fontActivityDpi);
+        TestJournalProvider.putExtras(this,
+                bundle -> bundle.putInt(EXTRA_FONT_ACTIVITY_DPI, fontActivityDpi));
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAddActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAddActivity.java
new file mode 100644
index 0000000..26fdfa0
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAddActivity.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.server.am;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+
+public class InheritShowWhenLockedAddActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstance) {
+        super.onCreate(savedInstance);
+        setInheritShowWhenLocked(true);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAttrActivity.java
new file mode 100644
index 0000000..630055c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedAttrActivity.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 android.server.am;
+
+import android.Manifest;
+import android.app.Activity;
+
+public class InheritShowWhenLockedAttrActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedRemoveActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedRemoveActivity.java
new file mode 100644
index 0000000..22e65c3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/InheritShowWhenLockedRemoveActivity.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.server.am;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+
+public class InheritShowWhenLockedRemoveActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstance) {
+        super.onCreate(savedInstance);
+        setInheritShowWhenLocked(false);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
index 05e4fec..2b767c6 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
@@ -23,20 +23,24 @@
 
 import android.app.KeyguardManager;
 import android.app.KeyguardManager.KeyguardDismissCallback;
+import android.content.ComponentName;
 import android.content.Context;
 import android.util.Log;
 
 class KeyguardDismissLoggerCallback extends KeyguardDismissCallback {
 
     private final Context mContext;
+    private final ComponentName mOwnerName;
 
-    KeyguardDismissLoggerCallback(Context context) {
+    KeyguardDismissLoggerCallback(Context context, ComponentName name) {
         mContext = context;
+        mOwnerName = name;
     }
 
     @Override
     public void onDismissError() {
         Log.i(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_ERROR);
+        putCallbackResult(ENTRY_ON_DISMISS_ERROR);
     }
 
     @Override
@@ -48,11 +52,18 @@
                     "dismiss succeeded was called but device is still locked.");
         } else {
             Log.i(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_SUCCEEDED);
+            putCallbackResult(ENTRY_ON_DISMISS_SUCCEEDED);
         }
     }
 
     @Override
     public void onDismissCancelled() {
         Log.i(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_CANCELLED);
+        putCallbackResult(ENTRY_ON_DISMISS_CANCELLED);
+    }
+
+    private void putCallbackResult(String callbackName) {
+        TestJournalProvider.putExtras(mContext, mOwnerName,
+                extras -> extras.putBoolean(callbackName, true));
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
index 777beee..18d7873 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
@@ -23,13 +23,12 @@
     @Override
     protected void onResume() {
         super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
+        dumpConfigInfo();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
+        dumpConfigInfo();
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
index f3cb966..8c2d0b5 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
@@ -16,9 +16,16 @@
 
 package android.server.am;
 
+import static android.server.am.Components.LaunchBroadcastReceiver.ACTION_TEST_ACTIVITY_START;
+import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_COMPONENT_NAME;
+import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_TARGET_DISPLAY;
+import static android.server.am.Components.LaunchBroadcastReceiver.LAUNCH_BROADCAST_ACTION;
+
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.util.Log;
 
 /** Broadcast receiver that can launch activities. */
@@ -27,10 +34,24 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        try {
-            ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
-        } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
+        final Bundle extras = intent.getExtras();
+        Log.i(TAG, "onReceive: extras=" + extras);
+        if (extras == null) {
+            Log.e(TAG, "No extras received");
+            return;
+        }
+
+        if (intent.getAction().equals(LAUNCH_BROADCAST_ACTION)) {
+            try {
+                ActivityLauncher.launchActivityFromExtras(context, extras,
+                        CommandSession.handleForward(extras));
+            } catch (SecurityException e) {
+                ActivityLauncher.handleSecurityException(context, e);
+            }
+        } else if (intent.getAction().equals(ACTION_TEST_ACTIVITY_START)) {
+            final ComponentName componentName = extras.getParcelable(EXTRA_COMPONENT_NAME);
+            final int displayId = extras.getInt(EXTRA_TARGET_DISPLAY);
+            ActivityLauncher.checkActivityStartOnDisplay(context, displayId, componentName);
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchTestOnDestroyActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchTestOnDestroyActivity.java
new file mode 100644
index 0000000..8380a5d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchTestOnDestroyActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.server.am;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.Components.TEST_ACTIVITY;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTestOnDestroyActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Intent intent = new Intent();
+        intent.setComponent(TEST_ACTIVITY);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
index 5ed370e..4e57693 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.server.am.CommandSession.ActivityCallback;
 import android.util.Log;
 
 /**
@@ -32,5 +33,7 @@
         super.onConfigurationChanged(newConfig);
         Log.i(TAG, "Configuration changed: " + newConfig.screenWidthDp + ","
                 + newConfig.screenHeightDp);
+
+        TestJournalProvider.putActivityCallback(this, ActivityCallback.ON_CONFIGURATION_CHANGED);
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoDisplayActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoDisplayActivity.java
new file mode 100644
index 0000000..1256f28
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoDisplayActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class NoDisplayActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoInheritShowWhenLockedAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoInheritShowWhenLockedAttrActivity.java
new file mode 100644
index 0000000..eaeed61
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoInheritShowWhenLockedAttrActivity.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 android.server.am;
+
+import android.Manifest;
+import android.app.Activity;
+
+public class NoInheritShowWhenLockedAttrActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
index 144e053..48e5d68 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
@@ -32,7 +32,6 @@
 import static android.server.am.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
 import static android.server.am.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
 import static android.server.am.Components.PipActivity.EXTRA_PIP_ORIENTATION;
-import static android.server.am.Components.PipActivity.EXTRA_REENTER_PIP_ON_EXIT;
 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
@@ -121,6 +120,7 @@
             setShowWhenLocked(true);
         }
 
+        boolean enteringPip = false;
         // Enter picture in picture with the given aspect ratio if provided
         if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
             if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
@@ -131,12 +131,13 @@
                     builder.setAspectRatio(getAspectRatio(getIntent(),
                             EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
                             EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
-                    enterPictureInPictureMode(builder.build());
+                    enteringPip = enterPictureInPictureMode(builder.build());
                 } catch (Exception e) {
                     // This call can fail intentionally if the aspect ratio is too extreme
                 }
             } else {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+                enteringPip = enterPictureInPictureMode(
+                        new PictureInPictureParams.Builder().build());
             }
         }
 
@@ -180,10 +181,14 @@
         filter.addAction(ACTION_FINISH);
         registerReceiver(mReceiver, filter);
 
-        // Dump applied display metrics.
-        Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-        dumpConfiguration(config);
+        // Don't dump configuration when entering PIP to avoid the verifier getting the intermediate
+        // state. In this case it is expected that the verifier will check the changed configuration
+        // after onConfigurationChanged.
+        if (!enteringPip) {
+            // Dump applied display metrics.
+            dumpConfiguration(getResources().getConfiguration());
+            dumpConfigInfo();
+        }
     }
 
     @Override
@@ -245,22 +250,13 @@
         if (isInPictureInPictureMode) {
             mEnteredPictureInPicture = true;
         }
-
-        if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
-            // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
-            // checking that the stacks ever changed). Therefor, we need to delay here slightly to
-            // allow the tests to verify that the stacks have changed before re-entering.
-            mHandler.postDelayed(() -> {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
-            }, 1000);
-        }
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
         dumpConfiguration(newConfig);
+        dumpConfigInfo();
     }
 
     /**
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
index e322bc7..1fe7656 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
@@ -23,13 +23,12 @@
     @Override
     protected void onResume() {
         super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
+        dumpConfigInfo();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
+        dumpConfigInfo();
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ReportFullyDrawnActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ReportFullyDrawnActivity.java
new file mode 100644
index 0000000..147b00d9
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ReportFullyDrawnActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.am;
+
+import android.app.Activity;
+import android.os.Handler;
+
+public class ReportFullyDrawnActivity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        new Handler().postDelayed(this::reportFullyDrawn, 500 /* delayMillis */);
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
index fc0110c..f03ab1a 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
@@ -24,12 +24,12 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setContentView(R.layout.resizeable_activity);
-        dumpDisplaySize(getResources().getConfiguration());
+        dumpConfigInfo();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
+        dumpConfigInfo();
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrImeActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrImeActivity.java
new file mode 100644
index 0000000..7bae38b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrImeActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import android.os.Bundle;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+public class ShowWhenLockedAttrImeActivity extends AbstractLifecycleLogActivity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final EditText editText = new EditText(this);
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+        layout.addView(editText);
+        setContentView(layout);
+
+        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        editText.requestFocus();
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity.java
new file mode 100644
index 0000000..bb7c512
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.server.am;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class SingleTaskInstanceDisplayActivity extends AbstractLifecycleLogActivity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Start SingleTaskInstanceDisplayActivity2 in the same task as this activity.
+        startActivity(new Intent(this, SingleTaskInstanceDisplayActivity2.class));
+
+        // Start SingleTaskInstanceDisplayActivity3 (mark as singleInstance in manifest) in
+        // different task as this activity.
+        startActivity(new Intent(this, SingleTaskInstanceDisplayActivity3.class));
+    }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity2.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity2.java
new file mode 100644
index 0000000..0c03f09
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity2.java
@@ -0,0 +1,20 @@
+/*
+ * 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.server.am;
+
+public class SingleTaskInstanceDisplayActivity2 extends AbstractLifecycleLogActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity3.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity3.java
new file mode 100644
index 0000000..a5a7133
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskInstanceDisplayActivity3.java
@@ -0,0 +1,20 @@
+/*
+ * 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.server.am;
+
+public class SingleTaskInstanceDisplayActivity3 extends AbstractLifecycleLogActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ToastActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ToastActivity.java
new file mode 100644
index 0000000..389c263
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ToastActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class ToastActivity extends Activity {
+    private static final String TEST_TOAST_TEXT = "test toast";
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Toast.makeText(this, TEST_TOAST_TEXT, Toast.LENGTH_LONG).show();
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
index 7b72466..2da7617 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
@@ -16,6 +16,8 @@
 
 package android.server.am;
 
+import static android.server.am.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
+
 import android.app.KeyguardManager;
 import android.os.Bundle;
 
@@ -24,7 +26,8 @@
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        ((KeyguardManager) getSystemService(KEYGUARD_SERVICE))
-                .requestDismissKeyguard(this, new KeyguardDismissLoggerCallback(this));
+        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+                new KeyguardDismissLoggerCallback(this,
+                        TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY));
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
index 8f4b947..8313ee1 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
@@ -16,6 +16,8 @@
 
 package android.server.am;
 
+import static android.server.am.Components.TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY;
+
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.os.Bundle;
@@ -28,6 +30,6 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
         getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
-                new KeyguardDismissLoggerCallback(this));
+                new KeyguardDismissLoggerCallback(this, TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY));
     }
 }
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
index 8b1cdb1..71fed73 100644
--- a/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
@@ -17,6 +17,7 @@
 package android.server.am;
 
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.server.am.ActivityLauncher.KEY_DISPLAY_ID;
 import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
@@ -28,8 +29,10 @@
 import static android.server.am.Components.VirtualDisplayActivity.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_COMMAND;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_COUNT;
+import static android.server.am.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_LAUNCH_TARGET_COMPONENT;
+import static android.server.am.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
@@ -45,6 +48,7 @@
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 
 import java.util.HashMap;
 
@@ -128,10 +132,12 @@
         Log.d(TAG, "createVirtualDisplays. requested count:" + requestedCount);
 
         for (int displayCount = 0; displayCount < requestedCount; ++displayCount) {
-            final ViewGroup root = findViewById(android.R.id.content);
+            final ViewGroup root = findViewById(R.id.display_content);
             final SurfaceView surfaceView = new SurfaceView(this);
-            surfaceView.setLayoutParams(new ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+            surfaceView.setLayoutParams(
+                    // Using WRAP_CONTENT & layout weight to allow multiple virtual displays shown.
+                    new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+                            LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f));
             surfaceView.getHolder().addCallback(this);
             mPendingDisplayRequests.put(surfaceView.getHolder().getSurface(),
                     new VirtualDisplayRequest(surfaceView, extras));
@@ -185,9 +191,21 @@
             flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
         }
 
-        Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: "
-                + densityDpi + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
-                + ", publicDisplay=" + publicDisplay);
+        final boolean showSystemDecorations = entry.extras.getBoolean(KEY_SHOW_SYSTEM_DECORATIONS);
+        if (showSystemDecorations) {
+            flags |= 1 << 9; // VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+        }
+
+        final boolean presentationDisplay = entry.extras.getBoolean(KEY_PRESENTATION_DISPLAY);
+        if (presentationDisplay) {
+            flags |= VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+        }
+
+        Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: " + densityDpi
+                + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
+                + ", publicDisplay=" + publicDisplay + ", presentationDisplay="
+                + presentationDisplay);
+
         try {
             VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
                     VIRTUAL_DISPLAY_PREFIX + mVirtualDisplays.size(), width,
diff --git a/tests/framework/base/activitymanager/app27/AndroidManifest.xml b/tests/framework/base/activitymanager/app27/AndroidManifest.xml
index 3ed378e..31771a4 100755
--- a/tests/framework/base/activitymanager/app27/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/app27/AndroidManifest.xml
@@ -24,5 +24,16 @@
                   android:supportsPictureInPicture="true"
                   android:exported="true"
         />
+
+        <activity android:name="android.server.am.app27.HomeActivity"
+                        android:enabled="false"
+                        android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/framework/base/activitymanager/app27/src/android/server/am/app27/Components.java b/tests/framework/base/activitymanager/app27/src/android/server/am/app27/Components.java
index ec50cd6..f085aa9 100644
--- a/tests/framework/base/activitymanager/app27/src/android/server/am/app27/Components.java
+++ b/tests/framework/base/activitymanager/app27/src/android/server/am/app27/Components.java
@@ -23,4 +23,7 @@
 
     public static final ComponentName SDK_27_LAUNCHING_ACTIVITY =
             component(Components.class, "android.server.am.LaunchingActivity");
+
+    public static final ComponentName SDK_27_HOME_ACTIVITY =
+            component(Components.class, "android.server.am.app27.HomeActivity");
 }
diff --git a/tests/framework/base/activitymanager/app27/src/android/server/am/app27/HomeActivity.java b/tests/framework/base/activitymanager/app27/src/android/server/am/app27/HomeActivity.java
new file mode 100644
index 0000000..a01bca9
--- /dev/null
+++ b/tests/framework/base/activitymanager/app27/src/android/server/am/app27/HomeActivity.java
@@ -0,0 +1,24 @@
+/*
+ * 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.server.am.app27;
+
+
+import android.app.Activity;
+
+public class HomeActivity extends Activity {
+
+}
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
index 4d4e98d..cb84628 100644
--- a/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
@@ -20,6 +20,10 @@
 
     <application>
         <activity
+            android:name=".EmbeddingActivity"
+            android:resizeableActivity="true"
+            android:exported="true" />
+        <activity
             android:name=".SecondActivity"
             android:resizeableActivity="true"
             android:allowEmbedded="true"
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java
index 264b83c..ccd4b67 100644
--- a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/Components.java
@@ -21,6 +21,15 @@
 
 public class Components extends ComponentsBase {
 
+    public static final ComponentName EMBEDDING_ACTIVITY = component("EmbeddingActivity");
+
+    public static class EmbeddingActivity {
+        public static final String ACTION_EMBEDDING_TEST_ACTIVITY_START =
+                "broadcast_test_activity_start";
+        public static final String EXTRA_EMBEDDING_COMPONENT_NAME = "component_name";
+        public static final String EXTRA_EMBEDDING_TARGET_DISPLAY = "target_display";
+    }
+
     public static final ComponentName SECOND_ACTIVITY = component("SecondActivity");
     public static final ComponentName SECOND_NO_EMBEDDING_ACTIVITY =
             component("SecondActivityNoEmbedding");
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/EmbeddingActivity.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/EmbeddingActivity.java
new file mode 100644
index 0000000..69a4924
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/EmbeddingActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.am.second;
+
+import static android.server.am.second.Components.EmbeddingActivity.ACTION_EMBEDDING_TEST_ACTIVITY_START;
+import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_COMPONENT_NAME;
+import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_TARGET_DISPLAY;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.server.am.ActivityLauncher;
+import android.util.Log;
+
+public class EmbeddingActivity extends Activity {
+    private static final String TAG = EmbeddingActivity.class.getSimpleName();
+
+    private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        IntentFilter broadcastFilter = new IntentFilter(ACTION_EMBEDDING_TEST_ACTIVITY_START);
+        registerReceiver(mBroadcastReceiver, broadcastFilter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    public class TestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final Bundle extras = intent.getExtras();
+            Log.i(TAG, "onReceive: extras=" + extras);
+
+            if (extras == null) {
+                return;
+            }
+
+            final ComponentName componentName =
+                    extras.getParcelable(EXTRA_EMBEDDING_COMPONENT_NAME);
+            final int displayId = extras.getInt(EXTRA_EMBEDDING_TARGET_DISPLAY);
+
+            ActivityLauncher.checkActivityStartOnDisplay(EmbeddingActivity.this, displayId,
+                    componentName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
index 1b975ac..c306a3c 100644
--- a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
@@ -31,7 +31,7 @@
         try {
             ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
         } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
+            ActivityLauncher.handleSecurityException(context, e);
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/app_base/Android.mk b/tests/framework/base/activitymanager/app_base/Android.mk
index 99a584c..0342724 100644
--- a/tests/framework/base/activitymanager/app_base/Android.mk
+++ b/tests/framework/base/activitymanager/app_base/Android.mk
@@ -16,6 +16,6 @@
 
 LOCAL_MODULE := cts-am-app-base
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
index 63e801e..59ab274 100644
--- a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
@@ -16,16 +16,15 @@
 
 package android.server.am;
 
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
+import static android.server.am.Components.TestActivity.EXTRA_CONFIGURATION;
 
-public abstract class AbstractLifecycleLogActivity extends Activity {
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.server.am.CommandSession.BasicTestActivity;
+import android.server.am.CommandSession.ConfigInfo;
+import android.util.Log;
+
+public abstract class AbstractLifecycleLogActivity extends BasicTestActivity {
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -35,7 +34,7 @@
 
     @Override
     protected void onStart() {
-        super.onResume();
+        super.onStart();
         Log.i(getTag(), "onStart");
     }
 
@@ -94,28 +93,21 @@
 
     protected void dumpConfiguration(Configuration config) {
         Log.i(getTag(), "Configuration: " + config);
+        withTestJournalClient(client -> {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(EXTRA_CONFIGURATION, config);
+            client.putExtras(extras);
+        });
     }
 
-    protected void dumpDisplaySize(Configuration config) {
-        // Dump the display size as seen by this Activity.
-        final WindowManager wm = getSystemService(WindowManager.class);
-        final Display display = wm.getDefaultDisplay();
-        final Point point = new Point();
-        display.getSize(point);
-        final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
-        final String line = "config"
-                + " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp)
-                + " displaySize=" + buildCoordString(point.x, point.y)
-                + " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels)
-                + " smallestScreenWidth=" + config.smallestScreenWidthDp
-                + " densityDpi=" + config.densityDpi
-                + " orientation=" + config.orientation;
-
-        Log.i(getTag(), line);
-    }
-
-    protected static String buildCoordString(int x, int y) {
-        return "(" + x + "," + y + ")";
+    protected void dumpConfigInfo() {
+        // Here dumps when idle because the {@link ConfigInfo} includes some information (display
+        // related) got from the attached decor view (after resume). Also if there are several
+        // incoming lifecycle callbacks in a short time, it prefers to dump in a stable state.
+        runWhenIdle(() -> withTestJournalClient(client -> {
+            final ConfigInfo configInfo = getConfigInfo();
+            Log.i(getTag(), configInfo.toString());
+            client.setLastConfigInfo(configInfo);
+        }));
     }
 }
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.java
index 1eadf61..ee3e315 100644
--- a/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.java
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.java
@@ -16,9 +16,9 @@
 
 package android.server.am;
 
-import android.os.Bundle;
 import android.app.Activity;
 import android.content.Intent;
+import android.os.Bundle;
 
 /**
  * Activity that launches another activities when new intent is received.
@@ -30,14 +30,19 @@
 
         final Intent intent = getIntent();
         if (savedInstanceState == null && intent != null) {
-            ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
+            launchActivityFromExtras(intent.getExtras());
         }
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
-        ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
+        launchActivityFromExtras(intent.getExtras());
+    }
+
+    private void launchActivityFromExtras(Bundle extras) {
+        ActivityLauncher.launchActivityFromExtras(
+                this, extras, CommandSession.handleForward(extras));
     }
 }
 
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
index 1dbc81d..b5d4945 100644
--- a/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
@@ -16,8 +16,9 @@
 
 package android.server.am;
 
-import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.am.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
 import static android.server.am.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
+import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -57,9 +58,10 @@
     @Override
     protected void onResume() {
         super.onResume();
-        final Configuration config = getResources().getConfiguration();
-        dumpDisplaySize(config);
-        dumpConfiguration(config);
+        final Configuration configuration = getResources().getConfiguration();
+        dumpConfiguration(configuration);
+        dumpAssetSeqNumber(configuration);
+        dumpConfigInfo();
     }
 
     @Override
@@ -71,7 +73,16 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        dumpDisplaySize(newConfig);
         dumpConfiguration(newConfig);
+        dumpAssetSeqNumber(newConfig);
+        dumpConfigInfo();
+    }
+
+    private void dumpAssetSeqNumber(Configuration newConfig) {
+        withTestJournalClient(client -> {
+            final Bundle extras = new Bundle();
+            extras.putInt(EXTRA_CONFIG_ASSETS_SEQ, newConfig.assetsSeq);
+            client.putExtras(extras);
+        });
     }
 }
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-1.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-1.json
new file mode 100644
index 0000000..a474887
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-1.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-2.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-2.json
new file mode 100644
index 0000000..26d5a9c
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-2.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-3.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-3.json
new file mode 100644
index 0000000..8e7fdf1
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-3.json
@@ -0,0 +1,64 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-4.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-4.json
new file mode 100644
index 0000000..96e6d50
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-4.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-5.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-5.json
new file mode 100644
index 0000000..185762f
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-5.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_PREVIOUS_IS_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/clearCases/test-6.json b/tests/framework/base/activitymanager/intent_tests/clearCases/test-6.json
new file mode 100644
index 0000000..9e70f30
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/clearCases/test-6.json
@@ -0,0 +1,78 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-1.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-1.json
new file mode 100644
index 0000000..bd1e268
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-1.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_SINGLE_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-2.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-2.json
new file mode 100644
index 0000000..eddebc3
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-2.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_SINGLE_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-3.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-3.json
new file mode 100644
index 0000000..7340ace
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-3.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-4.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-4.json
new file mode 100644
index 0000000..feac3aa
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-4.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-5.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-5.json
new file mode 100644
index 0000000..a474887
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-5.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-6.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-6.json
new file mode 100644
index 0000000..fb393eb
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-6.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TOP",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-7.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-7.json
new file mode 100644
index 0000000..20e3d56
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-7.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-8.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-8.json
new file mode 100644
index 0000000..dfa0aa7
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-8.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/forResult/test-9.json b/tests/framework/base/activitymanager/intent_tests/forResult/test-9.json
new file mode 100644
index 0000000..c88c21d
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/forResult/test-9.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-1.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-1.json
new file mode 100644
index 0000000..330d7f9
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-1.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-10.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-10.json
new file mode 100644
index 0000000..cb92240
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-10.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-11.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-11.json
new file mode 100644
index 0000000..fac176d
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-11.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-12.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-12.json
new file mode 100644
index 0000000..82fe0fd
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-12.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTaskActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$SingleTaskActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-2.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-2.json
new file mode 100644
index 0000000..714457e
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-2.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-3.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-3.json
new file mode 100644
index 0000000..978f3fe
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-3.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-4.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-4.json
new file mode 100644
index 0000000..00ca419
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-4.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-5.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-5.json
new file mode 100644
index 0000000..1c7b18b
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-5.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-6.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-6.json
new file mode 100644
index 0000000..619d6c2
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-6.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-7.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-7.json
new file mode 100644
index 0000000..268b07a
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-7.json
@@ -0,0 +1,98 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-8.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-8.json
new file mode 100644
index 0000000..5b897d5
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-8.json
@@ -0,0 +1,104 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-9.json b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-9.json
new file mode 100644
index 0000000..0d6973e
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newDocumentCases/test-9.json
@@ -0,0 +1,116 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity2",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-1.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-1.json
new file mode 100644
index 0000000..330d7f9
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-1.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-10.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-10.json
new file mode 100644
index 0000000..4d7984c
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-10.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-11.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-11.json
new file mode 100644
index 0000000..629a371
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-11.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-12.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-12.json
new file mode 100644
index 0000000..5c57171
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-12.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTaskActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$SingleTaskActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-13.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-13.json
new file mode 100644
index 0000000..4b330fb
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-13.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-14.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-14.json
new file mode 100644
index 0000000..c944103
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-14.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTaskActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTaskActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-15.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-15.json
new file mode 100644
index 0000000..03140d2
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-15.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-16.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-16.json
new file mode 100644
index 0000000..0f4a845
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-16.json
@@ -0,0 +1,84 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-2.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-2.json
new file mode 100644
index 0000000..f83daac
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-2.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleInstanceActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-3.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-3.json
new file mode 100644
index 0000000..978f3fe
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-3.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-4.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-4.json
new file mode 100644
index 0000000..e5edf6b
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-4.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-5.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-5.json
new file mode 100644
index 0000000..95a867c
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-5.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-6.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-6.json
new file mode 100644
index 0000000..d4052f4
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-6.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$SingleTopActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$SingleTopActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-7.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-7.json
new file mode 100644
index 0000000..e3a826f
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-7.json
@@ -0,0 +1,98 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-8.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-8.json
new file mode 100644
index 0000000..6760b49
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-8.json
@@ -0,0 +1,104 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/newTask/test-9.json b/tests/framework/base/activitymanager/intent_tests/newTask/test-9.json
new file mode 100644
index 0000000..7f2e4fc
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/newTask/test-9.json
@@ -0,0 +1,108 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity2",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-1.json b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-1.json
new file mode 100644
index 0000000..5aff52d
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-1.json
@@ -0,0 +1,72 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    },
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-2.json b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-2.json
new file mode 100644
index 0000000..7da81f0
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-2.json
@@ -0,0 +1,68 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": true
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-3.json b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-3.json
new file mode 100644
index 0000000..722814f
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-3.json
@@ -0,0 +1,102 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    },
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-4.json b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-4.json
new file mode 100644
index 0000000..126c753
--- /dev/null
+++ b/tests/framework/base/activitymanager/intent_tests/resetTaskIfNeeded/test-4.json
@@ -0,0 +1,86 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.am.intent.Activities$TaskAffinity1Activity2",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.am.intent.Activities$RegularActivity",
+                "package": "android.server.cts.am",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    },
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity2",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.cts.am\/android.server.am.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.cts.am\/android.server.am.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
index 71e2ee9..7d32409 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
@@ -17,60 +17,22 @@
 package android.server.am;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.server.am.ComponentNameUtils.getLogTag;
 import static android.server.am.Components.LOG_CONFIGURATION_ACTIVITY;
-import static android.server.am.StateLogger.log;
-import static android.server.am.StateLogger.logAlways;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import android.content.ComponentName;
-import android.os.SystemClock;
+import android.server.am.CommandSession.ActivityCallback;
 
 import org.junit.Test;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:ActivityAndWindowManagerOverrideConfigTests
  */
 public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
 
-    private static class ConfigurationChangeObserver {
-        private static final Pattern CONFIGURATION_CHANGED_PATTERN =
-            Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
-
-        private ConfigurationChangeObserver() {
-        }
-
-        private boolean findConfigurationChange(
-                ComponentName activityName, LogSeparator logSeparator) {
-            for (int retry = 1; retry <= 5; retry++) {
-                final String[] lines =
-                        getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
-                log("Looking at logcat");
-                for (int i = lines.length - 1; i >= 0; i--) {
-                    final String line = lines[i].trim();
-                    log(line);
-                    Matcher matcher = CONFIGURATION_CHANGED_PATTERN.matcher(line);
-                    if (matcher.matches()) {
-                        return true;
-                    }
-                }
-                logAlways("***Waiting configuration change of " + getLogTag(activityName)
-                        + " retry=" + retry);
-                SystemClock.sleep(500);
-            }
-            return false;
-        }
-    }
-
     @Test
     public void testReceiveOverrideConfigFromRelayout() throws Exception {
         assumeTrue("Device doesn't support freeform. Skipping test.", supportsFreeform());
@@ -79,20 +41,17 @@
 
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             resizeActivityTask(LOG_CONFIGURATION_ACTIVITY, 0, 0, 100, 100);
-            ConfigurationChangeObserver c = new ConfigurationChangeObserver();
-            final boolean reportedSizeAfterResize = c.findConfigurationChange(
-                    LOG_CONFIGURATION_ACTIVITY, logSeparator);
-            assertTrue("Expected to observe configuration change when resizing",
-                    reportedSizeAfterResize);
+            new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
+                    "Expected to observe configuration change when resizing",
+                    countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1));
 
-            logSeparator = separateLogs();
+            separateTestJournal();
             rotationSession.set(ROTATION_180);
-            final boolean reportedSizeAfterRotation = c.findConfigurationChange(
-                    LOG_CONFIGURATION_ACTIVITY, logSeparator);
-            assertFalse("Not expected to observe configuration change after flip rotation",
-                    reportedSizeAfterRotation);
+            new ActivityLifecycleCounts(LOG_CONFIGURATION_ACTIVITY).assertCountWithRetry(
+                    "Not expected to observe configuration change after flip rotation",
+                    countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
index 1ea220a..a696f58 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 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;
@@ -26,12 +26,9 @@
 import static android.server.am.ActivityManagerState.STATE_PAUSED;
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ActivityManagerState.STATE_STOPPED;
-import static android.server.am.ComponentNameUtils.getActivityName;
 import static android.server.am.Components.ALT_LAUNCHING_ACTIVITY;
 import static android.server.am.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
-import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.am.Components.DOCKED_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
 import static android.server.am.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
@@ -50,13 +47,16 @@
 import static android.server.am.Components.TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY;
 import static android.server.am.Components.TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY;
 import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.VirtualDisplayHelper.waitForDefaultDisplayState;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 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.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -72,6 +72,7 @@
 
     @Presubmit
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
         if (!supportsPip()) {
             return;
@@ -85,7 +86,7 @@
                 LAUNCH_PIP_ON_PIP_ACTIVITY);
 
         assertNotEquals(stackId, INVALID_STACK_ID);
-        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+        moveTopActivityToPinnedStack(stackId);
         mAmWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_PINNED)
@@ -136,7 +137,7 @@
                 ALWAYS_FOCUSABLE_PIP_ACTIVITY);
 
         assertNotEquals(stackId, INVALID_STACK_ID);
-        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+        moveTopActivityToPinnedStack(stackId);
         mAmWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_PINNED)
@@ -175,18 +176,20 @@
 
     @Presubmit
     @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());
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
     @Presubmit
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testFinishActivityInNonFocusedStack() throws Exception {
         if (!supportsSplitScreenMultiWindow()) {
             // Skipping test: no multi-window support
@@ -204,14 +207,14 @@
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
         // Finish activity in non-focused (docked) stack.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_PAUSED);
         mAmWmState.waitForAllExitingWindows();
 
         mAmWmState.computeState(LAUNCHING_ACTIVITY);
         mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
+        mAmWmState.assertNotExist(BROADCAST_RECEIVER_ACTIVITY);
     }
 
     @Test
@@ -244,7 +247,7 @@
         mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
 
         // Finish the top-most activity.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
         //TODO: BUG: MoveTaskToBackActivity returns to the top of the stack when
         // BroadcastActivity finishes, so homeActivity is not visible afterwards
 
@@ -367,11 +370,11 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.disableLockScreen()
                     .sleepDevice();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
-            assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY);
         }
     }
 
@@ -384,12 +387,12 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential()
                     .sleepDevice();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivityNoWait(TURN_SCREEN_ON_ATTR_ACTIVITY);
             // Wait for the activity stopped because lock screen prevent showing the activity.
             mAmWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_ACTIVITY, STATE_STOPPED);
-            assertFalse("Display keeps off", isDisplayOn());
-            assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY, logSeparator);
+            assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY);
         }
     }
 
@@ -398,11 +401,11 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.sleepDevice();
             mAmWmState.waitForAllStoppedActivities();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
-            assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, logSeparator);
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
         }
     }
 
@@ -411,19 +414,19 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.sleepDevice();
             mAmWmState.waitForAllStoppedActivities();
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
-            assertTrue("Display turns on", isDisplayOn());
-            assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
 
             lockScreenSession.sleepDevice();
             mAmWmState.waitForAllStoppedActivities();
-            logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
             // Display should keep off, because setTurnScreenOn(false) has been called at
             // {@link TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY}'s onStop.
-            assertFalse("Display keeps off", isDisplayOn());
-            assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+            assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY);
         }
     }
 
@@ -432,18 +435,25 @@
     public void testTurnScreenOnSingleTask() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.sleepDevice();
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
-            assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, logSeparator);
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
 
             lockScreenSession.sleepDevice();
-            logSeparator = separateLogs();
+            // We should make sure test activity stopped to prevent a false alarm stop state
+            // included in the lifecycle count.
+            waitAndAssertActivityState(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, STATE_STOPPED,
+                    "Activity should be stopped");
+            separateTestJournal();
             launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn());
-            assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY, logSeparator);
+            // Wait more for display state change since turning the display ON may take longer
+            // and reported after the activity launch.
+            waitForDefaultDisplayState(true /* wantOn */);
+            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+            assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY);
         }
     }
 
@@ -455,13 +465,63 @@
             mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
 
             lockScreenSession.sleepDevice();
-            mAmWmState.waitForActivityState(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, STATE_STOPPED);
-
-            // Ensure there was an actual stop if the waitFor timed out.
-            assertTrue(getActivityName(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY) + " stopped",
-                    mAmWmState.getAmState().hasActivityState(
-                            TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, STATE_STOPPED));
-            assertFalse("Display keeps off", isDisplayOn());
+            waitAndAssertActivityState(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, STATE_STOPPED,
+                    "Activity should be stopped");
+            assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
         }
     }
+
+    @Test
+    public void testGoingHomeMultipleTimes() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            // Start activity normally
+            launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity launched on default display must be focused");
+
+            // Press home button
+            launchHomeActivity();
+
+            mAmWmState.assertHomeActivityVisible(true);
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                    "Activity should become STOPPED");
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testPressingHomeButtonMultipleTimes() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            // Start activity normally
+            launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity launched on default display must be focused");
+
+            // Press home button
+            pressHomeButton();
+
+            // Wait and assert home and activity states
+            mAmWmState.waitForHomeActivityVisible();
+            mAmWmState.assertHomeActivityVisible(true);
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                    "Activity should become STOPPED");
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testPressingHomeButtonMultipleTimesQuick() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            // Start activity normally
+            launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
+
+            // Press home button
+            pressHomeButton();
+            mAmWmState.waitForHomeActivityVisible();
+            mAmWmState.assertHomeActivityVisible(true);
+        }
+        waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                "Activity should become STOPPED");
+        mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
index 4f3cdff..396e130 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -26,9 +26,6 @@
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ComponentNameUtils.getWindowName;
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
-import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
 import static android.server.am.Components.DIALOG_WHEN_LARGE_ACTIVITY;
 import static android.server.am.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
@@ -36,7 +33,6 @@
 import static android.server.am.Components.PORTRAIT_ORIENTATION_ACTIVITY;
 import static android.server.am.Components.RESIZEABLE_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
-import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logE;
 import static android.server.am.translucentapp.Components.TRANSLUCENT_LANDSCAPE_ACTIVITY;
 import static android.server.am.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
@@ -58,8 +54,10 @@
 import static com.android.compatibility.common.util.PackageUtil.supportsRotation;
 
 import android.content.ComponentName;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.server.am.CommandSession.SizeInfo;
 import android.support.test.filters.FlakyTest;
 
 import org.junit.Ignore;
@@ -73,16 +71,6 @@
  */
 public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
 
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to move {@link #BROADCAST_RECEIVER_ACTIVITY} task to back.
-    private static final String MOVE_TASK_TO_BACK_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_MOVE_BROADCAST_TO_BACK + " true";
-    // Shell command to request portrait orientation to {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String REQUEST_PORTRAIT_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ei " + EXTRA_BROADCAST_ORIENTATION + " 1";
-    private static final String REQUEST_LANDSCAPE_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ei " + EXTRA_BROADCAST_ORIENTATION + " 0";
-
     private static final int SMALL_WIDTH_DP = 426;
     private static final int SMALL_HEIGHT_DP = 320;
 
@@ -99,15 +87,13 @@
     public void testConfigurationUpdatesWhenResizedFromFullscreen() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                logSeparator);
+        final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                logSeparator);
+        final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
         assertSizesAreSane(fullscreenSizes, dockedSizes);
     }
@@ -122,15 +108,13 @@
     public void testConfigurationUpdatesWhenResizedFromDockedStack() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                logSeparator);
+        final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                logSeparator);
+        final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
         assertSizesAreSane(fullscreenSizes, dockedSizes);
     }
@@ -145,11 +129,10 @@
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(RESIZEABLE_ACTIVITY,
                     WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                    logSeparator);
+            final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
             rotateAndCheckSizes(rotationSession, initialSizes);
         }
@@ -161,13 +144,14 @@
      */
     @Presubmit
     @Test
+    @FlakyTest(bugId = 121165130)
     public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             // Launch our own activity to side in case Recents (or other activity to side) doesn't
             // support rotation.
             launchActivitiesInSplitScreen(
@@ -175,8 +159,7 @@
                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
             // Launch target activity in docked stack.
             getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
-            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                    logSeparator);
+            final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
             rotateAndCheckSizes(rotationSession, initialSizes);
         }
@@ -188,24 +171,24 @@
      */
     @Presubmit
     @Test
+    @FlakyTest(bugId = 121165130)
     public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivitiesInSplitScreen(
                     getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                     getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
-            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                    logSeparator);
+            final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
 
             rotateAndCheckSizes(rotationSession, initialSizes);
         }
     }
 
-    private void rotateAndCheckSizes(RotationSession rotationSession, ReportedSizes prevSizes)
+    private void rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes)
             throws Exception {
         final ActivityManagerState.ActivityTask task =
                 mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY);
@@ -215,7 +198,7 @@
 
         final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
         for (final int rotation : rotations) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             rotationSession.set(rotation);
             final int newDeviceRotation = getDeviceRotation(displayId);
             if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
@@ -223,8 +206,7 @@
                         + "Continuing the test despite of that, but it is likely to fail.");
             }
 
-            final ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
-                    logSeparator);
+            final SizeInfo rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
             assertSizesRotate(prevSizes, rotatedSizes,
                     // Skip orientation checks if we are not in fullscreen mode.
                     task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN);
@@ -261,16 +243,15 @@
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         // Launch to fullscreen stack and record size.
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
-                logSeparator);
+        final SizeInfo initialFullscreenSizes = getActivityDisplaySize(activityName);
         final Rect displayRect = getDisplayRect(activityName);
 
         // Move to docked stack.
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
+        final SizeInfo dockedSizes = getActivityDisplaySize(activityName);
         assertSizesAreSane(initialFullscreenSizes, dockedSizes);
         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
         // will come up.
@@ -282,14 +263,14 @@
 
         // Resize docked stack to fullscreen size. This will trigger activity relaunch with
         // non-empty override configuration corresponding to fullscreen size.
-        logSeparator = separateLogs();
-        mAm.resizeStack(stack.mStackId, displayRect);
+        separateTestJournal();
+        resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(),
+                displayRect.height());
 
         // Move activity back to fullscreen stack.
         setActivityTaskWindowingMode(activityName,
                 WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
-                logSeparator);
+        final SizeInfo finalFullscreenSizes = getActivityDisplaySize(activityName);
 
         // After activity configuration was changed twice it must report same size as original one.
         assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
@@ -318,6 +299,7 @@
      */
     @Presubmit
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testDialogWhenLargeSplitSmall() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
@@ -330,7 +312,7 @@
         final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
         final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
 
-        mAm.resizeStack(stack.mStackId, new Rect(0, 0, smallWidthPx, smallHeightPx));
+        resizeStack(stack.mStackId, 0, 0, smallWidthPx, smallHeightPx);
         mAmWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -347,43 +329,39 @@
     public void testFullscreenAppOrientationRequests() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
-        ReportedSizes reportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        SizeInfo reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         assertEquals("portrait activity should be in portrait",
                 1 /* portrait */, reportedSizes.orientation);
-        logSeparator = separateLogs();
+        separateTestJournal();
 
         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
-        reportedSizes =
-                getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator);
+        reportedSizes = getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
         assertEquals("landscape activity should be in landscape",
                 2 /* landscape */, reportedSizes.orientation);
-        logSeparator = separateLogs();
+        separateTestJournal();
 
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
-        reportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         assertEquals("portrait activity should be in portrait",
                 1 /* portrait */, reportedSizes.orientation);
-        logSeparator = separateLogs();
     }
 
     @Test
     public void testNonfullscreenAppOrientationRequests() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        final ReportedSizes initialReportedSizes =
-                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+        final SizeInfo initialReportedSizes =
+                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         assertEquals("portrait activity should be in portrait",
                 1 /* portrait */, initialReportedSizes.orientation);
-        logSeparator = separateLogs();
+        separateTestJournal();
 
         launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         assertEquals("Legacy non-fullscreen activity requested landscape orientation",
@@ -404,33 +382,33 @@
     public void testAppOrientationRequestConfigChanges() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
 
-        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
-                1 /* start */, 1 /* resume */, 0 /* pause */, 0 /* stop */, 0 /* destroy */,
-                0 /* config */);
+        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
+                1 /* create */, 1 /* start */, 1 /* resume */,
+                0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);
 
         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
 
-        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
-                1 /* start */, 1 /* resume */, 1 /* pause */, 1 /* stop */, 0 /* destroy */,
-                0 /* config */);
-        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
-                1 /* start */, 1 /* resume */, 0 /* pause */, 0 /* stop */, 0 /* destroy */,
-                0 /* config */);
+        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
+                1 /* create */, 1 /* start */, 1 /* resume */,
+                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
+        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
+                1 /* create */, 1 /* start */, 1 /* resume */,
+                0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);
 
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
 
-        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 2 /* create */,
-                2 /* start */, 2 /* resume */, 1 /* pause */, 1 /* stop */, 0 /* destroy */,
-                0 /* config */);
-        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
-                1 /* start */, 1 /* resume */, 1 /* pause */, 1 /* stop */, 0 /* destroy */,
-                0 /* config */);
+        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
+                2 /* create */, 2 /* start */, 2 /* resume */,
+                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
+        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
+                1 /* create */, 1 /* start */, 1 /* resume */,
+                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
     }
 
     /**
@@ -441,13 +419,11 @@
     public void testAppOrientationRequestConfigClears() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(TEST_ACTIVITY);
         mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
-        final ReportedSizes initialReportedSizes =
-                getLastReportedSizesForActivity(TEST_ACTIVITY, logSeparator);
-        final int  initialOrientation = initialReportedSizes.orientation;
-
+        final SizeInfo initialReportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
+        final int initialOrientation = initialReportedSizes.orientation;
 
         // Launch an activity that requests different orientation and check that it will be applied
         final boolean launchingPortrait;
@@ -461,21 +437,20 @@
         }
         final ComponentName differentOrientationActivity = launchingPortrait
                 ? PORTRAIT_ORIENTATION_ACTIVITY : LANDSCAPE_ORIENTATION_ACTIVITY;
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(differentOrientationActivity);
         mAmWmState.assertVisibility(differentOrientationActivity, true /* visible */);
-        final ReportedSizes rotatedReportedSizes =
-                getLastReportedSizesForActivity(differentOrientationActivity, logSeparator);
+        final SizeInfo rotatedReportedSizes =
+                getLastReportedSizesForActivity(differentOrientationActivity);
         assertEquals("Applied orientation must correspond to activity request",
                 launchingPortrait ? 1 : 2, rotatedReportedSizes.orientation);
 
         // Launch another activity on top and check that its orientation is not affected by previous
         // activity.
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(RESIZEABLE_ACTIVITY);
         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
-        final ReportedSizes finalReportedSizes =
-                getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+        final SizeInfo finalReportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
         assertEquals("Applied orientation must not be influenced by previously visible activity",
                 initialOrientation, finalReportedSizes.orientation);
     }
@@ -529,12 +504,12 @@
         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
 
         // Request portrait
-        executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
+        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
-        waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
+        waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
 
         // Finish activity
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
@@ -555,39 +530,38 @@
         final int initialServerOrientation = mAmWmState.getWmState().getLastOrientation();
 
         // Verify fixed-landscape
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-        executeShellCommand(REQUEST_LANDSCAPE_BROADCAST);
+        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        waitForBroadcastActivityReady(SCREEN_ORIENTATION_LANDSCAPE);
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        waitForBroadcastActivityReady(Configuration.ORIENTATION_LANDSCAPE);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
-        ReportedSizes reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY,
-                logSeparator);
+        SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
         assertNull("Should come back in original orientation", reportedSizes);
         assertEquals("Should come back in original server orientation",
                 initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
         assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
-                0 /* numConfigChange */, logSeparator);
+                0 /* numConfigChange */);
 
         // Verify fixed-portrait
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-        executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
+        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
-        waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
-        reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+        reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
         assertNull("Should come back in original orientation", reportedSizes);
         assertEquals("Should come back in original server orientation",
                 initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
         assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
-                0 /* numConfigChange */, logSeparator);
+                0 /* numConfigChange */);
     }
 
     /**
@@ -599,7 +573,7 @@
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
         // Start resizeable activity that handles configuration changes.
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(TEST_ACTIVITY);
         launchActivity(RESIZEABLE_ACTIVITY);
         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
@@ -613,23 +587,21 @@
                     supportsLockedUserRotation(rotationSession, displayId));
 
             rotationSession.set(ROTATION_0);
-            ReportedSizes reportedSizes =
-                    getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+            SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
             int prevOrientation = reportedSizes.orientation;
 
             final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
             for (final int rotation : rotations) {
-                logSeparator = separateLogs();
+                separateTestJournal();
                 rotationSession.set(rotation);
 
                 // Verify lifecycle count and orientation changes.
                 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
-                        1 /* numConfigChange */, logSeparator);
-                reportedSizes =
-                        getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+                        1 /* numConfigChange */);
+                reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
                 assertNotEquals(prevOrientation, reportedSizes.orientation);
                 assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
-                        0 /* numConfigChange */, logSeparator);
+                        0 /* numConfigChange */);
 
                 prevOrientation = reportedSizes.orientation;
             }
@@ -649,7 +621,7 @@
                 hasDisplayCutout());
 
         // Start portrait-fixed activity
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(RESIZEABLE_ACTIVITY);
         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
@@ -666,17 +638,17 @@
 
             final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
             for (final int rotation : rotations) {
-                logSeparator = separateLogs();
+                separateTestJournal();
                 rotationSession.set(rotation);
 
                 // Verify lifecycle count and orientation changes.
                 assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
-                        0 /* numConfigChange */, logSeparator);
-                final ReportedSizes reportedSizes = getLastReportedSizesForActivity(
-                        PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
+                        0 /* numConfigChange */);
+                final SizeInfo reportedSizes = getLastReportedSizesForActivity(
+                        PORTRAIT_ORIENTATION_ACTIVITY);
                 assertNull("No new sizes must be reported", reportedSizes);
                 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
-                        0 /* numConfigChange */, logSeparator);
+                        0 /* numConfigChange */);
             }
         }
     }
@@ -700,12 +672,12 @@
         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
 
         // Request portrait
-        executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
+        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
-        waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
+        waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
 
         // Finish activity
-        executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
+        mBroadcastActionTrigger.moveTopTaskToBack();
 
         // Verify that activity brought to front is in originally requested orientation.
         mAmWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
@@ -778,9 +750,9 @@
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         // Launch to docked stack and record size.
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivityInSplitScreenWithRecents(activityName);
-        final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+        final SizeInfo initialDockedSizes = getActivityDisplaySize(activityName);
         // 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);
@@ -788,16 +760,16 @@
                 new WaitForValidActivityState.Builder(activityName).build());
 
         // Move to fullscreen stack.
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(
                 activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
+        final SizeInfo fullscreenSizes = getActivityDisplaySize(activityName);
         assertSizesAreSane(fullscreenSizes, initialDockedSizes);
 
         // Move activity back to docked stack.
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+        final SizeInfo finalDockedSizes = getActivityDisplaySize(activityName);
 
         // After activity configuration was changed twice it must report same size as original one.
         assertSizesAreSame(initialDockedSizes, finalDockedSizes);
@@ -807,7 +779,7 @@
      * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
      * have flipped.
      */
-    private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes rotationB,
+    private static void assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB,
             boolean skipOrientationCheck) {
         assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
         assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
@@ -834,8 +806,7 @@
      * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
      * that are smaller than the dockedSizes.
      */
-    private static void assertSizesAreSane(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
-    {
+    private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) {
         final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
         if (portrait) {
             assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight));
@@ -851,7 +822,7 @@
     /**
      * Throws an AssertionError if sizes are different.
      */
-    private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize) {
+    private static void assertSizesAreSame(SizeInfo firstSize, SizeInfo secondSize) {
         assertEquals(firstSize.widthDp, secondSize.widthDp);
         assertEquals(firstSize.heightDp, secondSize.heightDp);
         assertEquals(firstSize.displayWidth, secondSize.displayWidth);
@@ -861,11 +832,10 @@
         assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
     }
 
-    private ReportedSizes getActivityDisplaySize(ComponentName activityName,
-            LogSeparator logSeparator) {
+    private SizeInfo getActivityDisplaySize(ComponentName activityName) {
         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
                 new WaitForValidActivityState(activityName));
-        final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
+        final SizeInfo details = getLastReportedSizesForActivity(activityName);
         assertNotNull(details);
         return details;
     }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
index 7873dbe..61ed964 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
@@ -185,7 +185,7 @@
                 expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
 
         // Now, tell it to finish itself and ensure that the assistant stack is brought back forward
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
         mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
         mAmWmState.assertFrontStackActivityType(
                 "Assistant stack should be on top.", ACTIVITY_TYPE_ASSISTANT);
@@ -210,8 +210,9 @@
         }
         waitForValidStateWithActivityTypeAndWindowingMode(
                 TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
-        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
-        mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, mAssistantDisplayId,
+                "TestActivity should be resumed");
+        mAmWmState.assertFocusedActivity("TestActivity should be focused", TEST_ACTIVITY);
         mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
index f041ebd..80e16e2 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -17,12 +17,14 @@
 package android.server.am;
 
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
-import static android.server.am.ComponentNameUtils.getLogTag;
 import static android.server.am.Components.FONT_SCALE_ACTIVITY;
 import static android.server.am.Components.FONT_SCALE_NO_RELAUNCH_ACTIVITY;
 import static android.server.am.Components.NO_RELAUNCH_ACTIVITY;
 import static android.server.am.Components.RESIZEABLE_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.FontScaleActivity.EXTRA_FONT_ACTIVITY_DPI;
+import static android.server.am.Components.FontScaleActivity.EXTRA_FONT_PIXEL_SIZE;
+import static android.server.am.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
 import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logE;
 import static android.view.Surface.ROTATION_0;
@@ -37,15 +39,20 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.server.am.CommandSession.SizeInfo;
+import android.server.am.TestJournalProvider.TestJournalContainer;
 import android.server.am.settings.SettingsSession;
 import android.support.test.filters.FlakyTest;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.Test;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Build/Install/Run:
@@ -132,10 +139,10 @@
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
 
-            final ReportedSizes[] sizes = new ReportedSizes[cutoutRotations.length];
+            final SizeInfo[] sizes = new SizeInfo[cutoutRotations.length];
             for (int i = 0; i < cutoutRotations.length; i++) {
+                separateTestJournal();
                 final int rotation = cutoutRotations[i];
-                final LogSeparator logSeparator = separateLogs();
                 rotationSession.set(rotation);
                 final int newDeviceRotation = getDeviceRotation(displayId);
                 if (rotation != newDeviceRotation) {
@@ -145,7 +152,7 @@
                 }
 
                 // Record configuration changes on rotations between opposite orientations
-                sizes[i] = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
+                sizes[i] = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
                 if (i == 0) {
                     configSame0_180 = sizes[i] == null;
                 } else if (i == 2) {
@@ -182,11 +189,10 @@
             }
 
             for (int rotation = 0; rotation < 4; rotation += rotationStep) {
-                final LogSeparator logSeparator = separateLogs();
+                separateTestJournal();
                 rotationSession.set(rotation);
                 mAmWmState.computeState(activityName);
-                assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
-                        logSeparator);
+                assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
             }
         }
     }
@@ -204,24 +210,22 @@
             ComponentName activityName, boolean relaunch) throws Exception {
         try (final FontScaleSession fontScaleSession = new FontScaleSession()) {
             fontScaleSession.set(1.0f);
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(activityName);
             mAmWmState.computeState(activityName);
 
-            final int densityDpi = getActivityDensityDpi(activityName, logSeparator);
+            final int densityDpi = getActivityDensityDpi(activityName);
 
             for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
-                logSeparator = separateLogs();
+                separateTestJournal();
                 fontScaleSession.set(fontScale);
                 mAmWmState.computeState(activityName);
-                assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
-                        logSeparator);
+                assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
 
                 // Verify that the display metrics are updated, and therefore the text size is also
                 // updated accordingly.
                 assertExpectedFontPixelSize(activityName,
-                        scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
-                        logSeparator);
+                        scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi));
             }
         }
     }
@@ -232,19 +236,19 @@
      */
     @Test
     public void testUpdateApplicationInfo() throws Exception {
-        final LogSeparator firstLogSeparator = separateLogs();
+        separateTestJournal();
 
         // Launch an activity that prints applied config.
         launchActivity(TEST_ACTIVITY);
-        final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY, firstLogSeparator);
+        final int assetSeq = getAssetSeqNumber(TEST_ACTIVITY);
 
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         // Update package info.
-        executeShellCommand("am update-appinfo all " + TEST_ACTIVITY.getPackageName());
+        updateApplicationInfo(Arrays.asList(TEST_ACTIVITY.getPackageName()));
         mAmWmState.waitForWithAmState((amState) -> {
             // Wait for activity to be resumed and asset seq number to be updated.
             try {
-                return readAssetSeqNumber(TEST_ACTIVITY, logSeparator) == assetSeq + 1
+                return getAssetSeqNumber(TEST_ACTIVITY) == assetSeq + 1
                         && amState.hasActivityState(TEST_ACTIVITY, STATE_RESUMED);
             } catch (Exception e) {
                 logE("Error waiting for valid state: " + e.getMessage());
@@ -254,31 +258,13 @@
 
         // Check if activity is relaunched and asset seq is updated.
         assertRelaunchOrConfigChanged(TEST_ACTIVITY, 1 /* numRelaunch */,
-                0 /* numConfigChange */, logSeparator);
-        final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY, logSeparator);
+                0 /* numConfigChange */);
+        final int newAssetSeq = getAssetSeqNumber(TEST_ACTIVITY);
         assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
     }
 
-    private static final Pattern sConfigurationPattern = Pattern.compile(
-            "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
-
-    /** Read asset sequence number in last applied configuration from logs. */
-    private int readAssetSeqNumber(ComponentName activityName, LogSeparator logSeparator)
-            throws Exception {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sConfigurationPattern.matcher(line);
-            if (matcher.matches()) {
-                final String assetSeqNumber = matcher.group(3);
-                try {
-                    return Integer.valueOf(assetSeqNumber);
-                } catch (NumberFormatException e) {
-                    // Ignore, asset seq number is not printed when not set.
-                }
-            }
-        }
-        return 0;
+    private static int getAssetSeqNumber(ComponentName activityName) {
+        return TestJournalContainer.get(activityName).extras.getInt(EXTRA_CONFIG_ASSETS_SEQ);
     }
 
     // Calculate the scaled pixel size just like the device is supposed to.
@@ -288,37 +274,30 @@
         return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
     }
 
-    private static Pattern sDeviceDensityPattern = Pattern.compile("^(.+): fontActivityDpi=(.+)$");
-
-    private int getActivityDensityDpi(ComponentName activityName, LogSeparator logSeparator)
+    private static int getActivityDensityDpi(ComponentName activityName)
             throws Exception {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sDeviceDensityPattern.matcher(line);
-            if (matcher.matches()) {
-                return Integer.parseInt(matcher.group(2));
-            }
+        final Bundle extras = TestJournalContainer.get(activityName).extras;
+        if (!extras.containsKey(EXTRA_FONT_ACTIVITY_DPI)) {
+            fail("No fontActivityDpi reported from activity " + activityName);
+            return -1;
         }
-        fail("No fontActivityDpi reported from activity " + activityName);
-        return -1;
+        return extras.getInt(EXTRA_FONT_ACTIVITY_DPI);
     }
 
-    private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
-
-    /** Read the font size in the last log line. */
-    private void assertExpectedFontPixelSize(ComponentName activityName, int fontPixelSize,
-            LogSeparator logSeparator) throws Exception {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sFontSizePattern.matcher(line);
-            if (matcher.matches()) {
-                assertEquals("Expected font pixel size does not match", fontPixelSize,
-                        Integer.parseInt(matcher.group(2)));
-                return;
-            }
+    private void assertExpectedFontPixelSize(ComponentName activityName, int fontPixelSize)
+            throws Exception {
+        final Bundle extras = TestJournalContainer.get(activityName).extras;
+        if (!extras.containsKey(EXTRA_FONT_PIXEL_SIZE)) {
+            fail("No fontPixelSize reported from activity " + activityName);
         }
-        fail("No fontPixelSize reported from activity " + activityName);
+        assertEquals("Expected font pixel size does not match", fontPixelSize,
+                extras.getInt(EXTRA_FONT_PIXEL_SIZE));
+    }
+
+    private void updateApplicationInfo(List<String> packages) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAm.scheduleApplicationInfoChanged(packages,
+                        android.os.Process.myUserHandle().getIdentifier())
+        );
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
index 300d9bd..7416460 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
@@ -17,7 +17,11 @@
 package android.server.am;
 
 import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 
 import android.server.am.ActivityManagerState.ActivityDisplay;
@@ -60,4 +64,39 @@
             mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
         }
     }
+
+    /**
+     * Tests keyguard dialog should shows on external display.
+     * @throws Exception
+     */
+    @Test
+    public void testShowKeyguardDialogOnSecondaryDisplay() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
+
+            lockScreenSession.gotoKeyguard();
+            WindowManagerState.WindowState keyguardWindowState =
+                    mAmWmState.waitForValidProduct(
+                            this::getKeyguardDialogWindowState, "KeyguardDialog",
+                            w -> w.isShown() && w.getDisplayId() == newDisplay.mId);
+            assertNotNull("KeyguardDialog must show up", keyguardWindowState);
+            assertEquals("KeyguardDialog should show on external display", newDisplay.mId,
+                    keyguardWindowState.getDisplayId());
+
+            // Keyguard dialog mustn't be removed when press back key
+            pressBackButton();
+            keyguardWindowState = mAmWmState.waitForValidProduct(
+                    this::getKeyguardDialogWindowState, "KeyguardDialog",
+                    w -> w.isShown() && w.getDisplayId() == newDisplay.mId);
+            assertNotNull("KeyguardDialog must show up", keyguardWindowState);
+        }
+    }
+
+    private WindowManagerState.WindowState getKeyguardDialogWindowState() {
+        final WindowManagerState wmState = mAmWmState.getWmState();
+        wmState.computeState();
+        return mAmWmState.getWmState().findFirstWindowWithType(TYPE_KEYGUARD_DIALOG);
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
index 84c2b5c..bc6171a 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
@@ -18,6 +18,7 @@
 
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.ActivityManagerTestBase.LockScreenSession.FLAG_REMOVE_ACTIVITIES_ON_CLOSE;
 import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
@@ -52,12 +53,13 @@
      */
     @Test
     public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
-             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             lockScreenSession.setLockCredential();
 
             // Create new usual virtual display.
-            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
 
             // Launch activity on new secondary display.
@@ -66,31 +68,63 @@
 
             // Lock the device.
             lockScreenSession.gotoKeyguard();
-            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                    "Expected stopped activity on secondary display ");
             mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
 
             // Unlock and check if visibility is back.
-            lockScreenSession.unlockDevice()
-                    .enterAndConfirmLockCredential();
+            lockScreenSession.unlockDevice();
+
+            lockScreenSession.enterAndConfirmLockCredential();
             mAmWmState.waitForKeyguardGone();
-            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+            mAmWmState.assertKeyguardGone();
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_RESUMED,
+                    "Expected resumed activity on secondary display");
             mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
         }
     }
 
     /**
+     * Tests that private display cannot show content while device locked.
+     */
+    @Test
+    public void testPrivateDisplayHideContentWhenLocked() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            lockScreenSession.setLockCredential();
+
+            final ActivityDisplay newDisplay =
+                    virtualDisplaySession.setPublicDisplay(false).createDisplay();
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+            lockScreenSession.gotoKeyguard();
+
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                    "Expected stopped activity on private display");
+            mAmWmState.assertVisibility(TEST_ACTIVITY, false /* visible */);
+        }
+    }
+
+    /**
      * Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display dismisses the keyguard.
      */
     @Test
     public void testDismissKeyguard_secondaryDisplay() throws Exception {
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
-             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+        try (final LockScreenSession lockScreenSession =
+                     new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE);
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             lockScreenSession.setLockCredential();
-            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true).
+                    createDisplay();
 
             lockScreenSession.gotoKeyguard();
             mAmWmState.assertKeyguardShowingAndNotOccluded();
-            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
+                    .setMultipleTask(true).setDisplayId(newDisplay.mId)
+                    .setWaitForLaunched(false).execute();
+            waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
+                    "Expected stopped activity on secondary display");
             lockScreenSession.enterAndConfirmLockCredential();
             mAmWmState.waitForKeyguardGone();
             mAmWmState.assertKeyguardGone();
@@ -100,17 +134,24 @@
 
     @Test
     public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
-             final LockScreenSession lockScreenSession = new LockScreenSession()) {
+        try (final LockScreenSession lockScreenSession =
+                     new LockScreenSession(FLAG_REMOVE_ACTIVITIES_ON_CLOSE);
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             lockScreenSession.setLockCredential();
-            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true).
+                    createDisplay();
 
             lockScreenSession.gotoKeyguard();
             mAmWmState.assertKeyguardShowingAndNotOccluded();
             launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(DISMISS_KEYGUARD_ACTIVITY).setNewTask(true)
+                    .setMultipleTask(true).setDisplayId(newDisplay.mId)
+                    .setWaitForLaunched(false).execute();
+            waitAndAssertActivityState(DISMISS_KEYGUARD_ACTIVITY, STATE_STOPPED,
+                    "Expected stopped activity on secondary display");
             lockScreenSession.enterAndConfirmLockCredential();
             mAmWmState.waitForKeyguardGone();
             mAmWmState.assertKeyguardGone();
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
index a82ae2d..d70c913 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -26,8 +26,10 @@
         .KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_COMMAND;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_COUNT;
+import static android.server.am.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_LAUNCH_TARGET_COMPONENT;
+import static android.server.am.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
 import static android.server.am.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
@@ -46,14 +48,18 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.CommandSession.ActivitySession;
+import android.server.am.CommandSession.ActivitySessionClient;
 import android.server.am.settings.SettingsSession;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+
 import android.util.Size;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -63,11 +69,15 @@
  * @see ActivityManagerDisplayTests
  * @see ActivityManagerDisplayLockedKeyguardTests
  */
-class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
+public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
 
     static final int CUSTOM_DENSITY_DPI = 222;
     private static final int INVALID_DENSITY_DPI = -1;
 
+    ActivityDisplay getDisplayState(int displayId) {
+        return getDisplayState(getDisplaysStates(), displayId);
+    }
+
     ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
         for (ActivityDisplay display : displays) {
             if (display.mId == displayId) {
@@ -112,7 +122,7 @@
         return result;
     }
 
-    static class ReportedDisplayMetrics {
+    public static class ReportedDisplayMetrics {
         private static final String WM_SIZE = "wm size";
         private static final String WM_DENSITY = "wm density";
         private static final Pattern PHYSICAL_SIZE =
@@ -133,10 +143,10 @@
         @Nullable
         final Integer overrideDensity;
 
-        /** Get physical and override display metrics from WM. */
-        static ReportedDisplayMetrics getDisplayMetrics() {
-            return new ReportedDisplayMetrics(
-                    executeShellCommand(WM_SIZE) + executeShellCommand(WM_DENSITY));
+        /** Get physical and override display metrics from WM for specified display. */
+        public static ReportedDisplayMetrics getDisplayMetrics(int displayId) {
+            return new ReportedDisplayMetrics(executeShellCommand(WM_SIZE + " -d " + displayId)
+                            + executeShellCommand(WM_DENSITY + " -d " + displayId));
         }
 
         void setDisplayMetrics(final Size size, final int density) {
@@ -166,7 +176,7 @@
         }
 
         /** Get display size that WM operates with. */
-        Size getSize() {
+        public Size getSize() {
             return overrideSize != null ? overrideSize : physicalSize;
         }
 
@@ -206,15 +216,18 @@
         }
     }
 
-    protected class VirtualDisplaySession implements AutoCloseable {
+    public class VirtualDisplaySession implements AutoCloseable {
         private int mDensityDpi = CUSTOM_DENSITY_DPI;
         private boolean mLaunchInSplitScreen = false;
         private boolean mCanShowWithInsecureKeyguard = false;
         private boolean mPublicDisplay = false;
         private boolean mResizeDisplay = true;
+        private boolean mShowSystemDecorations = false;
+        private boolean mPresentationDisplay = false;
         private ComponentName mLaunchActivity = null;
         private boolean mSimulateDisplay = false;
         private boolean mMustBeCreated = true;
+        private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */);
 
         private boolean mVirtualDisplayCreated = false;
         private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
@@ -245,23 +258,38 @@
             return this;
         }
 
+        VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
+            mShowSystemDecorations = showSystemDecorations;
+            return this;
+        }
+
+        VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) {
+            mPresentationDisplay = presentationDisplay;
+            return this;
+        }
+
         VirtualDisplaySession setLaunchActivity(ComponentName launchActivity) {
             mLaunchActivity = launchActivity;
             return this;
         }
 
-        VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
+        public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
             mSimulateDisplay = simulateDisplay;
             return this;
         }
 
+        VirtualDisplaySession setSimulationDisplaySize(int width, int height) {
+            mSimulationDisplaySize = new Size(width, height);
+            return this;
+        }
+
         VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
             mMustBeCreated = mustBeCreated;
             return this;
         }
 
         @Nullable
-        ActivityDisplay createDisplay() throws Exception {
+        public ActivityDisplay createDisplay() throws Exception {
             return createDisplays(1).stream().findFirst().orElse(null);
         }
 
@@ -298,8 +326,8 @@
         private List<ActivityDisplay> simulateDisplay() throws Exception {
             final List<ActivityDisplay> originalDs = getDisplaysStates();
 
-            // Create virtual display with custom density dpi.
-            mOverlayDisplayDeviceSession.set("1024x768/" + mDensityDpi);
+            // Create virtual display with custom density dpi and specified size.
+            mOverlayDisplayDeviceSession.set(mSimulationDisplaySize + "/" + mDensityDpi);
 
             return assertAndGetNewDisplays(1, originalDs);
         }
@@ -353,7 +381,10 @@
                     .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
                     .append(mCanShowWithInsecureKeyguard)
                     .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
-                    .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay);
+                    .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay)
+                    .append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ")
+                    .append(mShowSystemDecorations)
+                    .append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay);
             if (mLaunchActivity != null) {
                 createVirtualDisplayCommand
                         .append(" --es " + KEY_LAUNCH_TARGET_COMPONENT + " ")
@@ -424,6 +455,36 @@
         }
     }
 
+    // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
+    protected class VirtualDisplayLauncher extends VirtualDisplaySession {
+        private final ActivitySessionClient mActivitySessionClient = new ActivitySessionClient();
+
+        ActivitySession launchActivityOnDisplay(ComponentName activityName,
+                ActivityDisplay display) {
+            return launchActivity(builder -> builder
+                    // TODO(b/112837428): Use public display if possible.
+                    // VirtualDisplayActivity is in different package. If the display is not public,
+                    // it requires shell permission to launch activity ({@see com.android.server.am.
+                    // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}).
+                    .setWithShellPermission(true)
+                    .setTargetActivity(activityName)
+                    .setDisplayId(display.mId));
+        }
+
+        ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) {
+            final LaunchActivityBuilder builder = getLaunchActivityBuilder()
+                    .setUseInstrumentation();
+            setupBuilder.accept(builder);
+            return mActivitySessionClient.startActivity(builder);
+        }
+
+        @Override
+        public void close() throws Exception {
+            super.close();
+            mActivitySessionClient.close();
+        }
+    }
+
     /** Helper class to save, set, and restore overlay_display_devices preference. */
     private static class OverlayDisplayDevicesSession extends SettingsSession<String> {
         OverlayDisplayDevicesSession() {
@@ -464,7 +525,7 @@
     }
 
     /** Checks if the device supports multi-display. */
-    boolean supportsMultiDisplay() {
+    protected boolean supportsMultiDisplay() {
         return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
index 4bef8fe..ea0d7d1 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
@@ -118,7 +118,8 @@
     public void testForceDisplayMetrics() throws Exception {
         launchHomeActivity();
 
-        try (final DisplayMetricsSession displayMetricsSession = new DisplayMetricsSession();
+        try (final DisplayMetricsSession displayMetricsSession =
+                     new DisplayMetricsSession(DEFAULT_DISPLAY);
              final LockScreenSession lockScreenSession = new LockScreenSession()) {
             // Read initial sizes.
             final ReportedDisplayMetrics originalDisplayMetrics =
@@ -153,9 +154,11 @@
     private static class DisplayMetricsSession implements AutoCloseable {
 
         private final ReportedDisplayMetrics mInitialDisplayMetrics;
+        private final int mDisplayId;
 
-        DisplayMetricsSession() throws Exception {
-            mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics();
+        DisplayMetricsSession(int displayId) throws Exception {
+            mDisplayId = displayId;
+            mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
         }
 
         ReportedDisplayMetrics getInitialDisplayMetrics() {
@@ -163,7 +166,7 @@
         }
 
         ReportedDisplayMetrics getDisplayMetrics() throws Exception {
-            return ReportedDisplayMetrics.getDisplayMetrics();
+            return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
         }
 
         void overrideDisplayMetrics(final Size size, final int density) {
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
index 73b3988..0d4ed6e 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.server.am.ActivityManagerState.ActivityStack;
 import android.server.am.ActivityManagerState.ActivityTask;
+import android.view.Display;
 
 import org.junit.Test;
 
@@ -35,7 +36,7 @@
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:ActivityManagerFreeformStackTests
  */
-public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
+public class ActivityManagerFreeformStackTests extends ActivityManagerDisplayTestBase {
 
     private static final int TEST_TASK_OFFSET = 20;
     private static final int TEST_TASK_OFFSET_2 = 100;
@@ -49,25 +50,34 @@
 
     @Test
     public void testFreeformWindowManagementSupport() throws Exception {
+        try (VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            int displayId = Display.DEFAULT_DISPLAY;
+            if (supportsMultiDisplay()) {
+                final ActivityManagerState.ActivityDisplay display = virtualDisplaySession
+                        .setSimulateDisplay(true)
+                        .setSimulationDisplaySize(1920 /* width */, 1080 /* height */)
+                        .createDisplay();
+                displayId = display.mId;
+            }
+            launchActivityOnDisplay(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM, displayId);
 
-        launchActivity(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM);
+            mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
 
-        mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
+            if (!supportsFreeform()) {
+                mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+                        WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+                return;
+            }
 
-        if (!supportsFreeform()) {
-            mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
-                    WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
-            return;
+            mAmWmState.assertFrontStackOnDisplay("Freeform stack must be the front stack.",
+                    WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, displayId);
+            mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+            mAmWmState.assertFocusedActivity(
+                    TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
+            assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
+                    mAmWmState.getAmState().getTaskByActivity(TEST_ACTIVITY).getBounds());
         }
-
-        mAmWmState.assertFrontStack("Freeform stack must be the front stack.",
-                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
-        mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
-        mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-        mAmWmState.assertFocusedActivity(
-                TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
-        assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
-                mAmWmState.getAmState().getTaskByActivity(TEST_ACTIVITY).getBounds());
     }
 
     @Test
@@ -124,7 +134,7 @@
         mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
                 new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
 
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         resizeActivityTask(TEST_ACTIVITY,
                 TEST_TASK_OFFSET, TEST_TASK_OFFSET,
                 TEST_TASK_OFFSET + testTaskSize2, TEST_TASK_OFFSET + testTaskSize1);
@@ -133,7 +143,7 @@
                 TEST_TASK_OFFSET_2 + testTaskSize2, TEST_TASK_OFFSET_2 + testTaskSize1);
         mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
 
-        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
-        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */);
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java
index f625fdd..95370cc 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerGetConfigTests.java
@@ -292,12 +292,12 @@
         // XXX not comparing windowConfiguration, since by definition this is contextual.
     }
 
-    private void checkDeviceConfig(DisplayManager dm, DeviceConfigurationProto deviceConfig) {
-        Point stableSize = dm.getStableDisplaySize();
+    private void checkDeviceConfig(DisplayMetrics displayMetrics,
+            DeviceConfigurationProto deviceConfig) {
         assertEquals("Expected stable screen width does not match",
-                stableSize.x, deviceConfig.stableScreenWidthPx);
+                displayMetrics.widthPixels, deviceConfig.stableScreenWidthPx);
         assertEquals("Expected stable screen height does not match",
-                stableSize.y, deviceConfig.stableScreenHeightPx);
+                displayMetrics.heightPixels, deviceConfig.stableScreenHeightPx);
         assertEquals("Expected stable screen density does not match",
                 DisplayMetrics.DENSITY_DEVICE_STABLE, deviceConfig.stableDensityDpi);
 
@@ -376,6 +376,28 @@
         display.getMetrics(metrics);
 
         checkResourceConfig(config, metrics, globalConfig.resources);
-        checkDeviceConfig(dm, globalConfig.device);
+        checkDeviceConfig(metrics, globalConfig.device);
+    }
+
+    @Test
+    public void testDeviceConfigWithSecondaryDisplay() throws Exception {
+        VirtualDisplayHelper vd = new VirtualDisplayHelper();
+        final int displayId = vd.createAndWaitForPublicDisplay(false);
+
+        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        Display display = dm.getDisplay(displayId);
+        DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
+        String cmd = "cmd activity get-config --proto --device --display " + displayId;
+        byte[] dump = executeShellCommand(cmd);
+
+        GlobalConfigurationProto globalConfig;
+        globalConfig = GlobalConfigurationProto.parseFrom(dump);
+        Configuration config = mContext.getResources().getConfiguration();
+
+        checkResourceConfig(config, metrics, globalConfig.resources);
+        checkDeviceConfig(metrics, globalConfig.device);
+
+        vd.releaseDisplay();
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
index b491a9f..c9044b4 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
@@ -33,6 +33,7 @@
 
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
 import android.server.am.WindowManagerState.Display;
 import android.server.am.WindowManagerState.WindowState;
 
@@ -92,6 +93,7 @@
     }
 
     @Test
+    @Presubmit
     public void testMinimalSizeDocked() throws Exception {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
index 90658e5..c9b8dba 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -16,78 +16,126 @@
 
 package android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-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.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
 import static android.server.am.ActivityLauncher.KEY_NEW_TASK;
-import static android.server.am.ActivityLauncher.KEY_TARGET_COMPONENT;
 import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics;
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ActivityManagerState.STATE_STOPPED;
 import static android.server.am.ComponentNameUtils.getActivityName;
 import static android.server.am.ComponentNameUtils.getWindowName;
 import static android.server.am.Components.ALT_LAUNCHING_ACTIVITY;
+import static android.server.am.Components.BOTTOM_ACTIVITY;
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
-import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
+import static android.server.am.Components.HOME_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
-import static android.server.am.Components.LAUNCH_BROADCAST_ACTION;
 import static android.server.am.Components.LAUNCH_BROADCAST_RECEIVER;
+import static android.server.am.Components.LAUNCH_TEST_ON_DESTROY_ACTIVITY;
+import static android.server.am.Components.LaunchBroadcastReceiver.ACTION_TEST_ACTIVITY_START;
+import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_COMPONENT_NAME;
+import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_TARGET_DISPLAY;
+import static android.server.am.Components.LaunchBroadcastReceiver.LAUNCH_BROADCAST_ACTION;
 import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY;
 import static android.server.am.Components.RESIZEABLE_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
+import static android.server.am.Components.SINGLE_HOME_ACTIVITY;
+import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY;
+import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2;
+import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3;
 import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TOAST_ACTIVITY;
 import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
 import static android.server.am.StateLogger.logAlways;
 import static android.server.am.StateLogger.logE;
 import static android.server.am.UiDeviceUtils.pressSleepButton;
 import static android.server.am.UiDeviceUtils.pressWakeupButton;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_OPEN;
+import static android.server.am.app27.Components.SDK_27_HOME_ACTIVITY;
+import static android.server.am.lifecycle.ActivityStarterTests.StandardActivity;
+import static android.server.am.second.Components.EMBEDDING_ACTIVITY;
+import static android.server.am.second.Components.EmbeddingActivity.ACTION_EMBEDDING_TEST_ACTIVITY_START;
+import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_COMPONENT_NAME;
+import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_TARGET_DISPLAY;
 import static android.server.am.second.Components.SECOND_ACTIVITY;
 import static android.server.am.second.Components.SECOND_LAUNCH_BROADCAST_ACTION;
 import static android.server.am.second.Components.SECOND_LAUNCH_BROADCAST_RECEIVER;
 import static android.server.am.second.Components.SECOND_NO_EMBEDDING_ACTIVITY;
 import static android.server.am.third.Components.THIRD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 
 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 static org.junit.Assume.assumeNotNull;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.CommandSession.ActivityCallback;
+import android.server.am.CommandSession.ActivitySession;
+import android.server.am.CommandSession.SizeInfo;
+import android.server.am.TestJournalProvider.TestJournalContainer;
+import android.server.am.WindowManagerState.WindowState;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.FlakyTest;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
 
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.concurrent.TimeUnit;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 /**
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests
  */
 @Presubmit
-@FlakyTest(bugId = 77652261)
 public class ActivityManagerMultiDisplayTests extends ActivityManagerDisplayTestBase {
-
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to launch activity via {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String LAUNCH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + KEY_LAUNCH_ACTIVITY + " true --ez "
-            + KEY_NEW_TASK + " true --es " + KEY_TARGET_COMPONENT + " ";
-
     @Before
     @Override
     public void setUp() throws Exception {
@@ -101,32 +149,129 @@
      */
     @Test
     public void testLaunchActivityOnSecondaryDisplay() throws Exception {
+        validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * Tests launching a home activity on virtual display.
+     */
+    @Test
+    public void testLaunchHomeActivityOnSecondaryDisplay() throws Exception {
+        try (final HomeActivitySession homeSession = new HomeActivitySession(HOME_ACTIVITY)) {
+            try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+                final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+                assertEquals("No stacks on newly launched virtual display", 0,
+                        newDisplay.mStacks.size());
+            }
+        }
+    }
+
+    /**
+     * Tests launching a single instance home activity on virtual display that supports system
+     * decorations.
+     */
+    // TODO (b/118206886): Will add it back once launcher's patch is merged into master.
+    private void testLaunchSingleHomeActivityOnDisplayWithDecorations() throws Exception {
+        try (final HomeActivitySession session = new HomeActivitySession(SINGLE_HOME_ACTIVITY)) {
+            try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+                // Create new virtual display with system decoration support.
+                final ActivityDisplay newDisplay
+                        = virtualDisplaySession.setShowSystemDecorations(true).createDisplay();
+
+                assertEquals("No stacks on newly launched virtual display", 0,
+                        newDisplay.mStacks.size());
+            }
+        }
+    }
+
+    /**
+     * Tests launching a home activity on virtual display that supports system decorations.
+     */
+    // TODO (b/118206886): Will add it back once launcher's patch is merged into master.
+    private void testLaunchHomeActivityOnDisplayWithDecorations() throws Exception {
+        try (final HomeActivitySession homeSession = new HomeActivitySession(HOME_ACTIVITY)) {
+            try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+                // Create new virtual display with system decoration support.
+                final ActivityDisplay newDisplay
+                        = virtualDisplaySession.setShowSystemDecorations(true).createDisplay();
+
+                // Home activity should be automatically launched on the new display.
+                waitAndAssertTopResumedActivity(HOME_ACTIVITY, newDisplay.mId,
+                        "Activity launched on secondary display must be focused and on top");
+                assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
+                        mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
+            }
+        }
+    }
+
+    /**
+     * Tests home activity that target before Q won't be started on virtual display that supports
+     * system decorations.
+     */
+    // TODO (b/118206886): Will add it back once launcher's patch is merged into master.
+    private void testLaunchSdk27HomeActivityOnDisplayWithDecorations() throws Exception {
+        try (final HomeActivitySession homeSession
+                     = new HomeActivitySession(SDK_27_HOME_ACTIVITY)) {
+            try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+                // Create new virtual display with system decoration support.
+                final ActivityDisplay newDisplay
+                        = virtualDisplaySession.setShowSystemDecorations(true).createDisplay();
+
+                // Launching an activity on virtual display. There should only have one stack on the
+                // display, i.e. home stack should not be created.
+                getLaunchActivityBuilder()
+                        .setUseInstrumentation()
+                        .setWithShellPermission(true)
+                        .setNewTask(true)
+                        .setTargetActivity(TEST_ACTIVITY)
+                        .setDisplayId(newDisplay.mId)
+                        .execute();
+                waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                        "Activity launched on secondary display must be focused and on top");
+                assertEquals("There must have exactly one stack on virtual display.", 1,
+                        mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
+            }
+        }
+    }
+
+    /**
+     * Tests launching a recent activity on virtual display.
+     */
+    @Test
+    public void testLaunchRecentActivityOnSecondaryDisplay() throws Exception {
+        validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_RECENTS);
+    }
+
+    /**
+     * Tests launching an assistant activity on virtual display.
+     */
+    @Test
+    public void testLaunchAssistantActivityOnSecondaryDisplay() throws Exception {
+        validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_ASSISTANT);
+    }
+
+    private void validateActivityLaunchOnNewDisplay(int activityType) throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
 
             // Launch activity on new secondary display.
-            final LogSeparator logSeparator = separateLogs();
-            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(TEST_ACTIVITY);
-
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    TEST_ACTIVITY);
-
-            // Check that activity is on the right display.
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the secondary display and resumed",
-                    getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            separateTestJournal();
+            getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
+                    .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                    .setMultipleTask(true).setActivityType(activityType)
+                    .setDisplayId(newDisplay.mId).execute();
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be focused and on top");
 
             // Check that activity config corresponds to display config.
-            final ReportedSizes reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
-                    logSeparator);
+            final SizeInfo reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
             assertEquals("Activity launched on secondary display must have proper configuration",
                     CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
+
+            assertEquals("Top activity must have correct activity type", activityType,
+                    mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId));
         }
     }
 
@@ -136,41 +281,58 @@
     @Test
     public void testLaunchActivityOnPrimaryDisplay() throws Exception {
         // Launch activity on primary display explicitly.
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, 0);
-        mAmWmState.computeState(LAUNCHING_ACTIVITY);
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
 
-        mAmWmState.assertFocusedActivity("Activity launched on primary display must be focused",
-                LAUNCHING_ACTIVITY);
-
-        // Check that activity is on the right display.
-        int frontStackId = mAmWmState.getAmState().getFrontStackId(0 /* displayId */);
-        ActivityManagerState.ActivityStack frontStack
-                = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on primary display", frontStackId);
+        waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity launched on primary display must be focused and on top");
 
         // Launch another activity on primary display using the first one
         getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).setNewTask(true)
-                .setMultipleTask(true).setDisplayId(0).execute();
+                .setMultipleTask(true).setDisplayId(DEFAULT_DISPLAY).execute();
         mAmWmState.computeState(TEST_ACTIVITY);
 
-        mAmWmState.assertFocusedActivity("Activity launched on primary display must be focused",
-                TEST_ACTIVITY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity launched on primary display must be focused");
+    }
 
-        // Check that activity is on the right display.
-        frontStackId = mAmWmState.getAmState().getFrontStackId(0 /* displayId */);
-        frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals("Launched activity must be on the primary display and resumed",
-                getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
-        mAmWmState.assertFocusedStack("Focus must be on primary display", frontStackId);
+    /**
+     * Tests launching an existing activity from an activity that resided on secondary display.
+     */
+    @Test
+    public void testLaunchActivityFromSecondaryDisplay() throws Exception {
+        getLaunchActivityBuilder().setUseInstrumentation()
+                .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                .setDisplayId(DEFAULT_DISPLAY).execute();
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+            final int newDisplayId = newDisplay.mId;
+
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true)
+                    .setDisplayId(newDisplayId).execute();
+            waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+                    "Activity should be resumed on secondary display");
+
+            mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity should be the top resumed on default display");
+
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                    .setDisplayId(newDisplayId).execute();
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity should be resumed on secondary display");
+        }
     }
 
     /**
      * Tests launching a non-resizeable activity on virtual display. It should land on the
-     * default display.
+     * virtual display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -178,96 +340,46 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(NON_RESIZEABLE_ACTIVITY);
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    NON_RESIZEABLE_ACTIVITY);
-
-            // Check that activity is on the right display.
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the primary display and resumed",
-                    getActivityName(NON_RESIZEABLE_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
+            waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Activity requested to launch on secondary display must be focused");
         }
     }
 
     /**
-     * Tests launching a non-resizeable activity on virtual display while split-screen is active
-     * on the primary display. It should land on the primary display and dismiss docked stack.
-     */
-    @Test
-    public void testLaunchNonResizeableActivityWithSplitScreen() throws Exception {
-        assumeTrue(supportsSplitScreenMultiWindow());
-
-        // Start launching activity.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
-            // Create new virtual display.
-            final ActivityDisplay newDisplay = virtualDisplaySession.setLaunchInSplitScreen(true)
-                    .createDisplay();
-
-            // Launch activity on new secondary display.
-            launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(NON_RESIZEABLE_ACTIVITY);
-
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    NON_RESIZEABLE_ACTIVITY);
-
-            // Check that activity is on the right display.
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the primary display and resumed",
-                    getActivityName(NON_RESIZEABLE_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
-            mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        }
-    }
-
-    /**
-     * Tests moving a non-resizeable activity to a virtual display. It should stay on the default
-     * display with no action performed.
+     * Tests successfully moving a non-resizeable activity to a virtual display.
      */
     @Test
     public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+        try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) {
             // Create new virtual display.
-            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            final ActivityDisplay newDisplay = virtualLauncher.createDisplay();
             // Launch a non-resizeable activity on a primary display.
-            launchActivityInNewTask(NON_RESIZEABLE_ACTIVITY);
+            final ActivitySession nonResizeableSession = virtualLauncher.launchActivity(
+                    builder -> builder.setTargetActivity(NON_RESIZEABLE_ACTIVITY).setNewTask(true));
+
             // Launch a resizeable activity on new secondary display to create a new stack there.
-            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
             final int externalFrontStackId = mAmWmState.getAmState()
                     .getFrontStackId(newDisplay.mId);
 
-            // Try to move the non-resizeable activity to new secondary display.
+            // Clear lifecycle callback history before moving the activity so the later verification
+            // can get the callbacks which are related to the reparenting.
+            nonResizeableSession.takeCallbackHistory();
+
+            // Try to move the non-resizeable activity to the top of stack on secondary display.
             moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
-            mAmWmState.computeState(NON_RESIZEABLE_ACTIVITY);
+            // 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));
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    RESIZEABLE_ACTIVITY);
-
-            // Check that activity is in the same stack
-            final int defaultFrontStackId = mAmWmState.getAmState().getFrontStackId(
-                    DEFAULT_DISPLAY);
-            final ActivityManagerState.ActivityStack defaultFrontStack =
-                    mAmWmState.getAmState().getStackById(defaultFrontStackId);
-            assertEquals("Launched activity must be on the primary display and resumed",
-                    getActivityName(NON_RESIZEABLE_ACTIVITY),
-                    defaultFrontStack.getTopTask().mRealActivity);
-            mAmWmState.assertFocusedStack("Focus must remain on the secondary display",
-                    externalFrontStackId);
+            waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "The moved non-resizeable activity must be focused");
+            assertActivityLifecycle(nonResizeableSession, true /* relaunched */);
         }
     }
 
@@ -276,6 +388,7 @@
      * land on the secondary display based on the resizeability of the root activity of the task.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new simulated display.
@@ -284,40 +397,22 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    BROADCAST_RECEIVER_ACTIVITY);
-
-            // Check that launching activity is on the secondary display.
-            int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the secondary display and resumed",
-                    getActivityName(BROADCAST_RECEIVER_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
+            waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be focused");
 
             // Launch non-resizeable activity from secondary display.
-            executeShellCommand(
-                    LAUNCH_ACTIVITY_BROADCAST + getActivityName(NON_RESIZEABLE_ACTIVITY));
-            mAmWmState.computeState(NON_RESIZEABLE_ACTIVITY);
-
-            // Check that non-resizeable activity is on the secondary display, because of the
-            // resizeable root of the task.
-            frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the primary display and resumed",
-                    getActivityName(NON_RESIZEABLE_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
+            mBroadcastActionTrigger.launchActivityNewTask(getActivityName(NON_RESIZEABLE_ACTIVITY));
+            waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on the secondary display and resumed");
         }
     }
 
     /**
-     * Tests launching a non-resizeable activity on virtual display from activity there. It should
-     * land on some different suitable display (usually - on the default one).
+     * Tests launching a non-resizeable activity on virtual display in a new task from activity
+     * there. It must land on the display as its caller.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -325,33 +420,33 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    LAUNCHING_ACTIVITY);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be focused");
 
-            // Check that launching activity is on the secondary display.
-            int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be on the secondary display and resumed",
-                    getActivityName(LAUNCHING_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
-
-            // Launch non-resizeable activity from secondary display.
+            // Launch non-resizeable activity from secondary display in a new task.
             getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY)
                     .setNewTask(true).setMultipleTask(true).execute();
 
-            // Check that non-resizeable activity is on the primary display.
-            frontStackId = mAmWmState.getAmState().getFocusedStackId();
-            frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-            assertFalse("Launched activity must be on a different display",
-                    newDisplay.mId == frontStack.mDisplayId);
+            mAmWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+
+            // Check that non-resizeable activity is on the same display.
+            final int newFrontStackId = mAmWmState.getAmState().getFocusedStackId();
+            final ActivityStack newFrontStack =
+                    mAmWmState.getAmState().getStackById(newFrontStackId);
+            assertTrue("Launched activity must be on the same display",
+                    newDisplay.mId == newFrontStack.mDisplayId);
             assertEquals("Launched activity must be resumed",
                     getActivityName(NON_RESIZEABLE_ACTIVITY),
-                    frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on a just launched activity",
-                    frontStackId);
+                    newFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack(
+                    "Top stack must be the one with just launched activity",
+                    newFrontStackId);
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(newDisplay.mId, LAUNCHING_ACTIVITY);
+                        put(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY);
+                    }}
+            );
         }
     }
 
@@ -365,7 +460,7 @@
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
 
             // Try to launch an activity and check it security exception was triggered.
             getLaunchActivityBuilder()
@@ -375,7 +470,7 @@
                     .setTargetActivity(TEST_ACTIVITY)
                     .execute();
 
-            assertSecurityException("ActivityLauncher", logSeparator);
+            assertSecurityExceptionFromActivityLauncher();
 
             mAmWmState.computeState(TEST_ACTIVITY);
             assertFalse("Restricted activity must not be launched",
@@ -388,6 +483,7 @@
      * for activities with same UID.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -403,7 +499,7 @@
             mAmWmState.waitForValidState(TEST_ACTIVITY);
 
             final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            final ActivityManagerState.ActivityStack focusedStack =
+            final ActivityStack focusedStack =
                     mAmWmState.getAmState().getStackById(externalFocusedStackId);
             assertEquals("Focused stack must be on secondary display", newDisplay.mId,
                     focusedStack.mDisplayId);
@@ -428,26 +524,22 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(TEST_ACTIVITY);
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    TEST_ACTIVITY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be on top");
 
             // Launch second activity without specifying display.
             launchActivity(LAUNCHING_ACTIVITY);
-            mAmWmState.computeState(LAUNCHING_ACTIVITY);
 
             // Check that activity is launched in focused stack on primary display.
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    LAUNCHING_ACTIVITY);
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
-            assertEquals("Front stack must be on primary display",
-                    DEFAULT_DISPLAY, frontStack.mDisplayId);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+                    "Launched activity must be focused");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                        put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY);
+                    }}
+            );
         }
     }
 
@@ -456,6 +548,7 @@
      * first one - it must appear on the secondary display, because it was launched from there.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new simulated display.
@@ -464,24 +557,16 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be resumed",
-                    LAUNCHING_ACTIVITY);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be on top");
 
             // Launch second activity from app on secondary display without specifying display id.
             getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
-            mAmWmState.computeState(TEST_ACTIVITY);
 
             // Check that activity is launched in focused stack on external display.
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    TEST_ACTIVITY);
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
         }
     }
 
@@ -497,24 +582,17 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(LAUNCHING_ACTIVITY);
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be resumed",
-                    LAUNCHING_ACTIVITY);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be on top");
 
             // Launch second activity from app on secondary display without specifying display id.
             getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
             mAmWmState.computeState(TEST_ACTIVITY);
 
             // Check that activity is launched in focused stack on external display.
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    TEST_ACTIVITY);
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack frontStack = mAmWmState.getAmState()
-                    .getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
         }
     }
 
@@ -523,6 +601,7 @@
      * first one with specifying the target display - it must appear on the secondary display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -530,26 +609,19 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(LAUNCHING_ACTIVITY);
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be resumed",
-                    LAUNCHING_ACTIVITY);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be on top");
 
             // Launch second activity from app on secondary display specifying same display id.
             getLaunchActivityBuilder()
                     .setTargetActivity(SECOND_ACTIVITY)
                     .setDisplayId(newDisplay.mId)
                     .execute();
-            mAmWmState.computeState(TEST_ACTIVITY);
 
             // Check that activity is launched in focused stack on external display.
-            mAmWmState.assertFocusedActivity("Launched activity must be focused", SECOND_ACTIVITY);
-            int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(SECOND_ACTIVITY), frontStack.mResumedActivity);
+            waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
 
             // Launch other activity with different uid and check if it has launched successfully.
             getLaunchActivityBuilder()
@@ -558,14 +630,10 @@
                     .setDisplayId(newDisplay.mId)
                     .setTargetActivity(THIRD_ACTIVITY)
                     .execute();
-            mAmWmState.waitForValidState(THIRD_ACTIVITY);
 
             // Check that activity is launched in focused stack on external display.
-            mAmWmState.assertFocusedActivity("Launched activity must be focused", THIRD_ACTIVITY);
-            frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(THIRD_ACTIVITY), frontStack.mResumedActivity);
+            waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
         }
     }
 
@@ -574,6 +642,7 @@
      * doesn't allow embedding - it should fail with security exception.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -581,13 +650,11 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(LAUNCHING_ACTIVITY);
 
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be resumed",
-                    LAUNCHING_ACTIVITY);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be resumed");
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
 
             // Launch second activity from app on secondary display specifying same display id.
             getLaunchActivityBuilder()
@@ -595,7 +662,7 @@
                     .setDisplayId(newDisplay.mId)
                     .execute();
 
-            assertSecurityException("ActivityLauncher", logSeparator);
+            assertSecurityExceptionFromActivityLauncher();
         }
     }
 
@@ -603,6 +670,7 @@
      * Tests launching an activity to secondary display from activity on primary display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
         // Start launching activity.
         launchActivity(LAUNCHING_ACTIVITY);
@@ -617,19 +685,266 @@
                     .setDisplayId(newDisplay.mId).execute();
 
             // Check that activity is launched on external display.
-            mAmWmState.computeState(TEST_ACTIVITY);
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    TEST_ACTIVITY);
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack frontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Launched activity must be resumed in front stack",
-                    getActivityName(TEST_ACTIVITY), frontStack.mResumedActivity);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be focused");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
         }
     }
 
     /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for simulated display. It is owned by system and is public, so should be accessible.
+     */
+    @Test
+    public void testCanAccessSystemOwnedDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY);
+
+            assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                    newDisplay.mId, intent));
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a public virtual display and an activity that doesn't support embedding from shell.
+     */
+    @Test
+    public void testCanAccessPublicVirtualDisplayWithInternalPermission() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW)
+                    .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
+
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                            newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private virtual display and an activity that doesn't support embedding from shell.
+     */
+    @Test
+    public void testCanAccessPrivateVirtualDisplayWithInternalPermission() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW)
+                    .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
+
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                            newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a public virtual display, an activity that supports embedding but the launching entity
+     * does not have required permission to embed an activity from other app.
+     */
+    @Test
+    public void testCantAccessPublicVirtualDisplayNoEmbeddingPermission() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
+
+            assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                    newDisplay.mId, intent));
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a public virtual display and an activity that does not support embedding.
+     */
+    @Test
+    public void testCantAccessPublicVirtualDisplayActivityEmbeddingNotAllowed() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW)
+                    .setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
+
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                            newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a public virtual display and an activity that supports embedding.
+     */
+    @Test
+    public void testCanAccessPublicVirtualDisplayActivityEmbeddingAllowed() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW)
+                    .setComponent(SECOND_ACTIVITY);
+
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                            newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private virtual display.
+     */
+    @Test
+    public void testCantAccessPrivateVirtualDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
+                    .createDisplay();
+
+            final Context targetContext = InstrumentationRegistry.getTargetContext();
+            final ActivityManager activityManager =
+                    (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE);
+            final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
+
+            assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext,
+                    newDisplay.mId, intent));
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private virtual display to check the start of its own activity.
+     */
+    @Test
+    public void testCanAccessPrivateVirtualDisplayByOwner() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
+                    .createDisplay();
+
+            // Check the embedding call
+            separateTestJournal();
+            mContext.sendBroadcast(new Intent(ACTION_TEST_ACTIVITY_START)
+                    .setPackage(LAUNCH_BROADCAST_RECEIVER.getPackageName())
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                    .putExtra(EXTRA_COMPONENT_NAME, TEST_ACTIVITY)
+                    .putExtra(EXTRA_TARGET_DISPLAY, newDisplay.mId));
+
+            assertActivityStartCheckResult(true);
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private virtual display by UID present on that display and target activity that allows
+     * embedding.
+     */
+    @Test
+    public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingAllowed()
+            throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
+                    .createDisplay();
+            // Launch a test activity into the target display
+            launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
+
+            // Check the embedding call
+            separateTestJournal();
+            mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                    .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_ACTIVITY)
+                    .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
+
+            assertActivityStartCheckResult(true);
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private virtual display by UID present on that display and target activity that does
+     * not allow embedding.
+     */
+    @Test
+    public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingNotAllowed()
+            throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
+                    .createDisplay();
+            // Launch a test activity into the target display
+            launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
+
+            // Check the embedding call
+            separateTestJournal();
+            mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                    .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_NO_EMBEDDING_ACTIVITY)
+                    .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
+
+            assertActivityStartCheckResult(false);
+        }
+    }
+
+    private void assertActivityStartCheckResult(boolean expected) {
+        final String component = ActivityLauncher.TAG;
+        for (int retry = 1; retry <= 5; retry++) {
+            final Bundle extras = 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);
+        }
+        fail("Expected activity start check from " + component + " not found");
+    }
+
+    /**
      * Tests launching activities on secondary and then on primary display to see if the stack
      * visibility is not affected.
      */
@@ -654,6 +969,12 @@
             mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY);
             mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
             mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
         }
     }
 
@@ -666,31 +987,30 @@
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+            mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
                     VIRTUAL_DISPLAY_ACTIVITY);
             final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
-            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+            ActivityStack frontStack = mAmWmState.getAmState().getStackById(
                     defaultDisplayStackId);
-            assertEquals("Focus must remain on primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            assertEquals("Top stack must remain on primary display",
+                    DEFAULT_DISPLAY, frontStack.mDisplayId);
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    TEST_ACTIVITY);
-            int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
-            assertEquals("Focused stack must be on secondary display",
-                    newDisplay.mId, focusedStack.mDisplayId);
+
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Top activity must be on secondary display");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
 
             // Move activity from secondary display to primary.
             moveActivityToStack(TEST_ACTIVITY, defaultDisplayStackId);
-            mAmWmState.waitForFocusedStack(defaultDisplayStackId);
-            mAmWmState.assertFocusedActivity("Focus must be on moved activity", TEST_ACTIVITY);
-            focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
-            assertEquals("Focus must return to primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Moved activity must be on top");
         }
     }
 
@@ -700,6 +1020,7 @@
      * This version launches virtual display creator to fullscreen stack in split-screen.
      */
     @Test
+    @FlakyTest(bugId = 77207453)
     public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
@@ -719,6 +1040,7 @@
      * This version launches virtual display creator to docked stack in split-screen.
      */
     @Test
+    @FlakyTest(bugId = 77207453)
     public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
@@ -744,7 +1066,7 @@
         final int focusedStackWindowingMode = mAmWmState.getAmState().getFrontStackWindowingMode(
                 DEFAULT_DISPLAY);
         // Finish probing activity.
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
         tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */,
                 focusedStackWindowingMode);
@@ -756,7 +1078,6 @@
      */
     private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode)
             throws Exception {
-        LogSeparator logSeparator;
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession
@@ -770,17 +1091,17 @@
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    RESIZEABLE_ACTIVITY);
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Top activity must be on secondary display");
             final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            mAmWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId);
 
+            separateTestJournal();
             // Destroy virtual display.
-            logSeparator = separateLogs();
         }
 
         mAmWmState.computeState(true);
-        assertActivityLifecycle(RESIZEABLE_ACTIVITY, false /* relaunched */, logSeparator);
+        assertActivityLifecycle(RESIZEABLE_ACTIVITY, false /* relaunched */);
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(RESIZEABLE_ACTIVITY)
                 .setWindowingMode(windowingMode)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -788,7 +1109,7 @@
         mAmWmState.assertSanity();
         mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */);
 
-        // Check if the focus is switched back to primary display.
+        // Check if the top activity is now back on primary display.
         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
         mAmWmState.assertFocusedStack(
                 "Default stack on primary display must be focused after display removed",
@@ -803,64 +1124,90 @@
      * is moved correctly.
      */
     @Test
-    public void testStackFocusSwitchOnStackEmptied() throws Exception {
+    @FlakyTest(bugId = 112055644)
+    public void testStackFocusSwitchOnStackEmptiedInSleeping() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
              final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            // Create new virtual display.
-            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
-            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-            final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
+            validateStackFocusSwitchOnStackEmptied(virtualDisplaySession, lockScreenSession);
+        }
+    }
 
-            // Launch activity on new secondary display.
-            launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    BROADCAST_RECEIVER_ACTIVITY);
+    /**
+     * Tests launching activities on secondary display and then finishing it to see if stack focus
+     * is moved correctly.
+     */
+    @Test
+    @FlakyTest(bugId = 112055644)
+    public void testStackFocusSwitchOnStackEmptied() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            validateStackFocusSwitchOnStackEmptied(virtualDisplaySession,
+                    null /* lockScreenSession */);
+        }
+    }
 
+    private void validateStackFocusSwitchOnStackEmptied(VirtualDisplaySession virtualDisplaySession,
+            LockScreenSession lockScreenSession) throws Exception {
+        // Create new virtual display.
+        final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+        mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+        final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
+
+        // Launch activity on new secondary display.
+        launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+        waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+                "Top activity must be on secondary display");
+
+        if (lockScreenSession != null) {
             // Lock the device, so that activity containers will be detached.
             lockScreenSession.sleepDevice();
-
-            // Finish activity on secondary display.
-            executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
-            // Unlock and check if the focus is switched back to primary display.
-            lockScreenSession.wakeUpDevice()
-                    .unlockDevice();
-            mAmWmState.waitForFocusedStack(focusedStackId);
-            mAmWmState.waitForValidState(VIRTUAL_DISPLAY_ACTIVITY);
-            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
-                    VIRTUAL_DISPLAY_ACTIVITY);
         }
+
+        // Finish activity on secondary display.
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+
+        if (lockScreenSession != null) {
+            // Unlock and check if the focus is switched back to primary display.
+            lockScreenSession.wakeUpDevice().unlockDevice();
+        }
+
+        waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
+                "Top activity must be switched back to primary display");
     }
 
     /**
      * Tests that input events on the primary display take focus from the virtual display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testStackFocusSwitchOnTouchEvent() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
 
             mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY);
-            mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+            mAmWmState.assertFocusedActivity("Top activity must be the latest launched one",
                     VIRTUAL_DISPLAY_ACTIVITY);
 
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
 
-            mAmWmState.computeState(TEST_ACTIVITY);
-            mAmWmState.assertFocusedActivity(
-                    "Activity launched on secondary display must be focused",
-                    TEST_ACTIVITY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be on top");
 
-            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
             final int width = displayMetrics.getSize().getWidth();
             final int height = displayMetrics.getSize().getHeight();
-            executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
+            tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
 
-            mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY);
-            mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
-                    VIRTUAL_DISPLAY_ACTIVITY);
+            waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
+                    "Top activity must be on the primary display");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
+            mAmWmState.assertFocusedAppOnDisplay("App on secondary display must still be focused",
+                    TEST_ACTIVITY, newDisplay.mId);
         }
     }
 
@@ -871,22 +1218,25 @@
             // Create new virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+            mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
                     VIRTUAL_DISPLAY_ACTIVITY);
             final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+            ActivityStack frontStack = mAmWmState.getAmState().getStackById(
                     defaultDisplayFocusedStackId);
-            assertEquals("Focus must remain on primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            assertEquals("Top stack must remain on primary display",
+                    DEFAULT_DISPLAY, frontStack.mDisplayId);
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    TEST_ACTIVITY);
-            final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
-                    focusedStack.mDisplayId);
+
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Front activity must be on secondary display");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
 
             // Launch other activity with different uid and check it is launched on dynamic stack on
             // secondary display.
@@ -894,16 +1244,61 @@
                             + " --display " + newDisplay.mId;
             executeShellCommand(startCmd);
 
+            waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+                    "Focus must be on newly launched app");
+            mAmWmState.assertResumedActivities("Both displays must have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
+                        put(newDisplay.mId, SECOND_ACTIVITY);
+                    }}
+            );
+        }
+    }
+
+    /** Test that launching app from pending activity queue on external display is allowed. */
+    @Test
+    @FlakyTest(bugId = 118708868)
+    public void testLaunchPendingActivityOnSecondaryDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+            final Bundle bundle = ActivityOptions.makeBasic().
+                    setLaunchDisplayId(newDisplay.mId).toBundle();
+            final Intent intent = new Intent(Intent.ACTION_VIEW)
+                    .setComponent(SECOND_ACTIVITY)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                    .putExtra(KEY_LAUNCH_ACTIVITY, true)
+                    .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.
+            // 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
+            // ActivityTaskManagerService.APP_SWITCH_DELAY_TIME
+            mAmWmState.waitForPendingActivityContain(SECOND_ACTIVITY);
+            // If the activity is not pending, skip this test.
+            mAmWmState.assumePendingActivityContain(SECOND_ACTIVITY);
+            // In order to speed up test case without waiting for APP_SWITCH_DELAY_TIME, we launch
+            // another activity with LaunchActivityBuilder, in this way the activity can be start
+            // directly and also trigger pending activity to be launched.
+            getLaunchActivityBuilder()
+                    .setTargetActivity(THIRD_ACTIVITY)
+                    .execute();
             mAmWmState.waitForValidState(SECOND_ACTIVITY);
-            mAmWmState.assertFocusedActivity(
-                    "Focus must be on newly launched app", SECOND_ACTIVITY);
-            assertEquals("Activity launched by system must be on external display",
-                    externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+            waitAndAssertTopResumedActivity(THIRD_ACTIVITY, DEFAULT_DISPLAY,
+                    "Top activity must be the newly launched one");
+            mAmWmState.assertVisibility(SECOND_ACTIVITY, true);
+            assertEquals("Activity launched by app on secondary display must be on that display",
+                    newDisplay.mId, mAmWmState.getAmState().getDisplayByActivity(SECOND_ACTIVITY));
         }
     }
 
     /** Test that launching from app that is on external display is allowed. */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testPermissionLaunchFromAppOnSecondary() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new simulated display.
@@ -915,14 +1310,8 @@
                     + " --display " + newDisplay.mId;
             executeShellCommand(startCmd);
 
-            mAmWmState.waitForValidState(SECOND_ACTIVITY);
-            mAmWmState.assertFocusedActivity(
-                    "Focus must be on newly launched app", SECOND_ACTIVITY);
-            final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            ActivityManagerState.ActivityStack focusedStack =
-                    mAmWmState.getAmState().getStackById(externalFocusedStackId);
-            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
-                    focusedStack.mDisplayId);
+            waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
 
             // Launch another activity with third different uid from app on secondary display and
             // check it is launched on secondary display.
@@ -933,10 +1322,8 @@
                     .setTargetActivity(THIRD_ACTIVITY)
                     .execute();
 
-            mAmWmState.waitForValidState(THIRD_ACTIVITY);
-            mAmWmState.assertFocusedActivity("Focus must be on newly launched app", THIRD_ACTIVITY);
-            assertEquals("Activity launched by app on secondary display must be on that display",
-                    externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+            waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
         }
     }
 
@@ -952,20 +1339,20 @@
 
             // Check that the first activity is launched onto the secondary display
             final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            ActivityManagerState.ActivityStack frontStack = mAmWmState.getAmState().getStackById(
+            ActivityStack frontStack = mAmWmState.getAmState().getStackById(
                     frontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(LAUNCHING_ACTIVITY),
                     frontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            mAmWmState.assertFocusedStack("Top stack must be on secondary display",
+                    frontStackId);
 
             // Launch an activity from a different UID into the first activity's task
             getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute();
 
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
             frontStack = mAmWmState.getAmState().getStackById(frontStackId);
-            mAmWmState.assertFocusedActivity(
-                    "Focus must be on newly launched app", SECOND_ACTIVITY);
             assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
         }
     }
@@ -975,6 +1362,7 @@
      * doesn't have anything on the display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testPermissionLaunchFromOwner() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -983,23 +1371,18 @@
             mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
                     VIRTUAL_DISPLAY_ACTIVITY);
             final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            ActivityManagerState.ActivityStack focusedStack =
+            ActivityStack frontStack =
                     mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
-            assertEquals("Focus must remain on primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            assertEquals("Top stack must remain on primary display",
+                    DEFAULT_DISPLAY, frontStack.mDisplayId);
 
             // Launch other activity with different uid on secondary display.
             final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
                     + " --display " + newDisplay.mId;
             executeShellCommand(startCmd);
 
-            mAmWmState.waitForValidState(SECOND_ACTIVITY);
-            mAmWmState.assertFocusedActivity(
-                    "Focus must be on newly launched app", SECOND_ACTIVITY);
-            final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
-                    focusedStack.mDisplayId);
+            waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
 
             // Check that owner uid can launch its own activity on secondary display.
             getLaunchActivityBuilder()
@@ -1009,11 +1392,8 @@
                     .setDisplayId(newDisplay.mId)
                     .execute();
 
-            mAmWmState.waitForValidState(TEST_ACTIVITY);
-            mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
-                    TEST_ACTIVITY);
-            assertEquals("Activity launched by owner must be on external display",
-                    externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
         }
     }
 
@@ -1030,21 +1410,17 @@
             mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
                     VIRTUAL_DISPLAY_ACTIVITY);
             final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+            ActivityStack frontStack = mAmWmState.getAmState().getStackById(
                     defaultDisplayFocusedStackId);
-            assertEquals("Focus must remain on primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            assertEquals("Top stack must remain on primary display",
+                    DEFAULT_DISPLAY, frontStack.mDisplayId);
 
             // Launch activity on new secondary display.
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    TEST_ACTIVITY);
-            final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
-            assertEquals("Focused stack must be on secondary display", newDisplay.mId,
-                    focusedStack.mDisplayId);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
 
             // Launch other activity with different uid and check security exception is triggered.
             getLaunchActivityBuilder()
@@ -1054,39 +1430,32 @@
                     .setTargetActivity(THIRD_ACTIVITY)
                     .execute();
 
-            assertSecurityException("ActivityLauncher", logSeparator);
+            assertSecurityExceptionFromActivityLauncher();
 
             mAmWmState.waitForValidState(TEST_ACTIVITY);
-            mAmWmState.assertFocusedActivity("Focus must be on first activity", TEST_ACTIVITY);
-            assertEquals("Focused stack must be on secondary display's stack",
-                    externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+            mAmWmState.assertFocusedActivity("Top activity must be the first one launched",
+                    TEST_ACTIVITY);
         }
     }
 
-    private void assertSecurityException(String component, LogSeparator logSeparator)
-            throws Exception {
-        final Pattern pattern = Pattern.compile(".*SecurityException launching activity.*");
+    private void assertSecurityExceptionFromActivityLauncher() {
+        final String component = ActivityLauncher.TAG;
         for (int retry = 1; retry <= 5; retry++) {
-            String[] logs = getDeviceLogsForComponents(logSeparator, component);
-            for (String line : logs) {
-                Matcher m = pattern.matcher(line);
-                if (m.matches()) {
-                    return;
-                }
+            if (ActivityLauncher.hasCaughtSecurityException()) {
+                return;
             }
-            logAlways("***Waiting for SecurityException for " + component + " ... retry=" + retry);
-            try {
-                Thread.sleep(500);
-            } catch (InterruptedException e) {
-            }
+
+            logAlways("***Waiting for SecurityException from " + component + " ... retry=" + retry);
+            SystemClock.sleep(500);
         }
-        fail("Expected exception for " + component + " not found");
+        fail("Expected exception from " + component + " not found");
     }
 
     /**
      * Test that only private virtual display can show content with insecure keyguard.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Try to create new show-with-insecure-keyguard public virtual display.
@@ -1106,7 +1475,6 @@
      */
     @Test
     public void testContentDestroyOnDisplayRemoved() throws Exception {
-        LogSeparator logSeparator;
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new private virtual display.
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
@@ -1114,26 +1482,19 @@
 
             // Launch activities on new secondary display.
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    TEST_ACTIVITY);
-            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    RESIZEABLE_ACTIVITY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
 
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
+
+            separateTestJournal();
             // Destroy the display and check if activities are removed from system.
-            logSeparator = separateLogs();
         }
 
-        mAmWmState.waitForWithAmState(
-                (state) -> !state.containsActivity(TEST_ACTIVITY)
-                        && !state.containsActivity(RESIZEABLE_ACTIVITY),
-                "Waiting for activity to be removed");
-        mAmWmState.waitForWithWmState(
-                (state) -> !state.containsWindow(getWindowName(TEST_ACTIVITY))
-                        && !state.containsWindow(getWindowName(RESIZEABLE_ACTIVITY)),
-                "Waiting for activity window to be gone");
+        mAmWmState.waitForActivityRemoved(TEST_ACTIVITY);
+        mAmWmState.waitForActivityRemoved(RESIZEABLE_ACTIVITY);
 
         // Check AM state.
         assertFalse("Activity from removed display must be destroyed",
@@ -1146,8 +1507,33 @@
         assertFalse("Activity windows from removed display must be destroyed",
                 mAmWmState.getWmState().containsWindow(getWindowName(RESIZEABLE_ACTIVITY)));
         // Check activity logs.
-        assertActivityDestroyed(TEST_ACTIVITY, logSeparator);
-        assertActivityDestroyed(RESIZEABLE_ACTIVITY, logSeparator);
+        assertActivityDestroyed(TEST_ACTIVITY);
+        assertActivityDestroyed(RESIZEABLE_ACTIVITY);
+    }
+
+    /**
+     * Test that newly launched activity will be landing on default display on display removal.
+     */
+    @Test
+    public void testActivityLaunchOnContentDestroyDisplayRemoved() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new private virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+            // Launch activities on new secondary display.
+            getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
+                    .setTargetActivity(LAUNCH_TEST_ON_DESTROY_ACTIVITY).setNewTask(true)
+                    .setMultipleTask(true).setDisplayId(newDisplay.mId).execute();
+
+            waitAndAssertTopResumedActivity(LAUNCH_TEST_ON_DESTROY_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
+
+            // Destroy the display
+        }
+
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Newly launches activity should be landing on default display");
     }
 
     /**
@@ -1161,23 +1547,21 @@
             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
 
             // Launch a resizeable activity on new secondary display.
-            final LogSeparator initialLogSeparator = separateLogs();
+            separateTestJournal();
             launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
-            mAmWmState.assertFocusedActivity("Launched activity must be focused",
-                    RESIZEABLE_ACTIVITY);
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Launched activity must be on top");
 
             // Grab reported sizes and compute new with slight size change.
-            final ReportedSizes initialSize = getLastReportedSizesForActivity(
-                    RESIZEABLE_ACTIVITY, initialLogSeparator);
+            final SizeInfo initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
 
             // Resize the display
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             virtualDisplaySession.resizeDisplay();
 
             mAmWmState.waitForWithAmState(amState -> {
                 try {
-                    return readConfigChangeNumber(RESIZEABLE_ACTIVITY, logSeparator) == 1
+                    return readConfigChangeNumber(RESIZEABLE_ACTIVITY) == 1
                             && amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
                 } catch (Exception e) {
                     logE("Error waiting for valid state: " + e.getMessage());
@@ -1193,10 +1577,9 @@
 
             // Check if activity in virtual display was resized properly.
             assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
-                    1 /* numConfigChange */, logSeparator);
+                    1 /* numConfigChange */);
 
-            final ReportedSizes updatedSize = getLastReportedSizesForActivity(
-                    RESIZEABLE_ACTIVITY, logSeparator);
+            final SizeInfo updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
             assertTrue(updatedSize.widthDp <= initialSize.widthDp);
             assertTrue(updatedSize.heightDp <= initialSize.heightDp);
             assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
@@ -1205,9 +1588,10 @@
     }
 
     /** Read the number of configuration changes sent to activity from logs. */
-    private int readConfigChangeNumber(ComponentName activityName, LogSeparator logSeparator)
+    private int readConfigChangeNumber(ComponentName activityName)
             throws Exception {
-        return (new ActivityLifecycleCounts(activityName, logSeparator)).mConfigurationChangedCount;
+        return (new ActivityLifecycleCounts(activityName))
+                .getCount(ActivityCallback.ON_CONFIGURATION_CHANGED);
     }
 
     /**
@@ -1215,6 +1599,7 @@
      * matching task on some other display - that task will moved to the target display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testMoveToDisplayOnLaunch() throws Exception {
         // Launch activity with unique affinity, so it will the only one in its task.
         launchActivity(LAUNCHING_ACTIVITY);
@@ -1230,9 +1615,8 @@
 
             final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
                     .mStacks.size();
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final int taskNumOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
-                    .getTasks().size();
+            final int stackNumOnSecondary = mAmWmState.getAmState()
+                    .getDisplay(newDisplay.mId).mStacks.size();
 
             // Launch activity on new secondary display.
             // Using custom command here, because normally we add flags
@@ -1242,30 +1626,22 @@
             final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY)
                     + " --display " + newDisplay.mId;
             executeShellCommand(launchCommand);
-            mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_RESUMED);
 
             // Check that activity is brought to front.
-            mAmWmState.assertFocusedActivity("Existing task must be brought to front",
-                    LAUNCHING_ACTIVITY);
-            mAmWmState.assertResumedActivity("Existing task must be resumed",
-                    LAUNCHING_ACTIVITY);
-
-            // Check that activity is on the right display.
-            final ActivityManagerState.ActivityStack firstFrontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Activity must be moved to the secondary display",
-                    getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Existing task must be brought to front");
 
             // Check that task has moved from primary display to secondary.
+            // Since it is 1-to-1 relationship between task and stack for standard type &
+            // fullscreen activity, we check the number of stacks here
             final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
                     .mStacks.size();
             assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
                     stackNumFinal);
-            final int taskNumFinalOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
-                    .getTasks().size();
-            assertEquals("Task number in stack on external display must be incremented.",
-                    taskNumOnSecondary + 1, taskNumFinalOnSecondary);
+            final int stackNumFinalOnSecondary = mAmWmState.getAmState()
+                    .getDisplay(newDisplay.mId).mStacks.size();
+            assertEquals("Stack number on external display must be incremented.",
+                    stackNumOnSecondary + 1, stackNumFinalOnSecondary);
         }
     }
 
@@ -1275,8 +1651,10 @@
      */
     @Test
     public void testMoveToEmptyDisplayOnLaunch() throws Exception {
-        // Launch activity with unique affinity, so it will the only one in its task.
-        launchActivity(LAUNCHING_ACTIVITY);
+        // Launch activity with unique affinity, so it will the only one in its task. And choose
+        // resizeable activity to prevent the test activity be relaunched when launch it to another
+        // display, which may affect on this test case.
+        launchActivity(RESIZEABLE_ACTIVITY);
 
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create new virtual display.
@@ -1290,24 +1668,13 @@
             // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
             // when launching on some specific display. We don't do it here as we want an existing
             // task to be used.
-            final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY)
+            final String launchCommand = "am start -n " + getActivityName(RESIZEABLE_ACTIVITY)
                     + " --display " + newDisplay.mId;
             executeShellCommand(launchCommand);
-            mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_RESUMED);
 
             // Check that activity is brought to front.
-            mAmWmState.assertFocusedActivity("Existing task must be brought to front",
-                    LAUNCHING_ACTIVITY);
-            mAmWmState.assertResumedActivity("Existing task must be resumed",
-                    LAUNCHING_ACTIVITY);
-
-            // Check that activity is on the right display.
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack firstFrontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Activity must be moved to the secondary display",
-                    getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Existing task must be brought to front");
 
             // Check that task has moved from primary display to secondary.
             final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)
@@ -1322,54 +1689,49 @@
      */
     @Test
     public void testRotationNotAffectingSecondaryScreen() throws Exception {
-        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+        try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) {
             // Create new virtual display.
-            final ActivityDisplay newDisplay = virtualDisplaySession.setResizeDisplay(false)
+            final ActivityDisplay newDisplay = virtualLauncher.setResizeDisplay(false)
                     .createDisplay();
             mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
 
             // Launch activity on new secondary display.
-            LogSeparator logSeparator = separateLogs();
-            launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId);
-            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                    RESIZEABLE_ACTIVITY);
-            final ReportedSizes initialSizes = getLastReportedSizesForActivity(
-                    RESIZEABLE_ACTIVITY, logSeparator);
-            assertNotNull("Test activity must have reported initial sizes on launch", initialSizes);
+            final ActivitySession resizeableActivitySession =
+                    virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay);
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId,
+                    "Top activity must be on secondary display");
+            final SizeInfo initialSize = resizeableActivitySession.getConfigInfo().sizeInfo;
+
+            assertNotNull("Test activity must have reported initial size on launch", initialSize);
 
             try (final RotationSession rotationSession = new RotationSession()) {
                 // Rotate primary display and check that activity on secondary display is not
                 // affected.
-
-                rotateAndCheckSameSizes(rotationSession, RESIZEABLE_ACTIVITY);
+                rotateAndCheckSameSizes(rotationSession, resizeableActivitySession, initialSize);
 
                 // Launch activity to secondary display when primary one is rotated.
                 final int initialRotation = mAmWmState.getWmState().getRotation();
                 rotationSession.set((initialRotation + 1) % 4);
 
-                logSeparator = separateLogs();
-                launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
-                mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
-                mAmWmState.assertFocusedActivity("Focus must be on secondary display",
-                        TEST_ACTIVITY);
-                final ReportedSizes testActivitySizes = getLastReportedSizesForActivity(
-                        TEST_ACTIVITY, logSeparator);
-                assertEquals(
-                        "Sizes of secondary display must not change after rotation of primary "
-                                + "display",
-                        initialSizes, testActivitySizes);
+                final ActivitySession testActivitySession =
+                        virtualLauncher.launchActivityOnDisplay(TEST_ACTIVITY, newDisplay);
+                waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                        "Top activity must be on secondary display");
+                final SizeInfo testActivitySize = testActivitySession.getConfigInfo().sizeInfo;
+
+                assertEquals("Sizes of secondary display must not change after rotation of primary"
+                        + " display", initialSize, testActivitySize);
             }
         }
     }
 
-    private void rotateAndCheckSameSizes(
-            RotationSession rotationSession, ComponentName activityName) throws Exception {
+    private void rotateAndCheckSameSizes(RotationSession rotationSession,
+            ActivitySession activitySession, SizeInfo initialSize) throws Exception {
         for (int rotation = 3; rotation >= 0; --rotation) {
-            final LogSeparator logSeparator = separateLogs();
             rotationSession.set(rotation);
-            final ReportedSizes rotatedSizes = getLastReportedSizesForActivity(activityName,
-                    logSeparator);
-            assertNull("Sizes must not change after rotation", rotatedSizes);
+            final SizeInfo rotatedSize = activitySession.getConfigInfo().sizeInfo;
+
+            assertEquals("Sizes must not change after rotation", initialSize, rotatedSize);
         }
     }
 
@@ -1378,6 +1740,7 @@
      * matching the task component root does.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testTaskMatchAcrossDisplays() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
@@ -1387,11 +1750,12 @@
 
             // Check that activity is on the secondary display.
             final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack firstFrontStack =
+            final ActivityStack firstFrontStack =
                     mAmWmState.getAmState().getStackById(frontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            mAmWmState.assertFocusedStack("Top stack must be on secondary display",
+                    frontStackId);
 
             executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY));
             mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
@@ -1400,12 +1764,12 @@
             // the affinity match on the secondary display.
             final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
                     DEFAULT_DISPLAY);
-            final ActivityManagerState.ActivityStack defaultDisplayFrontStack =
+            final ActivityStack defaultDisplayFrontStack =
                     mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
             assertEquals("Activity launched on default display must be resumed",
                     getActivityName(ALT_LAUNCHING_ACTIVITY),
                     defaultDisplayFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on primary display",
+            mAmWmState.assertFocusedStack("Top stack must be on primary display",
                     defaultDisplayFrontStackId);
 
             executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY));
@@ -1413,22 +1777,54 @@
 
             // Check that the third intent is redirected to the first task due to the root
             // component match on the secondary display.
-            final ActivityManagerState.ActivityStack secondFrontStack =
+            final ActivityStack secondFrontStack =
                     mAmWmState.getAmState().getStackById(frontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on primary display", frontStackId);
-            assertEquals("Focused stack must only contain 1 task",
+            mAmWmState.assertFocusedStack("Top stack must be on primary display", frontStackId);
+            assertEquals("Top stack must only contain 1 task",
                     1, secondFrontStack.getTasks().size());
-            assertEquals("Focused task must only contain 1 activity",
+            assertEquals("Top task must only contain 1 activity",
                     1, secondFrontStack.getTasks().get(0).mActivities.size());
         }
     }
 
     /**
+     * Tests that an activity is launched on the preferred display where the caller resided when
+     * both displays have matching tasks.
+     */
+    @Test
+    public void testTaskMatchOrderAcrossDisplays() throws Exception {
+        getLaunchActivityBuilder().setUseInstrumentation()
+                .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                .setDisplayId(DEFAULT_DISPLAY).execute();
+        final int stackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY);
+
+        getLaunchActivityBuilder().setUseInstrumentation()
+                .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true)
+                .setDisplayId(DEFAULT_DISPLAY).execute();
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+            getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true)
+                    .setTargetActivity(TEST_ACTIVITY).setNewTask(true)
+                    .setDisplayId(newDisplay.mId).execute();
+            assertNotEquals("Top focus stack should not be on default display",
+                    stackId, mAmWmState.getAmState().getFocusedStackId());
+
+            mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY));
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity must be launched on default display");
+            mAmWmState.assertFocusedStack("Top focus stack must be on the default display",
+                    stackId);
+        }
+    }
+
+    /**
      * Tests that the task affinity search respects the launch display id.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchDisplayAffinityMatch() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
@@ -1437,7 +1833,7 @@
 
             // Check that activity is on the secondary display.
             final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack firstFrontStack =
+            final ActivityStack firstFrontStack =
                     mAmWmState.getAmState().getStackById(frontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
@@ -1453,66 +1849,63 @@
             // task on the secondary display
             final int secondFrontStackId =
                     mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack secondFrontStack =
+            final ActivityStack secondFrontStack =
                     mAmWmState.getAmState().getStackById(secondFrontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(ALT_LAUNCHING_ACTIVITY),
                     secondFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display",
+            mAmWmState.assertFocusedStack("Top stack must be on secondary display",
                     secondFrontStackId);
-            assertEquals("Focused stack must only contain 1 task",
+            assertEquals("Top stack must only contain 1 task",
                     1, secondFrontStack.getTasks().size());
-            assertEquals("Focused task must contain 2 activities",
+            assertEquals("Top stack task must contain 2 activities",
                     2, secondFrontStack.getTasks().get(0).mActivities.size());
         }
     }
 
     /**
-     * Tests than a new task launched by an activity will end up on that activity's display
+     * Tests that a new task launched by an activity will end up on that activity's display
      * even if the focused stack is not on that activity's display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testNewTaskSameDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
                     .createDisplay();
 
             launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
-            mAmWmState.computeState(BROADCAST_RECEIVER_ACTIVITY);
 
             // Check that the first activity is launched onto the secondary display
-            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack firstFrontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Activity launched on secondary display must be resumed",
-                    getActivityName(BROADCAST_RECEIVER_ACTIVITY),
-                    firstFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+                    "Activity launched on secondary display must be resumed");
 
             executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY));
-            mAmWmState.waitForValidState(TEST_ACTIVITY);
 
             // Check that the second activity is launched on the default display
-            final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
-            final ActivityManagerState.ActivityStack focusedStack =
-                    mAmWmState.getAmState().getStackById(focusedStackId);
-            assertEquals("Activity launched on default display must be resumed",
-                    getActivityName(TEST_ACTIVITY), focusedStack.mResumedActivity);
-            assertEquals("Focus must be on primary display",
-                    DEFAULT_DISPLAY, focusedStack.mDisplayId);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity launched on default display must be resumed");
+            mAmWmState.assertResumedActivities("Both displays should have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, TEST_ACTIVITY);
+                        put(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY);
+                    }}
+            );
 
-            executeShellCommand(LAUNCH_ACTIVITY_BROADCAST + getActivityName(LAUNCHING_ACTIVITY));
+            mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY));
 
-            // Check that the third activity ends up in a new task in the same stack as the
-            // first activity
-            mAmWmState.waitForValidState(LAUNCHING_ACTIVITY);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-            final ActivityManagerState.ActivityStack secondFrontStack =
-                    mAmWmState.getAmState().getStackById(frontStackId);
-            assertEquals("Activity must be launched on secondary display",
-                    getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
-            assertEquals("Secondary display must contain 2 tasks",
-                    2, secondFrontStack.getTasks().size());
+            // Check that the third activity ends up in a new stack in the same display where the
+            // first activity lands
+            waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                    "Activity must be launched on secondary display");
+            assertEquals("Secondary display must contain 2 stacks", 2,
+                    mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
+            mAmWmState.assertResumedActivities("Both displays should have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, TEST_ACTIVITY);
+                        put(newDisplay.mId, LAUNCHING_ACTIVITY);
+                    }}
+            );
         }
     }
 
@@ -1528,15 +1921,15 @@
                     .createDisplay();
 
             // Check that activity is launched and placed correctly.
-            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
-            mAmWmState.assertResumedActivity("Test activity must be launched on a new display",
-                    TEST_ACTIVITY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Test activity must be on top");
             final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
-            final ActivityManagerState.ActivityStack firstFrontStack =
+            final ActivityStack firstFrontStack =
                     mAmWmState.getAmState().getStackById(frontStackId);
             assertEquals("Activity launched on secondary display must be resumed",
                     getActivityName(TEST_ACTIVITY), firstFrontStack.mResumedActivity);
-            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+            mAmWmState.assertFocusedStack("Top stack must be on secondary display",
+                    frontStackId);
         }
     }
 
@@ -1548,7 +1941,7 @@
     public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
         // Launch something on the primary display so we know there is a resumed activity there
         launchActivity(RESIZEABLE_ACTIVITY);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+        waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity launched on primary display must be resumed");
 
         try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
@@ -1560,31 +1953,35 @@
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
 
             // Check that the activity is launched onto the external display
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
+            mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
+                    RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
 
-            final LogSeparator logSeparator = separateLogs();
-
+            separateTestJournal();
             displayStateSession.turnScreenOff();
 
             // Wait for the fullscreen stack to start sleeping, and then make sure the
             // test activity is still resumed.
             int retry = 0;
-            ActivityLifecycleCounts lifecycleCounts;
+            int stopCount = 0;
             do {
-                lifecycleCounts = new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY, logSeparator);
-                if (lifecycleCounts.mStopCount == 1) {
+                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 (lifecycleCounts.mStopCount != 1) {
-                fail(RESIZEABLE_ACTIVITY + " has received " + lifecycleCounts.mStopCount
+            if (stopCount != 1) {
+                fail(RESIZEABLE_ACTIVITY + " has received " + stopCount
                         + " onStop() calls, expecting 1");
             }
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+            // For this test we create this virtual display with flag showContentWhenLocked, so it
+            // cannot be effected when default display screen off.
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
         }
     }
@@ -1594,10 +1991,11 @@
      * display is off.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
         // Launch something on the primary display so we know there is a resumed activity there
         launchActivity(RESIZEABLE_ACTIVITY);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+        waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity launched on primary display must be resumed");
 
         try (final PrimaryDisplayStateSession displayStateSession =
@@ -1606,7 +2004,7 @@
             displayStateSession.turnScreenOff();
 
             // Make sure there is no resumed activity when the primary display is off
-            waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY,
+            waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
                     "Activity launched on primary display must be stopped after turning off");
             assertEquals("Unexpected resumed activity",
                     0, mAmWmState.getAmState().getResumedActivitiesCount());
@@ -1617,8 +2015,10 @@
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
 
             // Check that the test activity is resumed on the external display
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
+            mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused",
+                    RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY);
         }
     }
 
@@ -1634,19 +2034,19 @@
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
 
             // Check that the test activity is resumed on the external display
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
 
             externalDisplaySession.turnDisplayOff();
 
             // Check that turning off the external display stops the activity
-            waitAndAssertActivityStopped(TEST_ACTIVITY,
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
                     "Activity launched on external display must be stopped after turning off");
 
             externalDisplaySession.turnDisplayOn();
 
             // Check that turning on the external display resumes the activity
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
         }
     }
@@ -1656,10 +2056,11 @@
      * activity on the primary display.
      */
     @Test
+    @FlakyTest(bugId = 112055644)
     public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
         // Launch something on the primary display so we know there is a resumed activity there
         launchActivity(RESIZEABLE_ACTIVITY);
-        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+        waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity launched on primary display must be resumed");
 
         try (final LockScreenSession lockScreenSession = new LockScreenSession();
@@ -1667,7 +2068,7 @@
             lockScreenSession.sleepDevice();
 
             // Make sure there is no resumed activity when the primary display is off
-            waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY,
+            waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED,
                     "Activity launched on primary display must be stopped after turning off");
             assertEquals("Unexpected resumed activity",
                     0, mAmWmState.getAmState().getResumedActivitiesCount());
@@ -1677,53 +2078,41 @@
 
             launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
 
-            // Check that the test activity is resumed on the external display
-            waitAndAssertActivityResumed(TEST_ACTIVITY, newDisplay.mId,
-                    "Activity launched on external display must be resumed");
-
             // Unlock the device and tap on the middle of the primary display
             lockScreenSession.wakeUpDevice();
             executeShellCommand("wm dismiss-keyguard");
             mAmWmState.waitForKeyguardGone();
-            mAmWmState.waitForValidState(TEST_ACTIVITY);
-            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+            mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY, TEST_ACTIVITY);
+
+            // Check that the test activity is resumed on the external display and is on top
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Activity on external display must be resumed and on top");
+            mAmWmState.assertResumedActivities("Both displays should have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
+
+            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
             final int width = displayMetrics.getSize().getWidth();
             final int height = displayMetrics.getSize().getHeight();
-            executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
+            tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
 
-            // Check that the activity on the primary display is resumed
-            waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
-                    "Activity launched on primary display must be resumed");
-            assertEquals("Unexpected resumed activity",
-                    1, mAmWmState.getAmState().getResumedActivitiesCount());
+            // Check that the activity on the primary display is the topmost resumed
+            waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity on primary display must be resumed and on top");
+            mAmWmState.assertResumedActivities("Both displays should have resumed activities",
+                    new SparseArray<ComponentName>(){{
+                        put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY);
+                        put(newDisplay.mId, TEST_ACTIVITY);
+                    }}
+            );
+            mAmWmState.assertFocusedAppOnDisplay("App on external display must still be focused",
+                    TEST_ACTIVITY, newDisplay.mId);
         }
     }
 
-    private void waitAndAssertActivityResumed(
-            ComponentName activityName, int displayId, String message) throws Exception {
-        mAmWmState.waitForActivityState(activityName, STATE_RESUMED);
-
-        assertEquals(message,
-                getActivityName(activityName), mAmWmState.getAmState().getResumedActivity());
-        final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
-        ActivityManagerState.ActivityStack firstFrontStack =
-                mAmWmState.getAmState().getStackById(frontStackId);
-        assertEquals(message,
-                getActivityName(activityName), firstFrontStack.mResumedActivity);
-        assertTrue(message,
-                mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED));
-        mAmWmState.assertFocusedStack("Focus must be on external display", frontStackId);
-        mAmWmState.assertVisibility(activityName, true /* visible */);
-    }
-
-    private void waitAndAssertActivityStopped(ComponentName activityName, String message)
-            throws Exception {
-        mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
-
-        assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName,
-                STATE_STOPPED));
-    }
-
     /**
      * Tests that showWhenLocked works on a secondary display.
      */
@@ -1741,34 +2130,554 @@
 
             lockScreenSession.gotoKeyguard();
 
-            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
-            mAmWmState.waitForActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, STATE_RESUMED);
-
-            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
-            assertTrue("Expected resumed activity on secondary display", mAmWmState.getAmState()
-                    .hasActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, STATE_RESUMED));
+            waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED,
+                    "Expected stopped activity on default display");
+            waitAndAssertTopResumedActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId,
+                    "Expected resumed activity on secondary display");
         }
     }
 
-    /** Assert that component received onMovedToDisplay and onConfigurationChanged callbacks. */
-    private void assertMovedToDisplay(ComponentName componentName, LogSeparator logSeparator)
-            throws Exception {
-        final ActivityLifecycleCounts lifecycleCounts =
-                new ActivityLifecycleCounts(componentName, logSeparator);
-        if (lifecycleCounts.mDestroyCount != 0) {
-            fail(componentName + " has been destroyed " + lifecycleCounts.mDestroyCount
-                    + " time(s), wasn't expecting any");
-        } else if (lifecycleCounts.mCreateCount != 0) {
-            fail(componentName + " has been (re)created " + lifecycleCounts.mCreateCount
-                    + " time(s), wasn't expecting any");
-        } else if (lifecycleCounts.mConfigurationChangedCount != 1) {
-            fail(componentName + " has received "
-                    + lifecycleCounts.mConfigurationChangedCount
-                    + " onConfigurationChanged() calls, expecting " + 1);
-        } else if (lifecycleCounts.mMovedToDisplayCount != 1) {
-            fail(componentName + " has received "
-                    + lifecycleCounts.mMovedToDisplayCount
-                    + " onMovedToDisplay() calls, expecting " + 1);
+    /**
+     * Tests tap and set focus between displays.
+     */
+    @Test
+    public void testSecondaryDisplayFocus() throws Exception {
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            launchActivity(TEST_ACTIVITY);
+            mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+            launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+            waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
+                    "Virtual activity should be Top Resumed Activity.");
+            mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
+                    VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+
+            final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
+            final int width = displayMetrics.getSize().getWidth();
+            final int height = displayMetrics.getSize().getHeight();
+            tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity should be top resumed when tapped.");
+            mAmWmState.assertFocusedActivity("Activity on default display must be top focused.",
+                    TEST_ACTIVITY);
+
+            tapOnDisplay(VirtualDisplayHelper.WIDTH / 2, VirtualDisplayHelper.HEIGHT / 2,
+                    newDisplay.mId);
+            waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
+                    "Virtual display activity should be top resumed when tapped.");
+            mAmWmState.assertFocusedActivity("Activity on second display must be top focused.",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+            mAmWmState.assertFocusedAppOnDisplay(
+                    "Activity on default display must be still focused.",
+                    TEST_ACTIVITY, DEFAULT_DISPLAY);
+        }
+    }
+
+    /**
+     * Tests no leaking after external display removed.
+     */
+    @Test
+    public void testNoLeakOnExternalDisplay() throws Exception {
+        // How this test works:
+        // When receiving the request to remove a display and some activities still exist on that
+        // display, it will finish those activities first, so the display won't be removed
+        // immediately. Then, when all activities were destroyed, the display removes itself.
+
+        // Get display count before testing, as some devices may have more than one built-in
+        // display.
+        mAmWmState.getAmState().computeState();
+        final int displayCount = mAmWmState.getAmState().getDisplayCount();
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+            launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+            waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
+                    "Virtual activity should be Top Resumed Activity.");
+            mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
+                    VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
+        }
+        mAmWmState.waitFor((amState, wmState) -> amState.getDisplayCount() == displayCount,
+                "Waiting for external displays to be removed");
+        assertEquals(displayCount, mAmWmState.getAmState().getDisplayCount());
+        assertEquals(displayCount, mAmWmState.getAmState().getKeyguardControllerState().
+                mKeyguardOccludedStates.size());
+    }
+
+    @Test
+    public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
+        try (final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
+                TestActivitySession<>();
+             final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new
+                     TestActivitySession<>();
+             final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(
+                     mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            // Create a virtual display and launch an activity on it.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(true).createDisplay();
+            imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+                    newDisplay.mId);
+
+            // Make the activity to show soft input.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+
+            // Assert the configuration of the IME window is the same as the configuration of the
+            // virtual display.
+            assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(), newDisplay);
+
+            // Launch another activity on the default display.
+            imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
+                    DEFAULT_DISPLAY);
+
+            // Make the activity to show soft input.
+            imeTestActivitySession2.runOnMainSyncAndWait(
+                    imeTestActivitySession2.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+
+            // Assert the configuration of the IME window is the same as the configuration of the
+            // default display.
+            assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(),
+                    mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY));
+        }
+    }
+
+    @Test
+    public void testImeApiForBug118341760() throws Exception {
+        final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final TestActivitySession<ImeTestActivityWithBrokenContextWrapper>
+                     imeTestActivitySession = new TestActivitySession<>();
+
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(
+                     mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            // Create a virtual display and launch an activity on it.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(true).createDisplay();
+            imeTestActivitySession.launchTestActivityOnDisplaySync(
+                    ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId);
+
+            final ImeTestActivityWithBrokenContextWrapper activity =
+                    imeTestActivitySession.getActivity();
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            final String privateImeOption = activity.getEditText().getPrivateImeOptions();
+            expectEvent(stream, event -> {
+                if (!TextUtils.equals("onStartInput", event.getEventName())) {
+                    return false;
+                }
+                final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+                return TextUtils.equals(editorInfo.packageName, mContext.getPackageName())
+                        && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption);
+            }, TIMEOUT_START_INPUT);
+
+            imeTestActivitySession.runOnMainSyncAndWait(() -> {
+                final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
+                assertTrue("InputMethodManager.isActive() should work",
+                        imm.isActive(activity.getEditText()));
+            });
+        }
+    }
+
+    @Test
+    public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
+                     TestActivitySession<>();
+             final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new
+                     TestActivitySession<>();
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession1 = MockImeSession.create(
+                     mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            // Create 2 virtual displays and launch an activity on each display.
+            final List<ActivityDisplay> newDisplays = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(true).createDisplays(2);
+            final ActivityDisplay display1 = newDisplays.get(0);
+            final ActivityDisplay display2 = newDisplays.get(1);
+
+            imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+                    display1.mId);
+            imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
+                    display2.mId);
+            final ImeEventStream stream = mockImeSession1.openEventStream();
+
+            // Tap display1 as top focused display & request focus on EditText to show soft input.
+            tapOnDisplay(display1.mOverrideConfiguration.screenWidthDp / 2,
+                    display1.mOverrideConfiguration.screenHeightDp / 2, display1.mId);
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, display1.mId,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+
+            // Tap display2 as top focused display & request focus on EditText to show soft input.
+            tapOnDisplay(display2.mOverrideConfiguration.screenWidthDp / 2,
+                    display2.mOverrideConfiguration.screenHeightDp / 2, display2.mId);
+            imeTestActivitySession2.runOnMainSyncAndWait(
+                    imeTestActivitySession2.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, display2.mId,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+
+            // Tap display1 again to make sure the IME window will come back.
+            tapOnDisplay(display1.mOverrideConfiguration.screenWidthDp / 2,
+                    display1.mOverrideConfiguration.screenHeightDp / 2, display1.mId);
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, display1.mId,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+        }
+    }
+
+    /**
+     * Tests that toast works on a secondary display.
+     */
+    @Test
+    public void testSecondaryDisplayShowToast() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    virtualDisplaySession.setPublicDisplay(true).createDisplay();
+            final String TOAST_NAME = "Toast";
+            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 visible",
+                    mAmWmState.getWmState().isWindowVisible(TOAST_NAME));
+        }
+    }
+
+    /**
+     * Tests that the surface size of a fullscreen task is same as its display's surface size.
+     * Also check that the surface size has updated after reparenting to other display.
+     */
+    @Test
+    public void testTaskSurfaceSizeAfterReparentDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display and launch an activity on it.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+                    .createDisplay();
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                    "Top activity must be the newly launched one");
+            assertTopTaskSameSurfaceSizeWithDisplay(newDisplay.mId);
+
+            separateTestJournal();
+            // Destroy the display.
+        }
+
+        // Activity must be reparented to default display and relaunched.
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Top activity must be reparented to default display");
+
+        // Check the surface size after task was reparented to default display.
+        assertTopTaskSameSurfaceSizeWithDisplay(DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Tests that wallpaper shows on secondary displays.
+     */
+    @Test
+    public void testWallpaperShowOnSecondaryDisplays() throws Exception {
+        mAmWmState.computeState(true);
+        final WindowManagerState.WindowState wallpaper =
+                mAmWmState.getWmState().findFirstWindowWithType(TYPE_WALLPAPER);
+        // Skip if there is no wallpaper.
+        assumeNotNull(wallpaper);
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay noDecorDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(false).createDisplay();
+            // Tests when the system decor flag is included in that display, the wallpaper must
+            // be displayed on the secondary display. And at the same time we do not need to wait
+            // for the wallpaper which should not to be displayed.
+            final ActivityDisplay decorDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(true).createDisplay();
+            mAmWmState.waitForWithWmState((state) -> isWallpaperOnDisplay(state, decorDisplay.mId),
+                    "Waiting for wallpaper window to show");
+            assertTrue("Wallpaper must be displayed on secondary display with system decor flag",
+                    isWallpaperOnDisplay(mAmWmState.getWmState(), decorDisplay.mId));
+
+            assertFalse("Wallpaper must not be displayed on the display without system decor flag",
+                    isWallpaperOnDisplay(mAmWmState.getWmState(), noDecorDisplay.mId));
+        }
+    }
+
+    private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) {
+        List<WindowManagerState.WindowState> states =
+                windowManagerState.getMatchingWindowType(TYPE_WALLPAPER);
+        for (WindowManagerState.WindowState ws : states) {
+            if (ws.getDisplayId() == displayId) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Test that the IME should be shown in default display and verify committed texts can deliver
+     * to target display which does not support system decoration.
+     */
+    @Test
+    public void testImeShowAndCommitTextsInDefaultDisplayWhenNoSysDecor() throws Exception {
+        final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+        try (final VirtualDisplaySession virtualDisplaySession  = new VirtualDisplaySession();
+             final TestActivitySession<ImeTestActivity>
+                     imeTestActivitySession = new TestActivitySession<>();
+             // Leverage MockImeSession to ensure at least a test Ime exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(
+                     mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            // Create a virtual display and pretend display does not support system decoration.
+            final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(false).createDisplay();
+            // Verify the virtual display should not support system decoration.
+            final DisplayManager displayManager = InstrumentationRegistry.getTargetContext()
+                    .getSystemService(DisplayManager.class);
+            final Display display = displayManager.getDisplay(newDisplay.mId);
+            final boolean supportSystemDecoration =
+                    display != null && display.supportsSystemDecorations();
+            assertFalse("Display should not support system decoration", supportSystemDecoration);
+
+            // Launch Ime test activity in virtual display.
+            imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+                    newDisplay.mId);
+            // Make the activity to show soft input on the default display.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            final EditText editText = imeTestActivitySession.getActivity().mEditText;
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+                    editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+
+            // Commit text & make sure the input texts should be delivered to focused EditText on
+            // virtual display.
+            final String commitText = "test commit";
+            expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT);
+            imeTestActivitySession.runOnMainAndAssertWithTimeout(
+                    () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT,
+                    "The input text should be delivered");
+        }
+    }
+
+    @Test
+    public void testAppTransitionForActivityOnDifferentDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final TestActivitySession<StandardActivity> transitionActivitySession = new
+                     TestActivitySession<>()) {
+            // Create new simulated display.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setSimulateDisplay(true).createDisplay();
+
+            // Launch BottomActivity on top of launcher activity to prevent transition state
+            // affected by wallpaper theme.
+            launchActivityOnDisplay(BOTTOM_ACTIVITY, DEFAULT_DISPLAY);
+            waitAndAssertTopResumedActivity(BOTTOM_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity must be resumed");
+
+            // Launch StandardActivity on default display, verify last transition if is correct.
+            transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class,
+                    DEFAULT_DISPLAY);
+            mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+            mAmWmState.assertSanity();
+            assertEquals(TRANSIT_TASK_OPEN,
+                    mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
+
+            // Finish current activity & launch another TestActivity in virtual display in parallel.
+            transitionActivitySession.finishCurrentActivityNoWait();
+            launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+            mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
+            mAmWmState.assertSanity();
+
+            // Verify each display's last transition if is correct as expected.
+            assertEquals(TRANSIT_TASK_CLOSE,
+                    mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition());
+            assertEquals(TRANSIT_TASK_OPEN,
+                    mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
+        }
+    }
+
+    @Test
+    public void testNoTransitionWhenMovingActivityToDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display & capture new display's transition state.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setSimulateDisplay(true).createDisplay();
+
+            // Launch TestActivity in virtual display & capture its transition state.
+            launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+            mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
+            mAmWmState.assertSanity();
+            final String lastTranstionOnVirtualDisplay = mAmWmState.getWmState()
+                    .getDisplay(newDisplay.mId).getLastTransition();
+
+            // Move TestActivity from virtual display to default display.
+            getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                    .allowMultipleInstances(false).setNewTask(true)
+                    .setDisplayId(DEFAULT_DISPLAY).execute();
+
+            // Verify TestActivity moved to virtual display.
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Existing task must be brought to front");
+
+            // Make sure last transition will not change when task move to another display.
+            assertEquals(lastTranstionOnVirtualDisplay,
+                    mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition());
+        }
+    }
+
+    private void assertTopTaskSameSurfaceSizeWithDisplay(int displayId) {
+        final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(displayId);
+        final int stackId = mAmWmState.getWmState().getFrontStackId(displayId);
+        final WindowManagerState.WindowTask task =
+                mAmWmState.getWmState().getStack(stackId).mTasks.get(0);
+
+        assertEquals("Task must have same surface width with its display",
+                display.getSurfaceSize(), task.getSurfaceWidth());
+        assertEquals("Task must have same surface height with its display",
+                display.getSurfaceSize(), task.getSurfaceHeight());
+    }
+
+    // TODO(115978725): add runtime sys decor change test once we can do this.
+    /**
+     * Test that navigation bar should show on display with system decoration.
+     */
+    @Test
+    @FlakyTest(bugId = 120748674)
+    public void testNavBarShowingOnDisplayWithDecor() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setPublicDisplay(true).setShowSystemDecorations(true).createDisplay();
+
+            mAmWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId);
+        }
+    }
+
+    /**
+     * Test that navigation bar should not show on display without system decoration.
+     */
+    @Test
+    @FlakyTest(bugId = 120748674)
+    public void testNavBarNotShowingOnDisplayWithoutDecor() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            virtualDisplaySession.setPublicDisplay(true)
+                    .setShowSystemDecorations(false).createDisplay();
+
+            final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
+
+            waitAndAssertNavBarStatesAreTheSame(expected);
+        }
+    }
+
+    /**
+     * Test that navigation bar should not show on private display even if the display
+     * supports system decoration.
+     */
+    @Test
+    @FlakyTest(bugId = 120748674)
+    public void testNavBarNotShowingOnPrivateDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            virtualDisplaySession.setPublicDisplay(false)
+                    .setShowSystemDecorations(true).createDisplay();
+
+            final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
+
+            waitAndAssertNavBarStatesAreTheSame(expected);
+        }
+    }
+
+    /** Tests launching of activities on a single task instance display. */
+    @Test
+    public void testSingleTaskInstanceDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            ActivityDisplay display =
+                    virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+            final int displayId = display.mId;
+
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> mAtm.setDisplayToSingleTaskInstance(displayId));
+            display = getDisplayState(displayId);
+            assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance);
+
+            // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch
+            // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and
+            // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task.
+            launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId);
+
+            waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY,
+                    "Activity should be resumed on default display");
+
+            display = getDisplayState(displayId);
+            // Verify that the 2 activities in the same task are on the display and the one in a
+            // different task isn't on the display, but on the default display
+            assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY",
+                    display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY));
+            assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2",
+                    display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2));
+
+            assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
+                    display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
+            assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
+                    getDisplayState(DEFAULT_DISPLAY).containsActivity(
+                            SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
+        }
+    }
+
+    private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) throws Exception {
+        // This is used to verify that we have nav bars shown on the same displays
+        // as before the test.
+        //
+        // The strategy is:
+        // Once a display with system ui decor support is created and a nav bar shows on the
+        // display, go back to verify whether the nav bar states are unchanged to verify that no nav
+        // bars were added to a display that was added before executing this method that shouldn't
+        // have nav bars (i.e. private or without system ui decor).
+        try (final VirtualDisplaySession secondDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay supportsSysDecorDisplay = secondDisplaySession
+                    .setPublicDisplay(true).setShowSystemDecorations(true).createDisplay();
+            mAmWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId);
+            // This display has finished his task. Just close it.
+        }
+
+        final List<WindowState> result = mAmWmState.getWmState().getAllNavigationBarStates();
+
+        assertEquals("The number of nav bars should be the same", expected.size(), result.size());
+
+        // Nav bars should show on the same displays
+        for (int i = 0; i < expected.size(); i++) {
+            final int expectedDisplayId = expected.get(i).getDisplayId();
+            mAmWmState.waitAndAssertNavBarShownOnDisplay(expectedDisplayId);
         }
     }
 
@@ -1843,4 +2752,100 @@
             VirtualDisplayHelper.waitForDefaultDisplayState(wantOn);
         }
     }
+
+    public static class ImeTestActivity extends Activity {
+        EditText mEditText;
+
+        @Override
+        protected void onCreate(Bundle icicle) {
+            super.onCreate(icicle);
+            mEditText = new EditText(this);
+            // Set private IME option for editorMatcher to identify which TextView received
+            // onStartInput event.
+            mEditText.setPrivateImeOptions(
+                    getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
+            final LinearLayout layout = new LinearLayout(this);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.addView(mEditText);
+            mEditText.requestFocus();
+            setContentView(layout);
+        }
+
+        void showSoftInput() {
+            getSystemService(InputMethodManager.class).showSoftInput(mEditText, 0);
+        }
+    }
+
+    public static class ImeTestActivity2 extends ImeTestActivity { }
+
+    public static final class ImeTestActivityWithBrokenContextWrapper extends Activity {
+        private EditText mEditText;
+
+        /**
+         * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild.
+         *
+         * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to
+         * ApplicationContext except for {@link #getSystemService(String)}.</p>
+         *
+         **/
+        private static final class Bug118341760ContextWrapper extends ContextWrapper {
+            private final Context mOriginalContext;
+
+            Bug118341760ContextWrapper(Context base) {
+                super(base.getApplicationContext());
+                mOriginalContext = base;
+            }
+
+            /**
+             * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain
+             * {@link ContextWrapper} subclasses we found in the wild.
+             *
+             * @param name The name of the desired service.
+             * @return The service or {@link null} if the name does not exist.
+             */
+            @Override
+            public Object getSystemService(String name) {
+                return mOriginalContext.getSystemService(name);
+            }
+        }
+
+        @Override
+        protected void onCreate(Bundle icicle) {
+            super.onCreate(icicle);
+            mEditText = new EditText(new Bug118341760ContextWrapper(this));
+            // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text.
+            mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos()));
+            final LinearLayout layout = new LinearLayout(this);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.addView(mEditText);
+            mEditText.requestFocus();
+            setContentView(layout);
+        }
+
+        EditText getEditText() {
+            return mEditText;
+        }
+    }
+
+    void assertImeWindowAndDisplayConfiguration(
+            WindowManagerState.WindowState imeWinState, ActivityDisplay display) {
+        final Configuration configurationForIme = imeWinState.mMergedOverrideConfiguration;
+        final Configuration configurationForDisplay =  display.mMergedOverrideConfiguration;
+        final int displayDensityDpiForIme = configurationForIme.densityDpi;
+        final int displayDensityDpi = configurationForDisplay.densityDpi;
+        final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds();
+        final Rect displayBounds = configurationForDisplay.windowConfiguration.getBounds();
+
+        assertEquals("Display density not the same", displayDensityDpi, displayDensityDpiForIme);
+        assertEquals("Display bounds not the same", displayBounds, displayBoundsForIme);
+    }
+
+    void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId,
+            Predicate<ImeEvent>... conditions) throws Exception {
+        for (Predicate<ImeEvent> condition : conditions) {
+            expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+        }
+        // Assert the IME is shown on the expected display.
+        mAmWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
index 94e9b63..7a3b6c6 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -25,7 +25,6 @@
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
 import static android.server.am.ActivityManagerState.STATE_STOPPED;
 import static android.server.am.ComponentNameUtils.getActivityName;
-import static android.server.am.ComponentNameUtils.getLogTag;
 import static android.server.am.ComponentNameUtils.getWindowName;
 import static android.server.am.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
@@ -41,7 +40,6 @@
 import static android.server.am.Components.PipActivity.ACTION_EXPAND_PIP;
 import static android.server.am.Components.PipActivity.ACTION_FINISH;
 import static android.server.am.Components.PipActivity.ACTION_MOVE_TO_BACK;
-import static android.server.am.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
 import static android.server.am.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
@@ -50,17 +48,15 @@
 import static android.server.am.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
 import static android.server.am.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
 import static android.server.am.Components.PipActivity.EXTRA_PIP_ORIENTATION;
-import static android.server.am.Components.PipActivity.EXTRA_REENTER_PIP_ON_EXIT;
 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
-import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
-import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
 import static android.server.am.Components.PipActivity.EXTRA_START_ACTIVITY;
 import static android.server.am.Components.PipActivity.EXTRA_TAP_TO_FINISH;
 import static android.server.am.Components.RESUME_WHILE_PAUSING_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY;
 import static android.server.am.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
 import static android.server.am.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.am.Components.TestActivity.EXTRA_CONFIGURATION;
 import static android.server.am.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
 import static android.server.am.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
 import static android.server.am.UiDeviceUtils.pressWindowButton;
@@ -76,6 +72,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -84,17 +81,23 @@
 import android.provider.Settings;
 import android.server.am.ActivityManagerState.ActivityStack;
 import android.server.am.ActivityManagerState.ActivityTask;
+import android.server.am.CommandSession.ActivityCallback;
+import android.server.am.CommandSession.SizeInfo;
+import android.server.am.TestJournalProvider.TestJournalContainer;
 import android.server.am.WindowManagerState.WindowStack;
 import android.server.am.settings.SettingsSession;
 import android.support.test.filters.FlakyTest;
 import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 import android.util.Size;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -149,7 +152,6 @@
                 false /* isFocusable */);
     }
 
-    @FlakyTest(bugId = 71444628)
     @Presubmit
     @Test
     public void testMoveTopActivityToPinnedStack() throws Exception {
@@ -322,7 +324,7 @@
                 EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
         // portrait
-        executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         // Wait for animation complete since we are comparing bounds
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
@@ -585,20 +587,15 @@
         // Go home
         launchHomeActivity();
         // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_REENTER_PIP_ON_EXIT, "true");
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
         launchActivity(PIP_ACTIVITY);
-        mAmWmState.waitForWithAmState(amState ->
-                amState.getFrontStackWindowingMode(DEFAULT_DISPLAY) == WINDOWING_MODE_FULLSCREEN,
-                "Waiting for PIP to exit to fullscreen");
-        mAmWmState.waitForWithAmState(amState ->
-                amState.getFrontStackWindowingMode(DEFAULT_DISPLAY) == WINDOWING_MODE_PINNED,
-                "Waiting to re-enter PIP");
+        waitForExitPipToFullscreen(PIP_ACTIVITY);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+        waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         mAmWmState.assertHomeActivityVisible(true);
     }
 
@@ -610,17 +607,14 @@
         launchActivity(TEST_ACTIVITY);
 
         // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_REENTER_PIP_ON_EXIT, "true");
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
         launchActivity(PIP_ACTIVITY);
-        mAmWmState.waitForWithAmState(amState ->
-                amState.getFrontStackWindowingMode(DEFAULT_DISPLAY) == WINDOWING_MODE_FULLSCREEN,
-                "Waiting for PIP to exit to fullscreen");
+        waitForExitPipToFullscreen(PIP_ACTIVITY);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
     }
@@ -695,7 +689,7 @@
 
         // Remove the stack and ensure that the task is now in the fullscreen stack (when no
         // fullscreen stack existed before)
-        executeShellCommand("am broadcast -a " + ACTION_MOVE_TO_BACK);
+        mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
     }
@@ -714,7 +708,7 @@
 
         // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
         // top fullscreen activity
-        executeShellCommand("am broadcast -a " + ACTION_MOVE_TO_BACK);
+        mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
     }
@@ -735,7 +729,7 @@
 
         // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
         // stack, but that the home stack is still focused
-        executeShellCommand("am broadcast -a " + ACTION_MOVE_TO_BACK);
+        mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
         assertPinnedStackStateOnMoveToFullscreen(
                 PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
     }
@@ -804,13 +798,11 @@
          * but since we can't launch into the home stack directly, we have a workaround.
          *
          * 1) Launch an activity in a new dynamic stack
-         * 2) Resize the dynamic stack to non-fullscreen bounds
-         * 3) Start the PiP activity that will enter picture-in-picture when paused in the
+         * 2) Start the PiP activity that will enter picture-in-picture when paused in the
          *    fullscreen stack
-         * 4) Bring the activity in the dynamic stack forward to trigger PiP
+         * 3) Bring the activity in the dynamic stack forward to trigger PiP
          */
-        int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
-        resizeStack(stackId, 0, 0, 500, 500);
+        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
         // Launch an activity that will enter PiP when it is paused with a delay that is long enough
         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
         // trigger the current system pause timeout (currently 500ms)
@@ -832,13 +824,18 @@
 
         // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
         // when paused
-        executeShellCommand("am task lock " + task.mTaskId);
-        executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackDoesNotExist();
-        launchHomeActivity();
-        assertPinnedStackDoesNotExist();
-        executeShellCommand("am task lock stop");
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            try {
+                mAtm.startSystemLockTaskMode(task.mTaskId);
+                mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+                waitForEnterPip(PIP_ACTIVITY);
+                assertPinnedStackDoesNotExist();
+                launchHomeActivity();
+                assertPinnedStackDoesNotExist();
+            } finally {
+                mAtm.stopSystemLockTaskMode();
+            }
+        });
     }
 
     @FlakyTest(bugId = 70328524)
@@ -850,19 +847,19 @@
         // Launch a PiP activity and ensure configuration change only happened once, and that the
         // configuration change happened after the picture-in-picture and multi-window callbacks
         launchActivity(PIP_ACTIVITY);
-        LogSeparator logSeparator = separateLogs();
-        executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+        separateTestJournal();
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
-        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
-        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
+        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
 
         // Trigger it to go back to fullscreen and ensure that only triggered one configuration
         // change as well
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PIP_ACTIVITY);
-        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
-        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+        waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
+        assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
     }
 
     /** Helper class to save, set, and restore transition_animation_scale preferences. */
@@ -909,7 +906,7 @@
             assertPinnedStackExists();
 
             // Relaunch the PiP activity back into fullscreen
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(PIP_ACTIVITY);
             // Wait until the PiP activity is reparented into the fullscreen stack (happens after
             // the transition has finished)
@@ -918,16 +915,16 @@
             // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
             // configuration change (since none was sent)
             final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                    PIP_ACTIVITY, logSeparator);
-            assertEquals("onConfigurationChanged", 0, lifecycleCounts.mConfigurationChangedCount);
+                    PIP_ACTIVITY);
+            assertEquals("onConfigurationChanged", 0,
+                    lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
             assertEquals("onPictureInPictureModeChanged", 1,
-                    lifecycleCounts.mPictureInPictureModeChangedCount);
+                    lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
             assertEquals("onMultiWindowModeChanged", 1,
-                    lifecycleCounts.mMultiWindowModeChangedCount);
+                    lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
         }
     }
 
-    @FlakyTest(bugId = 71564769)
     @Presubmit
     @Test
     public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
@@ -935,29 +932,33 @@
 
         // Launch a PiP activity
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        waitForEnterPip(PIP_ACTIVITY);
+        // Wait for animation complete so that system has reported pip mode change event to
+        // client and the last reported pip mode has updated.
+        waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
 
         // Dismiss it
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
 
         // Confirm that we get stop before the multi-window and picture-in-picture mode change
         // callbacks
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
-                logSeparator);
-        assertEquals("onStop", 1, lifecycleCounts.mStopCount);
+        final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(PIP_ACTIVITY);
+        assertEquals("onStop", 1, lifecycles.getCount(ActivityCallback.ON_STOP));
         assertEquals("onPictureInPictureModeChanged", 1,
-                lifecycleCounts.mPictureInPictureModeChangedCount);
-        assertEquals("onMultiWindowModeChanged", 1, lifecycleCounts.mMultiWindowModeChangedCount);
-        final int lastStopLine = lifecycleCounts.mLastStopLineIndex;
-        final int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
-        final int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
+                lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
+        assertEquals("onMultiWindowModeChanged", 1,
+                lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        final int lastStopIndex = lifecycles.getLastIndex(ActivityCallback.ON_STOP);
+        final int lastPipIndex = lifecycles.getLastIndex(
+                ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
+        final int lastMwIndex = lifecycles.getLastIndex(
+                ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
         assertThat("onStop should be before onPictureInPictureModeChanged",
-                lastStopLine, lessThan(lastPipLine));
+                lastStopIndex, lessThan(lastPipIndex));
         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
-                lastPipLine, lessThan(lastMwLine));
+                lastPipIndex, lessThan(lastMwIndex));
     }
 
     @Test
@@ -970,9 +971,7 @@
 
         // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
         // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
-        executeShellCommand("am broadcast -a " + ACTION_EXPAND_PIP
-                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR + " 123456789"
-                + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR + " 100000000");
+        mBroadcastActionTrigger.expandPipWithAspectRatio("123456789", "100000000");
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         assertPinnedStackDoesNotExist();
     }
@@ -989,10 +988,7 @@
         assertPinnedStackExists();
 
         // Request that the orientation is set to landscape
-        executeShellCommand("am broadcast -a "
-                + ACTION_SET_REQUESTED_ORIENTATION + " -e "
-                + EXTRA_PIP_ORIENTATION + " "
-                + String.valueOf(ORIENTATION_LANDSCAPE));
+        mBroadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE);
 
         // Launch the activity back into fullscreen and ensure that it is now in landscape
         launchActivity(PIP_ACTIVITY);
@@ -1031,7 +1027,7 @@
         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
 
         // Finish the PiP activity and ensure that there is no pinned stack
-        executeShellCommand("am broadcast -a " + ACTION_FINISH);
+        mBroadcastActionTrigger.doAction(ACTION_FINISH);
         waitForPinnedStackRemoved();
         assertPinnedStackDoesNotExist();
     }
@@ -1053,17 +1049,17 @@
         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
 
         // Finish the task overlay activity while animating and ensure that the PiP activity never
-        // got resumed
-        LogSeparator logSeparator = separateLogs();
-        executeShellCommand("am stack resize-animated " + stackId + " 20 20 500 500");
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        // got resumed.
+        separateTestJournal();
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.resizeStack(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),
                 "Waiting for test activity to finish...");
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
-                logSeparator);
-        assertEquals("onResume", 0, lifecycleCounts.mResumeCount);
-        assertEquals("onPause", 0, lifecycleCounts.mPauseCount);
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
+        assertEquals("onResume", 0, lifecycleCounts.getCount(ActivityCallback.ON_RESUME));
+        assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
     }
 
     @Test
@@ -1190,23 +1186,21 @@
     public void testDisplayMetricsPinUnpin() throws Exception {
         assumeTrue(supportsPip());
 
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(TEST_ACTIVITY);
         final int defaultWindowingMode = mAmWmState.getAmState()
                 .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
-        final ReportedSizes initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
-                logSeparator);
-        final Rect initialAppBounds = readAppBounds(TEST_ACTIVITY, logSeparator);
+        final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
+        final Rect initialAppBounds = getAppBounds(TEST_ACTIVITY);
         assertNotNull("Must report display dimensions", initialSizes);
         assertNotNull("Must report app bounds", initialAppBounds);
 
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         // Wait for animation complete since we are comparing bounds
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
-        final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
-                logSeparator);
-        final Rect pinnedAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+        final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
+        final Rect pinnedAppBounds = getAppBounds(PIP_ACTIVITY);
         assertNotEquals("Reported display size when pinned must be different from default",
                 initialSizes, pinnedSizes);
         final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
@@ -1214,11 +1208,10 @@
         assertNotEquals("Reported app size when pinned must be different from default",
                 initialAppSize, pinnedAppSize);
 
-        logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(PIP_ACTIVITY, defaultWindowingMode);
-        final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
-                logSeparator);
-        final Rect finalAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+        final SizeInfo finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
+        final Rect finalAppBounds = getAppBounds(PIP_ACTIVITY);
         final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
         assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
         assertEquals("Must report default app size after exiting PiP", initialAppSize,
@@ -1249,9 +1242,9 @@
 
         // Expand the PiP back to fullscreen and back into PiP and ensure that it is in the same
         // position as before we expanded (and that the default bounds reflect that)
-        executeShellCommand("am broadcast -a " + ACTION_EXPAND_PIP);
+        mBroadcastActionTrigger.doAction(ACTION_EXPAND_PIP);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
-        executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         mAmWmState.computeState(true);
         // Due to rounding in how we save and apply the snap fraction we may be a pixel off, so just
@@ -1262,13 +1255,13 @@
 
         // Expand the PiP, then launch an activity in a new task, and ensure that the PiP goes back
         // to the default position (and not the saved position) the next time it is launched
-        executeShellCommand("am broadcast -a " + ACTION_EXPAND_PIP);
+        mBroadcastActionTrigger.doAction(ACTION_EXPAND_PIP);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         launchActivity(TEST_ACTIVITY);
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
         mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_RESUMED);
-        mAmWmState.waitForAppTransitionIdle();
-        executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+        mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
         assertTrue("Expected initialBounds=" + initialBounds + " to equal bounds="
@@ -1299,7 +1292,7 @@
         offsetPipWithinMovementBounds(100 /* offsetY */, initialBounds, offsetBounds);
 
         // Finish the activity
-        executeShellCommand("am broadcast -a " + ACTION_FINISH);
+        mBroadcastActionTrigger.doAction(ACTION_FINISH);
         waitForPinnedStackRemoved();
         assertPinnedStackDoesNotExist();
 
@@ -1333,22 +1326,12 @@
                 offsetBoundsOut.right, offsetBoundsOut.bottom);
     }
 
-    private static final Pattern sAppBoundsPattern = Pattern.compile(
-            "(.+)mAppBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)(.*)");
-
-    /** Read app bounds in last applied configuration from logs. */
-    private Rect readAppBounds(ComponentName activityName, LogSeparator logSeparator) {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, getLogTag(activityName));
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sAppBoundsPattern.matcher(line);
-            if (matcher.matches()) {
-                final int left = Integer.parseInt(matcher.group(2));
-                final int top = Integer.parseInt(matcher.group(3));
-                final int right = Integer.parseInt(matcher.group(4));
-                final int bottom = Integer.parseInt(matcher.group(5));
-                return new Rect(left, top, right - left, bottom - top);
-            }
+    /** Get app bounds in last applied configuration. */
+    private Rect getAppBounds(ComponentName activityName) {
+        final Configuration config = TestJournalContainer.get(activityName).extras
+                .getParcelable(EXTRA_CONFIGURATION);
+        if (config != null) {
+            return config.windowConfiguration.getAppBounds();
         }
         return null;
     }
@@ -1364,8 +1347,8 @@
             int windowingMode, int activityType) {
         mAmWmState.waitForFocusedStack(windowingMode, activityType);
         mAmWmState.assertFocusedStack("Wrong focused stack", windowingMode, activityType);
-        mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
-        assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
+        waitAndAssertActivityState(activityName, STATE_STOPPED,
+                "Activity should go to STOPPED");
         assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
                 activityName, WINDOWING_MODE_FULLSCREEN));
         assertPinnedStackDoesNotExist();
@@ -1434,24 +1417,25 @@
      * Asserts that the activity received exactly one of each of the callbacks when entering and
      * exiting picture-in-picture.
      */
-    private void assertValidPictureInPictureCallbackOrder(
-            ComponentName activityName, LogSeparator logSeparator) {
-        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
-                logSeparator);
+    private void assertValidPictureInPictureCallbackOrder(ComponentName activityName) {
+        final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
 
         assertEquals(getActivityName(activityName) + " onConfigurationChanged()",
-                1, lifecycleCounts.mConfigurationChangedCount);
+                1, lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
         assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
-                1, lifecycleCounts.mPictureInPictureModeChangedCount);
+                1, lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
         assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
-                1, lifecycleCounts.mMultiWindowModeChangedCount);
-        int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
-        int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
-        int lastConfigLine = lifecycleCounts.mLastConfigurationChangedLineIndex;
+                1, lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        final int lastPipIndex = lifecycles
+                .getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
+        final int lastMwIndex = lifecycles
+                .getLastIndex(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
+        final int lastConfigIndex = lifecycles
+                .getLastIndex(ActivityCallback.ON_CONFIGURATION_CHANGED);
         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
-                lastPipLine, lessThan(lastMwLine));
+                lastPipIndex, lessThan(lastMwIndex));
         assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
-                lastMwLine, lessThan(lastConfigLine));
+                lastMwIndex, lessThan(lastConfigIndex));
     }
 
     /**
@@ -1499,14 +1483,12 @@
     /**
      * Waits until the expected picture-in-picture callbacks have been made.
      */
-    private void waitForValidPictureInPictureCallbacks(ComponentName activityName,
-            LogSeparator logSeparator) {
+    private void waitForValidPictureInPictureCallbacks(ComponentName activityName) {
         mAmWmState.waitFor((amState, wmState) -> {
-            final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                    activityName, logSeparator);
-            return lifecycleCounts.mConfigurationChangedCount == 1 &&
-                    lifecycleCounts.mPictureInPictureModeChangedCount == 1 &&
-                    lifecycleCounts.mMultiWindowModeChangedCount == 1;
+            final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
+            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...");
     }
 
@@ -1564,7 +1546,7 @@
         Rect pinnedStackBounds = getPinnedStackBounds();
         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
-        executeShellCommand(String.format("input tap %d %d", tapX, tapY));
+        tapOnDisplay(tapX, tapY, DEFAULT_DISPLAY);
     }
 
     /**
@@ -1587,20 +1569,24 @@
             mPackageName = activityName.getPackageName();
         }
 
+        /**
+         * Sets an app-ops op for a given package to a given mode.
+         */
         void setOpToMode(String op, int mode) {
-            setAppOpsOpToMode(mPackageName, op, mode);
+            try {
+                AppOpsUtils.setOpMode(mPackageName, op, mode);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
         }
 
         @Override
         public void close() {
-            executeShellCommand("appops reset " + mPackageName);
-        }
-
-        /**
-         * Sets an app-ops op for a given package to a given mode.
-         */
-        private void setAppOpsOpToMode(String packageName, String op, int mode) {
-            executeShellCommand(String.format("appops set %s %s %d", packageName, op, mode));
+            try {
+                AppOpsUtils.reset(mPackageName);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
         }
     }
 
@@ -1617,7 +1603,7 @@
             final int stackId = mAmWmState.getAmState().getStackIdByActivity(topActivityName);
 
             assertNotEquals(stackId, INVALID_STACK_ID);
-            executeShellCommand(getMoveToPinnedStackCommand(stackId));
+            moveTopActivityToPinnedStack(stackId);
         }
 
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
index ae483d8..bb8c144 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
@@ -16,8 +16,8 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -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.am.Components.ALT_LAUNCHING_ACTIVITY;
 import static android.server.am.Components.DOCKED_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
 import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY;
@@ -55,6 +54,7 @@
 import android.content.ComponentName;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.server.am.CommandSession.ActivityCallback;
 import android.support.test.filters.FlakyTest;
 
 import org.junit.Before;
@@ -151,11 +151,11 @@
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
 
         // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         removeStacksInWindowingModes(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
-                TEST_ACTIVITY, logSeparator);
-        assertEquals(1, lifecycleCounts.mMultiWindowModeChangedCount);
+                TEST_ACTIVITY);
+        assertEquals(1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
     }
 
     @Test
@@ -165,25 +165,26 @@
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
 
         // Move to docked stack.
-        LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
-                TEST_ACTIVITY, logSeparator);
+        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
         assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.mMultiWindowModeChangedCount);
-        assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
 
         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
         // will come up.
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
         // Move activity back to fullscreen stack.
-        logSeparator = separateLogs();
+        separateTestJournal();
         setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY, logSeparator);
+        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
         assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.mMultiWindowModeChangedCount);
-        assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
     }
 
     @Test
@@ -277,7 +278,7 @@
 
         // Move to split-screen primary
         final int taskId = mAmWmState.getAmState().getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
-        moveTaskToPrimarySplitScreen(taskId, true /* launchSideActivityIfNeeded */);
+        moveTaskToPrimarySplitScreen(taskId, true /* showRecents */);
 
         // Launch target to side
         final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
@@ -488,12 +489,13 @@
                 mAmWmState.computeState(DOCKED_ACTIVITY);
 
                 // Go home and check the app transition
-                assertNotEquals(
-                        TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+                assertNotEquals(TRANSIT_WALLPAPER_OPEN,
+                        mAmWmState.getWmState().getDefaultDisplayLastTransition());
                 pressHomeButton();
                 mAmWmState.waitForHomeActivityVisible();
 
-                assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+                assertEquals(TRANSIT_WALLPAPER_OPEN,
+                        mAmWmState.getWmState().getDefaultDisplayLastTransition());
             }
         }
     }
@@ -504,9 +506,10 @@
     public void testFinishDockActivityWhileMinimized() throws Exception {
         launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
 
-        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
         waitForDockNotMinimized();
-        mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+        mAmWmState.waitForActivityRemoved(TEST_ACTIVITY);
+        mAmWmState.assertNotExist(TEST_ACTIVITY);
         assertDockNotMinimized();
     }
 
@@ -545,8 +548,12 @@
         }
     }
 
+    /**
+     * Verify split screen mode visibility after stack resize occurs.
+     */
     @Test
     @Presubmit
+    @FlakyTest(bugId = 110276714)
     public void testResizeDockedStack() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
@@ -559,10 +566,6 @@
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
         mAmWmState.assertContainsStack("Must contain primary split-screen stack.",
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        assertEquals(new Rect(0, 0, STACK_SIZE, STACK_SIZE),
-                mAmWmState.getAmState().getStandardStackByWindowingMode(
-                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).getBounds());
-        mAmWmState.assertDockedTaskBounds(TASK_SIZE, TASK_SIZE, DOCKED_ACTIVITY);
         mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
     }
@@ -582,7 +585,7 @@
         final Rect initialDockBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
 
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
 
         Rect newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
         resizeDockedStack(
@@ -595,8 +598,8 @@
         resizeDockedStack(
                 newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
         mAmWmState.computeState(TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY);
-        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
-        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */);
     }
 
     private Rect computeNewDockBounds(
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
index 4828c34..651bf48 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
@@ -313,6 +313,6 @@
         }
 
         assertEquals("Picked wrong transition", expectedTransit,
-                mAmWmState.getWmState().getLastTransition());
+                mAmWmState.getWmState().getDefaultDisplayLastTransition());
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
index 6bcdabd..589302a 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
@@ -28,7 +28,12 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.provider.Settings;
 import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.settings.SettingsSession;
+import com.android.cts.verifier.vr.MockVrListenerService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,15 +82,37 @@
     }
 
     /**
+     * Helper class to enable vr listener.
+     * VrManagerService uses SettingChangeListener to monitor ENABLED_VR_LISTENERS changed.
+     * We need to update Settings to let Vr service know if MockVrListenerService is enabled.
+     */
+    private static class EnableVrListenerSession extends SettingsSession<String> {
+        public EnableVrListenerSession() {
+            super(Settings.Secure.getUriFor(Settings.Secure.ENABLED_VR_LISTENERS),
+                    Settings.Secure::getString,
+                    Settings.Secure::putString);
+        }
+
+        public void enableVrListener(@NonNull ComponentName targetVrComponent) throws Exception {
+            ComponentName component = new ComponentName(targetVrComponent.getPackageName(),
+                    MockVrListenerService.class.getName());
+            set(component.flattenToString());
+        }
+    }
+
+    /**
      * Tests that any new activity launch in Vr mode is in Vr display.
      */
     @Test
     public void testVrActivityLaunch() throws Exception {
         assumeTrue(supportsMultiDisplay());
 
-        try (final VrModeSession vrModeSession = new VrModeSession()) {
+        try (final VrModeSession vrModeSession = new VrModeSession();
+             final EnableVrListenerSession enableVrListenerSession =
+                     new EnableVrListenerSession()) {
             // Put the device in persistent vr mode.
             vrModeSession.enablePersistentVrMode();
+            enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
 
             // Launch the VR activity.
             launchActivity(VR_TEST_ACTIVITY);
@@ -130,9 +157,12 @@
         // Launch a 2D activity.
         launchActivity(LAUNCHING_ACTIVITY);
 
-        try (final VrModeSession vrModeSession = new VrModeSession()) {
+        try (final VrModeSession vrModeSession = new VrModeSession();
+             final EnableVrListenerSession enableVrListenerSession =
+                     new EnableVrListenerSession()) {
             // Put the device in persistent vr mode.
             vrModeSession.enablePersistentVrMode();
+            enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
 
             // Launch the VR activity.
             launchActivity(VR_TEST_ACTIVITY);
@@ -177,9 +207,12 @@
         // there is no "post vr" behavior to verify.
         assumeFalse(isUiModeLockedToVrHeadset());
 
-        try (final VrModeSession vrModeSession = new VrModeSession()) {
+        try (final VrModeSession vrModeSession = new VrModeSession();
+             final EnableVrListenerSession enableVrListenerSession =
+                     new EnableVrListenerSession()) {
             // Put the device in persistent vr mode.
             vrModeSession.enablePersistentVrMode();
+            enableVrListenerSession.enableVrListener(VR_TEST_ACTIVITY);
 
             // Launch the VR activity.
             launchActivity(VR_TEST_ACTIVITY);
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityMetricsLoggerTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityMetricsLoggerTests.java
new file mode 100644
index 0000000..9ec5225
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityMetricsLoggerTests.java
@@ -0,0 +1,436 @@
+/*
+ * 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.server.am;
+
+import static android.os.SystemClock.sleep;
+import static android.server.am.Components.ENTRY_POINT_ALIAS_ACTIVITY;
+import static android.server.am.Components.NO_DISPLAY_ACTIVITY;
+import static android.server.am.Components.REPORT_FULLY_DRAWN_ACTIVITY;
+import static android.server.am.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.server.am.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.UiDeviceUtils.waitForDeviceIdle;
+import static android.server.am.second.Components.SECOND_ACTIVITY;
+import static android.server.am.third.Components.THIRD_ACTIVITY;
+import static android.util.TimeUtils.formatDuration;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+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;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_HOT_LAUNCH;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+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.app.Activity;
+import android.content.ComponentName;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.support.test.metricshelper.MetricsAsserts;
+import android.util.EventLog.Event;
+
+import org.hamcrest.collection.IsIn;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS device tests for {@link com.android.server.am.ActivityMetricsLogger}.
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityMetricsLoggerTests
+ */
+public class ActivityMetricsLoggerTests extends ActivityManagerTestBase {
+    private static final String TAG_ATM = "ActivityTaskManager";
+    private final MetricsReader mMetricsReader = new MetricsReader();
+    private long mPreUptimeMs;
+    private LogSeparator mLogSeparator;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mPreUptimeMs = SystemClock.uptimeMillis();
+        mMetricsReader.checkpoint(); // clear out old logs
+        mLogSeparator = separateLogs(); // add a new separator for logs
+    }
+
+    /**
+     * Launch an app and verify:
+     * - appropriate metrics logs are added
+     * - "Displayed activity ..." log is added to logcat
+     * - am_activity_launch_time event is generated
+     * In all three cases, verify the delay measurements are the same.
+     */
+    @Test
+    @Presubmit
+    public void testAppLaunchIsLogged() {
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
+        final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
+        final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
+                30009 /* AM_ACTIVITY_LAUNCH_TIME */);
+
+        final long postUptimeMs = SystemClock.uptimeMillis();
+        assertMetricsLogs(TEST_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
+        assertTransitionIsStartingWindow(metricsLog);
+        final int windowsDrawnDelayMs =
+                (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
+        final String expectedLog =
+                "Displayed " + TEST_ACTIVITY.flattenToShortString()
+                        + ": " + formatDuration(windowsDrawnDelayMs);
+        assertLogsContain(deviceLogs, expectedLog);
+        assertEventLogsContainsLaunchTime(eventLogs, TEST_ACTIVITY, windowsDrawnDelayMs);
+    }
+
+    private void assertMetricsLogs(ComponentName componentName,
+            int category, LogMaker log, long preUptimeMs, long postUptimeMs) {
+        assertNotNull("did not find the metrics log for: " + componentName
+                + " category:" + category, log);
+        int startUptimeSec =
+                ((Number) log.getTaggedData(APP_TRANSITION_DEVICE_UPTIME_SECONDS)).intValue();
+        int preUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(preUptimeMs));
+        int postUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(postUptimeMs));
+        long testElapsedTimeMs = postUptimeMs - preUptimeMs;
+        assertThat("must be either cold or warm launch", log.getType(),
+                IsIn.oneOf(TYPE_TRANSITION_COLD_LAUNCH, TYPE_TRANSITION_WARM_LAUNCH));
+        assertThat("reported uptime should be after the app was started", startUptimeSec,
+                greaterThanOrEqualTo(preUptimeSec));
+        assertThat("reported uptime should be before assertion time", startUptimeSec,
+                lessThanOrEqualTo(postUptimeSec));
+        assertNotNull("log should have delay", log.getTaggedData(APP_TRANSITION_DELAY_MS));
+        assertNotNull("log should have windows drawn delay",
+                log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS));
+        long windowsDrawnDelayMs = (int) log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
+        assertThat("windows drawn delay should be less that total elapsed time",
+                windowsDrawnDelayMs,  lessThanOrEqualTo(testElapsedTimeMs));
+    }
+
+    private void assertTransitionIsStartingWindow(LogMaker log) {
+        assertEquals("transition should be started because of starting window",
+                1 /* APP_TRANSITION_STARTING_WINDOW */, log.getSubtype());
+        assertNotNull("log should have starting window delay",
+                log.getTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS));
+    }
+
+    private void assertEventLogsContainsLaunchTime(List<Event> events, ComponentName componentName,
+            int windowsDrawnDelayMs) {
+        for (Event event : events) {
+            Object[] arr = (Object[]) event.getData();
+            assertEquals(4, arr.length);
+            final String name = (String) arr[2];
+            final int delay = (int) arr[3];
+            if (name.equals(componentName.flattenToShortString())) {
+                assertEquals("Unexpected windows drawn delay for " + componentName,
+                        delay, windowsDrawnDelayMs);
+                return;
+            }
+        }
+        fail("Could not find am_activity_launch_time for " + componentName);
+    }
+
+    /**
+     * Start an activity that reports full drawn and verify:
+     * - fully drawn metrics are added to metrics logs
+     * - "Fully drawn activity ..." log is added to logcat
+     * In both cases verify fully drawn delay measurements are equal.
+     * See {@link Activity#reportFullyDrawn()}
+     */
+    @Test
+    @Presubmit
+    public void testAppFullyDrawnReportIsLogged() {
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(REPORT_FULLY_DRAWN_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        // Sleep until activity under test has reported drawn (after 500ms)
+        sleep(1000);
+
+        final LogMaker metricsLog = getMetricsLog(REPORT_FULLY_DRAWN_ACTIVITY,
+                APP_TRANSITION_REPORTED_DRAWN);
+        final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
+
+        assertNotNull("did not find the metrics log for: " + REPORT_FULLY_DRAWN_ACTIVITY
+                + " category:" + APP_TRANSITION_REPORTED_DRAWN, metricsLog);
+        assertThat("test activity has a 500ms delay before reporting fully drawn",
+                (long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS),
+                greaterThanOrEqualTo(500L));
+        assertEquals(TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE, metricsLog.getType());
+
+        final long fullyDrawnDelayMs =
+                (long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS);
+        final String expectedLog =
+                "Fully drawn " + REPORT_FULLY_DRAWN_ACTIVITY.flattenToShortString()
+                        + ": " + formatDuration(fullyDrawnDelayMs);
+        assertLogsContain(deviceLogs, expectedLog);
+    }
+
+    /**
+     * Warm launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
+     * totalTime is set correctly. Make sure the reported value is consistent with value reported to
+     * metrics logs. Verify we output the correct launch state.
+     */
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 122118854)
+    public void testAppWarmLaunchSetsWaitResultDelayData() {
+        runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
+
+        // Test warm launch
+        pressBackButton();
+        waitForDeviceIdle(1000);
+        mMetricsReader.checkpoint(); // clear out old logs
+
+        final String amStartOutput =
+                runShellCommand("am start -W " + TEST_ACTIVITY.flattenToShortString());
+
+        final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
+        assertNotNull("log should have windows drawn delay", metricsLog);
+
+        final int windowsDrawnDelayMs =
+                (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
+
+        assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_WARM_LAUNCH);
+
+        assertThat("did not find component in am start output.", amStartOutput,
+                containsString(TEST_ACTIVITY.flattenToShortString()));
+
+        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("WARM"));
+    }
+
+
+    /**
+     * Hot launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
+     * totalTime is set correctly. Make sure the reported value is consistent with value reported to
+     * metrics logs. Verify we output the correct launch state.
+     */
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 122118854)
+    public void testAppHotLaunchSetsWaitResultDelayData() {
+        runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
+
+        // Test hot launch
+        pressHomeButton();
+        waitForDeviceIdle(1000);
+        mMetricsReader.checkpoint(); // clear out old logs
+
+        final String amStartOutput =
+                runShellCommand("am start -W " + TEST_ACTIVITY.flattenToShortString());
+
+        final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
+        assertNotNull("log should have windows drawn delay", metricsLog);
+
+        final int windowsDrawnDelayMs =
+                (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
+
+        assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_HOT_LAUNCH);
+
+        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 launch state in am start output.", amStartOutput,
+                containsString("HOT"));
+    }
+
+  /**
+     * Cold launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
+     * totalTime is set correctly. Make sure the reported value is consistent with value reported to
+     * metrics logs. Verify we output the correct launch state.
+     */
+    @Test
+    @Presubmit
+    public void testAppColdLaunchSetsWaitResultDelayData() {
+        final String amStartOutput =
+                runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
+
+        final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
+        assertNotNull("log should have windows drawn delay", metricsLog);
+
+        final int windowsDrawnDelayMs =
+                (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
+
+        assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_COLD_LAUNCH);
+
+        assertThat("did not find component in am start output.", amStartOutput,
+                containsString(TEST_ACTIVITY.flattenToShortString()));
+
+        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("COLD"));
+    }
+
+    /**
+     * Launch an app that is already visible and verify we handle cases where we will not
+     * receive a windows drawn message.
+     * see b/117148004
+     */
+    @Test
+    @Presubmit
+    public void testLaunchOfVisibleApp() {
+        // Launch an activity.
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(SECOND_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        // Launch a translucent activity on top.
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        // 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();
+        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);
+
+        // Verify metrics for subsequent launches are generated as expected.
+        mPreUptimeMs = SystemClock.uptimeMillis();
+        mMetricsReader.checkpoint(); // clear out old logs
+
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(THIRD_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        long postUptimeMs = SystemClock.uptimeMillis();
+        metricsLog = getMetricsLog(THIRD_ACTIVITY, APP_TRANSITION);
+        assertMetricsLogs(THIRD_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
+                postUptimeMs);
+        assertTransitionIsStartingWindow(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
+     * trampoline activity. See b/80380150 (Long warm launch times reported in dev play console)
+     */
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 80380150)
+    public void testNoDisplayActivityLaunch() {
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(NO_DISPLAY_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+
+        mPreUptimeMs = SystemClock.uptimeMillis();
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(SECOND_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+        final LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
+        final long postUptimeMs = SystemClock.uptimeMillis();
+        assertMetricsLogs(SECOND_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
+        assertTransitionIsStartingWindow(metricsLog);
+    }
+
+    /**
+     * Launch an activity with a trampoline activity and verify launch metrics measures the complete
+     * launch sequence from when the trampoline activity is launching to when the target activity
+     * draws on screen.
+     */
+    @Test
+    @Presubmit
+    public void testTrampolineActivityLaunch() {
+        // Launch a trampoline activity that will launch single task activity.
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(ENTRY_POINT_ALIAS_ACTIVITY)
+                .setWaitForLaunched(true)
+                .execute();
+        final LogMaker metricsLog = getMetricsLog(SINGLE_TASK_ACTIVITY, APP_TRANSITION);
+        final long postUptimeMs = SystemClock.uptimeMillis();
+        assertMetricsLogs(SINGLE_TASK_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
+                        postUptimeMs);
+    }
+
+    private LogMaker getMetricsLog(ComponentName componentName, int category) {
+        final Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
+                new LogMaker(category));
+        for (LogMaker log : startLogs) {
+            final String actualClassName = (String) log.getTaggedData(FIELD_CLASS_NAME);
+            final String actualPackageName = log.getPackageName();
+            if (componentName.getClassName().equals(actualClassName) &&
+                    componentName.getPackageName().equals(actualPackageName)) {
+                return log;
+            }
+        }
+        return null;
+    }
+
+    private void assertLogsContain(String[] logs, String expectedLog) {
+        for (String line : logs) {
+            if (line.contains(expectedLog)) {
+                return;
+            }
+        }
+        fail("Expected to find '" + expectedLog + "' in " + Arrays.toString(logs));
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityViewTest.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityViewTest.java
new file mode 100644
index 0000000..139c0cd
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityViewTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.Components.TEST_ACTIVITY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.ActivityView;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.FlakyTest;
+import android.support.test.rule.ActivityTestRule;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *      atest CtsActivityManagerDeviceTestCases:ActivityViewTest
+ */
+@Presubmit
+@FlakyTest
+public class ActivityViewTest extends ActivityManagerTestBase {
+
+    private Instrumentation mInstrumentation;
+    private ActivityView mActivityView;
+
+    @Rule
+    public ActivityTestRule<ActivityViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(ActivityViewTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityViewTestActivity activity = mActivityRule.launchActivity(null);
+        mActivityView = activity.getActivityView();
+        separateTestJournal();
+    }
+
+    @Test
+    public void testStartActivity() {
+        launchActivityInActivityView(TEST_ACTIVITY);
+        assertSingleLaunch(TEST_ACTIVITY);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testResizeActivityView() {
+        final int width = 500;
+        final int height = 500;
+
+        launchActivityInActivityView(TEST_ACTIVITY);
+        assertSingleLaunch(TEST_ACTIVITY);
+
+        mActivityView.layout(0, 0, width, height);
+
+        boolean boundsMatched = checkDisplaySize(TEST_ACTIVITY, width, height);
+        assertTrue("displayWidth and displayHeight must equal " + width + "x" + height,
+                boundsMatched);
+    }
+
+    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);
+            int avDisplayWidth = 0;
+            int avDisplayHeight = 0;
+            if (display != null) {
+                Rect bounds = display.mFullConfiguration.windowConfiguration.getAppBounds();
+                if (bounds != null) {
+                    avDisplayWidth = bounds.width();
+                    avDisplayHeight = bounds.height();
+                }
+            }
+            boundsMatched = avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
+            if (boundsMatched) {
+                return true;
+            }
+
+            // wait and try again
+            SystemClock.sleep(retryIntervalMs);
+        }
+
+        return boundsMatched;
+    }
+
+    @Test
+    public void testAppStoppedWithVisibilityGone() {
+        launchActivityInActivityView(TEST_ACTIVITY);
+        assertSingleLaunch(TEST_ACTIVITY);
+
+        separateTestJournal();
+        mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.GONE));
+        mInstrumentation.waitForIdleSync();
+        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
+
+        assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
+    }
+
+    @Test
+    public void testAppStoppedWithVisibilityInvisible() {
+        launchActivityInActivityView(TEST_ACTIVITY);
+        assertSingleLaunch(TEST_ACTIVITY);
+
+        separateTestJournal();
+        mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
+        mInstrumentation.waitForIdleSync();
+        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
+
+        assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
+    }
+
+    @Test
+    public void testAppStopAndStartWithVisibilityChange() {
+        launchActivityInActivityView(TEST_ACTIVITY);
+        assertSingleLaunch(TEST_ACTIVITY);
+
+        separateTestJournal();
+        mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
+        mInstrumentation.waitForIdleSync();
+        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
+
+        assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
+
+        separateTestJournal();
+        mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.VISIBLE));
+        mInstrumentation.waitForIdleSync();
+        mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+
+        assertLifecycleCounts(TEST_ACTIVITY, 0, 1, 1, 0, 0, 0, CountSpec.DONT_CARE);
+    }
+
+    private void launchActivityInActivityView(ComponentName activity) {
+        Intent intent = new Intent();
+        intent.setComponent(activity);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.startActivity(intent));
+        mAmWmState.waitForValidState(activity);
+    }
+
+    // Test activity
+    public static class ActivityViewTestActivity extends Activity {
+        private ActivityView mActivityView;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            mActivityView = new ActivityView(this);
+            setContentView(mActivityView);
+
+            ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
+            layoutParams.width = MATCH_PARENT;
+            layoutParams.height = MATCH_PARENT;
+            mActivityView.requestLayout();
+        }
+
+        ActivityView getActivityView() {
+            return mActivityView;
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
index 8022c87..d90464c 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
@@ -27,6 +27,7 @@
 import static org.junit.Assume.assumeFalse;
 
 import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
 import android.server.am.WindowManagerState.Display;
 
 import org.junit.Test;
@@ -35,6 +36,7 @@
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:AnimationBackgroundTests
  */
+@Presubmit
 public class AnimationBackgroundTests extends ActivityManagerTestBase {
 
     @Test
@@ -65,7 +67,7 @@
         launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
         getLaunchActivityBuilder().setTargetActivity(ANIMATION_TEST_ACTIVITY).execute();
         mAmWmState.computeState(ANIMATION_TEST_ACTIVITY);
-        mAmWmState.waitForAppTransitionIdle();
+        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.
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
index 0cb300a..a3b625c 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
@@ -16,22 +16,16 @@
 
 package android.server.am;
 
-import static android.content.Context.WINDOW_SERVICE;
-import static android.content.pm.PackageManager.FEATURE_WATCH;
-
-import static org.junit.Assert.fail;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeThat;
 
 import android.app.Activity;
-import android.content.Context;
 import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.DisplayMetrics;
 import android.view.Display;
-import android.view.WindowManager;
-
-import com.android.compatibility.common.util.PollingCheck;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -48,11 +42,8 @@
     // The max. aspect ratio the test activities are using.
     private static final float MAX_ASPECT_RATIO = 1.0f;
 
-    // The minimum supported device aspect ratio.
-    private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
-
-    // The minimum supported device aspect ratio for watches.
-    private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
+    // The min. aspect ratio the test activities are using.
+    private static final float MIN_ASPECT_RATIO = 3.0f;
 
     // Test target activity that has maxAspectRatio="true" and resizeableActivity="false".
     public static class MaxAspectRatioActivity extends Activity {
@@ -72,6 +63,18 @@
     public static class MetaDataMaxAspectRatioActivity extends Activity {
     }
 
+    // Test target activity that has minAspectRatio="true" and resizeableActivity="false".
+    public static class MinAspectRatioActivity extends Activity {
+    }
+
+    // Test target activity that has minAspectRatio="5.0" and resizeableActivity="true".
+    public static class MinAspectRatioResizeableActivity extends Activity {
+    }
+
+    // Test target activity that has no minAspectRatio defined and resizeableActivity="false".
+    public static class MinAspectRatioUnsetActivity extends Activity {
+    }
+
     @Rule
     public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
             new ActivityTestRule<>(MaxAspectRatioActivity.class,
@@ -92,74 +95,89 @@
             new ActivityTestRule<>(MaxAspectRatioUnsetActivity.class,
                     false /* initialTouchMode */, false /* launchActivity */);
 
-    @Test
-    public void testDeviceAspectRatio() throws Exception {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
-        final Display display = wm.getDefaultDisplay();
-        final DisplayMetrics metrics = new DisplayMetrics();
-        display.getRealMetrics(metrics);
+    @Rule
+    public ActivityTestRule<MinAspectRatioActivity> mMinAspectRatioActivity =
+            new ActivityTestRule<>(MinAspectRatioActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
-        float longSide = Math.max(metrics.widthPixels, metrics.heightPixels);
-        float shortSide = Math.min(metrics.widthPixels, metrics.heightPixels);
-        float deviceAspectRatio = longSide / shortSide;
-        float expectedMinAspectRatio = context.getPackageManager().hasSystemFeature(FEATURE_WATCH)
-                ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
+    @Rule
+    public ActivityTestRule<MinAspectRatioResizeableActivity> mMinAspectRatioResizeableActivity =
+            new ActivityTestRule<>(MinAspectRatioResizeableActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
-        if (deviceAspectRatio < expectedMinAspectRatio) {
-            fail("deviceAspectRatio=" + deviceAspectRatio
-                    + " is less than expectedMinAspectRatio=" + expectedMinAspectRatio);
-        }
-    }
+    @Rule
+    public ActivityTestRule<MinAspectRatioUnsetActivity> mMinAspectRatioUnsetActivity =
+            new ActivityTestRule<>(MinAspectRatioUnsetActivity.class,
+                    false /* initialTouchMode */, false /* launchActivity */);
 
     @Test
-    public void testMaxAspectRatio() throws Exception {
-        runAspectRatioTest(mMaxAspectRatioActivity, actual -> {
-            if (MAX_ASPECT_RATIO >= actual) return;
-            fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
+    public void testMaxAspectRatio() {
+        // Activity has a maxAspectRatio, assert that the actual ratio is less than that.
+        runAspectRatioTest(mMaxAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, lessThanOrEqualTo(MAX_ASPECT_RATIO));
         });
     }
 
     @Test
-    public void testMetaDataMaxAspectRatio() throws Exception {
-        runAspectRatioTest(mMetaDataMaxAspectRatioActivity, actual -> {
-            if (MAX_ASPECT_RATIO >= actual) return;
-            fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
+    public void testMetaDataMaxAspectRatio() {
+        // Activity has a maxAspectRatio, assert that the actual ratio is less than that.
+        runAspectRatioTest(mMetaDataMaxAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, lessThanOrEqualTo(MAX_ASPECT_RATIO));
         });
     }
 
     @Test
-    public void testMaxAspectRatioResizeableActivity() throws Exception {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final float expected = getAspectRatio(context);
-        final Activity testActivity = launchActivity(mMaxAspectRatioResizeableActivity);
-        PollingCheck.waitFor(testActivity::hasWindowFocus);
+    public void testMaxAspectRatioResizeableActivity() {
+        // Since this activity is resizeable, its max aspect ratio should be ignored.
+        runAspectRatioTest(mMaxAspectRatioResizeableActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
 
-        Display testDisplay = testActivity.findViewById(android.R.id.content).getDisplay();
-
-        // TODO(b/69982434): Fix DisplayManager NPE when getting display from Instrumentation
-        // context, then can use DisplayManager to get the aspect ratio of the correct display.
-        if (testDisplay.getDisplayId() != Display.DEFAULT_DISPLAY) {
-            return;
-        }
-
-        // Since this activity is resizeable, its aspect ratio shouldn't be less than the device's
-        runTest(testActivity, actual -> {
-            if (aspectRatioEqual(expected, actual) || expected < actual) return;
-            fail("actual=" + actual + " is less than expected=" + expected);
+            final float defaultDisplayAspectRatio = getDefaultDisplayAspectRatio();
+            assertThat(actual, greaterThanOrEqualToInexact(defaultDisplayAspectRatio));
         });
     }
 
     @Test
-    public void testMaxAspectRatioUnsetActivity() throws Exception {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final float expected = getAspectRatio(context);
+    public void testMaxAspectRatioUnsetActivity() {
+        // Since this activity didn't set an explicit maxAspectRatio, there should be no such
+        // ratio enforced.
+        runAspectRatioTest(mMaxAspectRatioUnsetActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
 
-        // Since this activity didn't set an aspect ratio, its aspect ratio shouldn't be less than
-        // the device's
-        runAspectRatioTest(mMaxAspectRatioUnsetActivity, actual -> {
-            if (aspectRatioEqual(expected, actual) || expected < actual) return;
-            fail("actual=" + actual + " is less than expected=" + expected);
+            assertThat(actual, greaterThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
+        });
+    }
+
+    @Test
+    public void testMinAspectRatio() {
+        // Activity has a minAspectRatio, assert the ratio is at least that.
+        runAspectRatioTest(mMinAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, greaterThanOrEqualToInexact(MIN_ASPECT_RATIO));
+        });
+    }
+
+    @Test
+    public void testMinAspectRatioResizeableActivity() {
+        // Since this activity is resizeable, the minAspectRatio should be ignored.
+        runAspectRatioTest(mMinAspectRatioResizeableActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
+
+            assertThat(actual, lessThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
+        });
+    }
+
+    @Test
+    public void testMinAspectRatioUnsetActivity() {
+        // Since this activity didn't set an explicit minAspectRatio, there should be no such
+        // ratio enforced.
+        runAspectRatioTest(mMinAspectRatioUnsetActivity, (actual, displayId) -> {
+            // TODO(b/69982434): Add ability to get native aspect ratio non-default display.
+            assumeThat(displayId, is(Display.DEFAULT_DISPLAY));
+
+            assertThat(actual, lessThanOrEqualToInexact(getDefaultDisplayAspectRatio()));
         });
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
index 0d1086c..eaf1276 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
@@ -16,28 +16,39 @@
 
 package android.server.am;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
 import android.app.Activity;
-import android.content.Context;
 import android.graphics.Point;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.hamcrest.Matcher;
+
 class AspectRatioTestsBase {
     // The delta allowed when comparing two floats for equality. We consider them equal if they are
     // within two significant digits of each other.
     private static final float FLOAT_EQUALITY_DELTA = .01f;
 
     interface AssertAspectRatioCallback {
-        void assertAspectRatio(float actual);
+        void assertAspectRatio(float actual, int displayId);
     }
 
     void runAspectRatioTest(final ActivityTestRule activityRule,
             final AssertAspectRatioCallback callback) {
         final Activity activity = launchActivity(activityRule);
-        runTest(activity, callback);
-        finishActivity(activityRule);
+        PollingCheck.waitFor(activity::hasWindowFocus);
+        try {
+            callback.assertAspectRatio(getActivityAspectRatio(activity),
+                    getDisplay(activity).getDisplayId());
+        } finally {
+            finishActivity(activityRule);
+        }
 
         // TODO(b/35810513): All this rotation stuff doesn't really work yet. Need to make sure
         // context is updated correctly here. Also, what does it mean to be holding a reference to
@@ -51,13 +62,20 @@
 //        callback.assertAspectRatio(getAspectRatio(activity));
     }
 
-    protected void runTest(Activity activity, AssertAspectRatioCallback callback) {
-        callback.assertAspectRatio(getAspectRatio(activity));
+    static float getDefaultDisplayAspectRatio() {
+        return getAspectRatio(InstrumentationRegistry.getContext().getSystemService(
+                WindowManager.class).getDefaultDisplay());
     }
 
-     static float getAspectRatio(Context context) {
-        final Display display =
-                context.getSystemService(WindowManager.class).getDefaultDisplay();
+    static float getActivityAspectRatio(Activity activity) {
+        return getAspectRatio(getDisplay(activity));
+    }
+
+    private static Display getDisplay(Activity activity) {
+        return activity.getWindow().peekDecorView().getDisplay();
+    }
+
+    private static float getAspectRatio(Display display) {
         final Point size = new Point();
         display.getSize(size);
         final float longSide = Math.max(size.x, size.y);
@@ -82,11 +100,11 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
-    static boolean aspectRatioEqual(float a, float b) {
-        return Math.abs(a - b) < FLOAT_EQUALITY_DELTA;
+    static Matcher<Float> greaterThanOrEqualToInexact(float expected) {
+        return greaterThanOrEqualTo(expected - FLOAT_EQUALITY_DELTA);
     }
 
-    static boolean aspectRatioLessThanEqual(float a, float b) {
-        return a < b || aspectRatioEqual(a, b);
+    static Matcher<Float> lessThanOrEqualToInexact(float expected) {
+        return lessThanOrEqualTo(expected + FLOAT_EQUALITY_DELTA);
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java b/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java
index a86e3f8..b24a859 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/DeprecatedTargetSdkTest.java
@@ -44,7 +44,7 @@
         super.tearDown();
 
         // Ensure app process is stopped.
-        stopTestPackage(MAIN_ACTIVITY);
+        stopTestPackage(MAIN_ACTIVITY.getPackageName());
     }
 
     @Test
diff --git a/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
index 83ceabd..f69a949 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
@@ -50,8 +50,8 @@
         super.tearDown();
 
         // Ensure app process is stopped.
-        stopTestPackage(SMALLEST_WIDTH_ACTIVITY);
-        stopTestPackage(TEST_ACTIVITY);
+        stopTestPackage(SMALLEST_WIDTH_ACTIVITY.getPackageName());
+        stopTestPackage(TEST_ACTIVITY.getPackageName());
     }
 
     @Test
@@ -93,12 +93,12 @@
                 EXTRA_LAUNCH_ANOTHER_ACTIVITY, getActivityName(TEST_ACTIVITY));
         executeShellCommand(startActivityOnTop);
         mAmWmState.assertActivityDisplayed(TEST_ACTIVITY);
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
 
         try (final ScreenDensitySession screenDensitySession = new ScreenDensitySession()) {
             screenDensitySession.setUnsupportedDensity();
 
-            assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+            assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */);
             pressBackButton();
 
             mAmWmState.assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
index 5d2d82b..28fe737 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
@@ -18,8 +18,6 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
 import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.am.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
 import static android.server.am.Components.PIP_ACTIVITY;
@@ -27,29 +25,45 @@
 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.am.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
+import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY;
 import static android.server.am.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.am.UiDeviceUtils.pressBackButton;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import android.support.test.InstrumentationRegistry;
+
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:KeyguardLockedTests
  */
 public class KeyguardLockedTests extends KeyguardTestBase {
-
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to dismiss keyguard via {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String DISMISS_KEYGUARD_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_DISMISS_KEYGUARD + " true";
-
     @Before
     @Override
     public void setUp() throws Exception {
@@ -77,6 +91,23 @@
     }
 
     @Test
+    public void testDisableKeyguard_thenSettingCredential_reenablesKeyguard_b119322269() {
+        final KeyguardManager.KeyguardLock keyguardLock = mContext.getSystemService(
+                KeyguardManager.class).newKeyguardLock("KeyguardLockedTests");
+
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            keyguardLock.disableKeyguard();
+
+            lockScreenSession.setLockCredential();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+        } finally {
+            keyguardLock.reenableKeyguard();
+        }
+    }
+
+    @Test
     public void testDismissKeyguard() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential()
@@ -103,6 +134,7 @@
             lockScreenSession.enterAndConfirmLockCredential();
             mAmWmState.waitForKeyguardGone();
             mAmWmState.assertKeyguardGone();
+            mAmWmState.computeState(DISMISS_KEYGUARD_ACTIVITY);
             boolean isDismissTranslucent =
                     mAmWmState.getAmState().isActivityTranslucent(DISMISS_KEYGUARD_ACTIVITY);
             mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
@@ -119,7 +151,7 @@
             launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-            executeShellCommand(DISMISS_KEYGUARD_BROADCAST);
+            mBroadcastActionTrigger.dismissKeyguardByFlag();
             lockScreenSession.enterAndConfirmLockCredential();
 
             // Make sure we stay on Keyguard.
@@ -132,7 +164,7 @@
     public void testDismissKeyguardActivity_method() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.gotoKeyguard();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
@@ -142,7 +174,7 @@
             mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
             mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-            assertOnDismissSucceededInLogcat(logSeparator);
+            assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
         }
     }
 
@@ -150,13 +182,13 @@
     public void testDismissKeyguardActivity_method_cancelled() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.gotoKeyguard();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             launchActivity(DISMISS_KEYGUARD_METHOD_ACTIVITY);
             pressBackButton();
-            assertOnDismissCancelledInLogcat(logSeparator);
+            assertOnDismissCancelled(DISMISS_KEYGUARD_METHOD_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, false);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
@@ -175,7 +207,7 @@
             mAmWmState.waitForKeyguardShowingAndNotOccluded();
             mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, false);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-            assertTrue(isDisplayOn());
+            assertTrue(isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
@@ -189,13 +221,13 @@
             // Show the PiP activity in fullscreen
             launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
 
-            // Go to the lockscreen
-            lockScreenSession.gotoKeyguard();
+            // Lock the screen and ensure that the PiP activity showing over the LockScreen.
+            lockScreenSession.gotoKeyguard(PIP_ACTIVITY);
             mAmWmState.waitForKeyguardShowingAndOccluded();
             mAmWmState.assertKeyguardShowingAndOccluded();
 
             // Request that the PiP activity enter picture-in-picture mode (ensure it does not)
-            executeShellCommand("am broadcast -a " + ACTION_ENTER_PIP);
+            mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
             waitForEnterPip(PIP_ACTIVITY);
             mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
@@ -232,7 +264,7 @@
 
             // Lock the screen and ensure that the fullscreen activity showing over the lockscreen
             // is visible, but not the PiP activity
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertKeyguardShowingAndOccluded();
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
@@ -263,6 +295,73 @@
         }
     }
 
+    @Test
+    public void testShowWhenLockedAttrImeActivityAndShowSoftInput() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(mContext,
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY, true);
+
+            // Make sure the activity has been called showSoftInput & IME window is visible.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                    TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+            // Assert the IME is shown on the expected display.
+            mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
+        }
+    }
+
+    @Test
+    public void testShowWhenLockedImeActivityAndShowSoftInput() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final TestActivitySession<ShowWhenLockedImeActivity> imeTestActivitySession = new
+                     TestActivitySession<>();
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(mContext,
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            imeTestActivitySession.launchTestActivityOnDisplaySync(ShowWhenLockedImeActivity.class,
+                    DEFAULT_DISPLAY);
+
+            // Make sure the activity has been called showSoftInput & IME window is visible.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                    TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+            // Assert the IME is shown on the expected display.
+            mAmWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
+        }
+    }
+
+    public static class ShowWhenLockedImeActivity extends Activity {
+
+        @Override
+        protected void onCreate(Bundle icicle) {
+            super.onCreate(icicle);
+            final EditText editText = new EditText(this);
+            // Set private IME option for editorMatcher to identify which TextView received
+            // onStartInput event.
+            editText.setPrivateImeOptions(
+                    getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
+            final LinearLayout layout = new LinearLayout(this);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.addView(editText);
+            setContentView(layout);
+
+            // Set showWhenLocked as true & request focus for showing soft input.
+            setShowWhenLocked(true);
+            getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            editText.requestFocus();
+        }
+    }
+
     /**
      * Waits until the given activity has entered picture-in-picture mode (allowing for the
      * subsequent animation to start).
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
index 6291eb8..3da3b6f 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
@@ -19,17 +19,15 @@
 import static android.server.am.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_CANCELLED;
 import static android.server.am.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_ERROR;
 import static android.server.am.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_SUCCEEDED;
-import static android.server.am.Components.KeyguardDismissLoggerCallback.KEYGUARD_DISMISS_LOG_TAG;
-import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logAlways;
 
 import static org.junit.Assert.fail;
 
 import android.app.KeyguardManager;
+import android.content.ComponentName;
 import android.os.SystemClock;
+import android.server.am.TestJournalProvider.TestJournalContainer;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 class KeyguardTestBase extends ActivityManagerTestBase {
 
@@ -41,29 +39,23 @@
         mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
     }
 
-    void assertOnDismissSucceededInLogcat(LogSeparator logSeparator) {
-        assertInLogcat(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_SUCCEEDED, logSeparator);
+    static void assertOnDismissSucceeded(ComponentName testingComponentName) {
+        assertDismissCallback(testingComponentName, ENTRY_ON_DISMISS_SUCCEEDED);
     }
 
-    void assertOnDismissCancelledInLogcat(LogSeparator logSeparator) {
-        assertInLogcat(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_CANCELLED, logSeparator);
+    static void assertOnDismissCancelled(ComponentName testingComponentName) {
+        assertDismissCallback(testingComponentName, ENTRY_ON_DISMISS_CANCELLED);
     }
 
-    void assertOnDismissErrorInLogcat(LogSeparator logSeparator) {
-        assertInLogcat(KEYGUARD_DISMISS_LOG_TAG, ENTRY_ON_DISMISS_ERROR, logSeparator);
+    static void assertOnDismissError(ComponentName testingComponentName) {
+        assertDismissCallback(testingComponentName, ENTRY_ON_DISMISS_ERROR);
     }
 
-    private void assertInLogcat(String logTag, String entry, LogSeparator logSeparator) {
-        final Pattern pattern = Pattern.compile("(.+)" + entry);
+    private static void assertDismissCallback(ComponentName testingComponentName, String entry) {
         for (int retry = 1; retry <= 5; retry++) {
-            final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
-            for (int i = lines.length - 1; i >= 0; i--) {
-                final String line = lines[i].trim();
-                log(line);
-                Matcher matcher = pattern.matcher(line);
-                if (matcher.matches()) {
-                    return;
-                }
+            if (TestJournalContainer.get(testingComponentName).extras
+                    .getBoolean(entry)) {
+                return;
             }
             logAlways("Waiting for " + entry + "... retry=" + retry);
             SystemClock.sleep(500);
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
index bf3d1ea..f8b4caf 100755
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -18,16 +18,17 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ComponentNameUtils.getActivityName;
 import static android.server.am.ComponentNameUtils.getWindowName;
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
-import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
-import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
 import static android.server.am.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.am.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
+import static android.server.am.Components.INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY;
+import static android.server.am.Components.INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
+import static android.server.am.Components.INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY;
 import static android.server.am.Components.KEYGUARD_LOCK_ACTIVITY;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_DIALOG_ACTIVITY;
@@ -38,6 +39,7 @@
 import static android.server.am.Components.TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.am.UiDeviceUtils.pressBackButton;
 import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
@@ -46,8 +48,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 import android.server.am.WindowManagerState.WindowState;
+import android.support.test.filters.FlakyTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,15 +61,6 @@
  *     atest CtsActivityManagerDeviceTestCases:KeyguardTests
  */
 public class KeyguardTests extends KeyguardTestBase {
-
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to dismiss keyguard via {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    private static final String DISMISS_KEYGUARD_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_DISMISS_KEYGUARD + " true";
-    // Shell command to dismiss keyguard via {@link #BROADCAST_RECEIVER_ACTIVITY} method.
-    private static final String DISMISS_KEYGUARD_METHOD_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_DISMISS_KEYGUARD_METHOD + " true";
-
     @Before
     @Override
     public void setUp() throws Exception {
@@ -91,12 +86,13 @@
     }
 
     @Test
+    @FlakyTest(bugId = 110276714)
     public void testShowWhenLockedActivity() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
             mAmWmState.assertKeyguardShowingAndOccluded();
@@ -113,7 +109,7 @@
             launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY, true);
             assertTrue(mAmWmState.getWmState().allWindowsVisible(
@@ -134,7 +130,8 @@
                     SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(
+                    SHOW_WHEN_LOCKED_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
@@ -151,7 +148,7 @@
             launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
             assertWallpaperShowing();
@@ -170,7 +167,7 @@
             mAmWmState.computeState(TEST_ACTIVITY, SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.assertVisibility(TEST_ACTIVITY, true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
             mAmWmState.computeState(true);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
             mAmWmState.assertVisibility(TEST_ACTIVITY, false);
@@ -208,7 +205,7 @@
                             .setMultipleTask(false)
             );
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
             mAmWmState.assertKeyguardShowingAndOccluded();
@@ -218,6 +215,149 @@
     }
 
     /**
+     * Tests whether an activity that has called setInheritShowWhenLocked(true) above a
+     * SHOW_WHEN_LOCKED activity is visible if Keyguard is locked.
+     */
+    @Test
+    public void testInheritShowWhenLockedAdd() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            launchActivity(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
+            mAmWmState.computeState(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ADD_ACTIVITY, true);
+        }
+    }
+
+    /**
+     * Tests whether an activity that has the manifest attribute inheritShowWhenLocked but then
+     * calls setInheritShowWhenLocked(false) above a SHOW_WHEN_LOCKED activity is invisible if
+     * Keyguard is locked.
+     */
+    @Test
+    public void testInheritShowWhenLockedRemove() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            launchActivity(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
+            mAmWmState.computeState(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            assertTrue(mKeyguardManager.isKeyguardLocked());
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_REMOVE_ACTIVITY, false);
+        }
+    }
+
+    /**
+     * Tests whether an activity that has the manifest attribute inheritShowWhenLocked above a
+     * SHOW_WHEN_LOCKED activity is visible if Keyguard is locked.
+     * */
+    @Test
+    public void testInheritShowWhenLockedAttr() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            launchActivity(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.computeState(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+        }
+    }
+
+    /**
+     * Tests whether an activity that doesn't have the manifest attribute inheritShowWhenLocked
+     * above a SHOW_WHEN_LOCKED activity is invisible if Keyguard is locked.
+     * */
+    @Test
+    public void testNoInheritShowWhenLocked() throws Exception {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            launchActivity(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.computeState(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
+
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.computeState(true);
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            assertTrue(mKeyguardManager.isKeyguardLocked());
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+            mAmWmState.assertVisibility(NO_INHERIT_SHOW_WHEN_LOCKED_ATTR_ACTIVITY, false);
+        }
+    }
+
+    /**
+     * Test that when a normal activity finished and an existing FLAG_DISMISS_KEYGUARD activity
+     * becomes the top activity, it should be resumed.
+     */
+    @Test
+    public void testResumeDismissKeyguardActivityFromBackground() {
+        testResumeOccludingActivityFromBackground(DISMISS_KEYGUARD_ACTIVITY);
+    }
+
+    /**
+     * Test that when a normal activity finished and an existing SHOW_WHEN_LOCKED activity becomes
+     * the top activity, it should be resumed.
+     */
+    @Test
+    public void testResumeShowWhenLockedActivityFromBackground() {
+        testResumeOccludingActivityFromBackground(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
+    }
+
+    private void testResumeOccludingActivityFromBackground(ComponentName occludingActivity) {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.gotoKeyguard();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+
+            // Launch an activity which is able to occlude keyguard.
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setTargetActivity(occludingActivity).execute();
+
+            // Launch an activity without SHOW_WHEN_LOCKED and finish it.
+            getLaunchActivityBuilder().setUseInstrumentation()
+                    .setMultipleTask(true)
+                    // Don't wait for activity visible because keyguard will show.
+                    .setWaitForLaunched(false)
+                    .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).execute();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+
+            mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+            mAmWmState.waitForKeyguardShowingAndOccluded();
+
+            // The occluding activity should be resumed because it becomes the top activity.
+            mAmWmState.computeState(occludingActivity);
+            mAmWmState.assertVisibility(occludingActivity, true);
+            assertTrue(occludingActivity + " must be resumed.",
+                    mAmWmState.getAmState().hasActivityState(occludingActivity,
+                            ActivityManagerState.STATE_RESUMED));
+        }
+    }
+
+    /**
      * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
      */
     @Test
@@ -237,7 +377,7 @@
     @Test
     public void testDismissKeyguardActivity_method() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.gotoKeyguard();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
@@ -246,28 +386,28 @@
             mAmWmState.computeState(DISMISS_KEYGUARD_METHOD_ACTIVITY);
             mAmWmState.assertVisibility(DISMISS_KEYGUARD_METHOD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-            assertOnDismissSucceededInLogcat(logSeparator);
+            assertOnDismissSucceeded(DISMISS_KEYGUARD_METHOD_ACTIVITY);
         }
     }
 
     @Test
     public void testDismissKeyguardActivity_method_notTop() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.gotoKeyguard();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             launchActivity(BROADCAST_RECEIVER_ACTIVITY);
             launchActivity(TEST_ACTIVITY);
-            executeShellCommand(DISMISS_KEYGUARD_METHOD_BROADCAST);
-            assertOnDismissErrorInLogcat(logSeparator);
+            mBroadcastActionTrigger.dismissKeyguardByMethod();
+            assertOnDismissError(BROADCAST_RECEIVER_ACTIVITY);
         }
     }
 
     @Test
     public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.sleepDevice();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
@@ -276,8 +416,8 @@
             mAmWmState.computeState(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
             mAmWmState.assertVisibility(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-            assertOnDismissSucceededInLogcat(logSeparator);
-            assertTrue(isDisplayOn());
+            assertOnDismissSucceeded(TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY);
+            assertTrue(isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
@@ -290,7 +430,7 @@
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
             mAmWmState.assertKeyguardShowingAndOccluded();
-            executeShellCommand(DISMISS_KEYGUARD_BROADCAST);
+            mBroadcastActionTrigger.dismissKeyguardByFlag();
             mAmWmState.assertKeyguardShowingAndOccluded();
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
         }
@@ -304,7 +444,7 @@
             launchActivity(KEYGUARD_LOCK_ACTIVITY);
             mAmWmState.computeState(KEYGUARD_LOCK_ACTIVITY);
             mAmWmState.assertVisibility(KEYGUARD_LOCK_ACTIVITY, true);
-            executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+            mBroadcastActionTrigger.finishBroadcastReceiverActivity();
             mAmWmState.waitForKeyguardShowingAndNotOccluded();
             mAmWmState.assertKeyguardShowingAndNotOccluded();
         }
@@ -327,11 +467,16 @@
             pressBackButton();
             mAmWmState.waitForKeyguardShowingAndNotOccluded();
             mAmWmState.waitForDisplayUnfrozen();
-            mAmWmState.waitForAppTransitionIdle();
+            mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
             mAmWmState.assertSanity();
             mAmWmState.assertHomeActivityVisible(false);
             mAmWmState.assertKeyguardShowingAndNotOccluded();
-            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
+            // 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");
+            // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of {@link pressBackButton()}.
+            mAmWmState.assertNotExist(SHOW_WHEN_LOCKED_ACTIVITY);
         }
     }
 
@@ -347,22 +492,22 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.sleepDevice();
 
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             mAmWmState.computeState(true);
             assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
             launchActivity(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
             mAmWmState.waitForKeyguardGone();
             mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY, true);
             assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-            assertOnDismissSucceededInLogcat(logSeparator);
-            assertTrue(isDisplayOn());
+            assertOnDismissSucceeded(TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY);
+            assertTrue(isDisplayOn(DEFAULT_DISPLAY));
         }
     }
 
     @Test
     public void testScreenOffWhileOccludedStopsActivity() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             lockScreenSession.gotoKeyguard();
             mAmWmState.assertKeyguardShowingAndNotOccluded();
             launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
@@ -370,18 +515,18 @@
             mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, true);
             mAmWmState.assertKeyguardShowingAndOccluded();
             lockScreenSession.sleepDevice();
-            assertSingleLaunchAndStop(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, logSeparator);
+            assertSingleLaunchAndStop(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
         }
     }
 
     @Test
     public void testScreenOffCausesSingleStop() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(TEST_ACTIVITY);
             mAmWmState.assertVisibility(TEST_ACTIVITY, true);
             lockScreenSession.sleepDevice();
-            assertSingleLaunchAndStop(TEST_ACTIVITY, logSeparator);
+            assertSingleLaunchAndStop(TEST_ACTIVITY);
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
index 105121b..427a2a0 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
@@ -16,6 +16,7 @@
 
 package android.server.am;
 
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY;
 import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY;
@@ -58,7 +59,7 @@
                     .unlockDevice();
             mAmWmState.computeState(TEST_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 
@@ -70,7 +71,7 @@
                     .unlockDevice();
             mAmWmState.computeState(WALLPAPAER_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 
@@ -81,7 +82,7 @@
             launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 
@@ -94,7 +95,7 @@
             mAmWmState.waitForKeyguardShowingAndNotOccluded();
             mAmWmState.computeState(true);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 
@@ -102,11 +103,11 @@
     public void testNewActivityDuringOccluded() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ACTIVITY);
             launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 
@@ -114,12 +115,12 @@
     public void testOccludeManifestAttr() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.gotoKeyguard();
-            final LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                    mAmWmState.getWmState().getLastTransition());
-            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, logSeparator);
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
+            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
         }
     }
 
@@ -127,18 +128,24 @@
     public void testOccludeAttrRemove() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.gotoKeyguard();
-            LogSeparator logSeparator = separateLogs();
+            separateTestJournal();
             launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
-                    mAmWmState.getWmState().getLastTransition());
-            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
+            assertSingleLaunch(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
 
+            // Waiting for the standard keyguard since
+            // {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} called
+            // {@link Activity#showWhenLocked(boolean)} and removed the attribute.
             lockScreenSession.gotoKeyguard();
-            logSeparator = separateLogs();
-            launchActivity(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
-            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
-            assertSingleStartAndStop(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, logSeparator);
+            separateTestJournal();
+            // Waiting for {@link SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY} stopped since it
+            // already lost show-when-locked attribute.
+            launchActivityNoWait(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
+            mAmWmState.waitForActivityState(
+                    SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY, STATE_STOPPED);
+            assertSingleStartAndStop(SHOW_WHEN_LOCKED_ATTR_REMOVE_ATTR_ACTIVITY);
         }
     }
 
@@ -146,11 +153,11 @@
     public void testNewActivityDuringOccludedWithAttr() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             launchActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
-            lockScreenSession.gotoKeyguard();
+            lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_ATTR_ACTIVITY);
             launchActivity(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             mAmWmState.computeState(SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY);
             assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
-                    mAmWmState.getWmState().getLastTransition());
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
index 556c4ca..2eb40f8 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
@@ -19,6 +19,8 @@
 import static android.server.am.UiDeviceUtils.pressBackButton;
 import static android.server.am.prerelease.Components.MAIN_ACTIVITY;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,7 +40,8 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mAm.alwaysShowUnsupportedCompileSdkWarning(MAIN_ACTIVITY);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAm.alwaysShowUnsupportedCompileSdkWarning(MAIN_ACTIVITY));
     }
 
     @After
@@ -47,7 +50,7 @@
         super.tearDown();
 
         // Ensure app process is stopped.
-        stopTestPackage(MAIN_ACTIVITY);
+        stopTestPackage(MAIN_ACTIVITY.getPackageName());
     }
 
     @Test
diff --git a/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
index 59d7e95..bf7a812 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
@@ -17,6 +17,7 @@
 package android.server.am;
 
 import static android.server.am.Components.SPLASHSCREEN_ACTIVITY;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.lessThan;
@@ -37,7 +38,7 @@
     @Test
     public void testSplashscreenContent() {
         launchActivityNoWait(SPLASHSCREEN_ACTIVITY);
-        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         mAmWmState.getWmState().getStableBounds();
         final Bitmap image = takeScreenshot();
         // Use ratios to flexibly accomodate circular or not quite rectangular displays
diff --git a/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java b/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java
index 11035ce..b85e42a 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java
@@ -15,12 +15,13 @@
  */
 
 package android.server.am;
-
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.server.am.ActivityManagerTestBase.isDisplayOn;
 import static android.server.am.StateLogger.logAlways;
 import static android.support.test.InstrumentationRegistry.getContext;
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.fail;
 
@@ -29,15 +30,10 @@
 import android.hardware.display.VirtualDisplay;
 import android.media.ImageReader;
 import android.os.SystemClock;
-import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.SystemUtil;
 
-import java.io.IOException;
-import java.util.Objects;
 import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Helper class to create virtual display.
@@ -48,48 +44,68 @@
     /** See {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD}. */
     private static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
 
-    private static final Pattern DISPLAY_DEVICE_PATTERN = Pattern.compile(
-            ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
     private static final int DENSITY = 160;
-    private static final int HEIGHT = 480;
-    private static final int WIDTH = 800;
+    static final int HEIGHT = 480;
+    static final int WIDTH = 800;
 
     private ImageReader mReader;
     private VirtualDisplay mVirtualDisplay;
     private boolean mCreated;
 
     void createAndWaitForDisplay(boolean requestShowWhenLocked) {
-        createVirtualDisplay(requestShowWhenLocked);
-        waitForDisplayState(false /* default */, true /* on */);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            createVirtualDisplay(requestShowWhenLocked, 0 /* virtualDisplayFlags */);
+            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* default */,
+                    true /* on */);
+            mCreated = true;
+        });
+    }
+
+    int createAndWaitForPublicDisplay(boolean requestShowWhenLocked) {
+        createVirtualDisplay(requestShowWhenLocked, VIRTUAL_DISPLAY_FLAG_PUBLIC);
+        waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* default */,
+                true /* on */);
         mCreated = true;
+        return mVirtualDisplay.getDisplay().getDisplayId();
     }
 
     void turnDisplayOff() {
-        mVirtualDisplay.setSurface(null);
-        waitForDisplayState(false /* default */, false /* on */);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mVirtualDisplay.setSurface(null);
+            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
+                    false /* on */);
+        });
     }
 
     void turnDisplayOn() {
-        mVirtualDisplay.setSurface(mReader.getSurface());
-        waitForDisplayState(false /* default */, true /* on */);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mVirtualDisplay.setSurface(mReader.getSurface());
+            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
+                    true /* on */);
+        });
     }
 
     void releaseDisplay() {
-        if (mCreated) {
-            mVirtualDisplay.release();
-            mReader.close();
-            waitForDisplayCondition(false /* defaultDisplay */, Objects::isNull,
-                    "Waiting for virtual display destroy");
-        }
-        mCreated = false;
+        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;
+        });
     }
 
-    private void createVirtualDisplay(boolean requestShowWhenLocked) {
+    private void createVirtualDisplay(boolean requestShowWhenLocked, int virtualDisplayFlags) {
         mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
 
         final DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
 
-        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+        int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                | virtualDisplayFlags;
+
         if (requestShowWhenLocked) {
             flags |= VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
         }
@@ -98,19 +114,20 @@
     }
 
     static void waitForDefaultDisplayState(boolean wantOn) {
-        waitForDisplayState(true /* default */, wantOn);
+        waitForDisplayState(DEFAULT_DISPLAY /* default */, wantOn);
     }
 
-    private static void waitForDisplayState(boolean defaultDisplay, boolean wantOn) {
-        waitForDisplayCondition(defaultDisplay, state -> state != null && state == wantOn,
-                "Waiting for " + (defaultDisplay ? "default" : "virtual") + " display "
+    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(boolean defaultDisplay,
+    private static void waitForDisplayCondition(int displayId,
             Predicate<Boolean> condition, String message) {
         for (int retry = 1; retry <= 10; retry++) {
-            if (condition.test(getDisplayState(defaultDisplay))) {
+            if (condition.test(isDisplayOn(displayId))) {
                 return;
             }
             logAlways(message + "... retry=" + retry);
@@ -118,28 +135,4 @@
         }
         fail(message + " failed");
     }
-
-    @Nullable
-    private static Boolean getDisplayState(boolean defaultDisplay) {
-        final String dump = executeShellCommand("dumpsys display");
-        final Predicate<Matcher> displayNameMatcher = defaultDisplay
-                ? m -> m.group(0).contains("FLAG_DEFAULT_DISPLAY")
-                : m -> m.group(1).equals(VIRTUAL_DISPLAY_NAME);
-        for (final String line : dump.split("\\n")) {
-            final Matcher matcher = DISPLAY_DEVICE_PATTERN.matcher(line);
-            if (matcher.matches() && displayNameMatcher.test(matcher)) {
-                return "ON".equals(matcher.group(2));
-            }
-        }
-        return null;
-    }
-
-    private static String executeShellCommand(String command) {
-        try {
-            return SystemUtil.runShellCommand(getInstrumentation(), command);
-        } catch (IOException e) {
-            //bubble it up
-            throw new RuntimeException(e);
-        }
-    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/Activities.java b/tests/framework/base/activitymanager/src/android/server/am/intent/Activities.java
new file mode 100644
index 0000000..72b5b0e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/Activities.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.server.am.intent;
+
+import android.app.Activity;
+
+/**
+ * A collection of activities with various launch modes used in the intent tests.
+ * Each activity is named after the LaunchMode it has enabled.
+ *
+ * They reflect the activities present in the IntentPlayground app. The IntentPlayground app
+ * resides in development/samples/IntentPlayground
+ */
+public class Activities {
+
+    public static class TrackerActivity extends Activity {
+    }
+
+    public static class RegularActivity extends Activity {
+    }
+
+    public static class SingleTopActivity extends Activity {
+    }
+
+    public static class SingleInstanceActivity extends Activity {
+    }
+
+    public static class SingleInstanceActivity2 extends Activity {
+    }
+
+    public static class SingleTaskActivity extends Activity {
+    }
+
+    public static class SingleTaskActivity2 extends Activity {
+    }
+
+    public static class TaskAffinity1Activity extends Activity {
+    }
+
+    public static class TaskAffinity1Activity2 extends Activity {
+    }
+
+    public static class TaskAffinity2Activity extends Activity {
+    }
+
+    public static class TaskAffinity3Activity extends Activity {
+    }
+
+    public static class ClearTaskOnLaunchActivity extends Activity {
+    }
+
+    public static class DocumentLaunchIntoActivity extends Activity {
+    }
+
+    public static class DocumentLaunchAlwaysActivity extends Activity {
+    }
+
+    public static class DocumentLaunchNeverActivity extends Activity {
+    }
+
+    public static class NoHistoryActivity extends Activity {
+    }
+
+    public static class LauncherActivity extends Activity {
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/Cases.java b/tests/framework/base/activitymanager/src/android/server/am/intent/Cases.java
new file mode 100644
index 0000000..cd542e3
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/Cases.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+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_DOCUMENT;
+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_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.server.am.intent.LaunchSequence.intent;
+import static android.server.am.intent.LaunchSequence.intentForResult;
+import static android.server.am.intent.Persistence.flag;
+
+import android.server.am.intent.Activities.RegularActivity;
+import android.server.am.intent.Persistence.IntentFlag;
+import android.server.am.intent.Persistence.LaunchIntent;
+
+import com.google.common.collect.Lists;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains all information to create and reuse intent test launches.
+ * It enumerates all the flags so they can easily be used.
+ *
+ * It also stores commonly used {@link LaunchSequence} objects for reuse.
+ */
+public class Cases {
+
+    public static final IntentFlag CLEAR_TASK = flag(FLAG_ACTIVITY_CLEAR_TASK,
+            "FLAG_ACTIVITY_CLEAR_TASK");
+    public static final IntentFlag CLEAR_TOP = flag(FLAG_ACTIVITY_CLEAR_TOP,
+            "FLAG_ACTIVITY_CLEAR_TOP");
+    private static final IntentFlag SINGLE_TOP = flag(FLAG_ACTIVITY_SINGLE_TOP,
+            "FLAG_ACTIVITY_SINGLE_TOP");
+    public static final IntentFlag NEW_TASK = flag(FLAG_ACTIVITY_NEW_TASK,
+            "FLAG_ACTIVITY_NEW_TASK");
+    public static final IntentFlag NEW_DOCUMENT = flag(FLAG_ACTIVITY_NEW_DOCUMENT,
+            "FLAG_ACTIVITY_NEW_DOCUMENT");
+    private static final IntentFlag MULTIPLE_TASK = flag(FLAG_ACTIVITY_MULTIPLE_TASK,
+            "FLAG_ACTIVITY_MULTIPLE_TASK");
+    public static final IntentFlag RESET_TASK_IF_NEEDED = flag(
+            FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
+            "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED");
+    public static final IntentFlag PREVIOUS_IS_TOP = flag(FLAG_ACTIVITY_PREVIOUS_IS_TOP,
+            "FLAG_ACTIVITY_PREVIOUS_IS_TOP");
+    public static final IntentFlag REORDER_TO_FRONT = flag(FLAG_ACTIVITY_REORDER_TO_FRONT,
+            "FLAG_ACTIVITY_REORDER_TO_FRONT");
+
+    // Flag only used for parsing intents that contain no flags.
+    private static final IntentFlag NONE = flag(0, "");
+
+    public final List<IntentFlag> flags = Lists.newArrayList(
+            CLEAR_TASK,
+            CLEAR_TOP,
+            SINGLE_TOP,
+            NEW_TASK,
+            NEW_DOCUMENT,
+            MULTIPLE_TASK,
+            RESET_TASK_IF_NEEDED,
+            PREVIOUS_IS_TOP,
+            REORDER_TO_FRONT
+    );
+
+    // Definition of intents used across multiple test cases.
+    private final LaunchIntent mRegularIntent = intent(RegularActivity.class);
+    private final LaunchIntent mSingleTopIntent = intent(Activities.SingleTopActivity.class);
+    private final LaunchIntent mAff1Intent = intent(Activities.TaskAffinity1Activity.class);
+    private final LaunchIntent mSecondAff1Intent = intent(Activities.TaskAffinity1Activity2.class);
+    private final LaunchIntent mSingleInstanceIntent = intent(
+            Activities.SingleInstanceActivity.class);
+    private final LaunchIntent mSingleTaskIntent = intent(Activities.SingleTaskActivity.class);
+    private final LaunchIntent mRegularForResultIntent = intentForResult(RegularActivity.class);
+
+    // LaunchSequences used across multiple test cases.
+    private final LaunchSequence mRegularSequence = LaunchSequence.create(mRegularIntent);
+
+    // To show that the most recent task get's resumed by new task
+    private final LaunchSequence mTwoAffinitiesSequence =
+            LaunchSequence.create(mAff1Intent)
+                    .append(mRegularIntent)
+                    .append(mAff1Intent.withFlags(NEW_TASK, MULTIPLE_TASK));
+
+    // Used to show that the task affinity is determined by the activity that started it,
+    // Not the affinity of the  current root activity.
+    private final LaunchSequence mRearrangedRootSequence = LaunchSequence.create(mRegularIntent)
+            .append(mAff1Intent.withFlags(NEW_TASK))
+            .append(mRegularIntent)
+            .append(mAff1Intent.withFlags(REORDER_TO_FRONT));
+
+
+    private final LaunchSequence mSingleInstanceActivitySequence =
+            LaunchSequence.create(mSingleInstanceIntent);
+
+    private final LaunchSequence mSingleTaskActivitySequence = LaunchSequence.create(
+            mSingleTaskIntent);
+
+    public List<LaunchSequence> newTaskCases() {
+        LaunchIntent aff1NewTask = mAff1Intent.withFlags(NEW_TASK);
+
+        return Lists.newArrayList(
+                // 1. Single instance will start a new task even without new task set
+                mRegularSequence.act(mSingleInstanceIntent),
+                // 2. With new task it will still end up in a new task
+                mRegularSequence.act(mSingleInstanceIntent.withFlags(NEW_TASK)),
+                // 3. Starting an activity with a different affinity without new task will put it in
+                // the existing task
+                mRegularSequence.act(mAff1Intent),
+                // 4. Starting a task with a different affinity and new task will start a new task
+                mRegularSequence.act(aff1NewTask),
+                // 5. Starting the intent with new task will not add a new activity to the task
+                mRegularSequence.act(mRegularIntent.withFlags(NEW_TASK)),
+                // 6. A different activity with the same affinity will start a new activity in
+                // the sam task
+                mRegularSequence.act(mSingleTopIntent.withFlags(NEW_TASK)),
+                // 7. To show that the most recent task get's resumed by new task, this can't
+                // be observed without differences in the same activity / task
+                mTwoAffinitiesSequence.act(aff1NewTask),
+                // 8. To show that new task respects the root as a single even
+                // if it is not at the bottom
+                mRearrangedRootSequence.act(mAff1Intent.withFlags(NEW_TASK)),
+                // 9. To show that new task with non root does start a new activity.
+                mRearrangedRootSequence.act(mSecondAff1Intent.withFlags(NEW_TASK)),
+                // 10. Multiple task will allow starting activities of the same affinity in
+                // different tasks
+                mRegularSequence.act(mRegularIntent.withFlags(NEW_TASK, MULTIPLE_TASK)),
+                // 11. Single instance will not start a new task even with multiple task on
+                mSingleInstanceActivitySequence.act(
+                        mSingleInstanceIntent.withFlags(NEW_TASK, MULTIPLE_TASK)),
+                // 12. The same should happen for single task.
+                mSingleTaskActivitySequence.act(
+                        mSingleTaskIntent.withFlags(NEW_TASK, MULTIPLE_TASK)),
+                // 13. This starts a regular in a new task
+                mSingleInstanceActivitySequence.act(mRegularIntent),
+                // 14. This adds regular in the same task
+                mSingleTaskActivitySequence.act(mRegularIntent),
+                // 15. Starting the activity for result keeps it in the same task
+                mSingleInstanceActivitySequence.act(mRegularForResultIntent),
+                // 16. Restarts the previous task with regular activity.
+                mRegularSequence.append(mSingleInstanceIntent).act(mRegularIntent)
+        );
+    }
+
+    /**
+     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT }test cases are the same as
+     * {@link Cases#newTaskCases()}, we should check them for differences.
+     */
+    public List<LaunchSequence> newDocumentCases() {
+        LaunchIntent aff1NewDocument = mAff1Intent.withFlags(NEW_DOCUMENT);
+
+        return Lists.newArrayList(
+                // 1. Single instance will start a new task even without new task set
+                mRegularSequence.act(mSingleInstanceIntent),
+                // 2. With new task it will still end up in a new task
+                mRegularSequence.act(mSingleInstanceIntent.withFlags(NEW_DOCUMENT)),
+                // 3. Starting an activity with a different affinity without new task will put it
+                // in the existing task
+                mRegularSequence.act(mAff1Intent),
+                // 4. With new document it will start it's own task
+                mRegularSequence.act(aff1NewDocument),
+                // 5. Starting the intent with new task will not add a new activity to the task
+                mRegularSequence.act(mRegularIntent.withFlags(NEW_DOCUMENT)),
+                // 6. Unlike the case with NEW_TASK, with new Document
+                mRegularSequence.act(mSingleTopIntent.withFlags(NEW_DOCUMENT)),
+                // 7. To show that the most recent task get's resumed by new task
+                mTwoAffinitiesSequence.act(aff1NewDocument),
+                // 8. To show that new task respects the root as a single
+                mRearrangedRootSequence.act(mAff1Intent.withFlags(NEW_DOCUMENT)),
+                // 9. New document starts a third task here, because there was no task for the
+                // document yet
+                mRearrangedRootSequence.act(mSecondAff1Intent.withFlags(NEW_DOCUMENT)),
+                // 10. Multiple task wil allow starting activities of the same affinity in different
+                // tasks
+                mRegularSequence.act(mRegularIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK)),
+                // 11. Single instance will not start a new task even with multiple task on
+                mSingleInstanceActivitySequence
+                        .act(mSingleInstanceIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK)),
+                // 12. The same should happen for single task.
+                mSingleTaskActivitySequence.act(
+                        mSingleTaskIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK))
+        );
+    }
+
+    public List<LaunchSequence> clearCases() {
+        LaunchSequence doubleRegularActivity = mRegularSequence.append(mRegularIntent);
+
+        return Lists.newArrayList(
+                // 1. This will clear the bottom and end up with just one activity
+                mRegularSequence.act(mRegularIntent.withFlags(CLEAR_TOP)),
+                // 2. This will result in still two regulars
+                doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP)),
+                // 3. This will result in a single regular it clears the top regular
+                // activity and then fails to start a new regular activity because it is already
+                // at the root of the task
+                doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, NEW_TASK)),
+                // 3. This will also result in two regulars, showing the first difference between
+                // new document and new task.
+                doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, NEW_DOCUMENT)),
+                // 4. This is here to show that previous is top has no effect on clear top or single
+                // top
+                doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, PREVIOUS_IS_TOP)),
+                // 5. Clear top finds the same activity in the task and clears from there
+                mRegularSequence.append(mAff1Intent).append(mRegularIntent).act(
+                        mAff1Intent.withFlags(CLEAR_TOP))
+        );
+    }
+
+    /**
+     * Tests for {@link android.app.Activity#startActivityForResult()}
+     */
+    //TODO: If b/122968776 is fixed these tests need to be updated
+    public List<LaunchSequence> forResultCases() {
+        LaunchIntent singleTopForResult = intentForResult(Activities.SingleTopActivity.class);
+
+        return Lists.newArrayList(
+                // 1. Start just a single regular activity
+                mRegularSequence.act(mRegularIntent.withFlags(SINGLE_TOP)),
+                // 2. For result will start a second activity
+                mRegularSequence.act(mRegularForResultIntent.withFlags(SINGLE_TOP)),
+                // 3. The same but for SINGLE_TOP as a launch mode
+                LaunchSequence.create(mSingleTopIntent).act(mSingleTopIntent),
+                // 4. Launch mode SINGLE_TOP when launched for result also starts a second activity.
+                LaunchSequence.create(mSingleTopIntent).act(singleTopForResult),
+                // 5. CLEAR_TOP results in a single regular activity
+                mRegularSequence.act(mRegularIntent.withFlags(CLEAR_TOP)),
+                // 6. Clear will still kill the for result
+                mRegularSequence.act(mRegularForResultIntent.withFlags(CLEAR_TOP)),
+                // 7. An activity started for result can go to a different task
+                mRegularSequence.act(mRegularForResultIntent.withFlags(NEW_TASK, MULTIPLE_TASK)),
+                // 8. Reorder to front with for result
+                mRegularSequence.append(mAff1Intent).act(
+                        mRegularForResultIntent.withFlags(REORDER_TO_FRONT)),
+                // 9. Reorder can move an activity above one that it started for result
+                mRegularSequence.append(intentForResult(Activities.TaskAffinity1Activity.class))
+                        .act(mRegularIntent.withFlags(REORDER_TO_FRONT))
+        );
+    }
+
+    /**
+     * Reset task if needed will trigger when it is delivered with new task set
+     * and there are activities in the task that have a different affinity.
+     *
+     * @return the test cases
+     */
+    //TODO: If b/122324373 is fixed these test need to be updated.
+    public List<LaunchSequence> resetTaskIfNeeded() {
+        // If a task with a different affinity like this get's reset
+        // it will create another task in the same stack with the now orphaned activity.
+        LaunchIntent resetRegularTask = mRegularIntent.withFlags(NEW_TASK,
+                RESET_TASK_IF_NEEDED);
+        LaunchSequence resetIfNeeded = mRegularSequence.append(mAff1Intent)
+                .act(resetRegularTask);
+
+        // If you try to reset a task with an activity that was started for result
+        // it will not move task.
+        LaunchSequence resetWontMoveResult = mRegularSequence.append(
+                intentForResult(Activities.TaskAffinity1Activity.class))
+                .act(resetRegularTask);
+
+        // Reset will not move activities with to a task with that affinity,
+        // instead it will always create a new task in that stack.
+        LaunchSequence resetToExistingTask2 = mRegularSequence
+                .append(mAff1Intent)
+                .append(mAff1Intent.withFlags(NEW_TASK))
+                .act(resetRegularTask);
+
+        // If a reset occurs the activities that move retain the same order
+        // in the new task as they had in the old task.
+        LaunchSequence resetOrdering = mRegularSequence
+                .append(mAff1Intent)
+                .append(mSecondAff1Intent)
+                .act(resetRegularTask);
+
+        return Lists.newArrayList(resetIfNeeded, resetWontMoveResult, resetToExistingTask2,
+                resetOrdering);
+    }
+
+    /**
+     * The human readable flags in the JSON files need to be converted back to the corresponding
+     * IntentFlag object when reading the file. This creates a map from the flags to their
+     * corresponding object.
+     *
+     * @return lookup table for the parsing of intent flags in the json files.
+     */
+    public Map<String, IntentFlag> createFlagParsingTable() {
+        HashMap<String, IntentFlag> flags = new HashMap<>();
+        for (IntentFlag flag : this.flags) {
+            flags.put(flag.name, flag);
+        }
+
+        flags.put(NONE.getName(), NONE);
+        return flags;
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/IntentGenerationTests.java b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentGenerationTests.java
new file mode 100644
index 0000000..c5daaaa
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentGenerationTests.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Environment;
+import android.server.am.intent.Persistence.IntentFlag;
+import android.server.am.intent.Persistence.TestCase;
+import android.support.test.InstrumentationRegistry;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The {@link IntentGenerationTests#generate()} method runs a very long generation process that
+ * takes over 10 minutes.
+ *
+ * The generated tests and results observed on a reference device can then be used in CTS for
+ * verification.
+ *
+ * It writes a bunch of files out to the directory obtained from
+ * {@link Environment#getExternalStoragePublicDirectory(String)} which is currently
+ * /storage/emulated/0/Documents/.
+ *
+ * These can then be pulled out via adb and put into the intent_tests asset directory.
+ * These are the tests that are checked by atest CtsActivityManagerDeviceTestCases:IntentTests
+ *
+ * Because this process takes so much time, certain timeouts in the AndroidTest.xml need to be
+ * raised.
+ *
+ * <option name="run-command" value="settings put system screen_off_timeout 1200000000" /> in the
+ * target preparer and <option name="test-timeout" value="3600000" /> in the <test class=></test>
+ *
+ * Since this test only exists to trigger this process using the test infrastructure
+ * it is ignored by default.
+ *
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:IntentGenerationTests
+ */
+@Ignore
+public class IntentGenerationTests extends IntentTestBase {
+    private static final Cases CASES = new Cases();
+
+    /**
+     * The flag parsing table used to parse the json files.
+     */
+    private static final Map<String, IntentFlag> TABLE = CASES.createFlagParsingTable();
+
+    /**
+     * Runner used to record testCases.
+     */
+    private LaunchRunner mLaunchRunner;
+
+    /**
+     * The target context we can launch the first activity from.
+     */
+    private Context mTargetContext = InstrumentationRegistry.getTargetContext();
+
+    //20 minute timeout.
+    @Test(timeout = 1_200_000)
+    public void generate() throws Exception {
+        mLaunchRunner.runAndWrite(mTargetContext, "forResult", CASES.forResultCases());
+        mLaunchRunner.runAndWrite(mTargetContext, "newTask", CASES.newTaskCases());
+        mLaunchRunner.runAndWrite(mTargetContext, "newDocumentCases", CASES.newDocumentCases());
+        mLaunchRunner.runAndWrite(mTargetContext, "resetTaskIfNeeded", CASES.resetTaskIfNeeded());
+        mLaunchRunner.runAndWrite(mTargetContext, "clearCases", CASES.clearCases());
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mLaunchRunner = new LaunchRunner(this);
+    }
+
+    /**
+     * Debugging utility to verify a single test in the assets folder.
+     *
+     * @throws IOException   if writing to storage fails
+     * @throws JSONException if the file has invalid json in it.
+     */
+    @Test
+    public void verifySingle() throws IOException, JSONException {
+        String test = "forResult/test-1.json";
+        TestCase testCase = readFromStorage(test);
+        mLaunchRunner.verify(mTargetContext, testCase);
+    }
+
+    private TestCase readFromStorage(String fileName) throws IOException, JSONException {
+        File documentsDirectory = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOCUMENTS);
+        Path testsFilePath = documentsDirectory.toPath().resolve(fileName);
+
+        JSONObject jsonInTestFile = new JSONObject(
+                new String(Files.readAllBytes(testsFilePath), StandardCharsets.UTF_8));
+        return TestCase.fromJson(jsonInTestFile, TABLE, fileName);
+    }
+
+    /**
+     * This class runs multiple {@link TestCase}-s in a single test.
+     * Therefore the list of componentNames that occur in the test is not known here.
+     *
+     * Instead {@link IntentTestBase#cleanUp(List)} is called from {@link
+     * LaunchRunner#runAndWrite(Context, String, List)} instead.
+     *
+     * @return an emptyList
+     */
+    @Override
+    List<ComponentName> activitiesUsedInTest() {
+        return Collections.emptyList();
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTestBase.java
new file mode 100644
index 0000000..ded10b7
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTestBase.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 android.server.am.intent;
+
+import android.content.ComponentName;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.UiDeviceUtils;
+
+import org.junit.After;
+
+import java.util.List;
+
+public abstract class IntentTestBase extends ActivityManagerTestBase {
+
+    /**
+     * This get's called from {@link IntentGenerationTests} because {@link IntentGenerationTests}
+     * run multiple {@link android.server.am.intent.Persistence.TestCase}-s in a single junit test.
+     *
+     * Normal test classes can just extend this class with manual cleanUp.
+     *
+     * @param activitiesInUsedInTest activities that should be gone after tearDown
+     */
+    public void cleanUp(List<ComponentName> activitiesInUsedInTest) throws Exception {
+        super.tearDown();
+        UiDeviceUtils.pressHomeButton();
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+        this.getAmWmState().waitForWithAmState(
+                state -> state.containsNoneOf(activitiesInUsedInTest),
+                "Waiting for activity to be removed");
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        cleanUp(activitiesUsedInTest());
+    }
+
+    /**
+     * @return The activities that are launched in this Test.
+     */
+    abstract List<ComponentName> activitiesUsedInTest();
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTests.java b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTests.java
new file mode 100644
index 0000000..e9ff1cb
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/IntentTests.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.os.Environment;
+import android.server.am.UiDeviceUtils;
+import android.server.am.intent.Persistence.IntentFlag;
+import android.server.am.intent.Persistence.TestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+
+import com.google.common.collect.Lists;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This test will verify every intent_test json file in a separately run test, through JUnits
+ * Parameterized runner.
+ *
+ * Running a single test using this class is not supported.
+ * For this use case look at {@link IntentGenerationTests#verifySingle()}
+ *
+ * Note: atest CtsActivityManagerDeviceTestCases:IntentTests#verify does not work because the
+ * Parameterized runner won't expose the test that way to atest.
+ *
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:IntentTests
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class IntentTests extends IntentTestBase {
+    private static final Cases CASES = new Cases();
+
+    /**
+     * The flag parsing table used to parse the json files.
+     */
+    private static final Map<String, IntentFlag> TABLE = CASES.createFlagParsingTable();
+    private static Context TARGET_CONTEXT = InstrumentationRegistry.getTargetContext();
+
+    private static final int JSON_INDENTATION_LEVEL = 4;
+
+    /**
+     * The runner used to verify the recorded test cases.
+     */
+    private LaunchRunner mLaunchRunner;
+
+    /**
+     * The Test case we are currently verifying.
+     */
+    private TestCase mTestCase;
+
+    public IntentTests(TestCase testCase, String name) {
+        mTestCase = testCase;
+    }
+
+    /**
+     * Read all the tests in the assets of the application and create a separate test to run for
+     * each of them using the {@link org.junit.runners.Parameterized}
+     */
+    @Parameterized.Parameters(name = "{1}")
+    public static Collection<Object[]> data() throws IOException, JSONException {
+        return readAllFromAssets().stream().map(
+                test -> new Object[]{test, test.getName()}).collect(
+                Collectors.toList());
+    }
+
+    @Test
+    public void verify() {
+        mLaunchRunner.verify(TARGET_CONTEXT, mTestCase);
+    }
+
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mLaunchRunner = new LaunchRunner(this);
+    }
+
+    @Override
+    List<ComponentName> activitiesUsedInTest() {
+        return mTestCase.getSetup().componentsInCase();
+    }
+
+    public static List<TestCase> readAllFromAssets() throws IOException, JSONException {
+        List<TestCase> testCases = Lists.newArrayList();
+        AssetManager assets = TARGET_CONTEXT.getAssets();
+
+        List<String> testFiles = Lists.newArrayList();
+        for (String dir : assets.list("")) {
+            for (String file : assets.list(dir)) {
+                if (file.endsWith(".json")) {
+                    testFiles.add(dir + "/" + file);
+                }
+            }
+        }
+
+        for (String testFile : testFiles) {
+            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
+                    new BufferedInputStream(assets.open(testFile))))) {
+                String jsonData = bufferedReader.lines().collect(
+                        Collectors.joining("\n"));
+
+                TestCase testCase = TestCase.fromJson(
+                        new JSONObject(jsonData), TABLE, testFile);
+
+                testCases.add(testCase);
+            }
+        }
+
+        return testCases;
+    }
+
+    static void writeToDocumentsStorage(TestCase testCase, int number, String dirName)
+            throws JSONException, IOException {
+        File documentsDirectory = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOCUMENTS);
+        Path testsFilePath = documentsDirectory.toPath().resolve(dirName);
+
+        if (!Files.exists(testsFilePath)) {
+            Files.createDirectories(testsFilePath);
+        }
+        Files.write(testsFilePath.resolve("test-" + number + ".json"),
+                Lists.newArrayList(testCase.toJson().toString(JSON_INDENTATION_LEVEL)));
+    }
+}
+
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchRunner.java b/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchRunner.java
new file mode 100644
index 0000000..5d702db
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchRunner.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+import static android.server.am.intent.Persistence.LaunchFromIntent.prepareSerialisation;
+import static android.server.am.intent.StateComparisonException.assertEndStatesEqual;
+import static android.server.am.intent.StateComparisonException.assertInitialStateEqual;
+
+import static com.google.common.collect.Iterables.getLast;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.server.am.ActivityAndWindowManagersState;
+import android.server.am.ActivityManagerState;
+import android.server.am.intent.LaunchSequence.LaunchSequenceExecutionInfo;
+import android.server.am.intent.Persistence.GenerationIntent;
+import android.server.am.intent.Persistence.LaunchFromIntent;
+import android.server.am.intent.Persistence.StateDump;
+import android.support.test.InstrumentationRegistry;
+import android.view.Display;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Launch runner is an interpreter for a {@link LaunchSequence} command object.
+ * It supports three main modes of operation.
+ *
+ * 1. The {@link LaunchRunner#runAndWrite} method to run a launch object and write out the
+ * resulting {@link Persistence.TestCase} to device storage
+ *
+ * 2. The {@link LaunchRunner#verify} method to rerun a previously recorded
+ * {@link Persistence.TestCase} and verify that the recorded states match the states resulting from
+ * the rerun.
+ *
+ * 3. The {@link LaunchRunner#run} method to run a launch object and return an {@link LaunchRecord}
+ * that can be used to do assertions directly in the same test.
+ */
+public class LaunchRunner {
+    private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
+    private static final int BEFORE_DUMP_TIMEOUT = 3000;
+
+    /**
+     * Used for the waiting utilities.
+     */
+    private IntentTestBase mTestBase;
+
+    /**
+     * The activities that were already present in the system when the test started.
+     * So they can be removed form the outputs, otherwise our tests would be system dependent.
+     */
+    private List<ActivityManagerState.ActivityStack> mBaseStacks;
+
+    public LaunchRunner(IntentTestBase testBase) {
+        mTestBase = testBase;
+        mBaseStacks = getBaseStacks();
+    }
+
+    /**
+     * Re-run a previously recorded {@link Persistence.TestCase} and verify that the recorded
+     * states match the states resulting from the rerun.
+     *
+     * @param initialContext the context to launch the first Activity from.
+     * @param testCase       the {@link Persistence.TestCase} we are verifying.
+     */
+    void verify(Context initialContext, Persistence.TestCase testCase) {
+        List<GenerationIntent> initialState = testCase.getSetup().getInitialIntents();
+        List<GenerationIntent> act = testCase.getSetup().getAct();
+
+        List<Activity> activityLog = Lists.newArrayList();
+
+        // Launch the first activity from the start context
+        GenerationIntent firstIntent = initialState.get(0);
+        activityLog.add(launchFromContext(initialContext, firstIntent.getActualIntent()));
+
+        // launch the rest from the initial intents
+        for (int i = 1; i < initialState.size(); i++) {
+            GenerationIntent generationIntent = initialState.get(i);
+            Activity activityToLaunchFrom = activityLog.get(generationIntent.getLaunchFromIndex(i));
+            Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
+                    generationIntent.startForResult());
+            activityLog.add(result);
+        }
+
+        // assert that the state after setup is the same this time as the recorded state.
+        StateDump setupStateDump = waitDumpAndTrimForVerification(getLast(activityLog),
+                testCase.getEndState());
+        assertInitialStateEqual(testCase.getInitialState(), setupStateDump);
+
+        // apply all the intents in the act stage
+        for (int i = 0; i < act.size(); i++) {
+            GenerationIntent generationIntent = act.get(i);
+            Activity activityToLaunchFrom = activityLog.get(
+                    generationIntent.getLaunchFromIndex(initialState.size() + i));
+            Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
+                    generationIntent.startForResult());
+            activityLog.add(result);
+        }
+
+        // assert that the endStates are the same.
+        StateDump endStateDump = waitDumpAndTrimForVerification(getLast(activityLog),
+                testCase.getEndState());
+        assertEndStatesEqual(testCase.getEndState(), endStateDump);
+    }
+
+    /**
+     * Runs a launch object and writes out the resulting {@link Persistence.TestCase} to
+     * device storage
+     *
+     * @param startContext the context to launch the first Activity from.
+     * @param name         the name of the directory to store the json files in.
+     * @param launches     a list of launches to run and record.
+     */
+    public void runAndWrite(Context startContext, String name, List<LaunchSequence> launches)
+            throws Exception {
+        for (int i = 0; i < launches.size(); i++) {
+            Persistence.TestCase testCase = this.runAndSerialize(launches.get(i), startContext,
+                    Integer.toString(i));
+            IntentTests.writeToDocumentsStorage(testCase, i + 1, name);
+            // Cleanup all the activities of this testCase before going to the next
+            // to preserve isolation across test cases.
+            mTestBase.cleanUp(testCase.getSetup().componentsInCase());
+        }
+    }
+
+    private Persistence.TestCase runAndSerialize(LaunchSequence launchSequence,
+            Context startContext, String name) {
+        LaunchRecord launchRecord = run(launchSequence, startContext);
+
+        LaunchSequenceExecutionInfo executionInfo = launchSequence.fold();
+        List<GenerationIntent> setupIntents = prepareSerialisation(executionInfo.setup);
+        List<GenerationIntent> actIntents = prepareSerialisation(executionInfo.acts,
+                setupIntents.size());
+
+        Persistence.Setup setup = new Persistence.Setup(setupIntents, actIntents);
+
+        return new Persistence.TestCase(setup, launchRecord.initialDump, launchRecord.endDump,
+                name);
+    }
+
+    /**
+     * Runs a launch object and returns a {@link LaunchRecord} that can be used to do assertions
+     * directly in the same test.
+     *
+     * @param launch       the {@link LaunchSequence}we want to run
+     * @param startContext the {@link android.content.Context} to launch the first Activity from.
+     * @return {@link LaunchRecord} that can be used to do assertions.
+     */
+    LaunchRecord run(LaunchSequence launch, Context startContext) {
+        LaunchSequence.LaunchSequenceExecutionInfo work = launch.fold();
+        List<Activity> activityLog = Lists.newArrayList();
+
+        if (work.setup.isEmpty() || work.acts.isEmpty()) {
+            throw new IllegalArgumentException("no intents to start");
+        }
+
+        // Launch the first activity from the start context.
+        LaunchFromIntent firstIntent = work.setup.get(0);
+        Activity firstActivity = this.launchFromContext(startContext,
+                firstIntent.getActualIntent());
+
+        activityLog.add(firstActivity);
+
+        // launch the rest from the initial intents.
+        for (int i = 1; i < work.setup.size(); i++) {
+            LaunchFromIntent launchFromIntent = work.setup.get(i);
+            Intent actualIntent = launchFromIntent.getActualIntent();
+            Activity activity = launch(activityLog.get(launchFromIntent.getLaunchFrom()),
+                    actualIntent, launchFromIntent.startForResult());
+            activityLog.add(activity);
+        }
+
+        // record the state after the initial intents.
+        StateDump initialDump = waitDumpAndTrim(getLast(activityLog));
+
+        // apply all the intents in the act stage
+        for (LaunchFromIntent launchFromIntent : work.acts) {
+            Intent actualIntent = launchFromIntent.getActualIntent();
+            Activity activity = launch(activityLog.get(launchFromIntent.getLaunchFrom()),
+                    actualIntent, launchFromIntent.startForResult());
+
+            activityLog.add(activity);
+        }
+
+        //record the end state after all intents are launched.
+        StateDump endDump = waitDumpAndTrim(getLast(activityLog));
+
+        return new LaunchRecord(initialDump, endDump, activityLog);
+    }
+
+    /**
+     * Results from the running of an {@link LaunchSequence} so the user can assert on the results
+     * directly.
+     */
+    class LaunchRecord {
+
+        /**
+         * The end state after the setup intents.
+         */
+        public final StateDump initialDump;
+
+        /**
+         * The end state after the setup and act intents.
+         */
+        public final StateDump endDump;
+
+        /**
+         * The activities that were started by every intent in the {@link LaunchSequence}.
+         */
+        public final List<Activity> mActivitiesLog;
+
+        public LaunchRecord(StateDump initialDump, StateDump endDump,
+                List<Activity> activitiesLog) {
+            this.initialDump = initialDump;
+            this.endDump = endDump;
+            mActivitiesLog = activitiesLog;
+        }
+    }
+
+
+    public Activity launchFromContext(Context context, Intent intent) {
+        Instrumentation.ActivityMonitor monitor = InstrumentationRegistry.getInstrumentation()
+                .addMonitor((String) null, null, false);
+
+        context.startActivity(intent);
+        Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
+        waitAndAssertActivityLaunched(activity, intent);
+
+        return activity;
+    }
+
+    public Activity launch(Activity activityContext, Intent intent, boolean startForResult) {
+        Instrumentation.ActivityMonitor monitor = InstrumentationRegistry.getInstrumentation()
+                .addMonitor((String) null, null, false);
+
+        if (startForResult) {
+            activityContext.startActivityForResult(intent, 1);
+        } else {
+            activityContext.startActivity(intent);
+        }
+        Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
+
+        if (activity == null) {
+            return activityContext;
+        } else {
+            waitAndAssertActivityLaunched(activity, intent);
+        }
+
+        return activity;
+    }
+
+    private void waitAndAssertActivityLaunched(Activity activity, Intent intent) {
+        assertNotNull("Intent: " + intent.toString(), activity);
+
+        final ComponentName testActivityName = activity.getComponentName();
+        mTestBase.waitAndAssertTopResumedActivity(testActivityName,
+                Display.DEFAULT_DISPLAY, "Activity must be resumed");
+    }
+
+    /**
+     * After the last activity has been launched we wait for a valid state + an extra three seconds
+     * so have a stable state of the system. Also all previously known stacks in
+     * {@link LaunchRunner#mBaseStacks} is excluded from the output.
+     *
+     * @param activity The last activity to be launched before dumping the state.
+     * @return A stable {@link StateDump}, meaning no more {@link android.app.Activity} is in a
+     * life cycle transition.
+     */
+    public StateDump waitDumpAndTrim(Activity activity) {
+        mTestBase.getAmWmState().waitForValidState(activity.getComponentName());
+        // The last activity that was launched before the dump could still be in an intermediate
+        // lifecycle state. wait an extra 3 seconds for it to settle
+        SystemClock.sleep(BEFORE_DUMP_TIMEOUT);
+        mTestBase.getAmWmState().computeState(activity.getComponentName());
+        List<ActivityManagerState.ActivityStack> endStateStacks =
+                mTestBase.getAmWmState().getAmState().getStacks();
+        return StateDump.fromStacks(endStateStacks, mBaseStacks);
+    }
+
+    /**
+     * Like {@link LaunchRunner#waitDumpAndTrim(Activity)} but also waits until the state becomes
+     * equal to the state we expect. It is therefore only used when verifying a recorded testcase.
+     *
+     * If we take a dump of an unstable state we allow it to settle into the expected state.
+     *
+     * @param activity The last activity to be launched before dumping the state.
+     * @param expected The state that was previously recorded for this testCase.
+     * @return A stable {@link StateDump}, meaning no more {@link android.app.Activity} is in a
+     * life cycle transition.
+     */
+    public StateDump waitDumpAndTrimForVerification(Activity activity, StateDump expected) {
+        mTestBase.getAmWmState().waitForValidState(activity.getComponentName());
+        // The last activity that was launched before the dump could still be in an intermediate
+        // lifecycle state. wait an extra 3 seconds for it to settle
+        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");
+        mTestBase.getAmWmState().computeState(activity.getComponentName());
+
+        List<ActivityManagerState.ActivityStack> endStateStacks =
+                mTestBase.getAmWmState().getAmState().getStacks();
+
+        return StateDump.fromStacks(endStateStacks, mBaseStacks);
+    }
+
+    private List<ActivityManagerState.ActivityStack> getBaseStacks() {
+        ActivityAndWindowManagersState amWmState = mTestBase.getAmWmState();
+        amWmState.computeState(new ComponentName[]{});
+        return amWmState.getAmState().getStacks();
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchSequence.java b/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchSequence.java
new file mode 100644
index 0000000..8d801e8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/LaunchSequence.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+
+import android.content.ComponentName;
+import android.server.am.intent.Persistence.LaunchFromIntent;
+import android.server.am.intent.Persistence.LaunchIntent;
+import android.support.test.InstrumentationRegistry;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.Optional;
+
+
+/**
+ * <pre>
+ * The main API entry point for specifying intent tests.
+ * A {@code Launch} object is an immutable command object to specify sequences of intents to
+ * launch.
+ *
+ * They can be run by a {@link LaunchRunner}
+ * Most tests using this api are defined in {@link Cases}.
+ * The two test classes actually using the tests specified there are:
+ *
+ * 1. {@link IntentGenerationTests}, which runs the cases, records the results and writes them
+ * out to device storage
+ * 2. {@link IntentTests}, which reads the recorded test files and verifies them again.
+ *
+ *
+ * Features supported by this API are:
+ * 1. Starting activities normally or for result.
+ * {@code
+ *  LaunchSequence.start(intentForResult(RegularActivity.class)) // starting an intent for result.
+ *                .append(intent(RegularActivity.class));         // starting an intent normally.
+ * }
+ * 2. Specifying Intent Flags
+ * {@code
+ *   LaunchSequence.start(intent(RegularActivity.class).withFlags(Cases.NEW_TASK));
+ * }
+ *
+ * 3. Launching an intent from any point earlier in the launch sequence.
+ * {@code
+ *   LaunchIntent multipleTask = intent(RegularActivity.class)
+ *                                     .withFlags(Cases.NEW_TASK,Cases.MULTIPLE_TASK);
+ *   LaunchSequence firstTask = LaunchSequence.start(multipleTask);
+ *   firstTask.append(multipleTask).append(intent(SingleTopActivity.class), firstTask);
+ * }
+ *
+ * The above will result in: the first task having two activities in it and the second task having
+ * one activity, instead of the normal behaviour adding only one task.
+ *
+ * It is completely immutable and can therefore be reused without hesitation.
+ * Note that making a launch object doesn't start anything yet until it is ran by a
+ * {@link LaunchRunner}
+ * </pre>
+ */
+public interface LaunchSequence {
+    /**
+     * @return the amount of intents that will launch before this {@link LaunchSequence} object.
+     */
+    int depth();
+
+    /**
+     * Extract all the information that has been built up in this {@link LaunchSequence} object, so
+     * that {@link LaunchRunner} can run the described sequence of intents.
+     */
+    LaunchSequenceExecutionInfo fold();
+
+    /**
+     * @return the {@link LaunchIntent} inside of this {@link LaunchSequence}.
+     */
+    LaunchIntent getIntent();
+
+    /**
+     * Create a {@link LaunchSequence} object this always has
+     * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} set because without it we can not
+     * launch from the application context.
+     *
+     * @param intent the first intent to be Launched.
+     * @return an {@link LaunchSequence} object launching just this intent.
+     */
+    static LaunchSequence create(LaunchIntent intent) {
+        return new RootLaunch(intent.withFlags(Cases.NEW_TASK));
+    }
+
+    /**
+     * @param intent the next intent that should be launched
+     * @return a {@link LaunchSequence} that will launch all the intents in this and then
+     * {@code intent}
+     */
+    default LaunchSequence append(LaunchIntent intent) {
+        return new ConsecutiveLaunch(this, intent, false, Optional.empty());
+    }
+
+    /**
+     * @param intent     the next intent that should be launched
+     * @param launchFrom a {@link LaunchSequence} to start the intent from, {@code launchFrom}
+     *                   should be a sub sequence of this.
+     * @return a {@link LaunchSequence} that will launch all the intents in this and then
+     * {@code intent}
+     */
+    default LaunchSequence append(LaunchIntent intent, LaunchSequence launchFrom) {
+        return new ConsecutiveLaunch(this, intent, false, Optional.of(launchFrom));
+    }
+
+    /**
+     * @param intent the intent to Launch
+     * @return a launch with the {@code intent} added to the Act stage.
+     */
+    default LaunchSequence act(LaunchIntent intent) {
+        return new ConsecutiveLaunch(this, intent, true, Optional.empty());
+    }
+
+    /**
+     * @param intent     the intent to Launch
+     * @param launchFrom a {@link LaunchSequence} to start the intent from, {@code launchFrom}
+     *                   should be a sub sequence of this.
+     * @return a launch with the {@code intent} added to the Act stage.
+     */
+    default LaunchSequence act(LaunchIntent intent, LaunchSequence launchFrom) {
+        return new ConsecutiveLaunch(this, intent, true, Optional.of(launchFrom));
+    }
+
+    /**
+     * @param activity the activity to create an intent for.
+     * @return Creates an {@link LaunchIntent} that will target the {@link android.app.activity}
+     * class passed in. see {@link LaunchIntent#withFlags} to add intent flags to the returned
+     * intent.
+     */
+    static LaunchIntent intent(Class<? extends android.app.Activity> activity) {
+        return new LaunchIntent(Lists.newArrayList(), createComponent(activity), false);
+    }
+
+
+    /**
+     * Creates an {@link LaunchIntent} that will be started with {@link
+     * android.app.Activity#startActivityForResult}
+     *
+     * @param activity the activity to create an intent for.
+     */
+    static LaunchIntent intentForResult(Class<? extends android.app.Activity> activity) {
+        return new LaunchIntent(Lists.newArrayList(), createComponent(activity), true);
+    }
+
+    String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+
+    static ComponentName createComponent(Class<? extends android.app.Activity> activity) {
+        return new ComponentName(packageName, activity.getName());
+    }
+
+    /**
+     * Allows {@link LaunchSequence} objects to form a linkedlist of intents to launch.
+     */
+    class ConsecutiveLaunch implements LaunchSequence {
+        private final LaunchSequence mPrevious;
+        private final LaunchIntent mLaunchIntent;
+        private final boolean mAct;
+
+        public ConsecutiveLaunch(LaunchSequence previous, LaunchIntent launchIntent,
+                boolean act, Optional<LaunchSequence> launchFrom) {
+            mPrevious = previous;
+            mLaunchIntent = launchIntent;
+            mAct = act;
+            mLaunchFrom = launchFrom;
+        }
+
+        /**
+         * This should always be a {@link LaunchSequence} that is in mPrevious.
+         */
+        @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+        private Optional<LaunchSequence> mLaunchFrom;
+
+        @Override
+        public int depth() {
+            return mPrevious.depth() + 1;
+        }
+
+        @Override
+        public LaunchSequenceExecutionInfo fold() {
+            LaunchSequenceExecutionInfo launchSequenceExecutionInfo = mPrevious.fold();
+            int launchSite = mLaunchFrom.map(LaunchSequence::depth).orElse(this.depth() - 1);
+
+            LaunchFromIntent intent = new LaunchFromIntent(mLaunchIntent, launchSite);
+            if (mAct) {
+                launchSequenceExecutionInfo.acts.add(intent);
+            } else {
+                launchSequenceExecutionInfo.setup.add(intent);
+            }
+            return launchSequenceExecutionInfo;
+        }
+
+        @Override
+        public LaunchIntent getIntent() {
+            return mLaunchIntent;
+        }
+    }
+
+    /**
+     * The first intent to launch in a {@link LaunchSequence} Object.
+     */
+    class RootLaunch implements LaunchSequence {
+        /**
+         * The intent that should be launched.
+         */
+        private final LaunchIntent mLaunchIntent;
+
+        public RootLaunch(LaunchIntent launchIntent) {
+            mLaunchIntent = launchIntent;
+        }
+
+        @Override
+        public int depth() {
+            return 0;
+        }
+
+        @Override
+        public LaunchSequenceExecutionInfo fold() {
+            return new LaunchSequenceExecutionInfo(
+                    Lists.newArrayList(new LaunchFromIntent(mLaunchIntent, -1)),
+                    Lists.newArrayList());
+        }
+
+        @Override
+        public LaunchIntent getIntent() {
+            return mLaunchIntent;
+        }
+    }
+
+    /**
+     * Information for the {@link LaunchRunner} to run the launch represented by a {@link
+     * LaunchSequence} object. The {@link LaunchSequence} object is "folded" as a list
+     * into the summary of all intents with the corresponding indexes from what context
+     * to launch them.
+     */
+    class LaunchSequenceExecutionInfo {
+        List<LaunchFromIntent> setup;
+        List<LaunchFromIntent> acts;
+
+        public LaunchSequenceExecutionInfo(List<LaunchFromIntent> setup,
+                List<LaunchFromIntent> acts) {
+            this.setup = setup;
+            this.acts = acts;
+        }
+    }
+}
+
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/Persistence.java b/tests/framework/base/activitymanager/src/android/server/am/intent/Persistence.java
new file mode 100644
index 0000000..e818631
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/Persistence.java
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am.intent;
+
+import static java.util.stream.Collectors.toList;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.server.am.ActivityManagerState;
+
+import com.google.common.collect.Lists;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * The intent tests are generated by running a series of intents and then recording the end state
+ * of the system. This class contains all the models needed to store the intents that were used to
+ * create the test case and the end states so that they can be asserted on.
+ *
+ * All test cases are serialized to JSON and stored in a single file per testcase.
+ */
+public class Persistence {
+
+    /**
+     * The highest level entity in the JSON file
+     */
+    public static class TestCase {
+        private static final String SETUP_KEY = "setup";
+        private static final String INITIAL_STATE_KEY = "initialState";
+        private static final String END_STATE_KEY = "endState";
+
+        /**
+         * Contains the {@link android.content.Intent}-s that will be launched in this test case.
+         */
+        private final Setup mSetup;
+
+        /**
+         * The state of the system after the {@link Setup#mInitialIntents} have been launched.
+         */
+        private final StateDump mInitialState;
+
+        /**
+         * The state of the system after the {@link Setup#mAct} have been launched
+         */
+        private final StateDump mEndState;
+
+        /**
+         * The name of the testCase, usually the file name it is stored in.
+         * Not actually persisted to json, since it is only used for presentation purposes.
+         */
+        private final String mName;
+
+        public TestCase(Setup setup, StateDump initialState,
+                StateDump endState, String name) {
+            mSetup = setup;
+            mInitialState = initialState;
+            mEndState = endState;
+            mName = name;
+        }
+
+        public JSONObject toJson() throws JSONException {
+            return new JSONObject()
+                    .put(SETUP_KEY, mSetup.toJson())
+                    .put(INITIAL_STATE_KEY, mInitialState.toJson())
+                    .put(END_STATE_KEY, mEndState.toJson());
+        }
+
+        public static TestCase fromJson(JSONObject object,
+                Map<String, IntentFlag> table, String name) throws JSONException {
+            return new TestCase(Setup.fromJson(object.getJSONObject(SETUP_KEY), table),
+                    StateDump.fromJson(object.getJSONObject(INITIAL_STATE_KEY)),
+                    StateDump.fromJson(object.getJSONObject(END_STATE_KEY)), name);
+        }
+
+        public Setup getSetup() {
+            return mSetup;
+        }
+
+        public StateDump getInitialState() {
+            return mInitialState;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public StateDump getEndState() {
+            return mEndState;
+        }
+    }
+
+    /**
+     * Setup consists of two stages. Firstly a list of intents to bring the system in the state we
+     * want to test something in. Secondly a list of intents to bring the system to the final state.
+     */
+    public static class Setup {
+        private static final String INITIAL_INTENT_KEY = "initialIntents";
+        private static final String ACT_KEY = "act";
+        /**
+         * The intent(s) used to bring the system to the initial state.
+         */
+        private final List<GenerationIntent> mInitialIntents;
+
+        /**
+         * The intent(s) that we actually want to test.
+         */
+        private final List<GenerationIntent> mAct;
+
+        public Setup(List<GenerationIntent> initialIntents, List<GenerationIntent> act) {
+            mInitialIntents = initialIntents;
+            mAct = act;
+        }
+
+        public List<ComponentName> componentsInCase() {
+            return Stream.concat(mInitialIntents.stream(), mAct.stream())
+                    .map(GenerationIntent::getActualIntent)
+                    .map(Intent::getComponent)
+                    .collect(Collectors.toList());
+        }
+
+        public JSONObject toJson() throws JSONException {
+            return new JSONObject()
+                    .put(INITIAL_INTENT_KEY, intentsToJson(mInitialIntents))
+                    .put(ACT_KEY, intentsToJson(mAct));
+        }
+
+        public static Setup fromJson(JSONObject object,
+                Map<String, IntentFlag> table) throws JSONException {
+            List<GenerationIntent> initialState = intentsFromJson(
+                    object.getJSONArray(INITIAL_INTENT_KEY), table);
+            List<GenerationIntent> act = intentsFromJson(object.getJSONArray(ACT_KEY), table);
+
+            return new Setup(initialState, act);
+        }
+
+
+        public static JSONArray intentsToJson(List<GenerationIntent> intents)
+                throws JSONException {
+
+            JSONArray intentArray = new JSONArray();
+            for (GenerationIntent intent : intents) {
+                intentArray.put(intent.toJson());
+            }
+            return intentArray;
+        }
+
+        public static List<GenerationIntent> intentsFromJson(JSONArray intentArray,
+                Map<String, IntentFlag> table) throws JSONException {
+            List<GenerationIntent> intents = new ArrayList<>();
+
+            for (int i = 0; i < intentArray.length(); i++) {
+                JSONObject object = (JSONObject) intentArray.get(i);
+                GenerationIntent intent = GenerationIntent.fromJson(object, table);
+
+                intents.add(intent);
+            }
+
+            return intents;
+        }
+
+        public List<GenerationIntent> getInitialIntents() {
+            return mInitialIntents;
+        }
+
+        public List<GenerationIntent> getAct() {
+            return mAct;
+        }
+    }
+
+    /**
+     * An representation of an {@link android.content.Intent} that can be (de)serialized to / from
+     * JSON. It abstracts whether the context it should be started from is implicitly or explicitly
+     * specified.
+     */
+    interface GenerationIntent {
+        Intent getActualIntent();
+
+        JSONObject toJson() throws JSONException;
+
+        int getLaunchFromIndex(int currentPosition);
+
+        boolean startForResult();
+
+        static GenerationIntent fromJson(JSONObject object, Map<String, IntentFlag> table)
+                throws JSONException {
+            if (object.has(LaunchFromIntent.LAUNCH_FROM_KEY)) {
+                return LaunchFromIntent.fromJson(object, table);
+            } else {
+                return LaunchIntent.fromJson(object, table);
+            }
+        }
+    }
+
+    /**
+     * Representation of {@link android.content.Intent} used by the {@link LaunchSequence} api.
+     * It be can used to normally start activities, to start activities for result and Intent Flags
+     * can be added using {@link LaunchIntent#withFlags(IntentFlag...)}
+     */
+    static class LaunchIntent implements GenerationIntent {
+        private static final String FLAGS_KEY = "flags";
+        private static final String PACKAGE_KEY = "package";
+        private static final String CLASS_KEY = "class";
+        private static final String START_FOR_RESULT_KEY = "startForResult";
+
+        private final List<IntentFlag> mIntentFlags;
+        private final ComponentName mComponentName;
+        private final boolean mStartForResult;
+
+        public LaunchIntent(List<IntentFlag> intentFlags, ComponentName componentName,
+                boolean startForResult) {
+            mIntentFlags = intentFlags;
+            mComponentName = componentName;
+            mStartForResult = startForResult;
+        }
+
+        @Override
+        public Intent getActualIntent() {
+            return new Intent().setComponent(mComponentName).setFlags(buildFlag());
+        }
+
+        @Override
+        public int getLaunchFromIndex(int currentPosition) {
+            return currentPosition - 1;
+        }
+
+        @Override
+        public boolean startForResult() {
+            return mStartForResult;
+        }
+
+        public int buildFlag() {
+            int flag = 0;
+            for (IntentFlag intentFlag : mIntentFlags) {
+                flag |= intentFlag.flag;
+            }
+
+            return flag;
+        }
+
+        public String humanReadableFlags() {
+            return mIntentFlags.stream().map(IntentFlag::toString).collect(
+                    Collectors.joining(" | "));
+        }
+
+        public static LaunchIntent fromJson(JSONObject fakeIntent, Map<String, IntentFlag> table)
+                throws JSONException {
+            List<IntentFlag> flags = IntentFlag.parse(table, fakeIntent.getString(FLAGS_KEY));
+
+            boolean startForResult = fakeIntent.optBoolean(START_FOR_RESULT_KEY, false);
+            return new LaunchIntent(flags,
+                    new ComponentName(
+                            fakeIntent.getString(PACKAGE_KEY),
+                            fakeIntent.getString(CLASS_KEY)), startForResult);
+
+        }
+
+        @Override
+        public JSONObject toJson() throws JSONException {
+            return new JSONObject().put(FLAGS_KEY, this.humanReadableFlags())
+                    .put(CLASS_KEY, this.mComponentName.getClassName())
+                    .put(PACKAGE_KEY, this.mComponentName.getPackageName())
+                    .put(START_FOR_RESULT_KEY, mStartForResult);
+        }
+
+        public LaunchIntent withFlags(IntentFlag... flags) {
+            List<IntentFlag> intentFlags = Lists.newArrayList(mIntentFlags);
+            Collections.addAll(intentFlags, flags);
+            return new LaunchIntent(intentFlags, mComponentName, mStartForResult);
+        }
+
+        public List<IntentFlag> getIntentFlags() {
+            return mIntentFlags;
+        }
+
+        public ComponentName getComponentName() {
+            return mComponentName;
+        }
+
+        public boolean isStartForResult() {
+            return mStartForResult;
+        }
+    }
+
+    /**
+     * Representation of {@link android.content.Intent} used by the {@link LaunchSequence} api.
+     * It can used to normally start activities, to start activities for result and Intent Flags
+     * can
+     * be added using {@link LaunchIntent#withFlags(IntentFlag...)} just like {@link LaunchIntent}
+     *
+     * However {@link LaunchFromIntent}  also supports launching from a activity earlier in the
+     * launch sequence. This can be done using {@link LaunchSequence#act} and related methods.
+     */
+    static class LaunchFromIntent implements GenerationIntent {
+        static final String LAUNCH_FROM_KEY = "launchFrom";
+
+        /**
+         * The underlying {@link LaunchIntent} that we are wrapping with the launch point behaviour.
+         */
+        private final LaunchIntent mLaunchIntent;
+
+        /**
+         * The index in the activityLog maintained by {@link LaunchRunner}, used to retrieve the
+         * activity from the log to start this {@link LaunchIntent} from.
+         */
+        private final int mLaunchFrom;
+
+        LaunchFromIntent(LaunchIntent fakeIntent, int launchFrom) {
+            mLaunchIntent = fakeIntent;
+            mLaunchFrom = launchFrom;
+        }
+
+
+        @Override
+        public Intent getActualIntent() {
+            return mLaunchIntent.getActualIntent();
+        }
+
+        @Override
+        public int getLaunchFromIndex(int currentPosition) {
+            return mLaunchFrom;
+        }
+
+        @Override
+        public boolean startForResult() {
+            return mLaunchIntent.mStartForResult;
+        }
+
+        @Override
+        public JSONObject toJson() throws JSONException {
+            return mLaunchIntent.toJson()
+                    .put(LAUNCH_FROM_KEY, mLaunchFrom);
+        }
+
+        public static LaunchFromIntent fromJson(JSONObject object, Map<String, IntentFlag> table)
+                throws JSONException {
+            LaunchIntent fakeIntent = LaunchIntent.fromJson(object, table);
+            int launchFrom = object.optInt(LAUNCH_FROM_KEY, -1);
+
+            return new LaunchFromIntent(fakeIntent, launchFrom);
+        }
+
+        static List<GenerationIntent> prepareSerialisation(List<LaunchFromIntent> intents) {
+            return prepareSerialisation(intents, 0);
+        }
+
+        // In serialized form we only want to store the launch from index if it deviates from the
+        // default, the default being the previous activity.
+        static List<GenerationIntent> prepareSerialisation(List<LaunchFromIntent> intents,
+                int base) {
+            List<GenerationIntent> serializeIntents = Lists.newArrayList();
+            for (int i = 0; i < intents.size(); i++) {
+                LaunchFromIntent launchFromIntent = intents.get(i);
+                serializeIntents.add(launchFromIntent.forget(base + i));
+            }
+
+            return serializeIntents;
+        }
+
+        public GenerationIntent forget(int currentIndex) {
+            if (mLaunchFrom == currentIndex - 1) {
+                return this.mLaunchIntent;
+            } else {
+                return this;
+            }
+        }
+
+        public int getLaunchFrom() {
+            return mLaunchFrom;
+        }
+    }
+
+    /**
+     * An intent flag that also stores the name of the flag.
+     * It is used to be able to put the flags in human readable form in the JSON file.
+     */
+    static class IntentFlag {
+        /**
+         * The underlying flag, should be a value from Intent.FLAG_ACTIVITY_*.
+         */
+        public final int flag;
+        /**
+         * The name of the flag.
+         */
+        public final String name;
+
+        public IntentFlag(int flag, String name) {
+            this.flag = flag;
+            this.name = name;
+        }
+
+        public int getFlag() {
+            return flag;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public int combine(IntentFlag other) {
+            return other.flag | flag;
+        }
+
+        public static List<IntentFlag> parse(Map<String, IntentFlag> names, String flagsToParse) {
+            String[] split = flagsToParse.replaceAll("\\s", "").split("\\|");
+            return Arrays.stream(split).map(names::get).collect(toList());
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    static IntentFlag flag(int flag, String name) {
+        return new IntentFlag(flag, name);
+    }
+
+    public static class StateDump {
+        final List<StackState> mStacks;
+
+        public static StateDump fromStacks(List<ActivityManagerState.ActivityStack> activityStacks,
+                List<ActivityManagerState.ActivityStack> baseStacks) {
+            List<StackState> stacks = new ArrayList<>();
+            for (ActivityManagerState.ActivityStack stack : trimStacks(activityStacks,
+                    baseStacks)) {
+                stacks.add(new StackState(stack));
+            }
+
+            return new StateDump(stacks);
+        }
+
+        public StateDump(List<StackState> stacks) {
+            mStacks = stacks;
+        }
+
+        JSONObject toJson() throws JSONException {
+            JSONArray stacks = new JSONArray();
+            for (StackState stack : mStacks) {
+                stacks.put(stack.toJson());
+            }
+
+            return new JSONObject().put("stacks", stacks);
+        }
+
+        static StateDump fromJson(JSONObject object) throws JSONException {
+            JSONArray jsonTasks = object.getJSONArray("stacks");
+            List<StackState> stacks = new ArrayList<>();
+
+            for (int i = 0; i < jsonTasks.length(); i++) {
+                stacks.add(StackState.fromJson((JSONObject) jsonTasks.get(i)));
+            }
+
+            return new StateDump(stacks);
+        }
+
+        /**
+         * To make the state dump non device specific we remove every stack that was present
+         * in the system before recording, by their ID. For example a stack containing the launcher
+         * activity.
+         */
+        public static List<ActivityManagerState.ActivityStack> trimStacks(
+                List<ActivityManagerState.ActivityStack> toTrim,
+                List<ActivityManagerState.ActivityStack> trimFrom) {
+
+            for (ActivityManagerState.ActivityStack stack : trimFrom) {
+                toTrim.removeIf(t -> t.getStackId() == stack.getStackId());
+            }
+
+            return toTrim;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            StateDump stateDump = (StateDump) o;
+            return Objects.equals(mStacks, stateDump.mStacks);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mStacks);
+        }
+    }
+
+    /**
+     * A simplified JSON version of the information in {@link ActivityManagerState.ActivityStack}
+     */
+    public static class StackState {
+        private static final String TASKS_KEY = "tasks";
+        private static final String RESUMED_ACTIVITY_KEY = "resumedActivity";
+
+        /**
+         * The component name of the resumedActivity in this Stack, empty string if there is none.
+         */
+        private final String mResumedActivity;
+        /**
+         * The Tasks in this stack ordered from most recent to least recent.
+         */
+        private final List<TaskState> mTasks;
+
+        public StackState(String resumedActivity, List<TaskState> tasks) {
+            mResumedActivity = resumedActivity;
+            mTasks = tasks;
+        }
+
+        public StackState(ActivityManagerState.ActivityStack stack) {
+            this.mResumedActivity = stack.getResumedActivity();
+            mTasks = new ArrayList<>();
+            for (ActivityManagerState.ActivityTask task : stack.getTasks()) {
+                this.mTasks.add(new TaskState(task));
+            }
+        }
+
+        JSONObject toJson() throws JSONException {
+            JSONArray jsonTasks = new JSONArray();
+
+            for (TaskState task : mTasks) {
+                jsonTasks.put(task.toJson());
+            }
+
+            return new JSONObject()
+                    .put(TASKS_KEY, jsonTasks)
+                    .put(RESUMED_ACTIVITY_KEY, mResumedActivity);
+        }
+
+        static StackState fromJson(JSONObject object) throws JSONException {
+            JSONArray jsonTasks = object.getJSONArray(TASKS_KEY);
+            List<TaskState> tasks = new ArrayList<>();
+
+            for (int i = 0; i < jsonTasks.length(); i++) {
+                tasks.add(TaskState.fromJson((JSONObject) jsonTasks.get(i)));
+            }
+
+            return new StackState(object.optString(RESUMED_ACTIVITY_KEY, ""), tasks);
+        }
+
+        public String getResumedActivity() {
+            return mResumedActivity;
+        }
+
+        public List<TaskState> getTasks() {
+            return mTasks;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            StackState stack = (StackState) o;
+            return Objects.equals(mTasks, stack.mTasks);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mResumedActivity, mTasks);
+        }
+    }
+
+    public static class TaskState {
+
+        private static final String ACTIVITIES_KEY = "activities";
+
+        /**
+         * The activities in this task ordered from most recent to least recent.
+         */
+        private List<ActivityState> mActivities = new ArrayList<>();
+
+        public TaskState(List<ActivityState> activities) {
+            mActivities = activities;
+        }
+
+        public TaskState(ActivityManagerState.ActivityTask state) {
+            for (ActivityManagerState.Activity activity : state.getActivities()) {
+                this.mActivities.add(new ActivityState(activity));
+            }
+        }
+
+        JSONObject toJson() throws JSONException {
+            JSONArray jsonActivities = new JSONArray();
+
+            for (ActivityState activity : mActivities) {
+                jsonActivities.put(activity.toJson());
+            }
+
+            return new JSONObject()
+                    .put(ACTIVITIES_KEY, jsonActivities);
+        }
+
+        static TaskState fromJson(JSONObject object) throws JSONException {
+            JSONArray jsonActivities = object.getJSONArray(ACTIVITIES_KEY);
+            List<ActivityState> activities = new ArrayList<>();
+
+            for (int i = 0; i < jsonActivities.length(); i++) {
+                activities.add(ActivityState.fromJson((JSONObject) jsonActivities.get(i)));
+            }
+
+            return new TaskState(activities);
+        }
+
+        public List<ActivityState> getActivities() {
+            return mActivities;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TaskState task = (TaskState) o;
+            return Objects.equals(mActivities, task.mActivities);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mActivities);
+        }
+    }
+
+    public static class ActivityState {
+        private static final String NAME_KEY = "name";
+        private static final String STATE_KEY = "state";
+        /**
+         * The componentName of this activity.
+         */
+        private final String mComponentName;
+
+        /**
+         * The lifecycle state this activity is in.
+         */
+        private final String mLifeCycleState;
+
+        public ActivityState(String name, String state) {
+            mComponentName = name;
+            mLifeCycleState = state;
+        }
+
+        public ActivityState(ActivityManagerState.Activity activity) {
+            mComponentName = activity.getName();
+            mLifeCycleState = activity.getState();
+        }
+
+
+        JSONObject toJson() throws JSONException {
+            return new JSONObject().put(NAME_KEY, mComponentName).put(STATE_KEY, mLifeCycleState);
+        }
+
+        static ActivityState fromJson(JSONObject object) throws JSONException {
+            return new ActivityState(object.getString(NAME_KEY), object.getString(STATE_KEY));
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            ActivityState activity = (ActivityState) o;
+            return Objects.equals(mComponentName, activity.mComponentName) &&
+                    Objects.equals(mLifeCycleState, activity.mLifeCycleState);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mComponentName, mLifeCycleState);
+        }
+
+        public String getName() {
+            return mComponentName;
+        }
+
+        public String getState() {
+            return mLifeCycleState;
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/intent/StateComparisonException.java b/tests/framework/base/activitymanager/src/android/server/am/intent/StateComparisonException.java
new file mode 100644
index 0000000..80a2370
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/intent/StateComparisonException.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.server.am.intent;
+
+import org.json.JSONException;
+
+/**
+ * Exception to report differences in the {@link android.server.am.intent.Persistence.StateDump}
+ * when verifying a {@link android.server.am.intent.Persistence.TestCase} using the
+ * {@linkLaunchRunner#verify} method
+ */
+public class StateComparisonException extends RuntimeException {
+    private Persistence.StateDump mExpected;
+    private Persistence.StateDump mActual;
+    private String stage;
+
+    public StateComparisonException(Persistence.StateDump expected,
+            Persistence.StateDump actual, String stage) {
+        mExpected = expected;
+        mActual = actual;
+        this.stage = stage;
+    }
+
+
+    @Override
+    public String getMessage() {
+        try {
+            String newLine = System.lineSeparator();
+            return new StringBuilder()
+                    .append(newLine)
+                    .append(stage)
+                    .append(newLine)
+                    .append("Expected:")
+                    .append(newLine)
+                    .append(mExpected.toJson().toString(2))
+                    .append(newLine)
+                    .append(newLine)
+                    .append("Actual:")
+                    .append(newLine)
+                    .append(mActual.toJson().toString(2))
+                    .append(newLine)
+                    .append(newLine)
+                    .toString();
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return "json parse exception during error message creation";
+        }
+    }
+
+    public static void assertEndStatesEqual(Persistence.StateDump expected,
+            Persistence.StateDump actual) {
+        compareAndThrow(expected, actual, "Different endSates");
+    }
+
+    public static void assertInitialStateEqual(Persistence.StateDump expected,
+            Persistence.StateDump actual) {
+        compareAndThrow(expected, actual, "Different initial states");
+    }
+
+    private static void compareAndThrow(Persistence.StateDump expected,
+            Persistence.StateDump actual, String stage) {
+        if (!expected.equals(actual)) {
+            throw new StateComparisonException(expected, actual, stage);
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
index f76979c..045bceb 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -1,5 +1,22 @@
+/*
+ * 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.server.am.lifecycle;
 
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.am.StateLogger.log;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback
@@ -8,15 +25,19 @@
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
 
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
+import android.server.am.ActivityManagerDisplayTestBase;
 import android.server.am.ActivityManagerTestBase;
 import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
 import android.support.test.InstrumentationRegistry;
@@ -31,7 +52,7 @@
 import java.util.List;
 
 /** Base class for device-side tests that verify correct activity lifecycle transitions. */
-public class ActivityLifecycleClientTestBase extends ActivityManagerTestBase {
+public class ActivityLifecycleClientTestBase extends ActivityManagerDisplayTestBase {
 
     static final String EXTRA_RECREATE = "recreate";
     static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
@@ -49,6 +70,9 @@
     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 */);
 
@@ -63,6 +87,14 @@
             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 */);
 
@@ -70,6 +102,13 @@
             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 */);
+
     private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
             .getInstance();
     private static LifecycleLog mLifecycleLog;
@@ -117,8 +156,22 @@
      */
     final void waitForActivityTransitions(Class<? extends Activity> activityClass,
             List<ActivityCallback> expectedTransitions) {
+        log("Start waitForActivityTransitions");
+        mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
+    }
+
+    /**
+     * Blocking call that will wait for activities to perform the expected sequence of transitions.
+     * After waiting it asserts that the sequence matches the expected.
+     * @see LifecycleTracker#waitForActivityTransitions(Class, List)
+     */
+    final void waitAndAssertActivityTransitions(Class<? extends Activity> activityClass,
+            List<ActivityCallback> expectedTransitions, String message) {
         log("Start waitAndAssertActivityTransition");
         mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
+
+        LifecycleVerifier.assertSequence(activityClass, getLifecycleLog(), expectedTransitions,
+                message);
     }
 
     LifecycleLog getLifecycleLog() {
@@ -166,6 +219,10 @@
     public static class SecondActivity extends Activity {
     }
 
+    // Test activity
+    public static class ThirdActivity extends Activity {
+    }
+
     // Translucent test activity
     public static class TranslucentActivity extends Activity {
     }
@@ -197,11 +254,30 @@
         }
 
         @Override
+        public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+            mLifecycleLog.onActivityCallback(this,
+                    isTopResumedActivity ? ON_TOP_POSITION_GAINED : ON_TOP_POSITION_LOST);
+        }
+
+        @Override
         public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
             mLifecycleLog.onActivityCallback(this, ON_MULTI_WINDOW_MODE_CHANGED);
         }
     }
 
+    // Translucent callback tracking test activity
+    public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
+    }
+
+    // Callback tracking activity that supports being shown on top of lock screen
+    public static class ShowWhenLockedCallbackTrackingActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setShowWhenLocked(true);
+        }
+    }
+
     /**
      * Test activity that launches {@link ResultActivity} for result.
      */
@@ -221,7 +297,7 @@
     }
 
     /** Test activity that is started for result and finishes itself in ON_RESUME. */
-    public static class ResultActivity extends Activity {
+    public static class ResultActivity extends CallbackTrackingActivity {
         @Override
         protected void onResume() {
             super.onResume();
@@ -251,6 +327,25 @@
     public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
     }
 
+    // Pip-capable activity
+    // TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so
+    // that they can verify other lifecycle transitions. This should be fixed and switched to
+    // extend CallbackTrackingActivity.
+    public static class PipActivity extends Activity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            // Enter picture in picture with the given aspect ratio if provided
+            if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+            }
+        }
+    }
+
+    public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
+    }
+
     static ComponentName getComponentName(Class<? extends Activity> activity) {
         return new ComponentName(InstrumentationRegistry.getContext(), activity);
     }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleFreeformTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleFreeformTests.java
new file mode 100644
index 0000000..3445708
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleFreeformTests.java
@@ -0,0 +1,257 @@
+/*
+ * 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.server.am.lifecycle;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.ActivityManagerState.STATE_PAUSED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.ComponentNameUtils.getWindowName;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleFreeformTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@FlakyTest(bugId = 77652261)
+public class ActivityLifecycleFreeformTests extends ActivityLifecycleClientTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsFreeform());
+    }
+
+    @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();
+
+        // Launch an activity in freeform
+        final Intent firstIntent =
+                new Intent(InstrumentationRegistry.getContext(), FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(firstIntent, bundle);
+
+        // Wait and assert resume
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED,
+                "Activity should be resumed after launch");
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testMultiLaunchInFreeform() 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();
+
+        // Launch three activities in freeform
+        final Intent firstIntent =
+                new Intent(InstrumentationRegistry.getContext(), FirstActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(firstIntent, bundle);
+
+        final Intent secondIntent =
+                new Intent(InstrumentationRegistry.getContext(), SecondActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(secondIntent, bundle);
+
+        final Intent thirdIntent =
+                new Intent(InstrumentationRegistry.getContext(), ThirdActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(thirdIntent, bundle);
+
+        // Wait for resume
+        final String message = "Activity should be resumed after launch";
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED, message);
+        waitAndAssertActivityState(getComponentName(SecondActivity.class), STATE_RESUMED, message);
+        waitAndAssertActivityState(getComponentName(ThirdActivity.class), STATE_RESUMED, message);
+
+        // Assert lifecycle
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testLaunchOccludingInFreeform() 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();
+
+        // Launch two activities in freeform in the same task
+        final Intent firstIntent =
+                new Intent(InstrumentationRegistry.getContext(), FirstActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(firstIntent, bundle);
+
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+
+        final Intent thirdIntent =
+                new Intent(InstrumentationRegistry.getContext(), ThirdActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(thirdIntent, bundle);
+
+        // Wait for valid states
+        final String stopMessage = "Activity should be stopped after being covered above";
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_STOPPED,
+                stopMessage);
+        final String message = "Activity should be resumed after launch";
+        waitAndAssertActivityState(getComponentName(SecondActivity.class), STATE_RESUMED, message);
+        waitAndAssertActivityState(getComponentName(ThirdActivity.class), STATE_RESUMED, message);
+
+        // Assert lifecycle
+        LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+
+        // Finish the activity that was occluding the first one
+        getLifecycleLog().clear();
+        secondActivity.finish();
+
+        // Wait and assert the lifecycle
+        mAmWmState.waitForActivityRemoved(getComponentName(SecondActivity.class));
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED,
+                "Activity must be resumed after occluding finished");
+
+        assertFalse("Activity must be destroyed",
+                mAmWmState.getAmState().containsActivity(getComponentName(SecondActivity.class)));
+        assertFalse("Activity must be destroyed",
+                mAmWmState.getWmState().containsWindow(
+                        getWindowName(getComponentName(SecondActivity.class))));
+        LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertEmptySequence(ThirdActivity.class, getLifecycleLog(),
+                "finishInOtherStack");
+        LifecycleVerifier.assertEmptySequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                "finishInOtherStack");
+    }
+
+    @Test
+    public void testLaunchTranslucentInFreeform() 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();
+
+        // Launch two activities in freeform in the same task
+        final Intent firstIntent =
+                new Intent(InstrumentationRegistry.getContext(), FirstActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(firstIntent, bundle);
+
+        final Activity transparentActivity = mTranslucentActivityTestRule
+                .launchActivity(new Intent());
+
+        final Intent thirdIntent =
+                new Intent(InstrumentationRegistry.getContext(), ThirdActivity.class)
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(thirdIntent, bundle);
+
+        // Wait for valid states
+        final String pauseMessage = "Activity should be stopped after transparent launch above";
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_PAUSED,
+                pauseMessage);
+        final String message = "Activity should be resumed after launch";
+        waitAndAssertActivityState(getComponentName(TranslucentActivity.class), STATE_RESUMED,
+                message);
+        waitAndAssertActivityState(getComponentName(ThirdActivity.class), STATE_RESUMED, message);
+
+        // Assert lifecycle
+        LifecycleVerifier.assertLaunchAndPauseSequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+
+        // Finish the activity that was occluding the first one
+        getLifecycleLog().clear();
+        transparentActivity.finish();
+
+        // Wait and assert the lifecycle
+        mAmWmState.waitForActivityRemoved(getComponentName(TranslucentActivity.class));
+        waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED,
+                "Activity must be resumed after occluding finished");
+
+
+        assertFalse("Activity must be destroyed",
+                mAmWmState.getAmState().containsActivity(
+                        getComponentName(TranslucentActivity.class)));
+        assertFalse("Activity must be destroyed",
+                mAmWmState.getWmState().containsWindow(
+                        getWindowName(getComponentName(TranslucentActivity.class))));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "finishTranslucentOnTop");
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+        LifecycleVerifier.assertEmptySequence(ThirdActivity.class, getLifecycleLog(),
+                "finishInOtherStack");
+        LifecycleVerifier.assertEmptySequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                "finishInOtherStack");
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
index 95fc60e..76c712c 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -1,5 +1,28 @@
+/*
+ * 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.server.am.lifecycle;
 
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 
 import android.app.Activity;
@@ -11,6 +34,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+
 /**
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleKeyguardTests
@@ -34,4 +59,106 @@
             LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
         }
     }
+
+    @Test
+    public void testKeyguardShowHide() throws Exception {
+        if (!supportsSecureLock()) {
+            return;
+        }
+
+        // Launch first activity and wait for resume
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        // Show and hide lock screen
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+            waitAndAssertActivityStates(state(activity, ON_STOP));
+
+            LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
+            getLifecycleLog().clear();
+        } // keyguard hidden
+
+        // Verify that activity was resumed
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+        LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testKeyguardShowHideOverSplitScreen() throws Exception {
+        if (!supportsSecureLock() || !supportsSplitScreenMultiWindow()) {
+            return;
+        }
+
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+
+        // Launch second activity to side
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to resume.
+        waitAndAssertActivityStates(state(secondActivity, 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(secondActivity, ON_STOP));
+
+            LifecycleVerifier.assertResumeToStopSequence(FirstActivity.class, getLifecycleLog());
+            LifecycleVerifier.assertResumeToStopSequence(SecondActivity.class, getLifecycleLog());
+            getLifecycleLog().clear();
+        } // keyguard hidden
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
+                state(secondActivity, ON_RESUME));
+        LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertRestartAndResumeSequence(SecondActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testKeyguardShowHideOverPip() throws Exception {
+        if (!supportsPip()) {
+            // Skipping test: no Picture-In-Picture support
+            return;
+        }
+
+        // Launch first activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+
+        // Wait and assert lifecycle
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+
+        // 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));
+
+            LifecycleVerifier.assertResumeToStopSequence(FirstActivity.class, getLifecycleLog());
+            LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                    Arrays.asList(ON_STOP), "keyguardShown");
+            getLifecycleLog().clear();
+        } // keyguard hidden
+
+        // Wait and assert lifecycle
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, 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/activitymanager/src/android/server/am/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecyclePipTests.java
new file mode 100644
index 0000000..022779b
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecyclePipTests.java
@@ -0,0 +1,302 @@
+/*
+ * 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.server.am.lifecycle;
+
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecyclePipTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@FlakyTest(bugId = 77652261)
+public class ActivityLifecyclePipTests extends ActivityLifecycleClientTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsPip());
+    }
+
+    @Test
+    public void testGoToPip() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // Launch Pip-capable activity
+        final Activity pipActivity = mPipActivityTestRule.launchActivity(new Intent());
+
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP), state(pipActivity, ON_RESUME));
+
+        // Move activity to Picture-In-Picture
+        getLifecycleLog().clear();
+        final ComponentName pipActivityName = getComponentName(PipActivity.class);
+        mAmWmState.computeState(pipActivityName);
+        final int stackId = mAmWmState.getAmState().getStackIdByActivity(pipActivityName);
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        moveTopActivityToPinnedStack(stackId);
+
+        // Wait and assert lifecycle
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+        LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE), "enterPip");
+    }
+
+    @Test
+    public void testPipOnLaunch() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+
+        // Wait and assert lifecycle
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(ON_PAUSE, ON_RESUME);
+        final List<LifecycleLog.ActivityCallback> extraCycleSequence =
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_RESTART, ON_START, ON_RESUME);
+        LifecycleVerifier.assertSequenceMatchesOneOf(FirstActivity.class,
+                getLifecycleLog(), Arrays.asList(expectedSequence, extraCycleSequence),
+                "activityEnteringPipOnTop");
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE),
+                "launchAndEnterPip");
+    }
+
+    @Test
+    public void testDestroyPip() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+
+        // Wait and assert lifecycle
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+
+        // Exit PiP
+        getLifecycleLog().clear();
+        pipActivity.finish();
+
+        waitAndAssertActivityStates(state(pipActivity, ON_DESTROY));
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "finishPip");
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_STOP, ON_DESTROY), "finishPip");
+    }
+
+    @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));
+
+        // Launch a regular activity below
+        getLifecycleLog().clear();
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent()
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait and verify the sequence
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
+                "launchBelowPip");
+    }
+
+    @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));
+
+        // Launch a regular activity into same task
+        getLifecycleLog().clear();
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // Wait and verify the sequence
+        waitAndAssertActivityStates(state(pipActivity, ON_STOP), state(firstActivity, ON_PAUSE));
+
+        // TODO(b/123013403): sometimes extra one or even more relaunches happen
+        //final List<LifecycleLog.ActivityCallback> extraDestroySequence =
+        //        Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP,
+        //                ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE);
+        //waitForActivityTransitions(FirstActivity.class, extraDestroySequence);
+        //final List<LifecycleLog.ActivityCallback> expectedSequence =
+        //        Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE);
+        //LifecycleVerifier.assertSequenceMatchesOneOf(FirstActivity.class, getLifecycleLog(),
+        //        Arrays.asList(extraDestroySequence, expectedSequence),
+        //        "launchIntoPip");
+
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_STOP), "launchIntoPip");
+    }
+
+    @Test
+    public void testDestroyBelowPip() throws Exception {
+        // Launch a regular activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+
+        // 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), state(firstActivity, ON_RESUME));
+
+        // Destroy the activity below
+        getLifecycleLog().clear();
+        firstActivity.finish();
+        waitAndAssertActivityStates(state(firstActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(FirstActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
+                "destroyBelowPip");
+    }
+
+    @Test
+    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));
+
+        // 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));
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_RESUME, ON_PAUSE), "moveToSplitScreen");
+        // TODO(b/123013403): will fail with callback tracking enabled - delivers extra
+        // MULTI_WINDOW_MODE_CHANGED
+        LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
+                "launchBelow");
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for activities to resume and verify lifecycle
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+        LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog(),
+                false /* includeCallbacks */);
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "launchToSide");
+        LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
+                "launchBelow");
+    }
+
+    @Test
+    public void testPipAboveSplitScreen() throws Exception {
+        // Launch first activity
+        final Activity firstActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+
+        // 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));
+
+        // Launch Pip-capable activity and enter Pip immediately
+        getLifecycleLog().clear();
+        final Activity pipActivity = mPipActivityTestRule.launchActivity(
+                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+
+        // Wait for it to launch and pause. Other activities should not be affected.
+        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE), state(secondActivity, ON_RESUME));
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE),
+                "launchAndEnterPip");
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
+                "launchPipOnTop");
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(ON_PAUSE, ON_RESUME);
+        final List<LifecycleLog.ActivityCallback> extraCycleSequence =
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_RESTART, ON_START, ON_RESUME);
+        // TODO(b/123013403): sometimes extra destroy is observed
+        LifecycleVerifier.assertSequenceMatchesOneOf(SecondActivity.class,
+                getLifecycleLog(), Arrays.asList(expectedSequence, extraCycleSequence),
+                "activityEnteringPipOnTop");
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleSplitScreenTests.java
new file mode 100644
index 0000000..a6c10fa
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleSplitScreenTests.java
@@ -0,0 +1,440 @@
+/*
+ * 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.server.am.lifecycle;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.am.lifecycle.LifecycleVerifier.transition;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@FlakyTest(bugId = 77652261)
+public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsSplitScreenMultiWindow());
+    }
+
+    @Test
+    public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+
+        // Launch second activity to stop first
+        final Activity secondActivity =
+                mSecondActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(secondActivity.getTaskId());
+
+        // CLear logs so we can capture just the destroy sequence
+        getLifecycleLog().clear();
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        getLaunchActivityBuilder().execute();
+
+        // Finish top activity
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+        waitAndAssertActivityStates(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());
+    }
+
+    @Test
+    public void testOccludingMovedBetweenStacks() throws Exception {
+        // Launch first activity
+        final Activity firstActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mAmWmState.computeState(firstActivityName);
+        int primarySplitStack = mAmWmState.getAmState().getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to resume.
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                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));
+
+        // Move occluding third activity to side, it will occlude first now
+        getLifecycleLog().clear();
+        moveActivityToStack(getComponentName(ThirdActivity.class), primarySplitStack);
+
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                state(firstActivity, ON_STOP));
+        LifecycleVerifier.assertEmptySequence(ThirdActivity.class, getLifecycleLog(), "moveToSide");
+        LifecycleVerifier.assertRestartAndResumeSequence(SecondActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE, ON_STOP), "moveToSide");
+    }
+
+    @Test
+    public void testTranslucentMovedBetweenStacks() throws Exception {
+        // Launch first activity
+        final Activity firstActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mAmWmState.computeState(firstActivityName);
+        int primarySplitStack = mAmWmState.getAmState().getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to resume.
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                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));
+        // Second activity should stay resumed, because it's in a separate stack below the
+        // translucent activity.
+        LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+                "moveToSide");
+
+        // Move translucent activity to side, it will be on top of the first now
+        getLifecycleLog().clear();
+        moveActivityToStack(getComponentName(TranslucentActivity.class), primarySplitStack);
+
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+        LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+                "moveToSide");
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE), "moveToSide");
+        // Translucent activity can be either relaunched or preserved depending on whether the split
+        // sizes match. Not verifying its lifecycle here.
+    }
+
+    @Test
+    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));
+
+        // Enter split screen, the activity will be relaunched.
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId());
+        // Wait for multi-window mode change that will come after activity relaunch and resume.
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_PAUSE));
+        final List<LifecycleLog.ActivityCallback> splitScreenMoveSequence = Arrays.asList(
+                ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE,
+                ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE);
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                splitScreenMoveSequence, "moveToPrimarySplitScreen");
+        getLifecycleLog().clear();
+
+        // Launch second activity
+        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
+        Instrumentation.ActivityMonitor activityMonitor = InstrumentationRegistry
+                .getInstrumentation()
+                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
+                        false /* block */);
+
+        callbackTrackingActivity.startActivityForResult(
+                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
+
+        // Wait for the ActivityMonitor to be hit
+        final Activity secondActivity = InstrumentationRegistry.getInstrumentation()
+                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
+
+        // Wait for second activity to resume
+        assertNotNull("Second activity should be started", secondActivity);
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+
+        // Verify if the first activity stopped (since it is not currently visible)
+        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));
+
+        // Finish top activity and verify that activity below became focused.
+        getLifecycleLog().clear();
+        secondActivity.setResult(Activity.RESULT_OK);
+        secondActivity.finish();
+
+        // Check that activity was resumed and result was delivered
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
+    }
+
+    @Test
+    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));
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(), Arrays.asList(
+                ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME,
+                ON_PAUSE), "enterSplitScreen");
+
+        // 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),
+                state(firstActivity, ON_RESUME));
+
+        // Launch second activity, first become stopped
+        getLifecycleLog().clear();
+        final Activity secondActivity =
+                mSecondActivityTestRule.launchActivity(new Intent());
+
+        // Wait for second activity to pause and first to stop
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
+
+        // Finish top activity
+        getLifecycleLog().clear();
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+
+        // Verify that the first activity was restarted to resumed state as it was brought back
+        // after windowing mode was switched
+        LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+    }
+
+    @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));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
+
+        // Launch a translucent activity, first become paused
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+
+        // Wait for translucent activity to resume and first to pause
+        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+        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));
+
+        getLifecycleLog().clear();
+
+        // Finish top activity
+        translucentActivity.finish();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
+
+        // Verify that the first activity was resumed
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "resume");
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+        // Launch a singleTop activity
+        final Activity testActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+
+        // Wait for the activity to resume
+        waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                        ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE);
+        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");
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(CallbackTrackingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
+                "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_MULTI_WINDOW_MODE_CHANGED,
+                        ON_RESUME, ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_DESTROY, ON_CREATE, ON_MULTI_WINDOW_MODE_CHANGED),
+                "moveFromSplitScreen");
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_RESUME, ON_TOP_POSITION_GAINED),
+                "moveFromSplitScreen");
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+        // Launch a singleTop activity
+        final Activity testActivity =
+                mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
+
+        // Wait for the activity to resume
+        waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to receive the change
+        waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST));
+        LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
+                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+                "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to receive the change
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
+        waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
+                "exitSplitScreen");
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
+                "exitSplitScreen");
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
index 153a4cc..d09d554 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -1,9 +1,27 @@
+/*
+ * 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.server.am.lifecycle;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.am.ActivityManagerState.STATE_PAUSED;
 import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.am.UiDeviceUtils.pressBackButton;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
@@ -17,17 +35,18 @@
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 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 org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
-import android.app.Instrumentation.ActivityMonitor;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -42,6 +61,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -79,6 +99,189 @@
     }
 
     @Test
+    public void testLaunchTranslucentOnTop() throws Exception {
+        // Launch fullscreen activity
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Launch translucent activity on top
+        getLifecycleLog().clear();
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
+                state(translucentActivity, ON_RESUME));
+
+        LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
+                getLifecycleLog(), true /* launchIsTranslucent */);
+    }
+
+    @Test
+    public void testLaunchDoubleTranslucentOnTop() throws Exception {
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Launch translucent activity on top
+        getLifecycleLog().clear();
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
+                state(translucentActivity, ON_RESUME));
+
+        LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
+                getLifecycleLog(), true /* launchIsTranslucent */);
+
+        // Launch another translucent activity on top
+        getLifecycleLog().clear();
+        final Activity secondTranslucentActivity =
+                mSecondTranslucentActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(translucentActivity, ON_PAUSE),
+                state(secondTranslucentActivity, ON_RESUME));
+        LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE), "launch");
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "launch");
+
+        // Finish top translucent activity
+        getLifecycleLog().clear();
+        secondTranslucentActivity.finish();
+
+        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondTranslucentActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(SecondTranslucentActivity.class,
+                getLifecycleLog());
+        LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "launch");
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "launch");
+    }
+
+    @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 ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mAmWmState.computeState(firstActivityName);
+        int firstActivityStack = mAmWmState.getAmState().getStackIdByActivity(firstActivityName);
+
+        // Move translucent activity into the stack with the first activity
+        getLifecycleLog().clear();
+        moveActivityToStack(getComponentName(TranslucentActivity.class), firstActivityStack);
+
+        // Wait for translucent activity to resume and first activity to pause
+        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
+                state(firstActivity, ON_PAUSE));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE), "launchOnTop");
+        LifecycleVerifier.assertRestartAndResumeSequence(TranslucentActivity.class,
+                getLifecycleLog());
+    }
+
+    @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));
+
+        // Finish translucent activity
+        getLifecycleLog().clear();
+        mTranslucentActivityTestRule.finishActivity();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
+                state(translucentActivity, ON_DESTROY));
+
+        // Verify destruction lifecycle
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "resumeAfterTopDestroyed");
+    }
+
+    @Test
+    public void testDestroyOnTopOfTranslucent() throws Exception {
+        // Launch fullscreen activity
+        final Activity firstActivity =
+                mFirstActivityTestRule.launchActivity(new Intent());
+
+        // Launch translucent activity
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+
+        // Launch another fullscreen activity
+        final Activity secondActivity =
+                mSecondActivityTestRule.launchActivity(new Intent());
+
+        // Wait for top activity to resume
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                occludedActivityState(translucentActivity, secondActivity),
+                occludedActivityState(firstActivity, secondActivity));
+
+        getLifecycleLog().clear();
+
+        final boolean secondActivityIsTranslucent = ActivityInfo.isTranslucentOrFloating(
+                secondActivity.getWindow().getWindowStyle());
+
+        // Finish top activity
+        mSecondActivityTestRule.finishActivity();
+
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+        if (secondActivityIsTranslucent) {
+            // In this case we don't expect the state of the firstActivity to change since it is
+            // already in the visible paused state. So, we just verify that translucentActivity
+            // transitions to resumed state.
+            waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+        } else {
+            // Wait for translucent activity to resume
+            waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
+                    state(firstActivity, ON_START));
+
+            // Verify that the first activity was restarted
+            LifecycleVerifier.assertRestartSequence(FirstActivity.class, getLifecycleLog());
+        }
+    }
+
+    @Test
+    public void testDestroyDoubleTranslucentOnTop() throws Exception {
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity translucentActivity =
+                mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity secondTranslucentActivity =
+                mSecondTranslucentActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
+                state(translucentActivity, ON_PAUSE), state(secondTranslucentActivity, ON_RESUME));
+
+        // Finish top translucent activity
+        getLifecycleLog().clear();
+        secondTranslucentActivity.finish();
+
+        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondTranslucentActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(SecondTranslucentActivity.class,
+                getLifecycleLog());
+        LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "destroy");
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "destroy");
+
+        // Finish first translucent activity
+        getLifecycleLog().clear();
+        translucentActivity.finish();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "secondDestroy");
+    }
+
+    @Test
     public void testLaunchAndDestroy() throws Exception {
         final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
 
@@ -205,211 +408,6 @@
     }
 
     @Test
-    public void testPausedWithTranslucentOnTop() throws Exception {
-        // Launch fullscreen activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Launch translucent activity on top
-        mTranslucentActivityTestRule.launchActivity(new Intent());
-
-        // Launch another translucent activity on top to make sure the fullscreen activity
-        // transitions to final state
-        final Activity secondTranslucentActivity =
-                mSecondTranslucentActivityTestRule.launchActivity(new Intent());
-
-        // Wait for the second translucent activity to become resumed.
-        waitAndAssertActivityStates(state(secondTranslucentActivity, ON_RESUME),
-                state(firstActivity, ON_PAUSE));
-
-        // Assert that the fullscreen activity was not stopped and is in the paused state.
-        LifecycleVerifier.assertLaunchAndPauseSequence(FirstActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testPausedWhenReturningWithTranslucentOnTop() throws Exception {
-        // Launch fullscreen activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Launch translucent activity
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-
-        // Launch another fullscreen activity
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
-
-        // Wait for top activity to resume
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                occludedActivityState(translucentActivity, secondActivity),
-                occludedActivityState(firstActivity, secondActivity));
-
-        getLifecycleLog().clear();
-
-        final boolean secondActivityIsTranslucent = ActivityInfo.isTranslucentOrFloating(
-                secondActivity.getWindow().getWindowStyle());
-
-        // Finish top activity
-        mSecondActivityTestRule.finishActivity();
-
-        if (secondActivityIsTranslucent) {
-            // In this case we don't expect the state of the firstActivity to change since it is
-            // already in the visible paused state. So, we just verify that translucentActivity
-            // transitions to resumed state.
-            waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
-        } else {
-            // Wait for translucent activity to resume
-            waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
-                    state(firstActivity, ON_START));
-
-            // Verify that the first activity was restarted
-            LifecycleVerifier.assertRestartSequence(FirstActivity.class, getLifecycleLog());
-        }
-    }
-
-    @Test
-    public void testPausedWhenRecreatedFromInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            // Skipping test: no split multi-window support
-            return;
-        }
-
-        // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Launch second activity to stop first
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
-
-        // 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));
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreen(secondActivity.getTaskId());
-
-        // CLear logs so we can capture just the destroy sequence
-        getLifecycleLog().clear();
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        getLaunchActivityBuilder().execute();
-
-        // Finish top activity
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
-
-        // Verify that the first activity was recreated to pause as it was created before
-        // windowing mode was switched
-        LifecycleVerifier.assertRecreateAndPauseSequence(FirstActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testResultInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            // Skipping test: no split multi-window support
-            return;
-        }
-
-        // Launch first activity
-        final Activity callbackTrackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for first activity to resume
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
-                true /* launchSideActivityIfNeeded */);
-
-        // Launch second activity to pause first
-        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
-        ActivityMonitor activityMonitor = InstrumentationRegistry.getInstrumentation()
-                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
-                        false /* block */);
-
-        callbackTrackingActivity.startActivityForResult(
-                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
-
-        // Wait for the ActivityMonitor to be hit
-        final Activity secondActivity = InstrumentationRegistry.getInstrumentation()
-                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
-
-        // Wait for second activity to resume
-        assertNotNull("Second activity should be started", secondActivity);
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        getLaunchActivityBuilder().execute();
-
-        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
-
-        // Finish top activity and verify that activity below became focused.
-        getLifecycleLog().clear();
-        secondActivity.setResult(Activity.RESULT_OK);
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_START));
-        LifecycleVerifier.assertRestartSequence(CallbackTrackingActivity.class, getLifecycleLog());
-
-        // Bring the first activity to front to verify that it receives the result.
-        getLifecycleLog().clear();
-        final Intent singleTopIntent = new Intent(InstrumentationRegistry.getTargetContext(),
-                CallbackTrackingActivity.class);
-        singleTopIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
-        InstrumentationRegistry.getTargetContext().startActivity(singleTopIntent);
-
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_ACTIVITY_RESULT, ON_NEW_INTENT, ON_RESUME), "bring to front");
-    }
-
-    @Test
-    public void testPausedWhenRestartedFromInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            // Skipping test: no split multi-window support
-            return;
-        }
-
-        // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Wait for first activity to resume
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(),
-                true /* launchSideActivityIfNeeded */);
-
-        // Launch second activity to pause first
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
-
-        // Wait for second activity to resume
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        getLaunchActivityBuilder().execute();
-
-        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
-
-        getLifecycleLog().clear();
-
-        // Finish top activity
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
-
-        // Verify that the first activity was restarted to pause as it was brought back after
-        // windowing mode was switched
-        LifecycleVerifier.assertRestartAndPauseSequence(FirstActivity.class, getLifecycleLog());
-    }
-
-    @Test
     public void testOnActivityResult() throws Exception {
         final Intent intent = new Intent();
         intent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
@@ -417,15 +415,29 @@
 
         final List<LifecycleLog.ActivityCallback> expectedSequence =
                 Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
-                        ON_PAUSE, ON_ACTIVITY_RESULT, ON_RESUME);
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST,
+                        ON_PAUSE, ON_ACTIVITY_RESULT, ON_RESUME, ON_TOP_POSITION_GAINED);
         waitForActivityTransitions(LaunchForResultActivity.class, expectedSequence);
 
         // TODO(b/79218023): First activity might also be stopped before getting result.
         final List<LifecycleLog.ActivityCallback> sequenceWithStop =
                 Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
-                        ON_PAUSE, ON_STOP, ON_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME);
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST,
+                        ON_PAUSE, ON_STOP, ON_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        final List<LifecycleLog.ActivityCallback> thirdSequence =
+                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_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        final List<LifecycleLog.ActivityCallback> fourthSequence =
+                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_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
         LifecycleVerifier.assertSequenceMatchesOneOf(LaunchForResultActivity.class,
-                getLifecycleLog(), Arrays.asList(expectedSequence, sequenceWithStop),
+                getLifecycleLog(),
+                Arrays.asList(expectedSequence, sequenceWithStop, thirdSequence, fourthSequence),
                 "activityResult");
     }
 
@@ -436,19 +448,29 @@
         mLaunchForResultActivityTestRule.launchActivity(intent);
         final boolean isTranslucent = isTranslucent(mLaunchForResultActivityTestRule.getActivity());
 
-        final List<LifecycleLog.ActivityCallback> expectedSequence;
+        final List<List<LifecycleLog.ActivityCallback>> expectedSequences;
         if (isTranslucent) {
-            expectedSequence = Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE,
-                    ON_RESUME, ON_PAUSE, ON_ACTIVITY_RESULT, ON_RESUME);
+            expectedSequences = Arrays.asList(
+                    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_ACTIVITY_RESULT, ON_RESUME, ON_TOP_POSITION_GAINED)
+            );
         } else {
-            expectedSequence = Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE,
-                    ON_RESUME, ON_PAUSE, ON_STOP, ON_ACTIVITY_RESULT, ON_RESTART, ON_START,
-                    ON_RESUME);
+            expectedSequences = Arrays.asList(
+                    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_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME,
+                            ON_TOP_POSITION_GAINED),
+                    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_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+                            ON_TOP_POSITION_GAINED)
+            );
         }
-        waitForActivityTransitions(LaunchForResultActivity.class, expectedSequence);
+        waitForActivityTransitions(LaunchForResultActivity.class, expectedSequences.get(0));
 
-        LifecycleVerifier.assertSequence(LaunchForResultActivity.class,
-                getLifecycleLog(), expectedSequence, "activityResult");
+        LifecycleVerifier.assertSequenceMatchesOneOf(LaunchForResultActivity.class,
+                getLifecycleLog(), expectedSequences, "activityResult");
     }
 
     @Test
@@ -456,12 +478,11 @@
         final Activity callbackTrackingActivity =
                 mCallbackTrackingActivityTestRule.launchActivity(new Intent());
 
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
+        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),
-                "create");
+        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
@@ -471,17 +492,17 @@
                 mCallbackTrackingActivityTestRule.launchActivity(new Intent());
 
         // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
 
         // Call "recreate" and assert sequence
         getLifecycleLog().clear();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(trackingActivity::recreate);
-        waitAndAssertActivityStates(state(trackingActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
 
         LifecycleVerifier.assertSequence(CallbackTrackingActivity.class,
                 getLifecycleLog(),
-                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
                 "recreate");
     }
 
@@ -492,7 +513,7 @@
                 mCallbackTrackingActivityTestRule.launchActivity(new Intent());
 
         // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
 
         // Launch translucent activity, which will make the first one paused.
         mTranslucentActivityTestRule.launchActivity(new Intent());
@@ -519,7 +540,7 @@
                 mCallbackTrackingActivityTestRule.launchActivity(new Intent());
 
         // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
 
         // Launch second activity to cover and stop first
         final Activity secondActivity =
@@ -585,7 +606,7 @@
     }
 
     /**
-     * The that recreate request from an activity is executed immediately.
+     * Tests that recreate request from an activity is executed immediately.
      */
     @Test
     public void testLocalRecreate() throws Exception {
@@ -593,36 +614,46 @@
         Activity recreatingActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
 
         // Launch second activity to cover and stop first
-        final LaunchActivityBuilder launchActivityBuilder = getLaunchActivityBuilder();
-        launchActivityBuilder.setNewTask(true).setMultipleTask(true).execute();
+        Activity secondActivity = mSecondActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
 
         // Wait for first activity to become stopped
-        waitAndAssertActivityStates(occludedActivityState(recreatingActivity,
-                launchActivityBuilder.isTargetActivityTranslucent()));
+        final boolean secondActivityIsTranslucent = ActivityInfo.isTranslucentOrFloating(
+                secondActivity.getWindow().getWindowStyle());
+        waitAndAssertActivityStates(
+                occludedActivityState(recreatingActivity, secondActivityIsTranslucent),
+                state(secondActivity, ON_RESUME));
 
         // Launch the activity again to recreate
         getLifecycleLog().clear();
         final Intent intent = new Intent(InstrumentationRegistry.getContext(),
                 SingleTopActivity.class);
         intent.putExtra(EXTRA_RECREATE, true);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
         // Wait for activity to relaunch and resume
-        final List<LifecycleLog.ActivityCallback> expectedRelaunchSequence;
-        if (launchActivityBuilder.isTargetActivityTranslucent()) {
-            expectedRelaunchSequence = Arrays.asList(ON_NEW_INTENT, ON_RESUME, ON_PAUSE, ON_STOP,
-                    ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
-                    ON_PAUSE, ON_RESUME);
-        } else {
-            expectedRelaunchSequence = Arrays.asList(ON_NEW_INTENT, ON_RESTART, ON_START, ON_RESUME,
+        final List<List<LifecycleLog.ActivityCallback>> expectedRelaunchSequences;
+        if (secondActivityIsTranslucent) {
+            expectedRelaunchSequences = Arrays.asList(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,
-                    ON_POST_CREATE, ON_RESUME);
+                    ON_POST_CREATE, ON_RESUME,ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED));
+        } else {
+            expectedRelaunchSequences = Arrays.asList(
+                    Arrays.asList(ON_RESTART, ON_START, 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, ON_POST_CREATE,
+                            ON_RESUME, ON_TOP_POSITION_GAINED),
+                    Arrays.asList(ON_NEW_INTENT, ON_RESTART, ON_START, ON_RESUME,
+                            ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+                            ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE,
+                            ON_RESUME, ON_TOP_POSITION_GAINED));
         }
 
-        waitForActivityTransitions(SingleTopActivity.class, expectedRelaunchSequence);
-        LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
-                expectedRelaunchSequence, "recreate");
+        waitForActivityTransitions(SingleTopActivity.class, expectedRelaunchSequences.get(0));
+        LifecycleVerifier.assertSequenceMatchesOneOf(SingleTopActivity.class, getLifecycleLog(),
+                expectedRelaunchSequences, "recreate");
     }
 
     @Test
@@ -632,7 +663,7 @@
                 mSingleTopActivityTestRule.launchActivity(new Intent());
 
         // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog(),
                 true /* includeCallbacks */);
 
@@ -640,15 +671,16 @@
         getLifecycleLog().clear();
         final Intent intent = new Intent(InstrumentationRegistry.getContext(),
                 SingleTopActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
         // Wait for the activity to resume again
-        waitAndAssertActivityStates(state(singleTopActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
 
         // Verify that the first activity was paused, new intent was delivered and resumed again
         LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_PAUSE, ON_NEW_INTENT, ON_RESUME), "newIntent");
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_NEW_INTENT, ON_RESUME,
+                        ON_TOP_POSITION_GAINED), "newIntent");
     }
 
     @Test
@@ -658,13 +690,13 @@
                 mSingleTopActivityTestRule.launchActivity(new Intent());
 
         // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog(),
                 true /* includeCallbacks */);
 
         // Launch something on top
         final Intent newTaskIntent = new Intent();
-        newTaskIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        newTaskIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
         final Activity secondActivity = mSecondActivityTestRule.launchActivity(newTaskIntent);
 
         // Wait for the activity to resume
@@ -675,7 +707,7 @@
         getLifecycleLog().clear();
         final Intent intent = new Intent(InstrumentationRegistry.getContext(),
                 SingleTopActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
         // Wait for the activity to resume again
@@ -703,7 +735,7 @@
                 mSingleTopActivityTestRule.launchActivity(new Intent());
 
         // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog(),
                 true /* includeCallbacks */);
 
@@ -717,7 +749,7 @@
         getLifecycleLog().clear();
         final Intent intent = new Intent(InstrumentationRegistry.getContext(),
                 SingleTopActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
         // Wait for the activity to resume again
@@ -733,91 +765,4 @@
         LifecycleVerifier.assertSequenceMatchesOneOf(SingleTopActivity.class, getLifecycleLog(),
                 Arrays.asList(expectedSequence, extraPauseSequence), "newIntent");
     }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            // Skipping test: no split multi-window support
-            return;
-        }
-
-        // Launch a singleTop activity
-        final Activity testActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
-        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                true /* includeCallbacks */);
-
-        // Enter split screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
-        // Wait for the activity to pause
-        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
-                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME, ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE);
-        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
-
-        // Verify that the activity was relaunched and received multi-window mode change
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                expectedEnterSequence, "moveToSplitScreen");
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to resume
-        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
-                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_MULTI_WINDOW_MODE_CHANGED,
-                        ON_RESUME);
-        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
-
-        // Verify that the activity was relaunched and received multi-window mode change
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                expectedExitSequence, "moveFromSplitScreen");
-    }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
-            // Skipping test: no split multi-window support
-            return;
-        }
-
-        // Launch a singleTop activity
-        final Activity testActivity =
-                mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
-        LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
-                getLifecycleLog(), true /* includeCallbacks */);
-
-        // Enter split screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
-        // Wait for the activity to pause
-        waitAndAssertActivityStates(state(testActivity, ON_PAUSE));
-
-        // Verify that the activity was relaunched and received multi-window mode change
-        LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_PAUSE), "moveToSplitScreen");
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(testActivity, ON_RESUME));
-
-        // Verify that the activity was relaunched and received multi-window mode change
-        LifecycleVerifier.assertSequence(ConfigChangeHandlingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_RESUME), "moveFromSplitScreen");
-    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java
new file mode 100644
index 0000000..5769014
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -0,0 +1,768 @@
+package android.server.am.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.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics;
+import static android.server.am.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
+import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
+import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
+import static android.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.am.lifecycle.LifecycleVerifier.transition;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerDisplayTestBase;
+import android.server.am.ActivityManagerDisplayTestBase.VirtualDisplaySession;
+import android.server.am.ActivityManagerState;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+import android.view.Display;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link Activity#onTopResumedActivityChanged}.
+ *
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@FlakyTest(bugId = 117135575)
+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));
+
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionLostOnFinish() throws Exception {
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        activity.finish();
+        mAmWmState.waitForActivityRemoved(getComponentName(CallbackTrackingActivity.class));
+
+        LifecycleVerifier.assertResumeToDestroySequence(CallbackTrackingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionSwitchToActivityOnTop() throws Exception {
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        final Activity topActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
+                state(activity, ON_STOP));
+
+        LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class,
+                CallbackTrackingActivity.class, getLifecycleLog(),
+                false /* launchingIsTranslucent */, true /* includingCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionSwitchToTranslucentActivityOnTop() throws Exception {
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
+                new Intent());
+        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
+                state(activity, ON_PAUSE));
+
+        LifecycleVerifier.assertLaunchSequence(TranslucentCallbackTrackingActivity.class,
+                CallbackTrackingActivity.class, getLifecycleLog(),
+                true /* launchingIsTranslucent */, true /* includingCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionSwitchOnDoubleLaunch() throws Exception {
+        final Activity baseActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        final Activity launchForResultActivity = mLaunchForResultActivityTestRule.launchActivity(
+                new Intent());
+
+        waitAndAssertActivityStates(state(launchForResultActivity, ON_STOP),
+                state(baseActivity, ON_STOP));
+
+        final List<LifecycleLog.ActivityCallback> expectedTopActivitySequence =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(ResultActivity.class, expectedTopActivitySequence);
+
+        final List<Pair<String, LifecycleLog.ActivityCallback>> observedTransitions =
+                getLifecycleLog().getLog();
+        final List<Pair<String, LifecycleLog.ActivityCallback>> expectedTransitions = Arrays.asList(
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_PAUSE),
+                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),
+                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_POST_CREATE),
+                transition(ResultActivity.class, ON_RESUME),
+                transition(ResultActivity.class, ON_TOP_POSITION_GAINED),
+                transition(LaunchForResultActivity.class, ON_STOP),
+                transition(CallbackTrackingActivity.class, ON_STOP));
+        assertEquals("Double launch sequence must match", expectedTransitions, observedTransitions);
+    }
+
+    @Test
+    public void testTopPositionSwitchOnDoubleLaunchAndTopFinish() throws Exception {
+        final Activity baseActivity = mCallbackTrackingActivityTestRule.launchActivity(
+                new Intent());
+        waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        final Intent launchAndFinishIntent = new Intent();
+        launchAndFinishIntent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
+        mLaunchForResultActivityTestRule.launchActivity(launchAndFinishIntent);
+
+        waitAndAssertActivityStates(state(baseActivity, ON_STOP));
+        final List<LifecycleLog.ActivityCallback> expectedLaunchingSequence =
+                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_ACTIVITY_RESULT, ON_RESUME, ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(LaunchForResultActivity.class, expectedLaunchingSequence);
+
+        final List<LifecycleLog.ActivityCallback> expectedTopActivitySequence =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(ResultActivity.class, expectedTopActivitySequence);
+
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_PAUSE),
+                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),
+                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_POST_CREATE),
+                transition(ResultActivity.class, ON_RESUME),
+                transition(ResultActivity.class, ON_TOP_POSITION_GAINED),
+                transition(ResultActivity.class, ON_TOP_POSITION_LOST),
+                transition(ResultActivity.class, ON_PAUSE),
+                transition(LaunchForResultActivity.class, ON_ACTIVITY_RESULT),
+                transition(LaunchForResultActivity.class, ON_RESUME),
+                transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
+                transition(ResultActivity.class, ON_STOP),
+                transition(ResultActivity.class, ON_DESTROY),
+                transition(CallbackTrackingActivity.class, ON_STOP)),
+                getLifecycleLog(), "Double launch sequence must match");
+    }
+
+    @Test
+    public void testTopPositionLostWhenDocked() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Launch first activity
+        final Activity firstActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                        ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE),
+                "moveToDocked");
+    }
+
+    @Test
+    public void testTopPositionSwitchToAnotherVisibleActivity() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Launch first activity
+        final Activity firstActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to become top.
+        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
+                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");
+        // Second activity must be on top now
+        LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionSwitchBetweenVisibleActivities() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Launch first activity
+        final Activity firstActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to become top.
+        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
+                state(firstActivity, ON_RESUME));
+
+        // Switch top between two activities
+        getLifecycleLog().clear();
+        final Intent switchToFirstIntent = new Intent(InstrumentationRegistry.getContext(),
+                CallbackTrackingActivity.class);
+        switchToFirstIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(switchToFirstIntent);
+
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
+                state(secondActivity, ON_TOP_POSITION_LOST));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_GAINED), "switchTop");
+        LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST), "switchTop");
+
+        // Switch top again
+        getLifecycleLog().clear();
+        final Intent switchToSecondIntent = new Intent(InstrumentationRegistry.getContext(),
+                SingleTopActivity.class);
+        switchToSecondIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(switchToSecondIntent);
+
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
+                state(secondActivity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST), "switchTop");
+        LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_NEW_INTENT,
+                        ON_RESUME, ON_TOP_POSITION_GAINED), "switchTop");
+    }
+
+    @Test
+    public void testTopPositionNewIntent() throws Exception {
+        // Launch single top activity
+        final Activity firstActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+        // Launch the activity again to observe new intent
+        getLifecycleLog().clear();
+        final Intent newIntent = new Intent(InstrumentationRegistry.getContext(),
+                SingleTopActivity.class);
+        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(newIntent);
+
+        waitAndAssertActivityTransitions(SingleTopActivity.class,
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_NEW_INTENT, ON_RESUME,
+                        ON_TOP_POSITION_GAINED), "newIntent");
+    }
+
+    @Test
+    public void testTopPositionNewIntentForStopped() throws Exception {
+        // Launch single top activity
+        final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+
+        // Launch another activity on top
+        final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(singleTopActivity, ON_STOP),
+                state(topActivity, ON_TOP_POSITION_GAINED));
+
+        // Launch the single top activity again to observe new intent
+        getLifecycleLog().clear();
+        final Intent newIntent = new Intent(InstrumentationRegistry.getContext(),
+                SingleTopActivity.class);
+        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
+        InstrumentationRegistry.getTargetContext().startActivity(newIntent);
+
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
+                state(topActivity, ON_DESTROY));
+
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_PAUSE),
+                transition(SingleTopActivity.class, ON_NEW_INTENT),
+                transition(SingleTopActivity.class, ON_RESTART),
+                transition(SingleTopActivity.class, ON_START),
+                transition(SingleTopActivity.class, ON_RESUME),
+                transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED),
+                transition(CallbackTrackingActivity.class, ON_STOP),
+                transition(CallbackTrackingActivity.class, ON_DESTROY)),
+                getLifecycleLog(), "Single top resolution sequence must match");
+    }
+
+    @Test
+    public void testTopPositionNewIntentForPaused() throws Exception {
+        // Launch single top activity
+        final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+
+        // Launch a translucent activity on top
+        final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
+                new Intent());
+        waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE),
+                state(topActivity, ON_TOP_POSITION_GAINED));
+
+        // Launch the single top activity again to observe new intent
+        getLifecycleLog().clear();
+        final Intent newIntent = new Intent(InstrumentationRegistry.getContext(),
+                SingleTopActivity.class);
+        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
+        InstrumentationRegistry.getTargetContext().startActivity(newIntent);
+
+        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
+                state(topActivity, ON_DESTROY));
+
+        LifecycleVerifier.assertEntireSequence(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),
+                // TODO(b/123432490): Fix extra pause-resume cycle
+                transition(SingleTopActivity.class, ON_PAUSE),
+                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");
+    }
+
+    @Test
+    public void testTopPositionSwitchWhenGoingHome() throws Exception {
+        final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED));
+
+        // Press HOME and verify the lifecycle
+        getLifecycleLog().clear();
+        pressHomeButton();
+        waitAndAssertActivityStates(state(topActivity, ON_STOP));
+
+        LifecycleVerifier.assertResumeToStopSequence(CallbackTrackingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionSwitchOnTap() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        // Launch first activity
+        final Activity firstActivity =
+                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId());
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
+                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+
+        // Wait for second activity to become top.
+        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
+                state(firstActivity, ON_RESUME));
+
+        // Tap on first activity to switch the focus
+        getLifecycleLog().clear();
+        final ActivityStack dockedStack = getStackForTaskId(firstActivity.getTaskId());
+        final Rect dockedStackBounds = dockedStack.getBounds();
+        int tapX = dockedStackBounds.left + dockedStackBounds.width() / 2;
+        int tapY = dockedStackBounds.top + dockedStackBounds.height() / 2;
+        tapOnDisplay(tapX, tapY, dockedStack.mDisplayId);
+
+        // Wait and assert focus switch
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
+                state(secondActivity, ON_TOP_POSITION_LOST));
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+                getLifecycleLog(), "Single top resolution sequence must match");
+
+        // Tap on second activity to switch the focus again
+        getLifecycleLog().clear();
+        final ActivityStack sideStack = getStackForTaskId(secondActivity.getTaskId());
+        final Rect sideStackBounds = sideStack.getBounds();
+        tapX = sideStackBounds.left + sideStackBounds.width() / 2;
+        tapY = sideStackBounds.top + sideStackBounds.height() / 2;
+        tapOnDisplay(tapX, tapY, sideStack.mDisplayId);
+
+        // Wait and assert focus switch
+        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
+                state(secondActivity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+                getLifecycleLog(), "Single top resolution sequence must match");
+    }
+
+    @Test
+    public void testTopPositionPreservedOnLocalRelaunch() throws Exception {
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(activity::recreate);
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        LifecycleVerifier.assertRelaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                ON_TOP_POSITION_GAINED);
+    }
+
+    @Test
+    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));
+            LifecycleVerifier.assertLaunchAndStopSequence(CallbackTrackingActivity.class,
+                    getLifecycleLog(), true /* includeCallbacks */, false /* onTop */);
+
+            getLifecycleLog().clear();
+        }
+
+        // Lock screen removed - activity should be on top now
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionRemovedBehindLockScreen() throws Exception {
+        assumeTrue(supportsSecureLock());
+
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+
+            waitAndAssertActivityStates(state(activity, ON_STOP));
+            LifecycleVerifier.assertResumeToStopSequence(CallbackTrackingActivity.class,
+                    getLifecycleLog(), true /* includeCallbacks */);
+
+            getLifecycleLog().clear();
+        }
+
+        // Lock screen removed - activity should be on top now
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+    }
+
+    @Test
+    public void testTopPositionLaunchedOnTopOfLockScreen() throws Exception {
+        assumeTrue(supportsSecureLock());
+
+        final Activity showWhenLockedActivity;
+        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+
+            showWhenLockedActivity = mShowWhenLockedCallbackTrackingActivityTestRule.launchActivity(
+                    new Intent());
+            waitAndAssertActivityStates(state(showWhenLockedActivity, ON_TOP_POSITION_GAINED));
+
+            // TODO(b/123432490): Fix extra pause/resume
+            LifecycleVerifier.assertSequence(ShowWhenLockedCallbackTrackingActivity.class,
+                    getLifecycleLog(), Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START,
+                            ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED),
+                    "launchAboveKeyguard");
+
+            getLifecycleLog().clear();
+        }
+
+        // Lock screen removed, but nothing should change.
+        // Wait for something here, but don't expect anything to happen.
+        waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
+        LifecycleVerifier.assertResumeToDestroySequence(
+                ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog(),
+                true /* includeCallbacks */);
+    }
+
+    @Test
+    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(InstrumentationRegistry.getContext(), CallbackTrackingActivity.class);
+        defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(defaultDisplayIntent,
+                launchOptions.toBundle());
+
+        waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
+                DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+        waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+                LifecycleVerifier.getLaunchSequence(true /* includeCallbacks */), "launch");
+
+        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();
+            launchOptions.setLaunchDisplayId(newDisplay.mId);
+            final Intent newDisplayIntent =
+                    new Intent(InstrumentationRegistry.getContext(), SingleTopActivity.class);
+            newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+            InstrumentationRegistry.getTargetContext().startActivity(newDisplayIntent,
+                    launchOptions.toBundle());
+            waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+                    newDisplay.mId, "Activity launched on secondary display must be focused");
+
+            waitAndAssertActivityTransitions(SingleTopActivity.class,
+                    LifecycleVerifier.getLaunchSequence(true /* includeCallbacks */), "launch");
+            LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+                    transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                    transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+                    "launchOnOtherDisplay");
+
+            getLifecycleLog().clear();
+        }
+
+        // Secondary display was removed - activity will be moved to the default display
+        waitAndAssertActivityTransitions(SingleTopActivity.class,
+                LifecycleVerifier.getResumeToDestroySequence(true /* includeCallbacks */),
+                "hostingDisplayRemoved");
+        waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+                Arrays.asList(ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP),
+                "hostingDisplayRemoved");
+        LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+                transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+                "hostingDisplayRemoved");
+    }
+
+    @Test
+    public void testTopPositionSwitchAcrossDisplaysOnTap() throws Exception {
+        assumeTrue(supportsMultiDisplay());
+
+        // Launch activity on default display.
+        final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+        launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+        final Intent defaultDisplayIntent =
+                new Intent(InstrumentationRegistry.getContext(), CallbackTrackingActivity.class);
+        defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getTargetContext().startActivity(defaultDisplayIntent,
+                launchOptions.toBundle());
+
+        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();
+            launchOptions.setLaunchDisplayId(newDisplay.mId);
+            final Intent newDisplayIntent =
+                    new Intent(InstrumentationRegistry.getContext(), SingleTopActivity.class);
+            newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+            InstrumentationRegistry.getTargetContext().startActivity(newDisplayIntent,
+                    launchOptions.toBundle());
+            waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+                    newDisplay.mId, "Activity launched on secondary display must be focused");
+
+            getLifecycleLog().clear();
+
+            // Tap on default display to switch the top activity
+            ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
+            int width = displayMetrics.getSize().getWidth();
+            int height = displayMetrics.getSize().getHeight();
+            tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+
+            // Wait and assert focus switch
+            waitAndAssertActivityTransitions(SingleTopActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
+            waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
+            LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                    transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
+                    transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+                    getLifecycleLog(), "Top activity must be switched on tap");
+
+            getLifecycleLog().clear();
+
+            // Tap on new display to switch the top activity
+            displayMetrics = getDisplayMetrics(newDisplay.mId);
+            width = displayMetrics.getSize().getWidth();
+            height = displayMetrics.getSize().getHeight();
+            tapOnDisplay(width / 2, height / 2, newDisplay.mId);
+
+            // Wait and assert focus switch
+            waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
+            waitAndAssertActivityTransitions(SingleTopActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
+            LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                    transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
+                    transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+                    getLifecycleLog(), "Top activity must be switched on tap");
+        }
+    }
+
+    @Test
+    public void testTopPositionNotSwitchedToPip() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch first activity
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED),
+                "startPIP");
+
+        // Exit PiP
+        getLifecycleLog().clear();
+        pipActivity.finish();
+
+        waitAndAssertActivityStates(state(pipActivity, ON_DESTROY));
+        LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_STOP, ON_DESTROY), "finishPip");
+        LifecycleVerifier.assertEmptySequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                "finishPip");
+    }
+
+    @Test
+    public void testTopPositionForAlwaysFocusableActivityInPip() throws Exception {
+        assumeTrue(supportsPip());
+
+        // Launch first activity
+        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+
+        // 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));
+
+        // Launch always focusable activity into PiP
+        getLifecycleLog().clear();
+        final Activity alwaysFocusableActivity = mAlwaysFocusableActivityTestRule.launchActivity(
+                new Intent());
+        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),
+                transition(AlwaysFocusablePipActivity.class, ON_TOP_POSITION_GAINED)),
+                "launchAlwaysFocusablePip");
+
+        // Finish always focusable activity - top position should go back to fullscreen activity
+        getLifecycleLog().clear();
+        alwaysFocusableActivity.finish();
+
+        waitAndAssertActivityStates(state(alwaysFocusableActivity, ON_DESTROY),
+                state(activity, ON_TOP_POSITION_GAINED), state(pipActivity, ON_PAUSE));
+        LifecycleVerifier.assertResumeToDestroySequence(AlwaysFocusablePipActivity.class,
+                getLifecycleLog(), true /* includeCallbacks */);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+                transition(AlwaysFocusablePipActivity.class, ON_TOP_POSITION_LOST),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+                "finishAlwaysFocusablePip");
+    }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityStarterTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityStarterTests.java
index b439756..e6aa995 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityStarterTests.java
@@ -16,19 +16,51 @@
 
 package android.server.am.lifecycle;
 
-import android.app.Activity;
-import android.content.Intent;
-import android.server.am.lifecycle.ActivityLifecycleClientTestBase;
-import org.junit.Test;
-
+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.am.ActivityManagerState.STATE_DESTROYED;
 import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.Components.ALIAS_TEST_ACTIVITY;
+import static android.server.am.Components.TEST_ACTIVITY;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.server.am.ActivityLauncher;
+
+import org.junit.Test;
 
 /**
  * Build/Install/Run:
  *     atest CtsActivityManagerDeviceTestCases:ActivityStarterTests
  */
 public class ActivityStarterTests extends ActivityLifecycleClientTestBase {
+
+    private static final ComponentName STANDARD_ACTIVITY
+            = getComponentName(StandardActivity.class);
+    private static final ComponentName SECOND_STANDARD_ACTIVITY
+            = getComponentName(SecondStandardActivity.class);
+    private static final ComponentName SINGLE_TOP_ACTIVITY
+            = getComponentName(SingleTopActivity.class);
+    private static final ComponentName SINGLE_INSTANCE_ACTIVITY
+            = getComponentName(SingleInstanceActivity.class);
+    private static final ComponentName SINGLE_TASK_ACTIVITY
+            = getComponentName(SingleTaskActivity.class);
+    private static final ComponentName STANDARD_SINGLE_TOP_ACTIVITY
+            = getComponentName(StandardWithSingleTopActivity.class);
+    private static final ComponentName TEST_LAUNCHING_ACTIVITY
+            = getComponentName(TestLaunchingActivity.class);
+
     /**
      * Ensures that the following launch flag combination works when starting an activity which is
      * already running:
@@ -52,4 +84,398 @@
         final Activity secondLaunchActivity = mFirstActivityTestRule.launchActivity(intent);
         waitAndAssertActivityStates(state(secondLaunchActivity, ON_RESUME));
     }
+
+    /**
+     * This test case tests "standard" activity behavior.
+     * A first launched standard activity and a second launched standard activity
+     * must be in same task.
+     */
+    @Test
+    public void testLaunchStandardActivity() {
+        // Launch a standard activity.
+        launchActivity(STANDARD_ACTIVITY);
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+        final int instances = mAmWmState.getAmState().getActivityCountInTask(taskId, null);
+
+        // Launch a second standard activity.
+        launchActivity(SECOND_STANDARD_ACTIVITY);
+
+        // Make sure instances in task are increased.
+        assertEquals("instances of activity in task must be increased.", instances + 1,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, null));
+
+        // Make sure the stack for the second standard activity is front.
+        assertEquals("The stack for the second standard activity must be front.",
+                getActivityName(SECOND_STANDARD_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the standard activity and the second standard activity are in same task.
+        assertEquals("Activity must be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(SECOND_STANDARD_ACTIVITY).getTaskId());
+    }
+
+    /**
+     * This test case tests "single top" activity behavior.
+     * - A first launched standard activity and a second launched single top
+     * activity are in same task.
+     * - A new instance of single top activity is not created if task
+     * already has a its activity at the top of its task.
+     */
+    @Test
+    public void testLaunchSingleTopActivity() {
+        // Launch a standard activity.
+        launchActivity(STANDARD_ACTIVITY);
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a single top activity.
+        launchActivity(SINGLE_TOP_ACTIVITY);
+
+        final int instances = mAmWmState.getAmState().getActivityCountInTask(taskId, null);
+
+        // Make sure the single top activity is in focus.
+        mAmWmState.assertFocusedActivity(SINGLE_TOP_ACTIVITY + "must be focused Activity",
+                SINGLE_TOP_ACTIVITY);
+
+        // Make sure the stack for the single top activity is front.
+        assertEquals("The stack for the single top activity must be front.",
+                getActivityName(SINGLE_TOP_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the standard activity and the single top activity are in same task.
+        assertEquals("Two activities must be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(SINGLE_TOP_ACTIVITY).getTaskId());
+
+        // Launch a single top activity.
+        launchActivity(SINGLE_TOP_ACTIVITY);
+
+        // Make sure that instances of activity are not increased.
+        assertEquals("instances of activity must not be increased.", instances,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, null));
+    }
+
+    /**
+     * This test case tests "single instance" activity behavior.
+     * - A first launched standard activity and a second launched single instance
+     * activity are not in same task.
+     * - A single instance activity is always the single and only member of its task.
+     */
+    @Test
+    public void testLaunchSingleInstanceActivity() {
+        // Launch a standard activity.
+        launchActivity(STANDARD_ACTIVITY);
+
+        final int firstTaskId = mAmWmState.getAmState()
+                .getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a single instance activity
+        launchActivity(SINGLE_INSTANCE_ACTIVITY);
+
+        final int secondTaskId = mAmWmState.getAmState()
+                .getTaskByActivity(SINGLE_INSTANCE_ACTIVITY).getTaskId();
+
+        // Make sure the single instance activity is in focus.
+        mAmWmState.assertFocusedActivity(SINGLE_INSTANCE_ACTIVITY + "must be focused Activity",
+                SINGLE_INSTANCE_ACTIVITY);
+        // Make sure the single instance activity is front.
+        assertEquals("The stack for the single instance activity must be front.",
+                getActivityName(SINGLE_INSTANCE_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the standard activity and the test activity are not in same task.
+        assertNotEquals("Activity must be in different task.", firstTaskId, secondTaskId);
+
+        // Make sure the single instance activity is only member of its task.
+        assertEquals("Single instance activity is only member of its task", 1,
+                mAmWmState.getAmState().getActivityCountInTask(secondTaskId, null));
+    }
+
+    /**
+     * This test case tests "single task" activity behavior.
+     * - A first launched standard activity and a second launched single task activity
+     * are in same task.
+     * - Instance of single task activity is only one in its task.
+     */
+    @Test
+    public void testLaunchSingleTaskActivity() {
+        // Launch a standard activity.
+        launchActivity(STANDARD_ACTIVITY);
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a single task activity
+        launchActivity(SINGLE_TASK_ACTIVITY);
+
+        // Make sure the single task activity is in focus.
+        mAmWmState.assertFocusedActivity(SINGLE_TASK_ACTIVITY + "must be focused Activity",
+                SINGLE_TASK_ACTIVITY);
+
+        // Make sure the stack for the single task activity is front.
+        assertEquals("The stack for the single task activity must be front.",
+                getActivityName(SINGLE_TASK_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the test activity is in same task.
+        assertEquals("Activity must be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(SINGLE_TASK_ACTIVITY).getTaskId());
+
+        // Launch a second standard activity
+        launchActivity(SECOND_STANDARD_ACTIVITY);
+
+        // Launch a single task activity again.
+        launchActivity(SINGLE_TASK_ACTIVITY);
+        mAmWmState.waitForActivityState(SECOND_STANDARD_ACTIVITY, STATE_DESTROYED);
+
+        // 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,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, SINGLE_TASK_ACTIVITY));
+        // Make sure that instance of standard activity does not exists.
+        assertEquals("Instance of second standard activity must not exist.", 0,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY));
+
+    }
+
+    /**
+     * Tests that the existing task would be brought to top while launching alias activity or
+     * real activity without creating new activity instances, tasks, or stacks.
+     */
+    @Test
+    public void testLaunchAliasActivity() {
+        // Launch alias activity.
+        getLaunchActivityBuilder().setUseInstrumentation().setTargetActivity(ALIAS_TEST_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
+        final int stacks = mAmWmState.getAmState().getStackCounts();
+        final int taskId =
+                mAmWmState.getAmState().getTaskByActivity(ALIAS_TEST_ACTIVITY).getTaskId();
+
+        // Return to home and launch the alias activity again.
+        launchHomeActivity();
+        getLaunchActivityBuilder().setUseInstrumentation().setTargetActivity(ALIAS_TEST_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
+        assertEquals("Instance of the activity in its task must be only one", 1,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, ALIAS_TEST_ACTIVITY));
+        assertEquals("Stacks counts should not be increased.", stacks,
+                mAmWmState.getAmState().getStackCounts());
+
+        // Return to home and launch the real activity.
+        launchHomeActivity();
+        getLaunchActivityBuilder().setUseInstrumentation().setTargetActivity(TEST_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
+        assertEquals("Instance of the activity in its task must be only one", 1,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, ALIAS_TEST_ACTIVITY));
+        assertEquals("Stacks counts should not be increased.", stacks,
+                mAmWmState.getAmState().getStackCounts());
+    }
+
+    /**
+     * This test case tests behavior of activities launched with FLAG_ACTIVITY_NEW_TASK
+     * and FLAG_ACTIVITY_CLEAR_TASK.
+     * A first launched activity is finished, then a second activity is created if the
+     * second activity is launched with FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TASK.
+     */
+    @Test
+    public void testLaunchActivityWithFlagNewTaskAndClearTask() {
+        // Launch a standard activity with FLAG_ACTIVITY_NEW_TASK.
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK)
+                .execute();
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch Activity with FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_CLEAR_TASK.
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)
+                .execute();
+
+        mAmWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_DESTROYED);
+
+        // Make sure the number of instances for standard activity is one
+        // because previous standard activity to be finished due to FLAG_ACTIVITY_CLEAR_TASK.
+        assertEquals("Instance of activity must be one", 1,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, STANDARD_ACTIVITY));
+
+        // Make sure the stack for the standard activity is front.
+        assertEquals("The stack for the standard activity must be front.",
+                getActivityName(STANDARD_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+    }
+
+    /**
+     * This test case tests behavior of activity launched with FLAG_ACTIVITY_CLEAR_TOP.
+     * A top activity is finished when an activity is launched with FLAG_ACTIVITY_CLEAR_TOP.
+     */
+    @Test
+    public void testLaunchActivityWithFlagClearTop() {
+        // Launch a standard activity
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .execute();
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a second standard activity
+        getLaunchActivityBuilder()
+                .setTargetActivity(SECOND_STANDARD_ACTIVITY)
+                .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+                .execute();
+
+        // Launch a standard activity again with CLEAR_TOP_FLAG
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_CLEAR_TOP)
+                .execute();
+
+        mAmWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_RESUMED);
+
+        // Make sure that the standard activity is in focus.
+        mAmWmState.assertFocusedActivity(STANDARD_ACTIVITY + "must be focused Activity",
+                STANDARD_ACTIVITY);
+
+        // Make sure the stack for the standard activity is front.
+        assertEquals("The stack for the standard activity must be front.",
+                getActivityName(STANDARD_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the activity is not in same task.
+        assertEquals("Activity must be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
+        // Make sure the second standard activity is finished.
+        assertEquals("Instance of second standard activity must not exist", 0,
+                mAmWmState.getAmState().getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY));
+    }
+
+    /**
+     * This test case tests behavior of activity launched with FLAG_ACTIVITY_SINGLE_TOP.
+     * A single top activity must not be launched if it is already running at the top
+     * of the history stack.
+     */
+    @Test
+    public void testLaunchActivityWithFlagSingleTop() {
+        // Launch a standard activity
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .execute();
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a standard activity with SINGLE_TOP flag.
+        // This standard activity launches a standard activity with single top flag.
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_SINGLE_TOP_ACTIVITY)
+                .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_SINGLE_TOP)
+                .execute();
+
+        mAmWmState.waitForActivityState(STANDARD_SINGLE_TOP_ACTIVITY, STATE_RESUMED);
+
+        // Make sure that a new instance is not created if it is already running at the top
+        // of the history stack.
+        assertEquals("Multiple single top activities must not be created.", 1,
+                mAmWmState.getAmState()
+                        .getActivityCountInTask(taskId, STANDARD_SINGLE_TOP_ACTIVITY));
+
+
+        // Make sure that activity is in focus.
+        mAmWmState.assertFocusedActivity(STANDARD_SINGLE_TOP_ACTIVITY + "must be focused Activity",
+                STANDARD_SINGLE_TOP_ACTIVITY);
+        // Make sure the stack for the single top activity is front.
+        assertEquals("The stack for the single top activity must be front.",
+                getActivityName(STANDARD_SINGLE_TOP_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+
+        // Make sure the standard activity and the single top activity are in same task.
+        assertEquals("Activity must be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(STANDARD_SINGLE_TOP_ACTIVITY)
+                        .getTaskId());
+    }
+
+    /**
+     * This test case tests behavior of activity launched with FLAG_ACTIVITY_MULTIPLE_TASK
+     * and FLAG_ACTIVITY_NEW_TASK.
+     * Second launched activity which is launched with FLAG_ACTIVITY_MULTIPLE_TASK and
+     * FLAG_ACTIVITY_NEW_TASK is created in new task/stack. So, a first launched activity
+     * and a second launched activity are in different task/stack.
+     */
+    @Test
+    public void testActivityWithFlagMultipleTaskAndNewTask() {
+        // Launch a standard activity
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .execute();
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+
+        // Launch a standard activity with FLAG_ACTIVITY_MULTIPLE_TASK|FLAG_ACTIVITY_NEW_TASK
+        getLaunchActivityBuilder()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setLaunchingActivity(TEST_LAUNCHING_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .execute();
+
+        // Make sure the stack for the standard activity is front.
+        assertEquals("The stack for the standard activity must be front.",
+                getActivityName(STANDARD_ACTIVITY),
+                mAmWmState.getAmState().getTopActivityName(0));
+        // Make sure the first standard activity and second standard activity are not in same task.
+        assertNotEquals("Activity must not be in same task.", taskId,
+                mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
+    }
+
+    // Test activity
+    public static class StandardActivity extends Activity {
+    }
+
+    // Test activity
+    public static class SecondStandardActivity extends Activity {
+    }
+
+    // Test activity
+    public static class StandardWithSingleTopActivity extends Activity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final Intent intent = new Intent().setComponent(
+                    new ComponentName(this /* context */, getClass()));
+            intent.addFlags(FLAG_ACTIVITY_SINGLE_TOP);
+            startActivity(intent);
+        }
+    }
+
+    // Test activity
+    public static class SingleTopActivity extends Activity {
+    }
+
+    // Test activity
+    public static class SingleTaskActivity extends Activity {
+    }
+
+    // Test activity
+    public static class SingleInstanceActivity extends Activity {
+    }
+
+    // Launching activity
+    public static class TestLaunchingActivity extends Activity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final Intent intent = getIntent();
+            if (intent != null) {
+                ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
+            }
+        }
+
+        @Override
+        protected void onNewIntent(Intent intent) {
+            super.onNewIntent(intent);
+            ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
+        }
+    }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
index da8296c..32c4f8b 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -1,3 +1,19 @@
+/*
+ * 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.server.am.lifecycle;
 
 import static android.server.am.StateLogger.log;
@@ -35,7 +51,9 @@
         ON_ACTIVITY_RESULT,
         ON_POST_CREATE,
         ON_NEW_INTENT,
-        ON_MULTI_WINDOW_MODE_CHANGED
+        ON_MULTI_WINDOW_MODE_CHANGED,
+        ON_TOP_POSITION_GAINED,
+        ON_TOP_POSITION_LOST
     }
 
     /**
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
index 24b384b..25374e5 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
@@ -1,3 +1,19 @@
+/*
+ * 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.server.am.lifecycle;
 
 import static org.junit.Assert.fail;
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
index 8c87439..1f35d1f 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
@@ -1,3 +1,19 @@
+/*
+ * 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.server.am.lifecycle;
 
 import static android.server.am.StateLogger.log;
@@ -9,15 +25,19 @@
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
 import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.server.am.lifecycle.LifecycleLog.ActivityCallback;
 import android.util.Pair;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -31,35 +51,50 @@
         log("Observed sequence: " + observedTransitions);
         final String errorMessage = errorDuringTransition(activityClass, "launch");
 
-        final List<ActivityCallback> expectedTransitions = includeCallbacks
-                ? Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME)
-                : Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+        final List<ActivityCallback> expectedTransitions = getLaunchSequence(includeCallbacks);
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
+    public static List<ActivityCallback> getLaunchSequence(boolean includeCallbacks) {
+        return includeCallbacks
+                ? Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                ON_TOP_POSITION_GAINED)
+                : Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+    }
+
     static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
             Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog,
             boolean launchingIsTranslucent) {
+        assertLaunchSequence(launchingActivity, existingActivity, lifecycleLog,
+                launchingIsTranslucent, false /* includingCallbacks */);
+    }
+
+    static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
+            Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog,
+            boolean launchingIsTranslucent, boolean includingCallbacks) {
         final List<Pair<String, ActivityCallback>> observedTransitions = lifecycleLog.getLog();
         log("Observed sequence: " + observedTransitions);
         final String errorMessage = errorDuringTransition(launchingActivity, "launch");
 
-        final List<Pair<String, ActivityCallback>> expectedTransitions;
-        if (launchingIsTranslucent) {
-            expectedTransitions = Arrays.asList(
-                    transition(existingActivity, ON_PAUSE),
-                    transition(launchingActivity, PRE_ON_CREATE),
-                    transition(launchingActivity, ON_CREATE),
-                    transition(launchingActivity, ON_START),
-                    transition(launchingActivity, ON_RESUME));
-        } else {
-            expectedTransitions = Arrays.asList(
-                    transition(existingActivity, ON_PAUSE),
-                    transition(launchingActivity, PRE_ON_CREATE),
-                    transition(launchingActivity, ON_CREATE),
-                    transition(launchingActivity, ON_START),
-                    transition(launchingActivity, ON_RESUME),
-                    transition(existingActivity, ON_STOP));
+        final List<Pair<String, ActivityCallback>> expectedTransitions = new ArrayList<>();
+        // First top position will be lost
+        if (includingCallbacks) {
+            expectedTransitions.add(transition(existingActivity, ON_TOP_POSITION_LOST));
+        }
+        // Next the existing activity is paused and the next one is launched
+        expectedTransitions.add(transition(existingActivity, ON_PAUSE));
+        expectedTransitions.add(transition(launchingActivity, PRE_ON_CREATE));
+        expectedTransitions.add(transition(launchingActivity, ON_CREATE));
+        expectedTransitions.add(transition(launchingActivity, ON_START));
+        if (includingCallbacks) {
+            expectedTransitions.add(transition(launchingActivity, ON_POST_CREATE));
+        }
+        expectedTransitions.add(transition(launchingActivity, ON_RESUME));
+        if (includingCallbacks) {
+            expectedTransitions.add(transition(launchingActivity, ON_TOP_POSITION_GAINED));
+        }
+        if (!launchingIsTranslucent) {
+            expectedTransitions.add(transition(existingActivity, ON_STOP));
         }
 
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
@@ -67,13 +102,27 @@
 
     static void assertLaunchAndStopSequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog) {
+        assertLaunchAndStopSequence(activityClass, lifecycleLog, false /* includeCallbacks */,
+                false /* onTop */);
+    }
+
+    static void assertLaunchAndStopSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, boolean includeCallbacks, boolean onTop) {
         final List<ActivityCallback> observedTransitions =
                 lifecycleLog.getActivityLog(activityClass);
         log("Observed sequence: " + observedTransitions);
         final String errorMessage = errorDuringTransition(activityClass, "launch and stop");
 
-        final List<ActivityCallback> expectedTransitions =
-                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP);
+        final List<ActivityCallback> expectedTransitions = new ArrayList<>();
+        expectedTransitions.addAll(Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START));
+        if (includeCallbacks) {
+            expectedTransitions.add(ON_POST_CREATE);
+        }
+        expectedTransitions.add(ON_RESUME);
+        if (includeCallbacks && onTop) {
+            expectedTransitions.addAll(Arrays.asList(ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST));
+        }
+        expectedTransitions.addAll(Arrays.asList(ON_PAUSE, ON_STOP));
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
@@ -101,7 +150,7 @@
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
-    static void assertRestartAndPauseSequence(Class<? extends Activity> activityClass,
+    static void assertRestartAndResumeSequence(Class<? extends Activity> activityClass,
                                               LifecycleLog lifecycleLog) {
         final List<ActivityCallback> observedTransitions =
                 lifecycleLog.getActivityLog(activityClass);
@@ -109,11 +158,11 @@
         final String errorMessage = errorDuringTransition(activityClass, "restart and pause");
 
         final List<ActivityCallback> expectedTransitions =
-                Arrays.asList(ON_RESTART, ON_START, ON_RESUME, ON_PAUSE);
+                Arrays.asList(ON_RESTART, ON_START, ON_RESUME);
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
-    static void assertRecreateAndPauseSequence(Class<? extends Activity> activityClass,
+    static void assertRecreateAndResumeSequence(Class<? extends Activity> activityClass,
                                               LifecycleLog lifecycleLog) {
         final List<ActivityCallback> observedTransitions =
                 lifecycleLog.getActivityLog(activityClass);
@@ -121,7 +170,7 @@
         final String errorMessage = errorDuringTransition(activityClass, "recreateA  and pause");
 
         final List<ActivityCallback> expectedTransitions =
-                Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE);
+                Arrays.asList(ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
@@ -137,6 +186,67 @@
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
+    static void assertResumeToDestroySequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        assertResumeToDestroySequence(activityClass, lifecycleLog, false /* includeCallbacks */);
+    }
+
+    static void assertResumeToDestroySequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, boolean includeCallbacks) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
+
+        final List<ActivityCallback> expectedTransitions =
+                getResumeToDestroySequence(includeCallbacks);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static List<ActivityCallback> getResumeToDestroySequence(boolean includeCallbacks) {
+        return includeCallbacks
+                ? Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY)
+                : Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY);
+    }
+
+    static void assertResumeToStopSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        assertResumeToStopSequence(activityClass, lifecycleLog, false /* includeCallbacks */);
+    }
+
+    static void assertResumeToStopSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, boolean includeCallbacks) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "resumed to stopped");
+
+        final List<ActivityCallback> expectedTransitions = new ArrayList<>();
+        if (includeCallbacks) {
+            expectedTransitions.add(ON_TOP_POSITION_LOST);
+        }
+        expectedTransitions.add(ON_PAUSE);
+        expectedTransitions.add(ON_STOP);
+
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertStopToResumeSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, boolean includeCallbacks) {
+        final List<ActivityCallback> observedTransitions =
+                lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "stopped to resumed");
+
+        final List<ActivityCallback> expectedTransitions = new ArrayList<>(
+                Arrays.asList(ON_RESTART, ON_START, ON_RESUME));
+        if (includeCallbacks) {
+            expectedTransitions.add(ON_TOP_POSITION_GAINED);
+        }
+
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
     static void assertRelaunchSequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, ActivityCallback startState) {
         final List<ActivityCallback> expectedTransitions;
@@ -146,9 +256,16 @@
         } else if (startState == ON_STOP) {
             expectedTransitions = Arrays.asList(
                     ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP);
-        } else {
+        } else if (startState == ON_RESUME) {
             expectedTransitions = Arrays.asList(
                     ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
+        } else if (startState == ON_TOP_POSITION_GAINED) {
+            // Looks like we're tracking the callbacks here
+            expectedTransitions = Arrays.asList(
+                    ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE,
+                    ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED);
+        } else {
+            throw new IllegalArgumentException("Start state not supported: " + startState);
         }
         assertSequence(activityClass, lifecycleLog, expectedTransitions, "relaunch");
     }
@@ -163,6 +280,63 @@
         assertEquals(errorMessage, expectedTransitions, observedTransitions);
     }
 
+    /**
+     * Assert that the observed transitions of a particular activity happened in expected order.
+     * There may be more observed transitions than in the expected array, only their order matters.
+     *
+     * Use this method when there is no need to verify the entire sequence, only that some
+     * transitions happened after another.
+     */
+    static void assertOrder(LifecycleLog lifecycleLog, Class<? extends Activity> activityClass,
+            List<LifecycleLog.ActivityCallback> expectedTransitionsOrder, String transition) {
+        List<Pair<String, LifecycleLog.ActivityCallback>> expectedTransitions = new ArrayList<>();
+        for (LifecycleLog.ActivityCallback callback : expectedTransitionsOrder) {
+            expectedTransitions.add(transition(activityClass, callback));
+        }
+        assertOrder(lifecycleLog, expectedTransitions, transition);
+    }
+
+    /**
+     * Assert that the observed transitions happened in expected order. There may be more observed
+     * transitions than in the expected array, only their order matters.
+     *
+     * Use this method when there is no need to verify the entire sequence, only that some
+     * transitions happened after another.
+     */
+    static void assertOrder(LifecycleLog lifecycleLog,
+            List<Pair<String, LifecycleLog.ActivityCallback>> expectedTransitionsOrder,
+            String transition) {
+        final List<Pair<String, LifecycleLog.ActivityCallback>> observedTransitions =
+                lifecycleLog.getLog();
+        int nextObservedPosition = 0;
+        for (Pair<String, LifecycleLog.ActivityCallback> expectedTransition
+                : expectedTransitionsOrder) {
+            while (nextObservedPosition < observedTransitions.size()
+                    && !observedTransitions.get(nextObservedPosition).equals(expectedTransition))
+            {
+                nextObservedPosition++;
+            }
+            if (nextObservedPosition == observedTransitions.size()) {
+                fail("Transition wasn't observed in the expected position: " + expectedTransition
+                        + " during transition: " + transition);
+            }
+        }
+    }
+
+    /**
+     * Assert that a transition was observer, no particular order.
+     */
+    static void assertTransitionObserved(LifecycleLog lifecycleLog,
+            Pair<String, LifecycleLog.ActivityCallback> expectedTransition, String transition) {
+        assertTrue("Transition " + expectedTransition + " must be observed during " + transition,
+                lifecycleLog.getLog().contains(expectedTransition));
+    }
+
+    static void assertEmptySequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog, String transition) {
+        assertSequence(activityClass, lifecycleLog, new ArrayList<>(), transition);
+    }
+
     /** Assert that a lifecycle sequence matches one of the possible variants. */
     static void assertSequenceMatchesOneOf(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, List<List<ActivityCallback>> expectedTransitions,
@@ -184,8 +358,17 @@
                 oneOfExpectedSequencesObserved);
     }
 
-    private static Pair<String, ActivityCallback> transition(
-            Class<? extends Activity> activityClass, ActivityCallback state) {
+    /** Assert the entire sequence for all involved activities. */
+    static void assertEntireSequence(
+            List<Pair<String, LifecycleLog.ActivityCallback>> expectedTransitions,
+            LifecycleLog lifecycleLog, String message) {
+        final List<Pair<String, LifecycleLog.ActivityCallback>> observedTransitions =
+                lifecycleLog.getLog();
+        assertEquals(message, expectedTransitions, observedTransitions);
+    }
+
+    static Pair<String, ActivityCallback> transition(Class<? extends Activity> activityClass,
+            ActivityCallback state) {
         return new Pair<>(activityClass.getCanonicalName(), state);
     }
 
diff --git a/tests/framework/base/activitymanager/testsdk25/Android.mk b/tests/framework/base/activitymanager/testsdk25/Android.mk
index e55d5f0..c9275f1 100644
--- a/tests/framework/base/activitymanager/testsdk25/Android.mk
+++ b/tests/framework/base/activitymanager/testsdk25/Android.mk
@@ -27,7 +27,8 @@
 LOCAL_SDK_VERSION := 25
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test
+    android-support-test \
+    cts-amwm-util
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
index 9bbec9e..adc7f10 100644
--- a/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
@@ -17,6 +17,8 @@
 <configuration description="Config for CTS ActivityManager SDK 25 compatibility test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- These tests require targeting API 25 which does not support instant apps -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <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/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java b/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
index ce83d59..f2c2477 100644
--- a/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
+++ b/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
@@ -16,6 +16,7 @@
 
 package android.server.am;
 
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
@@ -48,10 +49,9 @@
                     false /* initialTouchMode */, false /* launchActivity */);
 
     @Test
-    public void testMaxAspectRatioPreOActivity() throws Exception {
-        runAspectRatioTest(mSdk25MaxAspectRatioActivity, actual -> {
-            if (aspectRatioLessThanEqual(actual, MAX_PRE_O_ASPECT_RATIO)) return;
-            fail("actual=" + actual + " is greater than expected=" + MAX_PRE_O_ASPECT_RATIO);
+    public void testMaxAspectRatioPreOActivity() {
+        runAspectRatioTest(mSdk25MaxAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, lessThanOrEqualToInexact(MAX_PRE_O_ASPECT_RATIO));
         });
     }
 }
diff --git a/tests/framework/base/activitymanager/testsdk28/Android.mk b/tests/framework/base/activitymanager/testsdk28/Android.mk
new file mode 100644
index 0000000..e1bb155
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/Android.mk
@@ -0,0 +1,35 @@
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceSdk28TestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../src/android/server/am/AspectRatioTestsBase.java
+
+LOCAL_SDK_VERSION := 28
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    cts-amwm-util
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml b/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml
new file mode 100644
index 0000000..9ba1dc0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.server.cts.am.testsdk28">
+
+    <uses-sdk android:targetSdkVersion="28" />
+
+    <application android:label="CtsActivityManagerDeviceSdk28TestCases">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="android.server.am.AspectRatioSdk28Tests$Sdk28MinAspectRatioActivity"
+            android:label="Sdk28MinAspectRatioActivity"
+            android:exported="true"
+            android:resizeableActivity="false" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.server.cts.am.testsdk28" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml b/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml
new file mode 100644
index 0000000..0040fce
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<configuration description="Config for CTS ActivityManager SDK 28 compatibility test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <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="CtsActivityManagerDeviceSdk28TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.cts.am.testsdk28" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java b/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java
new file mode 100644
index 0000000..7eaa464
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk28/src/android/server/am/AspectRatioSdk28Tests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.am;
+
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+import static org.junit.Assert.assertThat;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Run: atest AspectRatioSdk28Tests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioSdk28Tests extends AspectRatioTestsBase {
+
+    // The minimum supported device aspect ratio for pre-Q devices.
+    private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
+
+    // The minimum supported device aspect ratio for watches.
+    private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
+
+
+    // Test target activity that has targetSdk="28" and resizeableActivity="false".
+    public static class Sdk28MinAspectRatioActivity extends Activity {
+    }
+
+    @Rule
+    public ActivityTestRule<?> mSdk28MinAspectRatioActivity = new ActivityTestRule<>(
+            Sdk28MinAspectRatioActivity.class, false /* initialTouchMode */,
+            false /* launchActivity */);
+
+    @Test
+    public void testMaxAspectRatioPreQActivity() {
+        boolean isWatch = InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(FEATURE_WATCH);
+        float minAspectRatio = isWatch ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
+
+        runAspectRatioTest(mSdk28MinAspectRatioActivity, (actual, displayId) -> {
+            assertThat(actual, greaterThanOrEqualToInexact(minAspectRatio));
+        });
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
index f503f98..80cf75a 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -32,6 +32,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.lessThan;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -41,6 +42,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
 import android.graphics.Rect;
@@ -51,6 +53,7 @@
 import android.server.am.WindowManagerState.WindowStack;
 import android.server.am.WindowManagerState.WindowState;
 import android.server.am.WindowManagerState.WindowTask;
+import android.util.SparseArray;
 
 import java.util.Arrays;
 import java.util.List;
@@ -58,6 +61,7 @@
 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;
 
 /**
@@ -211,7 +215,22 @@
         logE("***Waiting for debugger window failed");
     }
 
-    boolean waitForHomeActivityVisible() {
+    <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;
+    }
+
+    void waitForHomeActivityVisible() {
         ComponentName homeActivity = mAmState.getHomeActivityName();
         // Sometimes this function is called before we know what Home Activity is
         if (homeActivity == null) {
@@ -221,31 +240,26 @@
         }
         assertNotNull("homeActivity should not be null", homeActivity);
         waitForValidState(homeActivity);
-        return mAmState.isHomeActivityVisible();
     }
 
-    /**
-     * @return true if recents activity is visible. Devices without recents will return false
-     */
-    boolean waitForRecentsActivityVisible() {
+    void waitForRecentsActivityVisible() {
         if (mAmState.isHomeRecentsComponent()) {
-            return waitForHomeActivityVisible();
+            waitForHomeActivityVisible();
+        } else {
+            waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
+                    "***Waiting for recents activity to be visible...");
         }
-
-        waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
-                "***Waiting for recents activity to be visible...");
-        return mAmState.isRecentsActivityVisible();
     }
 
     void waitForKeyguardShowingAndNotOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
-                        && !state.getKeyguardControllerState().keyguardOccluded,
+                        && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "***Waiting for Keyguard showing...");
     }
 
     void waitForKeyguardShowingAndOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
-                        && state.getKeyguardControllerState().keyguardOccluded,
+                        && state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "***Waiting for Keyguard showing and occluded...");
     }
 
@@ -292,6 +306,13 @@
                 "***Waiting for Activity State: " + activityState);
     }
 
+    public void waitForActivityRemoved(ComponentName activityName) {
+        waitForWithAmState((state) -> !state.containsActivity(activityName),
+                "Waiting for activity to be removed");
+        waitForWithWmState((state) -> !state.containsWindow(getWindowName(activityName)),
+                "Waiting for activity window to be gone");
+    }
+
     @Deprecated
     void waitForFocusedStack(int stackId) {
         waitForWithAmState(state -> state.getFocusedStackId() == stackId,
@@ -307,17 +328,33 @@
                 "***Waiting for focused stack...");
     }
 
-    void waitForAppTransitionIdle() {
-        waitForWithWmState(
-                state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
-                "***Waiting for app transition idle...");
+    void waitForPendingActivityContain(ComponentName activity) {
+        waitForWithAmState(state -> state.pendingActivityContain(activity),
+                "***Waiting for activity in pending list...");
     }
 
-    void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
+    void waitForAppTransitionIdleOnDisplay(int displayId) {
+        waitForWithWmState(
+                state -> WindowManagerState.APP_STATE_IDLE.equals(
+                        state.getDisplay(displayId).getAppTransitionState()),
+                "***Waiting for app transition idle on Display " + displayId + " ...");
+    }
+
+
+    void waitAndAssertNavBarShownOnDisplay(int displayId) {
+        waitForWithWmState(
+                state -> state.getAndAssertSingleNavBarWindowOnDisplay(displayId) != null,
+                "***Waiting for navigation bar #" + displayId + " show...");
+        final WindowState ws = getWmState().getAndAssertSingleNavBarWindowOnDisplay(displayId);
+
+        assertNotNull(ws);
+    }
+
+    public void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
         waitFor((amState, wmState) -> waitCondition.test(amState), message);
     }
 
-    void waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
+    public void waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
         waitFor((amState, wmState) -> waitCondition.test(wmState), message);
     }
 
@@ -371,7 +408,7 @@
             return true;
         }
         final int resumedActivitiesCount = mAmState.getResumedActivitiesCount();
-        if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) {
+        if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount < 1) {
             logAlways("***resumedActivitiesCount=" + resumedActivitiesCount);
             return true;
         }
@@ -493,7 +530,7 @@
         return false;
     }
 
-    ActivityManagerState getAmState() {
+    public ActivityManagerState getAmState() {
         return mAmState;
     }
 
@@ -503,9 +540,10 @@
 
     void assertSanity() {
         assertThat("Must have stacks", mAmState.getStackCount(), greaterThan(0));
+        // TODO: Update when keyguard will be shown on multiple displays
         if (!mAmState.getKeyguardControllerState().keyguardShowing) {
-            assertEquals("There should be one and only one resumed activity in the system.",
-                    1, mAmState.getResumedActivitiesCount());
+            assertThat("There should be at least one resumed activity in the system.",
+                    mAmState.getResumedActivitiesCount(), greaterThanOrEqualTo(1));
         }
         assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
 
@@ -531,18 +569,17 @@
         assertFalse(msg, mWmState.containsStack(windowingMode, activityType));
     }
 
-    void assertFrontStack(String msg, int stackId) {
-        assertEquals(msg, stackId, mAmState.getFrontStackId(DEFAULT_DISPLAY));
-        assertEquals(msg, stackId, mWmState.getFrontStackId(DEFAULT_DISPLAY));
+    public void assertFrontStack(String msg, int windowingMode, int activityType) {
+        assertFrontStackOnDisplay(msg, windowingMode, activityType, DEFAULT_DISPLAY);
     }
 
-    void assertFrontStack(String msg, int windowingMode, int activityType) {
+    void assertFrontStackOnDisplay(String msg, int windowingMode, int activityType, int displayId) {
         if (windowingMode != WINDOWING_MODE_UNDEFINED) {
             assertEquals(msg, windowingMode,
-                    mAmState.getFrontStackWindowingMode(DEFAULT_DISPLAY));
+                    mAmState.getFrontStackWindowingMode(displayId));
         }
         if (activityType != ACTIVITY_TYPE_UNDEFINED) {
-            assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY));
+            assertEquals(msg, activityType, mAmState.getFrontStackActivityType(displayId));
         }
     }
 
@@ -551,7 +588,6 @@
         assertEquals(msg, activityType, mWmState.getFrontStackActivityType(DEFAULT_DISPLAY));
     }
 
-    @Deprecated
     void assertFocusedStack(String msg, int stackId) {
         assertEquals(msg, stackId, mAmState.getFocusedStackId());
     }
@@ -565,23 +601,42 @@
         }
     }
 
-    void assertFocusedActivity(final String msg, final ComponentName activityName) {
+    public void assertFocusedActivity(final String msg, final ComponentName activityName) {
         final String activityComponentName = getActivityName(activityName);
         assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
         assertEquals(msg, activityComponentName, mWmState.getFocusedApp());
     }
 
+    void assertFocusedAppOnDisplay(final String msg, final ComponentName activityName,
+            final int displayId) {
+        final String activityComponentName = getActivityName(activityName);
+        assertEquals(msg, activityComponentName, mWmState.getDisplay(displayId).getFocusedApp());
+    }
+
     void assertNotFocusedActivity(String msg, ComponentName activityName) {
         assertNotEquals(msg, mAmState.getFocusedActivity(), getActivityName(activityName));
         assertNotEquals(msg, mWmState.getFocusedApp(), getActivityName(activityName));
     }
 
     public void assertResumedActivity(final String msg, final ComponentName activityName) {
-        assertEquals(msg, getActivityName(activityName), mAmState.getResumedActivity());
+        assertEquals(msg, getActivityName(activityName),
+                mAmState.getFocusedActivity());
+    }
+
+    /** Asserts that each display has correct resumed activity. */
+    public void assertResumedActivities(final String msg,
+            SparseArray<ComponentName> resumedActivities) {
+        for (int i = 0; i < resumedActivities.size(); i++) {
+            final int displayId = resumedActivities.keyAt(i);
+            final ComponentName activityComponent = resumedActivities.valueAt(i);
+            assertEquals("Error asserting resumed activity on display " + displayId + ": " + msg,
+                    activityComponent != null ? getActivityName(activityComponent) : null,
+                    mAmState.getResumedActivityOnDisplay(displayId));
+        }
     }
 
     void assertNotResumedActivity(String msg, ComponentName activityName) {
-        assertNotEquals(msg, mAmState.getResumedActivity(), getActivityName(activityName));
+        assertNotEquals(msg, mAmState.getFocusedActivity(), getActivityName(activityName));
     }
 
     void assertFocusedWindow(String msg, String windowName) {
@@ -592,19 +647,26 @@
         assertNotEquals(msg, mWmState.getFocusedWindow(), windowName);
     }
 
-    void assertFrontWindow(String msg, String windowName) {
-        assertEquals(msg, windowName, mWmState.getFrontWindow());
+    void assertNotExist(final ComponentName activityName) {
+        final String windowName = getWindowName(activityName);
+        assertFalse("Activity=" + getActivityName(activityName) + " must NOT exist.",
+                mAmState.containsActivity(activityName));
+        assertFalse("Window=" + windowName + " must NOT exits.",
+                mWmState.containsWindow(windowName));
     }
 
     public void assertVisibility(final ComponentName activityName, final boolean visible) {
         final String windowName = getWindowName(activityName);
-        final boolean activityVisible = mAmState.isActivityVisible(activityName);
-        final boolean windowVisible = mWmState.isWindowVisible(windowName);
+        // Check existence of activity and window.
+        assertTrue("Activity=" + getActivityName(activityName) + " must exist.",
+                mAmState.containsActivity(activityName));
+        assertTrue("Window=" + windowName + " must exist.", mWmState.containsWindow(windowName));
 
+        // Check visibility of activity and window.
         assertEquals("Activity=" + getActivityName(activityName) + " must" + (visible ? "" : " NOT")
-                + " be visible.", visible, activityVisible);
+                + " be visible.", visible, mAmState.isActivityVisible(activityName));
         assertEquals("Window=" + windowName + " must" + (visible ? "" : " NOT") + " be visible.",
-                visible, windowVisible);
+                visible, mWmState.isWindowVisible(windowName));
     }
 
     void assertHomeActivityVisible(boolean visible) {
@@ -630,14 +692,14 @@
         assertTrue("Keyguard is showing",
                 getAmState().getKeyguardControllerState().keyguardShowing);
         assertTrue("Keyguard is occluded",
-                getAmState().getKeyguardControllerState().keyguardOccluded);
+                getAmState().getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY));
     }
 
     public void assertKeyguardShowingAndNotOccluded() {
         assertTrue("Keyguard is showing",
                 getAmState().getKeyguardControllerState().keyguardShowing);
         assertFalse("Keyguard is not occluded",
-                getAmState().getKeyguardControllerState().keyguardOccluded);
+                getAmState().getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY));
     }
 
     public void assertKeyguardGone() {
@@ -645,6 +707,10 @@
                 getAmState().getKeyguardControllerState().keyguardShowing);
     }
 
+    public void assumePendingActivityContain(ComponentName activity) {
+        assumeTrue(getAmState().pendingActivityContain(activity));
+    }
+
     boolean taskListsInAmAndWmAreEqual() {
         for (ActivityStack aStack : mAmState.getStacks()) {
             final int stackId = aStack.mStackId;
@@ -872,6 +938,22 @@
         assertTrue(windowName + "is visible", getWmState().isWindowVisible(windowName));
     }
 
+    void waitAndAssertImeWindowShownOnDisplay(int displayId) {
+        final WindowManagerState.WindowState imeWinState = waitForValidProduct(
+                this::getImeWindowState, "IME window",
+                w -> 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,
+                imeWinState.getDisplayId());
+    }
+
+    WindowManagerState.WindowState getImeWindowState() {
+        final WindowManagerState wmState = getWmState();
+        wmState.computeState();
+        return wmState.getInputMethodWindowState();
+    }
+
     boolean isScreenPortrait() {
         final int displayId = mAmState.getStandardStackByWindowingMode(
             WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).mDisplayId;
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityLauncher.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityLauncher.java
index 9542dbb..14ee43f 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityLauncher.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityLauncher.java
@@ -22,6 +22,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.server.am.Components.TEST_ACTIVITY;
 
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -29,12 +30,14 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.server.am.CommandSession.LaunchInjector;
+import android.server.am.TestJournalProvider.TestJournalContainer;
 import android.text.TextUtils;
 import android.util.Log;
 
 /** Utility class which contains common code for launching activities. */
 public class ActivityLauncher {
-    private static final String TAG = ActivityLauncher.class.getSimpleName();
+    public static final String TAG = ActivityLauncher.class.getSimpleName();
 
     /** Key for boolean extra, indicates whether it should launch an activity. */
     public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
@@ -90,10 +93,40 @@
      * it's always written to logs.
      */
     public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
+    /**
+     * Key for boolean extra, indicates the result of
+     * {@link ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     */
+    public static final String KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY =
+            "is_activity_start_allowed_on_display";
+    /**
+     * Key for boolean extra, indicates a security exception is caught when launching activity by
+     * {@link #launchActivityFromExtras}.
+     */
+    private static final String KEY_CAUGHT_SECURITY_EXCEPTION = "caught_security_exception";
+    /**
+     * Key for int extra with target activity type where activity should be launched as.
+     */
+    public static final String KEY_ACTIVITY_TYPE = "activity_type";
+    /**
+     * Key for int extra with intent flags which are used for launching an activity.
+     */
+    public static final String KEY_INTENT_FLAGS = "intent_flags";
+    /**
+     * Key for boolean extra, indicates if need to automatically applies
+     * {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to
+     * the intent when target display id set.
+     */
+    public static final String KEY_MULTIPLE_INSTANCES = "multiple_instances";
 
 
     /** Perform an activity launch configured by provided extras. */
     public static void launchActivityFromExtras(final Context context, Bundle extras) {
+        launchActivityFromExtras(context, extras, null /* launchInjector */);
+    }
+
+    public static void launchActivityFromExtras(final Context context, Bundle extras,
+            LaunchInjector launchInjector) {
         if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
             return;
         }
@@ -129,7 +162,23 @@
         if (displayId != -1) {
             options = ActivityOptions.makeBasic();
             options.setLaunchDisplayId(displayId);
-            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+            if (extras.getBoolean(KEY_MULTIPLE_INSTANCES, true)) {
+                newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+            }
+        }
+        if (launchInjector != null) {
+            launchInjector.setupIntent(newIntent);
+        }
+        final int activityType = extras.getInt(KEY_ACTIVITY_TYPE, -1);
+        if (activityType != -1) {
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchActivityType(activityType);
+        }
+        final int intentFlags = extras.getInt(KEY_INTENT_FLAGS); // 0 if key doesn't exist.
+        if (intentFlags != 0) {
+            newIntent.addFlags(intentFlags);
         }
         final Bundle optionsBundle = options != null ? options.toBundle() : null;
 
@@ -141,21 +190,26 @@
                 // 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, 0, optionsBundle);
+                        newIntent, PendingIntent.FLAG_CANCEL_CURRENT, optionsBundle);
                 pendingIntent.send();
             } else {
                 launchContext.startActivity(newIntent, optionsBundle);
             }
         } catch (SecurityException e) {
-            Log.e(TAG, "SecurityException launching activity");
+            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);
             }
-            Log.e(TAG, "SecurityException launching activity");
+            // 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");
@@ -164,4 +218,27 @@
             }
         }
     }
+
+    public static void checkActivityStartOnDisplay(Context context, int displayId,
+            ComponentName componentName) {
+        final Intent launchIntent = new Intent(Intent.ACTION_VIEW).setComponent(componentName);
+
+        final boolean isAllowed = context.getSystemService(ActivityManager.class)
+                .isActivityStartAllowedOnDisplay(context, displayId, launchIntent);
+        Log.i(TAG, "isActivityStartAllowedOnDisplay=" + isAllowed);
+        TestJournalProvider.putExtras(context, TAG, bundle -> {
+            bundle.putBoolean(KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY, isAllowed);
+        });
+    }
+
+    public static void handleSecurityException(Context context, Exception e) {
+        Log.e(TAG, "SecurityException launching activity: " + e);
+        TestJournalProvider.putExtras(context, TAG, bundle -> {
+            bundle.putBoolean(KEY_CAUGHT_SECURITY_EXCEPTION, true);
+        });
+    }
+
+    static boolean hasCaughtSecurityException() {
+        return TestJournalContainer.get(TAG).extras.containsKey(KEY_CAUGHT_SECURITY_EXCEPTION);
+    }
 }
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
index d50e622..adc0ea9 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -33,6 +33,10 @@
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 
+import androidx.annotation.Nullable;
+
+import android.util.SparseArray;
+
 import com.android.server.am.nano.ActivityDisplayProto;
 import com.android.server.am.nano.ActivityManagerServiceDumpActivitiesProto;
 import com.android.server.am.nano.ActivityRecordProto;
@@ -67,10 +71,12 @@
     // Stacks in z-order with the top most at the front of the list, starting with primary display.
     private final List<ActivityStack> mStacks = new ArrayList<>();
     private KeyguardControllerState mKeyguardControllerState;
-    private int mFocusedStackId = -1;
+    private final List<String> mPendingActivities = new ArrayList<>();
+    private int mTopFocusedStackId = -1;
     private Boolean mIsHomeRecentsComponent;
-    private String mResumedActivityRecord = null;
-    private final List<String> mResumedActivities = new ArrayList<>();
+    private String mTopResumedActivityRecord = null;
+    final List<String> mResumedActivitiesInStacks = new ArrayList<>();
+    final List<String> mResumedActivitiesInDisplays = new ArrayList<>();
 
     void computeState() {
         computeState(DUMP_MODE_ACTIVITIES);
@@ -109,20 +115,21 @@
                         + new String(dump, StandardCharsets.UTF_8), ex);
             }
 
-            retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
-                    || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
+            retry = mStacks.isEmpty() || mTopFocusedStackId == -1
+                    || (mTopResumedActivityRecord == null || mResumedActivitiesInStacks.isEmpty())
+                    && !mKeyguardControllerState.keyguardShowing;
         } while (retry && retriesLeft-- > 0);
 
         if (mStacks.isEmpty()) {
             logE("No stacks found...");
         }
-        if (mFocusedStackId == -1) {
+        if (mTopFocusedStackId == -1) {
             logE("No focused stack found...");
         }
-        if (mResumedActivityRecord == null) {
+        if (mTopResumedActivityRecord == null) {
             logE("No focused activity found...");
         }
-        if (mResumedActivities.isEmpty()) {
+        if (mResumedActivitiesInStacks.isEmpty()) {
             logE("No resumed activities found...");
         }
     }
@@ -156,21 +163,27 @@
             mDisplays.add(new ActivityDisplay(activityDisplay, this));
         }
         mKeyguardControllerState = new KeyguardControllerState(state.keyguardController);
-        mFocusedStackId = state.focusedStackId;
+        mTopFocusedStackId = state.focusedStackId;
         if (state.resumedActivity != null) {
-            mResumedActivityRecord = state.resumedActivity.title;
+            mTopResumedActivityRecord = state.resumedActivity.title;
         }
         mIsHomeRecentsComponent = new Boolean(state.isHomeRecentsComponent);
+
+        for (int i = 0; i < state.pendingActivities.length; i++) {
+            mPendingActivities.add(state.pendingActivities[i].title);
+        }
     }
 
     private void reset() {
         mDisplays.clear();
         mStacks.clear();
-        mFocusedStackId = -1;
-        mResumedActivityRecord = null;
-        mResumedActivities.clear();
+        mTopFocusedStackId = -1;
+        mTopResumedActivityRecord = null;
+        mResumedActivitiesInStacks.clear();
+        mResumedActivitiesInDisplays.clear();
         mKeyguardControllerState = null;
         mIsHomeRecentsComponent = null;
+        mPendingActivities.clear();
     }
 
     /**
@@ -204,30 +217,43 @@
         return getDisplay(displayId).mStacks.get(0).getWindowingMode();
     }
 
+    public String getTopActivityName(int displayId) {
+        if (!getDisplay(displayId).mStacks.isEmpty()) {
+            final ActivityStack topStack = getDisplay(displayId).mStacks.get(0);
+            if (!topStack.mTasks.isEmpty()) {
+                final ActivityTask topTask = topStack.mTasks.get(0);
+                if (!topTask.mActivities.isEmpty()) {
+                    return topTask.mActivities.get(0).name;
+                }
+            }
+        }
+        return null;
+    }
+
     int getFocusedStackId() {
-        return mFocusedStackId;
+        return mTopFocusedStackId;
     }
 
     int getFocusedStackActivityType() {
-        final ActivityStack stack = getStackById(mFocusedStackId);
+        final ActivityStack stack = getStackById(mTopFocusedStackId);
         return stack != null ? stack.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
     }
 
     int getFocusedStackWindowingMode() {
-        final ActivityStack stack = getStackById(mFocusedStackId);
+        final ActivityStack stack = getStackById(mTopFocusedStackId);
         return stack != null ? stack.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
     }
 
     String getFocusedActivity() {
-        return mResumedActivityRecord;
-    }
-
-    String getResumedActivity() {
-        return mResumedActivities.get(0);
+        return mTopResumedActivityRecord;
     }
 
     int getResumedActivitiesCount() {
-        return mResumedActivities.size();
+        return mResumedActivitiesInStacks.size();
+    }
+
+    String getResumedActivityOnDisplay(int displayId) {
+        return getDisplay(displayId).mResumedActivity;
     }
 
     public KeyguardControllerState getKeyguardControllerState() {
@@ -283,7 +309,7 @@
         return null;
     }
 
-    int getStandardTaskCountByWindowingMode(int windowingMode) {
+    public int getStandardTaskCountByWindowingMode(int windowingMode) {
         int count = 0;
         for (ActivityStack stack : mStacks) {
             if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
@@ -340,7 +366,7 @@
         return new ArrayList<>(mDisplays);
     }
 
-    List<ActivityStack> getStacks() {
+    public List<ActivityStack> getStacks() {
         return new ArrayList<>(mStacks);
     }
 
@@ -348,7 +374,11 @@
         return mStacks.size();
     }
 
-    boolean containsActivity(ComponentName activityName) {
+    int getDisplayCount() {
+        return mDisplays.size();
+    }
+
+    public boolean containsActivity(ComponentName activityName) {
         final String fullName = getActivityName(activityName);
         for (ActivityStack stack : mStacks) {
             for (ActivityTask task : stack.mTasks) {
@@ -362,6 +392,24 @@
         return false;
     }
 
+    public boolean containsNoneOf(Iterable<ComponentName> activityNames) {
+        for (ComponentName activityName : activityNames) {
+            String fullName = getActivityName(activityName);
+
+            for (ActivityStack stack : mStacks) {
+                for (ActivityTask task : stack.mTasks) {
+                    for (Activity activity : task.mActivities) {
+                        if (activity.name.equals(fullName)) {
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
     boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
         final String fullName = getActivityName(activityName);
         for (ActivityStack stack : mStacks) {
@@ -510,12 +558,12 @@
                 : null;
     }
 
-    int getStackIdByActivity(ComponentName activityName) {
+    public int getStackIdByActivity(ComponentName activityName) {
         final ActivityTask task = getTaskByActivity(activityName);
         return  (task == null) ? INVALID_STACK_ID : task.mStackId;
     }
 
-    ActivityTask getTaskByActivity(ComponentName activityName) {
+    public ActivityTask getTaskByActivity(ComponentName activityName) {
         return getTaskByActivityInternal(getActivityName(activityName), WINDOWING_MODE_UNDEFINED);
     }
 
@@ -539,29 +587,95 @@
         return null;
     }
 
-    static class ActivityDisplay extends ActivityContainer {
+    /**
+     * Get the number of activities in the task, with the option to count only activities with
+     * specific name.
+     * @param taskId Id of the task where we're looking for the number of activities.
+     * @param activityName Optional name of the activity we're interested in.
+     * @return Number of all activities in the task if activityName is {@code null}, otherwise will
+     *         report number of activities that have specified name.
+     */
+    public int getActivityCountInTask(int taskId, @Nullable ComponentName activityName) {
+        // If activityName is null, count all activities in the task.
+        // Otherwise count activities that have specified name.
+        for (ActivityStack stack : mStacks) {
+            for (ActivityTask task : stack.mTasks) {
+                if (task.mTaskId == taskId) {
+                    if (activityName == null) {
+                        return task.mActivities.size();
+                    }
+                    final String fullName = getActivityName(activityName);
+                    int count = 0;
+                    for (Activity activity : task.mActivities) {
+                        if (activity.name.equals(fullName)) {
+                            count++;
+                        }
+                    }
+                    return count;
+                }
+            }
+        }
+        return 0;
+    }
 
-        int mId;
+    public int getStackCounts() {
+        return mStacks.size();
+    }
+
+    boolean pendingActivityContain(ComponentName activityName) {
+        return mPendingActivities.contains(getActivityName(activityName));
+    }
+
+    public static class ActivityDisplay extends ActivityContainer {
+
+        public int mId;
         ArrayList<ActivityStack> mStacks = new ArrayList<>();
+        int mFocusedStackId;
+        String mResumedActivity;
+        boolean mSingleTaskInstance;
 
         ActivityDisplay(ActivityDisplayProto proto, ActivityManagerState amState) {
             super(proto.configurationContainer);
             mId = proto.id;
+            mFocusedStackId = proto.focusedStackId;
+            mSingleTaskInstance = proto.singleTaskInstance;
+            if (proto.resumedActivity != null) {
+                mResumedActivity = proto.resumedActivity.title;
+                amState.mResumedActivitiesInDisplays.add(mResumedActivity);
+            }
             for (int i = 0; i < proto.stacks.length; i++) {
                 ActivityStack activityStack = new ActivityStack(proto.stacks[i]);
                 mStacks.add(activityStack);
                 // Also update activity manager state
                 amState.mStacks.add(activityStack);
                 if (activityStack.mResumedActivity != null) {
-                    amState.mResumedActivities.add(activityStack.mResumedActivity);
+                    amState.mResumedActivitiesInStacks.add(activityStack.mResumedActivity);
                 }
             }
         }
+
+        boolean containsActivity(ComponentName activityName) {
+            final String fullName = getActivityName(activityName);
+            for (ActivityStack stack : mStacks) {
+                for (ActivityTask task : stack.mTasks) {
+                    for (Activity activity : task.mActivities) {
+                        if (activity.name.equals(fullName)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+        
+        public ArrayList<ActivityStack> getStacks() {
+            return mStacks;
+        }
     }
 
-    static class ActivityStack extends ActivityContainer {
+    public static class ActivityStack extends ActivityContainer {
 
-        int mDisplayId;
+        public int mDisplayId;
         int mStackId;
         String mResumedActivity;
         ArrayList<ActivityTask> mTasks = new ArrayList<>();
@@ -604,7 +718,7 @@
             return null;
         }
 
-        List<ActivityTask> getTasks() {
+        public List<ActivityTask> getTasks() {
             return new ArrayList<>(mTasks);
         }
 
@@ -616,9 +730,17 @@
             }
             return null;
         }
+
+        public int getStackId() {
+            return mStackId;
+        }
+
+        public String getResumedActivity() {
+            return mResumedActivity;
+        }
     }
 
-    static class ActivityTask extends ActivityContainer {
+    public static class ActivityTask extends ActivityContainer {
 
         int mTaskId;
         int mStackId;
@@ -651,16 +773,12 @@
             return mResizeMode;
         }
 
-        /**
-         * @return whether this task contains the given activity.
-         */
-        public boolean containsActivity(String activityName) {
-            for (Activity activity : mActivities) {
-                if (activity.name.equals(activityName)) {
-                    return true;
-                }
-            }
-            return false;
+        public int getTaskId() {
+            return mTaskId;
+        }
+
+        public ArrayList<Activity> getActivities() {
+            return mActivities;
         }
     }
 
@@ -684,6 +802,14 @@
             }
             translucent = proto.translucent;
         }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getState() {
+            return state;
+        }
     }
 
     static abstract class ActivityContainer extends WindowManagerState.ConfigurationContainer {
@@ -696,7 +822,7 @@
             super(proto);
         }
 
-        Rect getBounds() {
+        public Rect getBounds() {
             return mBounds;
         }
 
@@ -716,13 +842,23 @@
     static class KeyguardControllerState {
 
         boolean keyguardShowing = false;
-        boolean keyguardOccluded = false;
+        SparseArray<Boolean> mKeyguardOccludedStates = new SparseArray<>();
 
         KeyguardControllerState(KeyguardControllerProto proto) {
             if (proto != null) {
                 keyguardShowing = proto.keyguardShowing;
-                keyguardOccluded = proto.keyguardOccluded;
+                for (int i = 0;  i < proto.keyguardOccludedStates.length; i++) {
+                    mKeyguardOccludedStates.append(proto.keyguardOccludedStates[i].displayId,
+                            proto.keyguardOccludedStates[i].keyguardOccluded);
+                }
             }
         }
+
+        boolean isKeyguardOccluded(int displayId) {
+            if (mKeyguardOccludedStates.get(displayId) != null) {
+                return mKeyguardOccludedStates.get(displayId);
+            }
+            return false;
+        }
     }
 }
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
index 59ea4ab..f429418 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -16,15 +16,19 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.Instrumentation.ActivityMonitor;
 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.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
@@ -33,9 +37,13 @@
 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+import static android.server.am.ActivityLauncher.KEY_ACTIVITY_TYPE;
 import static android.server.am.ActivityLauncher.KEY_DISPLAY_ID;
+import static android.server.am.ActivityLauncher.KEY_INTENT_FLAGS;
 import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
 import static android.server.am.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
+import static android.server.am.ActivityLauncher.KEY_MULTIPLE_INSTANCES;
 import static android.server.am.ActivityLauncher.KEY_MULTIPLE_TASK;
 import static android.server.am.ActivityLauncher.KEY_NEW_TASK;
 import static android.server.am.ActivityLauncher.KEY_RANDOM_DATA;
@@ -50,8 +58,18 @@
 import static android.server.am.ComponentNameUtils.getLogTag;
 import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
 import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
+import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
 import static android.server.am.Components.LAUNCHING_ACTIVITY;
+import static android.server.am.Components.PipActivity.ACTION_EXPAND_PIP;
+import static android.server.am.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
+import static android.server.am.Components.PipActivity.EXTRA_PIP_ORIENTATION;
+import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
+import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
 import static android.server.am.Components.TEST_ACTIVITY;
 import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logAlways;
@@ -64,9 +82,12 @@
 import static android.server.am.UiDeviceUtils.pressUnlockButton;
 import static android.server.am.UiDeviceUtils.pressWakeupButton;
 import static android.server.am.UiDeviceUtils.waitForDeviceIdle;
+import static android.support.test.InstrumentationRegistry.getContext;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Surface.ROTATION_0;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -76,17 +97,42 @@
 import android.accessibilityservice.AccessibilityService;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.server.am.CommandSession.ActivityCallback;
+import android.server.am.CommandSession.ActivitySession;
+import android.server.am.CommandSession.ConfigInfo;
+import android.server.am.CommandSession.LaunchInjector;
+import android.server.am.CommandSession.LaunchProxy;
+import android.server.am.CommandSession.SizeInfo;
+import android.server.am.TestJournalProvider.TestJournalContainer;
 import android.server.am.settings.SettingsSession;
 import android.support.test.InstrumentationRegistry;
-
 import android.support.test.rule.ActivityTestRule;
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -98,61 +144,53 @@
 import org.junit.runners.model.Statement;
 
 import java.io.IOException;
+import java.util.ArrayList;
 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.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 public abstract class ActivityManagerTestBase {
     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
+    // Use one of the test tags as a separator
+    private static final int EVENT_LOG_SEPARATOR_TAG = 42;
 
     protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
             ACTIVITY_TYPE_UNDEFINED
     };
 
-    private static final String TASK_ID_PREFIX = "taskId";
+    private static final String TEST_PACKAGE = "android.server.am";
+    private static final String SECOND_TEST_PACKAGE = "android.server.am.second";
+    private static final String THIRD_TEST_PACKAGE = "android.server.am.third";
+    private static final List<String> TEST_PACKAGES;
 
-    private static final String AM_STACK_LIST = "am stack list";
-
-    private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am";
-    private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
-            = "am force-stop android.server.am.second";
-    private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
-            = "am force-stop android.server.am.third";
+    static {
+        final List<String> testPackages = new ArrayList<>(3);
+        testPackages.add(TEST_PACKAGE);
+        testPackages.add(SECOND_TEST_PACKAGE);
+        testPackages.add(THIRD_TEST_PACKAGE);
+        testPackages.add("android.server.cts.am");
+        TEST_PACKAGES = Collections.unmodifiableList(testPackages);
+    }
 
     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
 
-    private static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT =
-            "am stack move-top-activity-to-pinned-stack %1d 0 0 500 500";
-
-    private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
-    private static final String AM_RESIZE_STACK = "am stack resize ";
-
-    static final String AM_MOVE_TASK = "am stack move-task ";
-
-    private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
-
     private static final String LOCK_CREDENTIAL = "1234";
 
-    // TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
-    // Shell command to finish {@link #BROADCAST_RECEIVER_ACTIVITY}.
-    static final String FINISH_ACTIVITY_BROADCAST = "am broadcast -a "
-            + ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_FINISH_BROADCAST + " true";
-
     private static final int UI_MODE_TYPE_MASK = 0x0f;
     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
 
@@ -162,6 +200,15 @@
 
     protected Context mContext;
     protected ActivityManager mAm;
+    protected ActivityTaskManager mAtm;
+
+    /**
+     * Callable to clear launch params for all test packages.
+     */
+    private final Callable<Void> mClearLaunchParamsCallable = () -> {
+        mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
+        return null;
+    };
 
     @Rule
     public final ActivityTestRule<SideActivity> mSideActivityRule =
@@ -170,7 +217,7 @@
 
     /**
      * @return the am command to start the given activity with the following extra key/value pairs.
-     *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
+     * {@param keyValuePairs} must be a list of arguments defining each key/value extra.
      */
     // TODO: Make this more generic, for instance accepting flags or extras of other types.
     protected static String getAmStartCmd(final ComponentName activityName,
@@ -226,26 +273,153 @@
         return "am start --activity-task-on-home -n " + getActivityName(activityName);
     }
 
-    protected static String getMoveToPinnedStackCommand(int stackId) {
-        return String.format(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT, stackId);
+    protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
+
+    public ActivityAndWindowManagersState getAmWmState() {
+        return mAmWmState;
     }
 
-    protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
+    protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
+
+    /**
+     * Helper class to process test actions by broadcast.
+     */
+    protected class BroadcastActionTrigger {
+
+        private Intent createIntentWithAction(String broadcastAction) {
+            return new Intent(broadcastAction)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+
+        void doAction(String broadcastAction) {
+            mContext.sendBroadcast(createIntentWithAction(broadcastAction));
+        }
+
+        void finishBroadcastReceiverActivity() {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(EXTRA_FINISH_BROADCAST, true));
+        }
+
+        void launchActivityNewTask(String launchComponent) {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(KEY_LAUNCH_ACTIVITY, true)
+                    .putExtra(KEY_NEW_TASK, true)
+                    .putExtra(KEY_TARGET_COMPONENT, launchComponent));
+        }
+
+        void moveTopTaskToBack() {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true));
+        }
+
+        void requestOrientation(int orientation) {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation));
+        }
+
+        void dismissKeyguardByFlag() {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(EXTRA_DISMISS_KEYGUARD, true));
+        }
+
+        void dismissKeyguardByMethod() {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
+                    .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
+        }
+
+        void expandPipWithAspectRatio(String extraNum, String extraDenom) {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP)
+                    .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum)
+                    .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom));
+        }
+
+        void requestOrientationForPip(int orientation) {
+            mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
+                    .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation)));
+        }
+    }
+
+    /**
+     * Helper class to launch / close test activity by instrumentation way.
+     */
+    protected class TestActivitySession<T extends Activity> implements AutoCloseable {
+        private T mTestActivity;
+        boolean mFinishAfterClose;
+        private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
+        private static final int WAIT_SLICE = 50;
+
+        void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) {
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                final Bundle bundle = ActivityOptions.makeBasic()
+                        .setLaunchDisplayId(displayId).toBundle();
+                final ActivityMonitor monitor = InstrumentationRegistry.getInstrumentation()
+                        .addMonitor((String) null, null, false);
+                mContext.startActivity(new Intent(mContext, activityClass)
+                        .addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
+                // Wait for activity launch with timeout.
+                mTestActivity = (T) monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
+                assertNotNull(mTestActivity);
+                // Check activity is launched and resumed.
+                final ComponentName testActivityName = mTestActivity.getComponentName();
+                waitAndAssertTopResumedActivity(testActivityName, displayId,
+                        "Activity must be resumed");
+            });
+        }
+
+        void finishCurrentActivityNoWait() {
+            if (mTestActivity != null) {
+                mTestActivity.finishAndRemoveTask();
+                mTestActivity = null;
+            }
+        }
+
+        void runOnMainSyncAndWait(Runnable runnable) {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
+
+        void runOnMainAndAssertWithTimeout(@NonNull BooleanSupplier condition, long timeoutMs,
+                String message) {
+            final AtomicBoolean result = new AtomicBoolean();
+            final long expiredTime = System.currentTimeMillis() + timeoutMs;
+            while (!result.get()) {
+                if (System.currentTimeMillis() >= expiredTime) {
+                    fail(message);
+                }
+                runOnMainSyncAndWait(() -> {
+                    if (condition.getAsBoolean()) {
+                        result.set(true);
+                    }
+                });
+                SystemClock.sleep(WAIT_SLICE);
+            }
+        }
+
+        T getActivity() {
+            return mTestActivity;
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (mTestActivity != null && mFinishAfterClose) {
+                mTestActivity.finishAndRemoveTask();
+            }
+        }
+    }
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
         mAm = mContext.getSystemService(ActivityManager.class);
-
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
-                mContext.getPackageName(), android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
-                mContext.getPackageName(), android.Manifest.permission.ACTIVITY_EMBEDDING);
+        mAtm = mContext.getSystemService(ActivityTaskManager.class);
 
         pressWakeupButton();
         pressUnlockButton();
         pressHomeButton();
         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+        // Clear launch params for all test packages to make sure each test is run in a clean state.
+        SystemUtil.callWithShellPermissionIdentity(mClearLaunchParamsCallable);
     }
 
     @After
@@ -254,19 +428,66 @@
         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
         // might be asynchronous and could interrupt the stack cleanup process if executed first.
         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
-        executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
-        executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
-        executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
+        stopTestPackage(TEST_PACKAGE);
+        stopTestPackage(SECOND_TEST_PACKAGE);
+        stopTestPackage(THIRD_TEST_PACKAGE);
         pressHomeButton();
+
+    }
+
+    protected void moveTopActivityToPinnedStack(int stackId) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500))
+        );
+    }
+
+    protected void startActivityOnDisplay(int displayId, ComponentName component) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
+
+        mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setComponent(component), options.toBundle());
+    }
+
+    protected boolean noHomeScreen() {
+        try {
+            return mContext.getResources().getBoolean(
+                    Resources.getSystem().getIdentifier("config_noHomeScreen", "bool",
+                            "android"));
+        } catch (Resources.NotFoundException e) {
+            // Assume there's a home screen.
+            return false;
+        }
+    }
+
+    protected void tapOnDisplay(int x, int y, int displayId) {
+        final long downTime = SystemClock.uptimeMillis();
+        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId);
+
+        final long upTime = SystemClock.uptimeMillis();
+        injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
+    }
+
+    private static void injectMotion(long downTime, long eventTime, int action,
+            int x, int y, int displayId) {
+        final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
+                x, y, 0 /* metaState */);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        event.setDisplayId(displayId);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().injectInputEvent(
+                event, true /* sync */);
     }
 
     protected void removeStacksWithActivityTypes(int... activityTypes) {
-        mAm.removeStacksWithActivityTypes(activityTypes);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.removeStacksWithActivityTypes(activityTypes));
         waitForIdle();
     }
 
     protected void removeStacksInWindowingModes(int... windowingModes) {
-        mAm.removeStacksInWindowingModes(windowingModes);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.removeStacksInWindowingModes(windowingModes)
+        );
         waitForIdle();
     }
 
@@ -301,25 +522,6 @@
         mAmWmState.waitForValidState(activityName);
     }
 
-    /**
-     * Starts an activity in a new stack.
-     * @return the stack id of the newly created stack.
-     */
-    @Deprecated
-    protected int launchActivityInNewDynamicStack(ComponentName activityName) {
-        HashSet<Integer> stackIds = getStackIds();
-        executeShellCommand("am stack start " + DEFAULT_DISPLAY + " "
-                + getActivityName(activityName));
-        HashSet<Integer> newStackIds = getStackIds();
-        newStackIds.removeAll(stackIds);
-        if (newStackIds.isEmpty()) {
-            return INVALID_STACK_ID;
-        } else {
-            assertTrue(newStackIds.size() == 1);
-            return newStackIds.iterator().next();
-        }
-    }
-
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -335,6 +537,20 @@
         return stackIds;
     }
 
+    /** Returns the stack that contains the provided task. */
+    protected ActivityManagerState.ActivityStack getStackForTaskId(int taskId) {
+        mAmWmState.computeState(true);
+        final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
+        for (ActivityManagerState.ActivityStack stack : stacks) {
+            for (ActivityManagerState.ActivityTask task : stack.mTasks) {
+                if (task.mTaskId == taskId) {
+                    return stack;
+                }
+            }
+        }
+        return null;
+    }
+
     protected void launchHomeActivity() {
         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
         mAmWmState.waitForHomeActivityVisible();
@@ -349,6 +565,15 @@
                 .build());
     }
 
+    protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode,
+            int displayId, final String... keyValuePairs) {
+        executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)
+                + " --windowingMode " + windowingMode);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
             String... keyValuePairs) {
         launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs);
@@ -361,7 +586,7 @@
     }
 
     /**
-     * Launches {@param  activityName} into split-screen primary windowing mode and also makes
+     * Launches {@param activityName} into split-screen primary windowing mode and also makes
      * the recents activity visible to the side of it.
      * NOTE: Recents view may be combined with home screen on some devices, so using this to wait
      * for Recents only makes sense when {@link ActivityManagerState#isHomeRecentsComponent()} is
@@ -373,40 +598,48 @@
 
     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName,
             int createMode) {
-        launchActivity(activityName);
-        final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
-        mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
-                false /* animate */, null /* initialBounds */, true /* showRecents */);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(activityName);
+            final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
+            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode,
+                    true /* onTop */, false /* animate */,
+                    null /* initialBounds */, true /* showRecents */);
 
-        mAmWmState.waitForValidState(
-                new WaitForValidActivityState.Builder(activityName)
-                        .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
-                        .setActivityType(ACTIVITY_TYPE_STANDARD)
-                        .build());
-        mAmWmState.waitForRecentsActivityVisible();
+            mAmWmState.waitForValidState(
+                    new WaitForValidActivityState.Builder(activityName)
+                            .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                            .setActivityType(ACTIVITY_TYPE_STANDARD)
+                            .build());
+            mAmWmState.waitForRecentsActivityVisible();
+        });
     }
 
     public void moveTaskToPrimarySplitScreen(int taskId) {
-        moveTaskToPrimarySplitScreen(taskId, false /* launchSideActivityIfNeeded */);
+        moveTaskToPrimarySplitScreen(taskId, false /* showRecents */);
     }
 
     /**
      * Moves the device into split-screen with the specified task into the primary stack.
-     * @param taskId                        The id of the task to move into the primary stack.
-     * @param launchSideActivityIfNeeded    Whether a placeholder activity should be launched if no
-     *                                      recents activity is available.
+     * @param taskId             The id of the task to move into the primary stack.
+     * @param showSideActivity   Whether to show the Recents activity (or a placeholder activity in
+     *                           place of the Recents activity if home is the recents component)
      */
-    public void moveTaskToPrimarySplitScreen(int taskId, boolean launchSideActivityIfNeeded) {
-        mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
-                true /* onTop */, false /* animate */, null /* initialBounds */,
-                true /* showRecents */);
-        mAmWmState.waitForRecentsActivityVisible();
+    public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) {
+        final boolean isHomeRecentsComponent = mAmWmState.getAmState().isHomeRecentsComponent();
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
+                    SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
+                    false /* animate */,
+                    null /* initialBounds */, showSideActivity && !isHomeRecentsComponent);
+            mAmWmState.waitForRecentsActivityVisible();
 
-        if (mAmWmState.getAmState().isHomeRecentsComponent() && launchSideActivityIfNeeded) {
-            // Launch Placeholder Recents
-            final Activity recentsActivity = mSideActivityRule.launchActivity(new Intent());
-            mAmWmState.waitForActivityState(recentsActivity.getComponentName(), STATE_RESUMED);
-        }
+            if (isHomeRecentsComponent && showSideActivity) {
+                // Launch Placeholder Side Activity
+                final Activity sideActivity = mSideActivityRule.launchActivity(
+                        new Intent());
+                mAmWmState.waitForActivityState(sideActivity.getComponentName(), STATE_RESUMED);
+            }
+        });
     }
 
     /**
@@ -438,7 +671,8 @@
     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
         mAmWmState.computeState(activityName);
         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
-        mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */));
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
                 .setWindowingMode(windowingMode)
@@ -448,8 +682,8 @@
     protected void moveActivityToStack(ComponentName activityName, int stackId) {
         mAmWmState.computeState(activityName);
         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
-        final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
-        executeShellCommand(cmd);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.moveTaskToStack(taskId, stackId, true));
 
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setStackId(stackId)
@@ -460,36 +694,37 @@
             ComponentName activityName, int left, int top, int right, int bottom) {
         mAmWmState.computeState(activityName);
         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
-        final String cmd = "am task resize "
-                + taskId + " " + left + " " + top + " " + right + " " + bottom;
-        executeShellCommand(cmd);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
     }
 
     protected void resizeDockedStack(
             int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
-        executeShellCommand(AM_RESIZE_DOCKED_STACK
-                + "0 0 " + stackWidth + " " + stackHeight
-                + " 0 0 " + taskWidth + " " + taskHeight);
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight),
+                        new Rect(0, 0, taskWidth, taskHeight)));
     }
 
     protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
             int stackHeight) {
-        executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
-                stackTop, stackWidth, stackHeight));
+        SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizeStack(stackId,
+                new Rect(stackLeft, stackTop, stackWidth, stackHeight)));
     }
 
     protected void pressAppSwitchButtonAndWaitForRecents() {
         pressAppSwitchButton();
         mAmWmState.waitForRecentsActivityVisible();
-        mAmWmState.waitForAppTransitionIdle();
+        mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
     }
 
     // Utility method for debugging, not used directly here, but useful, so kept around.
     protected void printStacksAndTasks() {
-        String output = executeShellCommand(AM_STACK_LIST);
-        for (String line : output.split("\\n")) {
-            log(line);
-        }
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            final String output = mAtm.listAllStacks();
+            for (String line : output.split("\\n")) {
+                log(line);
+            }
+        });
     }
 
     protected boolean supportsVrMode() {
@@ -528,6 +763,35 @@
         return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
 
+    protected void waitAndAssertActivityState(ComponentName activityName,
+            String state, String message) {
+        mAmWmState.waitForActivityState(activityName, state);
+
+        assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName, state));
+    }
+
+    public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId,
+            String message) {
+        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.assertSanity();
+        mAmWmState.assertFocusedActivity(message, activityName);
+        assertTrue("Activity must be resumed",
+                mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED));
+        final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
+        ActivityManagerState.ActivityStack frontStackOnDisplay =
+                mAmWmState.getAmState().getStackById(frontStackId);
+        assertEquals("Resumed activity of front stack of the target display must match. " + message,
+                activityClassName, frontStackOnDisplay.mResumedActivity);
+        mAmWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId);
+        mAmWmState.assertVisibility(activityName, true /* visible */);
+    }
+    
     // TODO: Switch to using a feature flag, when available.
     protected static boolean isUiModeLockedToVrHeadset() {
         final String output = runCommandAndPrintOutput("dumpsys uimode");
@@ -557,12 +821,12 @@
     }
 
     protected boolean supportsSplitScreenMultiWindow() {
-        return ActivityManager.supportsSplitScreenMultiWindow(mContext);
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
     }
 
     protected boolean hasHomeScreen() {
         if (sHasHomeScreen == null) {
-            sHasHomeScreen = !executeShellCommand(AM_NO_HOME_SCREEN).startsWith("true");
+            sHasHomeScreen = !noHomeScreen();
         }
         return sHasHomeScreen;
     }
@@ -584,16 +848,10 @@
                 .hasSystemFeature(requiredFeature);
     }
 
-    protected boolean isDisplayOn() {
-        final String output = executeShellCommand("dumpsys power");
-        final Matcher matcher = sDisplayStatePattern.matcher(output);
-        if (matcher.find()) {
-            final String state = matcher.group(1);
-            log("power state=" + state);
-            return "ON".equals(state);
-        }
-        logAlways("power state :(");
-        return false;
+    protected static boolean isDisplayOn(int displayId) {
+        final DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+        final Display display = displayManager.getDisplay(displayId);
+        return display != null && display.getState() == Display.STATE_ON;
     }
 
     /**
@@ -637,17 +895,74 @@
         }
     }
 
+    /**
+     * HomeActivitySession is used to replace the default home component, so that you can use
+     * your preferred home for testing within the session. The original default home will be
+     * restored automatically afterward.
+     */
+    protected class HomeActivitySession implements AutoCloseable {
+        private PackageManager mPackageManager;
+        private ComponentName mOrigHome;
+        private ComponentName mSessionHome;
+
+        public HomeActivitySession(ComponentName sessionHome) {
+            mSessionHome = sessionHome;
+            mPackageManager = mContext.getPackageManager();
+
+            final Intent intent = new Intent(ACTION_MAIN);
+            intent.addCategory(CATEGORY_HOME);
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+            final ResolveInfo resolveInfo =
+                    mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY);
+            if (resolveInfo != null) {
+                mOrigHome = new ComponentName(resolveInfo.activityInfo.packageName,
+                        resolveInfo.activityInfo.name);
+            }
+
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
+                            COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+            setDefaultHome(mSessionHome);
+        }
+
+        @Override
+        public void close() {
+            SystemUtil.runWithShellPermissionIdentity(
+                    () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
+                            COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */));
+            if (mOrigHome != null) {
+                setDefaultHome(mOrigHome);
+            }
+        }
+
+        private void setDefaultHome(ComponentName componentName) {
+            executeShellCommand("cmd package set-home-activity --user "
+                    + android.os.Process.myUserHandle().getIdentifier() + " "
+                    + componentName.flattenToString());
+        }
+    }
+
     protected class LockScreenSession implements AutoCloseable {
         private static final boolean DEBUG = false;
 
         private final boolean mIsLockDisabled;
         private boolean mLockCredentialSet;
+        private boolean mRemoveActivitiesOnClose;
+
+        public static final int FLAG_REMOVE_ACTIVITIES_ON_CLOSE = 1;
 
         public LockScreenSession() {
+            this(0 /* flags */);
+        }
+
+        public LockScreenSession(int flags) {
             mIsLockDisabled = isLockDisabled();
             mLockCredentialSet = false;
             // Enable lock screen (swipe) by default.
             setLockDisabled(false);
+            if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) {
+                mRemoveActivitiesOnClose = true;
+            }
         }
 
         public LockScreenSession setLockCredential() {
@@ -657,9 +972,13 @@
         }
 
         public LockScreenSession enterAndConfirmLockCredential() {
-            waitForDeviceIdle(3000);
+            // 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);
 
-            runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
+            waitForDeviceIdle(3000);
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    InstrumentationRegistry.getInstrumentation().sendStringSync(LOCK_CREDENTIAL));
             pressEnterButton();
             return this;
         }
@@ -681,7 +1000,7 @@
             // puts the device to sleep, but kept around for clarity.
             InstrumentationRegistry.getInstrumentation().getUiAutomation().performGlobalAction(
                     AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
-            for (int retry = 1; isDisplayOn() && retry <= 5; retry++) {
+            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));
             }
@@ -698,18 +1017,26 @@
             return this;
         }
 
-        public LockScreenSession gotoKeyguard() {
+        public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) {
             if (DEBUG && isLockDisabled()) {
                 logE("LockScreenSession.gotoKeyguard() is called without lock enabled.");
             }
             sleepDevice();
             wakeUpDevice();
-            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            if (showWhenLockedActivities.length == 0) {
+                mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            } else {
+                mAmWmState.waitForValidState(showWhenLockedActivities);
+            }
             return this;
         }
 
         @Override
-        public void close() throws Exception {
+        public void close() {
+            if (mRemoveActivitiesOnClose) {
+                removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+            }
+
             setLockDisabled(mIsLockDisabled);
             if (mLockCredentialSet) {
                 removeLockCredential();
@@ -717,7 +1044,15 @@
 
             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
             // the stale credential.
+            // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected.
+            // LockScreenSession#close is always calls before stop all test activities,
+            // which could cause keyguard stay at occluded after wakeup.
+            // If Keyguard is occluded, press back key can close ShowWhenLocked activity.
             pressBackButton();
+
+            // If device is unlocked, there might have ShowWhenLocked activity runs on,
+            // use home key to clear all activity at foreground.
+            pressHomeButton();
             sleepDevice();
             wakeUpDevice();
             unlockDevice();
@@ -747,6 +1082,10 @@
     /** Helper class to save, set & wait, and restore rotation related preferences. */
     protected class RotationSession extends SettingsSession<Integer> {
         private final SettingsSession<Integer> mUserRotation;
+        private final HandlerThread mThread;
+        private final Handler mRunnableHandler;
+        private final SettingsObserver mRotationObserver;
+        private int mPreviousDegree;
 
         public RotationSession() throws Exception {
             // Save accelerometer_rotation preference.
@@ -755,23 +1094,82 @@
             mUserRotation = new SettingsSession<>(
                     Settings.System.getUriFor(Settings.System.USER_ROTATION),
                     Settings.System::getInt, Settings.System::putInt);
+
+            mThread = new HandlerThread("Observer_Thread");
+            mThread.start();
+            mRunnableHandler = new Handler(mThread.getLooper());
+            mRotationObserver = new SettingsObserver(mRunnableHandler);
+
+            mPreviousDegree = mUserRotation.get();
             // Disable accelerometer_rotation.
             super.set(0);
         }
 
         @Override
         public void set(@NonNull Integer value) throws Exception {
+            // 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) {
+                mRotationObserver.observe();
+            }
             mUserRotation.set(value);
+            mPreviousDegree = value;
+
+            if (waitSystemUI) {
+                waitForRotationNotified();
+            }
             // Wait for settling rotation.
             mAmWmState.waitForRotation(value);
+
+            if (waitSystemUI) {
+                mRotationObserver.stopObserver();
+            }
         }
 
         @Override
         public void close() throws Exception {
+            mThread.quitSafely();
             mUserRotation.close();
             // Restore accelerometer_rotation preference.
             super.close();
         }
+
+        private void waitForRotationNotified() {
+            for (int retry = 1; retry <= 5; retry++) {
+                if (mRotationObserver.notified) {
+                    return;
+                }
+                logAlways("waitForRotationNotified retry=" + retry);
+                SystemClock.sleep(500);
+            }
+            logE("waitForRotationNotified skip");
+        }
+
+        private class SettingsObserver extends ContentObserver {
+            boolean notified;
+
+            SettingsObserver(Handler handler) { super(handler); }
+
+            void observe() {
+                notified = false;
+                final ContentResolver resolver = mContext.getContentResolver();
+                resolver.registerContentObserver(Settings.System.getUriFor(
+                        Settings.System.USER_ROTATION), false, this);
+            }
+
+            void stopObserver() {
+                final ContentResolver resolver = mContext.getContentResolver();
+                resolver.unregisterContentObserver(this);
+            }
+
+            @Override
+            public void onChange(boolean selfChange) {
+                if (!selfChange) notified = true;
+            }
+        }
     }
 
     /**
@@ -784,10 +1182,10 @@
      * rotation, because there is no requirement that an Android device that supports both
      * orientations needs to support user rotation mode.
      *
-     * @param session the rotation session used to set user rotation
+     * @param session   the rotation session used to set user rotation
      * @param displayId the display ID to check rotation against
      * @return {@code true} if test device respects settings of locked user rotation mode;
-     *      {@code false} if not.
+     * {@code false} if not.
      */
     protected boolean supportsLockedUserRotation(RotationSession session, int displayId)
             throws Exception {
@@ -814,6 +1212,11 @@
         return INVALID_DEVICE_ROTATION;
     }
 
+    /** Empties the test journal so the following events won't be mixed-up with previous records. */
+    protected void separateTestJournal() {
+        TestJournalContainer.start();
+    }
+
     protected static String runCommandAndPrintOutput(String command) {
         final String output = executeShellCommand(command);
         log(output);
@@ -836,11 +1239,13 @@
     /**
      * Inserts a log separator so we can always find the starting point from where to evaluate
      * following logs.
+     *
      * @return Unique log separator.
      */
     protected LogSeparator separateLogs() {
         final LogSeparator logSeparator = new LogSeparator();
         executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
+        EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString);
         return logSeparator;
     }
 
@@ -872,6 +1277,29 @@
         return filteredResult;
     }
 
+    protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) {
+        List<Event> events = new ArrayList<>();
+
+        int[] searchTags = Arrays.copyOf(tags, tags.length + 1);
+        searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG;
+
+        try {
+            EventLog.readEvents(searchTags, events);
+        } catch (IOException e) {
+            fail("Could not read from event log." + e);
+        }
+
+        for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) {
+            Event event = itr.next();
+            itr.remove();
+            if (event.getTag() == EVENT_LOG_SEPARATOR_TAG &&
+                    logSeparator.mUniqueString.equals(event.getData())) {
+                break;
+            }
+        }
+        return events;
+    }
+
     /**
      * Base helper class for retrying validator success.
      */
@@ -906,331 +1334,218 @@
         }
     }
 
-    private static class ActivityLifecycleCountsValidator extends RetryValidator {
-        private final ComponentName mActivityName;
-        private final LogSeparator mLogSeparator;
-        private final int mCreateCount;
-        private final int mStartCount;
-        private final int mResumeCount;
-        private final int mPauseCount;
-        private final int mStopCount;
-        private final int mDestroyCount;
+    static class CountSpec<T> {
+        static final int DONT_CARE = Integer.MIN_VALUE;
+        static final int EQUALS = 1;
+        static final int GREATER_THAN = 2;
+        static final int LESS_THAN = 3;
 
-        ActivityLifecycleCountsValidator(ComponentName activityName, LogSeparator logSeparator,
-                int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
-                int destroyCount) {
-            mActivityName = activityName;
-            mLogSeparator = logSeparator;
-            mCreateCount = createCount;
-            mStartCount = startCount;
-            mResumeCount = resumeCount;
-            mPauseCount = pauseCount;
-            mStopCount = stopCount;
-            mDestroyCount = destroyCount;
+        final T mEvent;
+        final int mRule;
+        final int mCount;
+        final String mMessage;
+
+        CountSpec(T event, int rule, int count, String message) {
+            mEvent = event;
+            mRule = count == DONT_CARE ? DONT_CARE : rule;
+            mCount = count;
+            if (message != null) {
+                mMessage = message;
+            } else {
+                switch (rule) {
+                    case EQUALS:
+                        mMessage = event + " + must equal to " + count;
+                        break;
+                    case GREATER_THAN:
+                        mMessage = event + " + must be greater than " + count;
+                        break;
+                    case LESS_THAN:
+                        mMessage = event + " + must be less than " + count;
+                        break;
+                    default:
+                        mMessage = "Don't care";
+                }
+            }
         }
 
-        @Override
-        @Nullable
-        protected String validate() {
-            final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
-                    mActivityName, mLogSeparator);
-            if (lifecycleCounts.mCreateCount == mCreateCount
-                    && lifecycleCounts.mStartCount == mStartCount
-                    && lifecycleCounts.mResumeCount == mResumeCount
-                    && lifecycleCounts.mPauseCount == mPauseCount
-                    && lifecycleCounts.mStopCount == mStopCount
-                    && lifecycleCounts.mDestroyCount == mDestroyCount) {
-                return null;
+        /** @return {@code true} if the given value is satisfied the condition. */
+        boolean validate(int value) {
+            switch (mRule) {
+                case DONT_CARE:
+                    return true;
+                case EQUALS:
+                    return value == mCount;
+                case GREATER_THAN:
+                    return value > mCount;
+                case LESS_THAN:
+                    return value < mCount;
+                default:
             }
-            final String expected = IntStream.of(mCreateCount, mStopCount, mResumeCount,
-                    mPauseCount, mStopCount, mDestroyCount)
-                    .mapToObj(Integer::toString)
-                    .collect(Collectors.joining("/"));
-            return getActivityName(mActivityName) + " lifecycle count mismatched:"
-                    + " expected=" + expected
-                    + " actual=" + lifecycleCounts.counters();
+            throw new RuntimeException("Unknown CountSpec rule");
         }
     }
 
-    void assertActivityLifecycle(ComponentName activityName, boolean relaunched,
-            LogSeparator logSeparator) {
+    static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) {
+        return new CountSpec<>(event, rule, count, message);
+    }
+
+    static <T> CountSpec<T> countSpec(T event, int rule, int count) {
+        return new CountSpec<>(event, rule, count, null /* message */);
+    }
+
+    static void assertLifecycleCounts(ComponentName activityName, String message,
+            int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
+            int destroyCount, int configChangeCount) {
+        new ActivityLifecycleCounts(activityName).assertCountWithRetry(
+                message,
+                countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount),
+                countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount),
+                countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount),
+                countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount),
+                countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount),
+                countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount),
+                countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
+                        configChangeCount));
+    }
+
+    static void assertLifecycleCounts(ComponentName activityName,
+            int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
+            int destroyCount, int configChangeCount) {
+        assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName),
+                createCount, startCount, resumeCount, pauseCount, stopCount,
+                destroyCount, configChangeCount);
+    }
+
+    static void assertSingleLaunch(ComponentName activityName) {
+        assertLifecycleCounts(activityName,
+                "***Waiting for activity create, start, and resume",
+                1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
+                0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
+                CountSpec.DONT_CARE /* configChangeCount */);
+    }
+
+    static void assertSingleLaunchAndStop(ComponentName activityName) {
+        assertLifecycleCounts(activityName,
+                "***Waiting for activity create, start, resume, pause, and stop",
+                1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
+                1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
+                CountSpec.DONT_CARE /* configChangeCount */);
+    }
+
+    static void assertSingleStartAndStop(ComponentName activityName) {
+        assertLifecycleCounts(activityName,
+                "***Waiting for activity start, resume, pause, and stop",
+                0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
+                1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
+                CountSpec.DONT_CARE /* configChangeCount */);
+    }
+
+    static void assertSingleStart(ComponentName activityName) {
+        assertLifecycleCounts(activityName,
+                "***Waiting for activity start and resume",
+                0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
+                0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
+                CountSpec.DONT_CARE /* configChangeCount */);
+    }
+
+    /** 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 ActivityLifecycleCounts lifecycleCounts =
-                        new ActivityLifecycleCounts(activityName, logSeparator);
-                final String logTag = getLogTag(activityName);
-                if (relaunched) {
-                    if (lifecycleCounts.mDestroyCount < 1) {
-                        return logTag + " must have been destroyed. mDestroyCount="
-                                + lifecycleCounts.mDestroyCount;
-                    }
-                    if (lifecycleCounts.mCreateCount < 1) {
-                        return logTag + " must have been (re)created. mCreateCount="
-                                + lifecycleCounts.mCreateCount;
-                    }
-                    return null;
-                }
-                if (lifecycleCounts.mDestroyCount > 0) {
-                    return logTag + " must *NOT* have been destroyed. mDestroyCount="
-                            + lifecycleCounts.mDestroyCount;
-                }
-                if (lifecycleCounts.mCreateCount > 0) {
-                    return logTag + " must *NOT* have been (re)created. mCreateCount="
-                            + lifecycleCounts.mCreateCount;
-                }
-                if (lifecycleCounts.mConfigurationChangedCount < 1) {
-                    return logTag + " must have received configuration changed. "
-                            + "mConfigurationChangedCount="
-                            + lifecycleCounts.mConfigurationChangedCount;
+                final String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
+                        getActivityName(activityName),
+                        TestJournalContainer.get(activityName).callbacks, relaunched);
+                if (failedReason != null) {
+                    return failedReason;
                 }
                 return null;
             }
         }.assertValidator("***Waiting for valid lifecycle state");
     }
 
-    protected void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
-            int numConfigChange, LogSeparator logSeparator) {
-        new RetryValidator() {
-
-            @Nullable
-            @Override
-            protected String validate() {
-                final ActivityLifecycleCounts lifecycleCounts =
-                        new ActivityLifecycleCounts(activityName, logSeparator);
-                final String logTag = getLogTag(activityName);
-                if (lifecycleCounts.mDestroyCount != numRelaunch) {
-                    return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
-                            + " time(s), expecting " + numRelaunch;
-                } else if (lifecycleCounts.mCreateCount != numRelaunch) {
-                    return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
-                            + " time(s), expecting " + numRelaunch;
-                } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
-                    return logTag + " has received "
-                            + lifecycleCounts.mConfigurationChangedCount
-                            + " onConfigurationChanged() calls, expecting " + numConfigChange;
-                }
-                return null;
-            }
-        }.assertValidator("***Waiting for relaunch or config changed");
+    /** Assert the activity is either relaunched or received configuration changed. */
+    static List<ActivityCallback> assertActivityLifecycle(ActivitySession activitySession,
+            boolean relaunched) {
+        final String name = activitySession.getName();
+        final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory();
+        String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
+                name, callbackHistory, relaunched);
+        if (failedReason != null) {
+            fail(failedReason);
+        }
+        return callbackHistory;
     }
 
-    protected void assertActivityDestroyed(ComponentName activityName, LogSeparator logSeparator) {
-        new RetryValidator() {
-
-            @Nullable
-            @Override
-            protected String validate() {
-                final ActivityLifecycleCounts lifecycleCounts =
-                        new ActivityLifecycleCounts(activityName, logSeparator);
-                final String logTag = getLogTag(activityName);
-                if (lifecycleCounts.mDestroyCount != 1) {
-                    return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
-                            + " time(s), expecting single destruction.";
-                }
-                if (lifecycleCounts.mCreateCount != 0) {
-                    return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
-                            + " time(s), not expecting any.";
-                }
-                if (lifecycleCounts.mConfigurationChangedCount != 0) {
-                    return logTag + " has received " + lifecycleCounts.mConfigurationChangedCount
-                            + " onConfigurationChanged() calls, not expecting any.";
-                }
-                return null;
-            }
-        }.assertValidator("***Waiting for activity destroyed");
+    private static String checkActivityIsRelaunchedOrConfigurationChanged(String name,
+            List<ActivityCallback> callbackHistory, boolean relaunched) {
+        final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory);
+        if (relaunched) {
+            return lifecycles.validateCount(
+                    countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0,
+                            name + " must have been destroyed."),
+                    countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0,
+                            name + " must have been (re)created."));
+        }
+        return lifecycles.validateCount(
+                countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1,
+                        name + " must *NOT* have been destroyed."),
+                countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1,
+                        name + " must *NOT* have been (re)created."),
+                countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0,
+                                name + " must have received configuration changed."));
     }
 
-    void assertLifecycleCounts(ComponentName activityName, LogSeparator logSeparator,
-            int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
-            int destroyCount, int configurationChangeCount) {
-        new RetryValidator() {
-            @Override
-            protected String validate() {
-                final ActivityLifecycleCounts lifecycleCounts =
-                        new ActivityLifecycleCounts(activityName, logSeparator);
-                final String logTag = getLogTag(activityName);
-                if (createCount != lifecycleCounts.mCreateCount) {
-                    return logTag + " has been created " + lifecycleCounts.mCreateCount
-                            + " time(s), expecting " + createCount;
-                }
-                if (startCount != lifecycleCounts.mStartCount) {
-                    return logTag + " has been started " + lifecycleCounts.mStartCount
-                            + " time(s), expecting " + startCount;
-                }
-                if (resumeCount != lifecycleCounts.mResumeCount) {
-                    return logTag + " has been resumed " + lifecycleCounts.mResumeCount
-                            + " time(s), expecting " + resumeCount;
-                }
-                if (pauseCount != lifecycleCounts.mPauseCount) {
-                    return logTag + " has been paused " + lifecycleCounts.mPauseCount
-                            + " time(s), expecting " + pauseCount;
-                }
-                if (stopCount != lifecycleCounts.mStopCount) {
-                    return logTag + " has been stopped " + lifecycleCounts.mStopCount
-                            + " time(s), expecting " + stopCount;
-                }
-                if (destroyCount != lifecycleCounts.mDestroyCount) {
-                    return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
-                            + " time(s), expecting " + destroyCount;
-                }
-                if (configurationChangeCount != lifecycleCounts.mConfigurationChangedCount) {
-                    return logTag + " has received config changes "
-                            + lifecycleCounts.mConfigurationChangedCount
-                            + " time(s), expecting " + configurationChangeCount;
-                }
-                return null;
-            }
-        }.assertValidator("***Waiting for activity lifecycle counts");
+    static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
+            int numConfigChange) {
+        new ActivityLifecycleCounts(activityName).assertCountWithRetry(
+                "***Waiting for 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,
+                        numConfigChange));
     }
 
-    void assertSingleLaunch(ComponentName activityName, LogSeparator logSeparator) {
-        new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
-                1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
-                0 /* destroyCount */)
-                .assertValidator("***Waiting for activity create, start, and resume");
+    static void assertActivityDestroyed(ComponentName activityName) {
+        new ActivityLifecycleCounts(activityName).assertCountWithRetry(
+                "***Waiting for activity destroyed",
+                countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
+                countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
+                countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
     }
 
-    void assertSingleLaunchAndStop(ComponentName activityName, LogSeparator logSeparator) {
-        new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
-                1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
-                0 /* destroyCount */)
-                .assertValidator("***Waiting for activity create, start, resume, pause, and stop");
-    }
-
-    void assertSingleStartAndStop(ComponentName activityName, LogSeparator logSeparator) {
-        new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
-                1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
-                0 /* destroyCount */)
-                .assertValidator("***Waiting for activity start, resume, pause, and stop");
-    }
-
-    void assertSingleStart(ComponentName activityName, LogSeparator logSeparator) {
-        new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
-                1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
-                0 /* destroyCount */)
-                .assertValidator("***Waiting for activity start and resume");
-    }
-
-    // TODO: Now that our test are device side, we can convert these to a more direct communication
-    // channel vs. depending on logs.
-    private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
-    private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
-    private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
-    private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
-    private static final Pattern sConfigurationChangedPattern =
-            Pattern.compile("(.+): onConfigurationChanged");
-    private static final Pattern sMovedToDisplayPattern =
-            Pattern.compile("(.+): onMovedToDisplay");
-    private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
-    private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
-    private static final Pattern sMultiWindowModeChangedPattern =
-            Pattern.compile("(.+): onMultiWindowModeChanged");
-    private static final Pattern sPictureInPictureModeChangedPattern =
-            Pattern.compile("(.+): onPictureInPictureModeChanged");
-    private static final Pattern sUserLeaveHintPattern = Pattern.compile("(.+): onUserLeaveHint");
-    private static final Pattern sNewConfigPattern = Pattern.compile(
-            "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
-            + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
-            + " orientation=(\\d+)");
-    private static final Pattern sDisplayCutoutPattern = Pattern.compile(
-            "(.+): cutout=(true|false)");
-    private static final Pattern sDisplayStatePattern =
-            Pattern.compile("Display Power: state=(.+)");
     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
     private static final Pattern sUiModeLockedPattern =
             Pattern.compile("mUiModeLocked=(true|false)");
 
-    static class ReportedSizes {
-        int widthDp;
-        int heightDp;
-        int displayWidth;
-        int displayHeight;
-        int metricsWidth;
-        int metricsHeight;
-        int smallestWidthDp;
-        int densityDpi;
-        int orientation;
-
-        @Override
-        public String toString() {
-            return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
-                    + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
-                    + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
-                    + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
-                    + " orientation=" + orientation + "}";
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if ( this == obj ) return true;
-            if ( !(obj instanceof ReportedSizes) ) return false;
-            ReportedSizes that = (ReportedSizes) obj;
-            return widthDp == that.widthDp
-                    && heightDp == that.heightDp
-                    && displayWidth == that.displayWidth
-                    && displayHeight == that.displayHeight
-                    && metricsWidth == that.metricsWidth
-                    && metricsHeight == that.metricsHeight
-                    && smallestWidthDp == that.smallestWidthDp
-                    && densityDpi == that.densityDpi
-                    && orientation == that.orientation;
-        }
-    }
-
     @Nullable
-    ReportedSizes getLastReportedSizesForActivity(
-            ComponentName activityName, LogSeparator logSeparator) {
-        final String logTag = getLogTag(activityName);
-        for (int retry = 1; retry <= 5; retry++ ) {
-            final ReportedSizes result = readLastReportedSizes(logSeparator, logTag);
-            if (result != null) {
-                return result;
+    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=" + logTag);
-        return null;
-    }
-
-    private ReportedSizes readLastReportedSizes(LogSeparator logSeparator, String logTag) {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sNewConfigPattern.matcher(line);
-            if (matcher.matches()) {
-                ReportedSizes details = new ReportedSizes();
-                details.widthDp = Integer.parseInt(matcher.group(2));
-                details.heightDp = Integer.parseInt(matcher.group(3));
-                details.displayWidth = Integer.parseInt(matcher.group(4));
-                details.displayHeight = Integer.parseInt(matcher.group(5));
-                details.metricsWidth = Integer.parseInt(matcher.group(6));
-                details.metricsHeight = Integer.parseInt(matcher.group(7));
-                details.smallestWidthDp = Integer.parseInt(matcher.group(8));
-                details.densityDpi = Integer.parseInt(matcher.group(9));
-                details.orientation = Integer.parseInt(matcher.group(10));
-                return details;
-            }
-        }
+        logE("***Waiting for activity size failed: activityName=" + getActivityName(activityName));
         return null;
     }
 
     /** Check if a device has display cutout. */
     boolean hasDisplayCutout() {
         // Launch an activity to report cutout state
-        final LogSeparator logSeparator = separateLogs();
+        separateTestJournal();
         launchActivity(BROADCAST_RECEIVER_ACTIVITY);
 
         // Read the logs to check if cutout is present
-        final Boolean displayCutoutPresent =
-                getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY, logSeparator);
+        final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY);
         assertNotNull("The activity should report cutout state", displayCutoutPresent);
 
         // Finish activity
-        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
         mAmWmState.waitForWithAmState(
                 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
                 "Waiting for activity to be removed");
@@ -1243,13 +1558,12 @@
      * after timeout.
      */
     @Nullable
-    private Boolean getCutoutStateForActivity(ComponentName activityName,
-            LogSeparator logSeparator) {
+    private Boolean getCutoutStateForActivity(ComponentName activityName) {
         final String logTag = getLogTag(activityName);
-        for (int retry = 1; retry <= 5; retry++ ) {
-            final Boolean result = readLastReportedCutoutState(logSeparator, logTag);
-            if (result != null) {
-                return result;
+        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);
@@ -1258,27 +1572,13 @@
         return null;
     }
 
-    /** Read display cutout state from device logs. */
-    private Boolean readLastReportedCutoutState(LogSeparator logSeparator, String logTag) {
-        final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            final Matcher matcher = sDisplayCutoutPattern.matcher(line);
-            if (matcher.matches()) {
-                return "true".equals(matcher.group(2));
-            }
-        }
-        return null;
-    }
-
     /** Waits for at least one onMultiWindowModeChanged event. */
-    ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName,
-            LogSeparator logSeparator) {
+    ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
         int retry = 1;
         ActivityLifecycleCounts result;
         do {
-            result = new ActivityLifecycleCounts(activityName, logSeparator);
-            if (result.mMultiWindowModeChangedCount >= 1) {
+            result = new ActivityLifecycleCounts(activityName);
+            if (result.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) >= 1) {
                 return result;
             }
             logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
@@ -1287,121 +1587,68 @@
         return result;
     }
 
-    // TODO: Now that our test are device side, we can convert these to a more direct communication
-    // channel vs. depending on logs.
     static class ActivityLifecycleCounts {
-        int mCreateCount;
-        int mStartCount;
-        int mResumeCount;
-        int mConfigurationChangedCount;
-        int mLastConfigurationChangedLineIndex;
-        int mMovedToDisplayCount;
-        int mMultiWindowModeChangedCount;
-        int mLastMultiWindowModeChangedLineIndex;
-        int mPictureInPictureModeChangedCount;
-        int mLastPictureInPictureModeChangedLineIndex;
-        int mUserLeaveHintCount;
-        int mPauseCount;
-        int mStopCount;
-        int mLastStopLineIndex;
-        int mDestroyCount;
+        final int[] mCounts = new int[ActivityCallback.SIZE];
+        final int[] mLastIndexes = new int[ActivityCallback.SIZE];
+        final List<ActivityCallback> mCallbackHistory;
 
-        ActivityLifecycleCounts(ComponentName componentName, LogSeparator logSeparator) {
-            int lineIndex = 0;
-            waitForIdle();
-            for (String line : getDeviceLogsForComponents(logSeparator, getLogTag(componentName))) {
-                line = line.trim();
-                lineIndex++;
+        ActivityLifecycleCounts(ComponentName componentName) {
+            this(TestJournalContainer.get(componentName).callbacks);
+        }
 
-                Matcher matcher = sCreatePattern.matcher(line);
-                if (matcher.matches()) {
-                    mCreateCount++;
-                    continue;
-                }
-
-                matcher = sStartPattern.matcher(line);
-                if (matcher.matches()) {
-                    mStartCount++;
-                    continue;
-                }
-
-                matcher = sResumePattern.matcher(line);
-                if (matcher.matches()) {
-                    mResumeCount++;
-                    continue;
-                }
-
-                matcher = sConfigurationChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mConfigurationChangedCount++;
-                    mLastConfigurationChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sMovedToDisplayPattern.matcher(line);
-                if (matcher.matches()) {
-                    mMovedToDisplayCount++;
-                    continue;
-                }
-
-                matcher = sMultiWindowModeChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mMultiWindowModeChangedCount++;
-                    mLastMultiWindowModeChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sPictureInPictureModeChangedPattern.matcher(line);
-                if (matcher.matches()) {
-                    mPictureInPictureModeChangedCount++;
-                    mLastPictureInPictureModeChangedLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sUserLeaveHintPattern.matcher(line);
-                if (matcher.matches()) {
-                    mUserLeaveHintCount++;
-                    continue;
-                }
-
-                matcher = sPausePattern.matcher(line);
-                if (matcher.matches()) {
-                    mPauseCount++;
-                    continue;
-                }
-
-                matcher = sStopPattern.matcher(line);
-                if (matcher.matches()) {
-                    mStopCount++;
-                    mLastStopLineIndex = lineIndex;
-                    continue;
-                }
-
-                matcher = sDestroyPattern.matcher(line);
-                if (matcher.matches()) {
-                    mDestroyCount++;
-                    continue;
-                }
+        ActivityLifecycleCounts(List<ActivityCallback> callbacks) {
+            mCallbackHistory = callbacks;
+            for (int i = 0; i < callbacks.size(); i++) {
+                final ActivityCallback callback = callbacks.get(i);
+                final int ordinal = callback.ordinal();
+                mCounts[ordinal]++;
+                mLastIndexes[ordinal] = i;
             }
         }
 
-        String counters() {
-            return IntStream.of(mCreateCount, mStartCount, mResumeCount, mPauseCount, mStopCount,
-                    mDestroyCount)
-                    .mapToObj(Integer::toString)
-                    .collect(Collectors.joining("/"));
+        int getCount(ActivityCallback callback) {
+            return mCounts[callback.ordinal()];
+        }
+
+        int getLastIndex(ActivityCallback callback) {
+            return mLastIndexes[callback.ordinal()];
+        }
+
+        @SafeVarargs
+        final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) {
+            new RetryValidator() {
+                @Override
+                protected String validate() {
+                    return validateCount(countSpecs);
+                }
+            }.assertValidator(message);
+        }
+
+        @SafeVarargs
+        final String validateCount(CountSpec<ActivityCallback>... countSpecs) {
+            ArrayList<String> failedReasons = null;
+            for (CountSpec<ActivityCallback> spec : countSpecs) {
+                final int realCount = mCounts[spec.mEvent.ordinal()];
+                if (!spec.validate(realCount)) {
+                    if (failedReasons == null) {
+                        failedReasons = new ArrayList<>();
+                    }
+                    failedReasons.add(spec.mMessage);
+                }
+            }
+            return failedReasons == null ? null : String.join("\n", failedReasons);
         }
     }
 
-    protected void stopTestPackage(final ComponentName activityName) {
-        executeShellCommand("am force-stop " + activityName.getPackageName());
+    protected void stopTestPackage(final String packageName) {
+        SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(packageName));
     }
 
     protected LaunchActivityBuilder getLaunchActivityBuilder() {
         return new LaunchActivityBuilder(mAmWmState);
     }
 
-    protected static class LaunchActivityBuilder {
+    protected static class LaunchActivityBuilder implements LaunchProxy {
         private final ActivityAndWindowManagersState mAmWmState;
 
         // The activity to be launched
@@ -1411,25 +1658,32 @@
         private boolean mRandomData;
         private boolean mNewTask;
         private boolean mMultipleTask;
+        private boolean mAllowMultipleInstances = true;
         private int mDisplayId = INVALID_DISPLAY;
+        private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
         // A proxy activity that launches other activities including mTargetActivityName
         private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY;
         private boolean mReorderToFront;
         private boolean mWaitForLaunched;
         private boolean mSuppressExceptions;
+        private boolean mWithShellPermission;
         // Use of the following variables indicates that a broadcast receiver should be used instead
         // of a launching activity;
         private ComponentName mBroadcastReceiver;
         private String mBroadcastReceiverAction;
+        private int mIntentFlags;
+        private LaunchInjector mLaunchInjector;
 
         private enum LauncherType {
             INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
         }
+
         private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
 
         public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) {
             mAmWmState = amWmState;
             mWaitForLaunched = true;
+            mWithShellPermission = true;
         }
 
         public LaunchActivityBuilder setToSide(boolean toSide) {
@@ -1452,6 +1706,11 @@
             return this;
         }
 
+        public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) {
+            mAllowMultipleInstances = allowMultipleInstances;
+            return this;
+        }
+
         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
             mReorderToFront = reorderToFront;
             return this;
@@ -1480,6 +1739,11 @@
             return this;
         }
 
+        public LaunchActivityBuilder setActivityType(int type) {
+            mActivityType = type;
+            return this;
+        }
+
         public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) {
             mLaunchingActivity = launchingActivity;
             mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
@@ -1514,10 +1778,35 @@
             return this;
         }
 
+        public LaunchActivityBuilder setWithShellPermission(boolean withShellPermission) {
+            mWithShellPermission = withShellPermission;
+            return this;
+        }
+
+        @Override
+        public boolean shouldWaitForLaunched() {
+            return mWaitForLaunched;
+        }
+
+        public LaunchActivityBuilder setIntentFlags(int flags) {
+            mIntentFlags = flags;
+            return this;
+        }
+
+        @Override
+        public void setLaunchInjector(LaunchInjector injector) {
+            mLaunchInjector = injector;
+        }
+
+        @Override
         public void execute() {
             switch (mLauncherType) {
                 case INSTRUMENTATION:
-                    launchUsingInstrumentation();
+                    if (mWithShellPermission) {
+                        SystemUtil.runWithShellPermissionIdentity(this::launchUsingInstrumentation);
+                    } else {
+                        launchUsingInstrumentation();
+                    }
                     break;
                 case LAUNCHING_ACTIVITY:
                 case BROADCAST_RECEIVER:
@@ -1538,13 +1827,16 @@
             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_REORDER_TO_FRONT, mReorderToFront);
             b.putInt(KEY_DISPLAY_ID, mDisplayId);
+            b.putInt(KEY_ACTIVITY_TYPE, mActivityType);
             b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext);
             b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity));
             b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
+            b.putInt(KEY_INTENT_FLAGS, mIntentFlags);
             final Context context = InstrumentationRegistry.getContext();
-            launchActivityFromExtras(context, b);
+            launchActivityFromExtras(context, b, mLaunchInjector);
         }
 
         /** Build and execute a shell command to launch an activity. */
@@ -1577,12 +1869,18 @@
             if (mMultipleTask) {
                 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true");
             }
+            if (mAllowMultipleInstances) {
+                commandBuilder.append(" --ez " + KEY_MULTIPLE_INSTANCES + " true");
+            }
             if (mReorderToFront) {
                 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
             }
             if (mDisplayId != INVALID_DISPLAY) {
                 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
             }
+            if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
+                commandBuilder.append(" --ei " + KEY_ACTIVITY_TYPE + " ").append(mActivityType);
+            }
 
             if (mUseApplicationContext) {
                 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true");
@@ -1598,6 +1896,14 @@
             if (mSuppressExceptions) {
                 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true");
             }
+
+            if (mIntentFlags != 0) {
+                commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags);
+            }
+
+            if (mLaunchInjector != null) {
+                mLaunchInjector.setupShellCommand(commandBuilder);
+            }
             executeShellCommand(commandBuilder.toString());
         }
     }
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/CommandSession.java b/tests/framework/base/activitymanager/util/src/android/server/am/CommandSession.java
new file mode 100644
index 0000000..62f759e
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/CommandSession.java
@@ -0,0 +1,1106 @@
+/*
+ * 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.server.am;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.server.am.TestJournalProvider.TestJournalClient;
+import android.support.test.InstrumentationRegistry;
+import android.util.ArrayMap;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+/**
+ * A mechanism for communication between the started activity and its caller in different package or
+ * process. Generally, a test case is the client, and the testing activity is the host. The client
+ * can control whether to send an async or sync command with response data.
+ * <p>Sample:</p>
+ * <pre>
+ * try (ActivitySessionClient client = new ActivitySessionClient(context)) {
+ *     final ActivitySession session = client.startActivity(
+ *             new Intent(context, TestActivity.class));
+ *     final Bundle response = session.requestOrientation(
+ *             ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ *     Log.i("Test", "Config: " + CommandSession.getConfigInfo(response));
+ *     Log.i("Test", "Callbacks: " + CommandSession.getCallbackHistory(response));
+ *
+ *     session.startActivity(session.getOriginalLaunchIntent());
+ *     Log.i("Test", "New intent callbacks: " + session.takeCallbackHistory());
+ * }
+ * </pre>
+ * <p>To perform custom command, use sendCommand* in {@link ActivitySession} to send the request,
+ * and the receiving side (activity) can extend {@link BasicTestActivity} or
+ * {@link CommandSessionActivity} with overriding handleCommand to do the corresponding action.</p>
+ */
+public final class CommandSession {
+    private static final boolean DEBUG = "eng".equals(Build.TYPE);
+    private static final String TAG = "CommandSession";
+
+    private static final String EXTRA_PREFIX = "s_";
+
+    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";
+
+    private static final String COMMAND_FINISH = EXTRA_PREFIX + "command_finish";
+    private static final String COMMAND_GET_CONFIG = EXTRA_PREFIX + "command_get_config";
+    private static final String COMMAND_ORIENTATION = EXTRA_PREFIX + "command_orientation";
+    private static final String COMMAND_TAKE_CALLBACK_HISTORY = EXTRA_PREFIX
+            + "command_take_callback_history";
+    private static final String COMMAND_WAIT_IDLE = EXTRA_PREFIX + "command_wait_idle";
+
+    private static final long INVALID_REQUEST_TOKEN = -1;
+
+    private CommandSession() {
+    }
+
+    /** Get {@link ConfigInfo} from bundle. */
+    public static ConfigInfo getConfigInfo(Bundle data) {
+        return data.getParcelable(KEY_CONFIG_INFO);
+    }
+
+    /** Get list of {@link ActivityCallback} from bundle. */
+    public static ArrayList<ActivityCallback> getCallbackHistory(Bundle data) {
+        return data.getParcelableArrayList(KEY_CALLBACK_HISTORY);
+    }
+
+    /** Return non-null if the session info should forward to launch target. */
+    public static LaunchInjector handleForward(Bundle data) {
+        if (data == null || !data.getBoolean(KEY_FORWARD)) {
+            return null;
+        }
+
+        // Only keep the necessary data which relates to session.
+        final Bundle sessionInfo = new Bundle(data);
+        sessionInfo.remove(KEY_FORWARD);
+        for (String key : sessionInfo.keySet()) {
+            if (!key.startsWith(EXTRA_PREFIX)) {
+                sessionInfo.remove(key);
+            }
+        }
+
+        return new LaunchInjector() {
+            @Override
+            public void setupIntent(Intent intent) {
+                intent.putExtras(sessionInfo);
+            }
+
+            @Override
+            public void setupShellCommand(StringBuilder shellCommand) {
+                // Currently there is no use case from shell.
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    private static String generateId(String prefix, Object obj) {
+        return prefix + "_" + Integer.toHexString(System.identityHashCode(obj));
+    }
+
+    private static String commandIntentToString(Intent intent) {
+        return intent.getStringExtra(KEY_COMMAND)
+                + "@" + intent.getLongExtra(KEY_REQUEST_TOKEN, INVALID_REQUEST_TOKEN);
+    }
+
+    /** Get an unique token to match the request and reply. */
+    private static long generateRequestToken() {
+        return SystemClock.elapsedRealtimeNanos();
+    }
+
+    /**
+     * As a controller associated with the testing activity. It can only process one sync command
+     * (require response) at a time.
+     */
+    public static class ActivitySession {
+        private final ActivitySessionClient mClient;
+        private final String mHostId;
+        private final Response mPendingResponse = new Response();
+        // Only set when requiring response.
+        private long mPendingRequestToken = INVALID_REQUEST_TOKEN;
+        private String mPendingCommand;
+        private boolean mFinished;
+        private Intent mOriginalLaunchIntent;
+
+        ActivitySession(ActivitySessionClient client, boolean requireReply) {
+            mClient = client;
+            mHostId = generateId("activity", this);
+            if (requireReply) {
+                mPendingRequestToken = generateRequestToken();
+                mPendingCommand = COMMAND_WAIT_IDLE;
+            }
+        }
+
+        /** Start the activity again. The intent must have the same filter as original one. */
+        public void startActivity(Intent intent) {
+            if (!intent.filterEquals(mOriginalLaunchIntent)) {
+                throw new IllegalArgumentException("The intent filter is different " + intent);
+            }
+            mClient.mContext.startActivity(intent);
+            mFinished = false;
+        }
+
+        /**
+         * Request the activity to set the given orientation. The returned bundle contains the
+         * changed config info and activity lifecycles during the change.
+         *
+         * @param orientation An orientation constant as used in
+         *                    {@link android.content.pm.ActivityInfo#screenOrientation}.
+         */
+        public Bundle requestOrientation(int orientation) {
+            final Bundle data = new Bundle();
+            data.putInt(KEY_ORIENTATION, orientation);
+            return sendCommandAndWaitReply(COMMAND_ORIENTATION, data);
+        }
+
+        /** Get {@link ConfigInfo} of the associated activity. */
+        public ConfigInfo getConfigInfo() {
+            return CommandSession.getConfigInfo(sendCommandAndWaitReply(COMMAND_GET_CONFIG));
+        }
+
+        /**
+         * Get executed callbacks of the activity since the last command. The current callback
+         * history will also be cleared.
+         */
+        public ArrayList<ActivityCallback> takeCallbackHistory() {
+            return getCallbackHistory(sendCommandAndWaitReply(COMMAND_TAKE_CALLBACK_HISTORY,
+                    null /* data */));
+        }
+
+        /** Get the intent that launches the activity. Null if launch from shell command. */
+        public Intent getOriginalLaunchIntent() {
+            return mOriginalLaunchIntent;
+        }
+
+        /** Get a name to represent this session by the original launch intent if possible. */
+        public String getName() {
+            if (mOriginalLaunchIntent != null) {
+                final ComponentName componentName = mOriginalLaunchIntent.getComponent();
+                if (componentName != null) {
+                    return componentName.flattenToShortString();
+                }
+                return mOriginalLaunchIntent.toString();
+            }
+            return "Activity";
+        }
+
+        /** Send command to the associated activity. */
+        public void sendCommand(String command) {
+            sendCommand(command, null /* data */);
+        }
+
+        /** Send command with extra parameters to the associated activity. */
+        public void sendCommand(String command, Bundle data) {
+            if (mFinished) {
+                throw new IllegalStateException("The session is finished");
+            }
+
+            final Intent intent = new Intent(mHostId);
+            if (data != null) {
+                intent.putExtras(data);
+            }
+            intent.putExtra(KEY_COMMAND, command);
+            mClient.mContext.sendBroadcast(intent);
+            if (DEBUG) {
+                Log.i(TAG, mClient.mClientId + " sends " + commandIntentToString(intent)
+                        + " to " + mHostId);
+            }
+        }
+
+        public Bundle sendCommandAndWaitReply(String command) {
+            return sendCommandAndWaitReply(command, null /* data */);
+        }
+
+        /** Returns the reply data by the given command. */
+        public Bundle sendCommandAndWaitReply(String command, Bundle data) {
+            if (data == null) {
+                data = new Bundle();
+            }
+
+            if (mPendingRequestToken != INVALID_REQUEST_TOKEN) {
+                throw new IllegalStateException("The previous pending request "
+                        + mPendingCommand + " has not replied");
+            }
+            mPendingRequestToken = generateRequestToken();
+            mPendingCommand = command;
+            data.putLong(KEY_REQUEST_TOKEN, mPendingRequestToken);
+
+            sendCommand(command, data);
+            return waitReply();
+        }
+
+        private Bundle waitReply() {
+            if (mPendingRequestToken == INVALID_REQUEST_TOKEN) {
+                throw new IllegalStateException("No pending request to wait");
+            }
+
+            if (DEBUG) Log.i(TAG, "Waiting for request " + mPendingRequestToken);
+            try {
+                return mPendingResponse.takeResult();
+            } catch (TimeoutException e) {
+                throw new RuntimeException("Timeout on command "
+                        + mPendingCommand + " with token " + mPendingRequestToken, e);
+            } finally {
+                mPendingRequestToken = INVALID_REQUEST_TOKEN;
+                mPendingCommand = null;
+            }
+        }
+
+        // This method should run on an independent thread.
+        void receiveReply(Bundle reply) {
+            final long incomingToken = reply.getLong(KEY_REQUEST_TOKEN);
+            if (incomingToken == mPendingRequestToken) {
+                mPendingResponse.setResult(reply);
+            } else {
+                throw new IllegalStateException("Mismatched token: incoming=" + incomingToken
+                        + " pending=" + mPendingRequestToken);
+            }
+        }
+
+        /** Finish the activity that associates with this session. */
+        public void finish() {
+            if (!mFinished) {
+                sendCommand(COMMAND_FINISH);
+                mClient.mSessions.remove(mHostId);
+                mFinished = true;
+            }
+        }
+
+        private static class Response {
+            static final int TIMEOUT_MILLIS = 5000;
+            private volatile boolean mHasResult;
+            private Bundle mResult;
+
+            synchronized void setResult(Bundle result) {
+                mHasResult = true;
+                mResult = result;
+                notifyAll();
+            }
+
+            synchronized Bundle takeResult() throws TimeoutException {
+                final long startTime = SystemClock.uptimeMillis();
+                while (!mHasResult) {
+                    try {
+                        wait(TIMEOUT_MILLIS);
+                    } catch (InterruptedException ignored) {
+                    }
+                    if (!mHasResult && (SystemClock.uptimeMillis() - startTime > TIMEOUT_MILLIS)) {
+                        throw new TimeoutException("No response over " + TIMEOUT_MILLIS + "ms");
+                    }
+                }
+
+                final Bundle result = mResult;
+                mHasResult = false;
+                mResult = null;
+                return result;
+            }
+        }
+    }
+
+    /** For LaunchProxy to setup launch parameter that establishes session. */
+    interface LaunchInjector {
+        void setupIntent(Intent intent);
+        void setupShellCommand(StringBuilder shellCommand);
+    }
+
+    /** A proxy to launch activity by intent or shell command. */
+    interface LaunchProxy {
+        void setLaunchInjector(LaunchInjector injector);
+        void execute();
+        boolean shouldWaitForLaunched();
+    }
+
+    /** Created by test case to control testing activity that implements the session protocol. */
+    public static class ActivitySessionClient extends BroadcastReceiver implements AutoCloseable {
+        private final Context mContext;
+        private final String mClientId;
+        private final HandlerThread mThread;
+        private final ArrayMap<String, ActivitySession> mSessions = new ArrayMap<>();
+        private boolean mClosed;
+
+        public ActivitySessionClient() {
+            this(InstrumentationRegistry.getContext());
+        }
+
+        public ActivitySessionClient(Context context) {
+            mContext = context;
+            mClientId = generateId("testcase", this);
+            mThread = new HandlerThread(mClientId);
+            mThread.start();
+            context.registerReceiver(this, new IntentFilter(mClientId),
+                    null /* broadcastPermission */, new Handler(mThread.getLooper()));
+        }
+
+        /** Start the activity by the given intent and wait it becomes idle. */
+        public ActivitySession startActivity(Intent intent) {
+            return startActivity(intent, null /* options */, true /* waitIdle */);
+        }
+
+        /**
+         * Launch the activity and establish a new session.
+         *
+         * @param intent The description of the activity to start.
+         * @param options Additional options for how the Activity should be started.
+         * @param waitIdle Block in this method until the target activity is idle.
+         * @return The session to communicate with the started activity.
+         */
+        public ActivitySession startActivity(Intent intent, Bundle options, boolean waitIdle) {
+            ensureNotClosed();
+            final ActivitySession session = new ActivitySession(this, waitIdle);
+            mSessions.put(session.mHostId, session);
+            setupLaunchIntent(intent, waitIdle, session);
+
+            if (!(mContext instanceof Activity)) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            }
+            mContext.startActivity(intent, options);
+            if (waitIdle) {
+                session.waitReply();
+            }
+            return session;
+        }
+
+        /** Launch activity via proxy that allows to inject session parameters. */
+        public ActivitySession startActivity(LaunchProxy proxy) {
+            ensureNotClosed();
+            final boolean waitIdle = proxy.shouldWaitForLaunched();
+            final ActivitySession session = new ActivitySession(this, waitIdle);
+            mSessions.put(session.mHostId, session);
+
+            proxy.setLaunchInjector(new LaunchInjector() {
+                @Override
+                public void setupIntent(Intent intent) {
+                    setupLaunchIntent(intent, waitIdle, session);
+                }
+
+                @Override
+                public void setupShellCommand(StringBuilder commandBuilder) {
+                    commandBuilder.append(" --es " + KEY_HOST_ID + " " + session.mHostId);
+                    commandBuilder.append(" --es " + KEY_CLIENT_ID + " " + mClientId);
+                    if (waitIdle) {
+                        commandBuilder.append(
+                                " --el " + KEY_REQUEST_TOKEN + " " + session.mPendingRequestToken);
+                        commandBuilder.append(" --es " + KEY_COMMAND + " " + COMMAND_WAIT_IDLE);
+                    }
+                }
+            });
+
+            proxy.execute();
+            if (waitIdle) {
+                session.waitReply();
+            }
+            return session;
+        }
+
+        private void setupLaunchIntent(Intent intent, boolean waitIdle, ActivitySession session) {
+            intent.putExtra(KEY_HOST_ID, session.mHostId);
+            intent.putExtra(KEY_CLIENT_ID, mClientId);
+            if (waitIdle) {
+                intent.putExtra(KEY_REQUEST_TOKEN, session.mPendingRequestToken);
+                intent.putExtra(KEY_COMMAND, COMMAND_WAIT_IDLE);
+            }
+            session.mOriginalLaunchIntent = intent;
+        }
+
+        private void ensureNotClosed() {
+            if (mClosed) {
+                throw new IllegalStateException("This session client is closed.");
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final ActivitySession session = mSessions.get(intent.getStringExtra(KEY_HOST_ID));
+            if (DEBUG) Log.i(TAG, mClientId + " receives " + commandIntentToString(intent));
+            if (session != null) {
+                session.receiveReply(intent.getExtras());
+            } else {
+                Log.w(TAG, "No available session for " + commandIntentToString(intent));
+            }
+        }
+
+        /** Complete cleanup with finishing all associated activities. */
+        @Override
+        public void close() {
+            close(true /* finishSession */);
+        }
+
+        /** Cleanup except finish associated activities. */
+        public void closeAndKeepSession() {
+            close(false /* finishSession */);
+        }
+
+        /**
+         * Closes this client. Once a client is closed, all methods on it will throw an
+         * IllegalStateException and all responses from host are ignored.
+         *
+         * @param finishSession Whether to finish activities launched from this client.
+         */
+        public void close(boolean finishSession) {
+            ensureNotClosed();
+            mClosed = true;
+            if (finishSession) {
+                for (int i = mSessions.size() - 1; i >= 0; i--) {
+                    mSessions.valueAt(i).finish();
+                }
+            }
+            mContext.unregisterReceiver(this);
+            mThread.quit();
+        }
+    }
+
+    /**
+     * Interface definition for session host to process command from {@link ActivitySessionClient}.
+     */
+    interface CommandReceiver {
+        /** Called when the session host is receiving command. */
+        void receiveCommand(String command, Bundle data);
+    }
+
+    /** The host receives command from the test client. */
+    public static class ActivitySessionHost extends BroadcastReceiver {
+        private final CommandReceiver mCallback;
+        private final Context mContext;
+        private final String mClientId;
+        private final String mHostId;
+
+        ActivitySessionHost(Context context, String hostId, String clientId,
+                CommandReceiver callback) {
+            mContext = context;
+            mHostId = hostId;
+            mClientId = clientId;
+            mCallback = callback;
+            context.registerReceiver(this, new IntentFilter(hostId));
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.i(TAG, mHostId + "(" + mContext.getClass().getSimpleName()
+                        + ") receives " + commandIntentToString(intent));
+            }
+            mCallback.receiveCommand(intent.getStringExtra(KEY_COMMAND), intent.getExtras());
+        }
+
+        void reply(String command, Bundle data) {
+            final Intent intent = new Intent(mClientId);
+            intent.putExtras(data);
+            intent.putExtra(KEY_COMMAND, command);
+            intent.putExtra(KEY_HOST_ID, mHostId);
+            mContext.sendBroadcast(intent);
+            if (DEBUG) {
+                Log.i(TAG, mHostId + "(" + mContext.getClass().getSimpleName()
+                        + ") replies " + commandIntentToString(intent) + " to " + mClientId);
+            }
+        }
+
+        void destory() {
+            mContext.unregisterReceiver(this);
+        }
+    }
+
+    /**
+     * A map to store data by host id. The usage should be declared as static that is able to keep
+     * data after activity is relaunched.
+     */
+    private static class StaticHostStorage<T> {
+        final ArrayMap<String, ArrayList<T>> mStorage = new ArrayMap<>();
+
+        void add(String hostId, T data) {
+            ArrayList<T> commands = mStorage.get(hostId);
+            if (commands == null) {
+                commands = new ArrayList<>();
+                mStorage.put(hostId, commands);
+            }
+            commands.add(data);
+        }
+
+        ArrayList<T> get(String hostId) {
+            return mStorage.get(hostId);
+        }
+
+        void clear(String hostId) {
+            mStorage.remove(hostId);
+        }
+    }
+
+    /** Store the commands which have not been handled. */
+    private static class CommandStorage extends StaticHostStorage<Bundle> {
+
+        /** Remove the oldest matched command and return its request token. */
+        long consume(String hostId, String command) {
+            final ArrayList<Bundle> commands = mStorage.get(hostId);
+            if (commands != null) {
+                final Iterator<Bundle> iterator = commands.iterator();
+                while (iterator.hasNext()) {
+                    final Bundle data = iterator.next();
+                    if (command.equals(data.getString(KEY_COMMAND))) {
+                        iterator.remove();
+                        return data.getLong(KEY_REQUEST_TOKEN);
+                    }
+                }
+                if (commands.isEmpty()) {
+                    clear(hostId);
+                }
+            }
+            return INVALID_REQUEST_TOKEN;
+        }
+
+        boolean containsCommand(String receiverId, String command) {
+            final ArrayList<Bundle> dataList = mStorage.get(receiverId);
+            if (dataList != null) {
+                for (Bundle data : dataList) {
+                    if (command.equals(data.getString(KEY_COMMAND))) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * The base activity which supports the session protocol. If the caller does not use
+     * {@link ActivitySessionClient}, it behaves as a normal activity.
+     */
+    public static class CommandSessionActivity extends Activity implements CommandReceiver {
+        /** Static command storage for across relaunch. */
+        private static CommandStorage sCommandStorage;
+        private ActivitySessionHost mReceiver;
+
+        protected TestJournalClient mTestJournalClient;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mTestJournalClient = TestJournalClient.create(this /* context */, getComponentName());
+
+            final String hostId = getIntent().getStringExtra(KEY_HOST_ID);
+            final String clientId = getIntent().getStringExtra(KEY_CLIENT_ID);
+            if (hostId != null && clientId != null) {
+                if (sCommandStorage == null) {
+                    sCommandStorage = new CommandStorage();
+                }
+                mReceiver = new ActivitySessionHost(this /* context */, hostId, clientId,
+                        this /* callback */);
+            }
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            if (mReceiver != null) {
+                if (!isChangingConfigurations()) {
+                    sCommandStorage.clear(getHostId());
+                }
+                mReceiver.destory();
+            }
+            if (mTestJournalClient != null) {
+                mTestJournalClient.close();
+            }
+        }
+
+        @Override
+        public final void receiveCommand(String command, Bundle data) {
+            if (mReceiver == null) {
+                throw new IllegalStateException("The receiver is not created");
+            }
+            sCommandStorage.add(getHostId(), data);
+            handleCommand(command, data);
+        }
+
+        /** Handle the incoming command from client. */
+        protected void handleCommand(String command, Bundle data) {
+        }
+
+        protected final void reply(String command) {
+            reply(command, null /* data */);
+        }
+
+        /** Reply data to client for the command. */
+        protected final void reply(String command, Bundle data) {
+            if (mReceiver == null) {
+                throw new IllegalStateException("The receiver is not created");
+            }
+            final long requestToke = sCommandStorage.consume(getHostId(), command);
+            if (requestToke == INVALID_REQUEST_TOKEN) {
+                throw new IllegalStateException("There is no pending command " + command);
+            }
+            if (data == null) {
+                data = new Bundle();
+            }
+            data.putLong(KEY_REQUEST_TOKEN, requestToke);
+            mReceiver.reply(command, data);
+        }
+
+        protected boolean hasPendingCommand(String command) {
+            return mReceiver != null && sCommandStorage.containsCommand(getHostId(), command);
+        }
+
+        /** Returns null means this activity does support the session protocol. */
+        final String getHostId() {
+            return mReceiver != null ? mReceiver.mHostId : null;
+        }
+    }
+
+    /** The default implementation that supports basic commands to interact with activity. */
+    public static class BasicTestActivity extends CommandSessionActivity {
+        /** Static callback history for across relaunch. */
+        private static final StaticHostStorage<ActivityCallback> sCallbackStorage =
+                new StaticHostStorage<>();
+
+        protected boolean mPrintCallbackLog;
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            onCallback(ActivityCallback.ON_CREATE);
+
+            if (getHostId() != null) {
+                final int orientation = getIntent().getIntExtra(KEY_ORIENTATION, Integer.MIN_VALUE);
+                if (orientation != Integer.MIN_VALUE) {
+                    setRequestedOrientation(orientation);
+                }
+                if (COMMAND_WAIT_IDLE.equals(getIntent().getStringExtra(KEY_COMMAND))) {
+                    receiveCommand(COMMAND_WAIT_IDLE, getIntent().getExtras());
+                    // No need to execute again if the activity is relaunched.
+                    getIntent().removeExtra(KEY_COMMAND);
+                }
+            }
+        }
+
+        @Override
+        public void handleCommand(String command, Bundle data) {
+            switch (command) {
+                case COMMAND_ORIENTATION:
+                    clearCallbackHistory();
+                    setRequestedOrientation(data.getInt(KEY_ORIENTATION));
+                    getWindow().getDecorView().postDelayed(() -> {
+                        if (reportConfigIfNeeded()) {
+                            Log.w(getTag(), "Fallback report. The orientation may not change.");
+                        }
+                    }, ActivitySession.Response.TIMEOUT_MILLIS / 2);
+                    break;
+
+                case COMMAND_GET_CONFIG:
+                    runWhenIdle(() -> {
+                        final Bundle replyData = new Bundle();
+                        replyData.putParcelable(KEY_CONFIG_INFO, getConfigInfo());
+                        reply(COMMAND_GET_CONFIG, replyData);
+                    });
+                    break;
+
+                case COMMAND_FINISH:
+                    if (!isFinishing()) {
+                        finish();
+                    }
+                    break;
+
+                case COMMAND_TAKE_CALLBACK_HISTORY:
+                    final Bundle replyData = new Bundle();
+                    replyData.putParcelableArrayList(KEY_CALLBACK_HISTORY, getCallbackHistory());
+                    reply(command, replyData);
+                    clearCallbackHistory();
+                    break;
+
+                case COMMAND_WAIT_IDLE:
+                    runWhenIdle(() -> reply(command));
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        protected final void clearCallbackHistory() {
+            sCallbackStorage.clear(getHostId());
+        }
+
+        protected final ArrayList<ActivityCallback> getCallbackHistory() {
+            return sCallbackStorage.get(getHostId());
+        }
+
+        protected void runWhenIdle(Runnable r) {
+            Looper.getMainLooper().getQueue().addIdleHandler(() -> {
+                r.run();
+                return false;
+            });
+        }
+
+        protected boolean reportConfigIfNeeded() {
+            if (!hasPendingCommand(COMMAND_ORIENTATION)) {
+                return false;
+            }
+            runWhenIdle(() -> {
+                final Bundle replyData = new Bundle();
+                replyData.putParcelable(KEY_CONFIG_INFO, getConfigInfo());
+                replyData.putParcelableArrayList(KEY_CALLBACK_HISTORY, getCallbackHistory());
+                reply(COMMAND_ORIENTATION, replyData);
+                clearCallbackHistory();
+            });
+            return true;
+        }
+
+        @Override
+        protected void onStart() {
+            super.onStart();
+            onCallback(ActivityCallback.ON_START);
+        }
+
+        @Override
+        protected void onRestart() {
+            super.onRestart();
+            onCallback(ActivityCallback.ON_RESTART);
+        }
+
+        @Override
+        protected void onResume() {
+            super.onResume();
+            onCallback(ActivityCallback.ON_RESUME);
+            reportConfigIfNeeded();
+        }
+
+        @Override
+        protected void onPause() {
+            super.onPause();
+            onCallback(ActivityCallback.ON_PAUSE);
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            onCallback(ActivityCallback.ON_STOP);
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            onCallback(ActivityCallback.ON_DESTROY);
+        }
+
+        @Override
+        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+            super.onActivityResult(requestCode, resultCode, data);
+            onCallback(ActivityCallback.ON_ACTIVITY_RESULT);
+        }
+
+        @Override
+        protected void onUserLeaveHint() {
+            super.onUserLeaveHint();
+            onCallback(ActivityCallback.ON_USER_LEAVE_HINT);
+        }
+
+        @Override
+        protected void onNewIntent(Intent intent) {
+            super.onNewIntent(intent);
+            onCallback(ActivityCallback.ON_NEW_INTENT);
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            super.onConfigurationChanged(newConfig);
+            onCallback(ActivityCallback.ON_CONFIGURATION_CHANGED);
+            reportConfigIfNeeded();
+        }
+
+        @Override
+        public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+            super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+            onCallback(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
+        }
+
+        @Override
+        public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+                Configuration newConfig) {
+            super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+            onCallback(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
+        }
+
+        @Override
+        public void onMovedToDisplay(int displayId, Configuration config) {
+            super.onMovedToDisplay(displayId, config);
+            onCallback(ActivityCallback.ON_MOVED_TO_DISPLAY);
+        }
+
+        private void onCallback(ActivityCallback callback) {
+            if (mPrintCallbackLog) {
+                Log.i(getTag(), callback + " @ "
+                        + Integer.toHexString(System.identityHashCode(this)));
+            }
+            final String hostId = getHostId();
+            if (hostId != null) {
+                sCallbackStorage.add(hostId, callback);
+            }
+            if (mTestJournalClient != null) {
+                mTestJournalClient.addCallback(callback);
+            }
+        }
+
+        protected void withTestJournalClient(Consumer<TestJournalClient> client) {
+            if (mTestJournalClient != null) {
+                client.accept(mTestJournalClient);
+            }
+        }
+
+        protected String getTag() {
+            return getClass().getSimpleName();
+        }
+
+        /** Get configuration and display info. It should be called only after resumed. */
+        protected ConfigInfo getConfigInfo() {
+            final View view = getWindow().getDecorView();
+            if (!view.isAttachedToWindow()) {
+                Log.w(getTag(), "Decor view has not attached");
+            }
+            return new ConfigInfo(view);
+        }
+    }
+
+    public enum ActivityCallback implements Parcelable {
+        ON_CREATE,
+        ON_START,
+        ON_RESUME,
+        ON_PAUSE,
+        ON_STOP,
+        ON_RESTART,
+        ON_DESTROY,
+        ON_ACTIVITY_RESULT,
+        ON_USER_LEAVE_HINT,
+        ON_NEW_INTENT,
+        ON_CONFIGURATION_CHANGED,
+        ON_MULTI_WINDOW_MODE_CHANGED,
+        ON_PICTURE_IN_PICTURE_MODE_CHANGED,
+        ON_MOVED_TO_DISPLAY;
+
+        private static final ActivityCallback[] sValues = ActivityCallback.values();
+        public static final int SIZE = sValues.length;
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(final Parcel dest, final int flags) {
+            dest.writeInt(ordinal());
+        }
+
+        public static final Creator<ActivityCallback> CREATOR = new Creator<ActivityCallback>() {
+            @Override
+            public ActivityCallback createFromParcel(final Parcel source) {
+                return sValues[source.readInt()];
+            }
+
+            @Override
+            public ActivityCallback[] newArray(final int size) {
+                return new ActivityCallback[size];
+            }
+        };
+    }
+
+    public static class ConfigInfo implements Parcelable {
+        public int displayId = Display.INVALID_DISPLAY;
+        public int rotation;
+        public SizeInfo sizeInfo;
+
+        ConfigInfo() {
+        }
+
+        public ConfigInfo(View view) {
+            final Resources res = view.getContext().getResources();
+            final DisplayMetrics metrics = res.getDisplayMetrics();
+            final Configuration config = res.getConfiguration();
+            final Display display = view.getDisplay();
+
+            if (display != null) {
+                displayId = display.getDisplayId();
+                rotation = display.getRotation();
+            }
+            sizeInfo = new SizeInfo(display, metrics, config);
+        }
+
+        @Override
+        public String toString() {
+            return "ConfigInfo: {displayId=" + displayId + " rotation=" + rotation
+                    + " " + sizeInfo + "}";
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(displayId);
+            dest.writeInt(rotation);
+            dest.writeParcelable(sizeInfo, 0 /* parcelableFlags */);
+        }
+
+        public void readFromParcel(Parcel in) {
+            displayId = in.readInt();
+            rotation = in.readInt();
+            sizeInfo = in.readParcelable(SizeInfo.class.getClassLoader());
+        }
+
+        public static final Creator<ConfigInfo> CREATOR = new Creator<ConfigInfo>() {
+            @Override
+            public ConfigInfo createFromParcel(Parcel source) {
+                final ConfigInfo sizeInfo = new ConfigInfo();
+                sizeInfo.readFromParcel(source);
+                return sizeInfo;
+            }
+
+            @Override
+            public ConfigInfo[] newArray(int size) {
+                return new ConfigInfo[size];
+            }
+        };
+    }
+
+    public static class SizeInfo implements Parcelable {
+        public int widthDp;
+        public int heightDp;
+        public int displayWidth;
+        public int displayHeight;
+        public int metricsWidth;
+        public int metricsHeight;
+        public int smallestWidthDp;
+        public int densityDpi;
+        public int orientation;
+
+        SizeInfo() {
+        }
+
+        public SizeInfo(Display display, DisplayMetrics metrics, Configuration config) {
+            if (display != null) {
+                final Point displaySize = new Point();
+                display.getSize(displaySize);
+                displayWidth = displaySize.x;
+                displayHeight = displaySize.y;
+            }
+
+            widthDp = config.screenWidthDp;
+            heightDp = config.screenHeightDp;
+            metricsWidth = metrics.widthPixels;
+            metricsHeight = metrics.heightPixels;
+            smallestWidthDp = config.smallestScreenWidthDp;
+            densityDpi = config.densityDpi;
+            orientation = config.orientation;
+        }
+
+        @Override
+        public String toString() {
+            return "SizeInfo: {widthDp=" + widthDp + " heightDp=" + heightDp
+                    + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
+                    + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
+                    + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
+                    + " orientation=" + orientation + "}";
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof SizeInfo)) {
+                return false;
+            }
+            final SizeInfo that = (SizeInfo) obj;
+            return widthDp == that.widthDp
+                    && heightDp == that.heightDp
+                    && displayWidth == that.displayWidth
+                    && displayHeight == that.displayHeight
+                    && metricsWidth == that.metricsWidth
+                    && metricsHeight == that.metricsHeight
+                    && smallestWidthDp == that.smallestWidthDp
+                    && densityDpi == that.densityDpi
+                    && orientation == that.orientation;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(widthDp);
+            dest.writeInt(heightDp);
+            dest.writeInt(displayWidth);
+            dest.writeInt(displayHeight);
+            dest.writeInt(metricsWidth);
+            dest.writeInt(metricsHeight);
+            dest.writeInt(smallestWidthDp);
+            dest.writeInt(densityDpi);
+            dest.writeInt(orientation);
+        }
+
+        public void readFromParcel(Parcel in) {
+            widthDp = in.readInt();
+            heightDp = in.readInt();
+            displayWidth = in.readInt();
+            displayHeight = in.readInt();
+            metricsWidth = in.readInt();
+            metricsHeight = in.readInt();
+            smallestWidthDp = in.readInt();
+            densityDpi = in.readInt();
+            orientation = in.readInt();
+        }
+
+        public static final Creator<SizeInfo> CREATOR = new Creator<SizeInfo>() {
+            @Override
+            public SizeInfo createFromParcel(Parcel source) {
+                final SizeInfo sizeInfo = new SizeInfo();
+                sizeInfo.readFromParcel(source);
+                return sizeInfo;
+            }
+
+            @Override
+            public SizeInfo[] newArray(int size) {
+                return new SizeInfo[size];
+            }
+        };
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/TestJournalProvider.java b/tests/framework/base/activitymanager/util/src/android/server/am/TestJournalProvider.java
new file mode 100644
index 0000000..3c2864c
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/TestJournalProvider.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.am;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.server.am.CommandSession.ActivityCallback;
+import android.server.am.CommandSession.ConfigInfo;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Let other testing packages put information for test cases to verify.
+ *
+ * This is a global container that there is no connection between test cases and testing components.
+ * If a precise communication is required, use {@link CommandSession.ActivitySessionClient} instead.
+ *
+ * <p>Sample:</p>
+ * <pre>
+ * // In test case:
+ * void testSomething() {
+ *     TestJournalContainer.start();
+ *     // Launch the testing activity.
+ *     // ...
+ *     assertTrue(TestJournalContainer.get(COMPONENT_NAME_OF_TESTING_ACTIVITY).extras
+ *             .getBoolean("test"));
+ * }
+ *
+ * // In the testing activity:
+ * protected void onResume() {
+ *     super.onResume();
+ *     TestJournalProvider.putExtras(this, bundle -> bundle.putBoolean("test", true));
+ * }
+ * </pre>
+ */
+public class TestJournalProvider extends ContentProvider {
+    private static final boolean DEBUG = "eng".equals(Build.TYPE);
+    private static final String TAG = TestJournalProvider.class.getSimpleName();
+    private static final Uri URI = Uri.parse("content://android.server.am.testjournalprovider");
+
+    /** Indicates who owns the events. */
+    private static final String EXTRA_KEY_OWNER = "key_owner";
+    /** Puts a {@link ActivityCallback} into the journal container for who receives the callback. */
+    private static final String METHOD_ADD_CALLBACK = "add_callback";
+    /** Sets the {@link ConfigInfo} for who reports the configuration info. */
+    private static final String METHOD_SET_LAST_CONFIG_INFO = "set_last_config_info";
+    /** Puts any additional information. */
+    private static final String METHOD_PUT_EXTRAS = "put_extras";
+
+    /** Avoid accidentally getting data from {@link #TestJournalContainer} in another process. */
+    private static boolean sCrossProcessAccessGuard;
+
+    @Override
+    public boolean onCreate() {
+        sCrossProcessAccessGuard = true;
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        switch (method) {
+            case METHOD_ADD_CALLBACK:
+                ensureExtras(method, extras);
+                TestJournalContainer.get().addCallback(
+                        extras.getString(EXTRA_KEY_OWNER), extras.getParcelable(method));
+                break;
+
+            case METHOD_SET_LAST_CONFIG_INFO:
+                ensureExtras(method, extras);
+                TestJournalContainer.get().setLastConfigInfo(
+                        extras.getString(EXTRA_KEY_OWNER), extras.getParcelable(method));
+                break;
+
+            case METHOD_PUT_EXTRAS:
+                ensureExtras(method, extras);
+                TestJournalContainer.get().putExtras(
+                        extras.getString(EXTRA_KEY_OWNER), extras);
+                break;
+        }
+        return null;
+    }
+
+    private static void ensureExtras(String method, Bundle extras) {
+        if (extras == null) {
+            throw new IllegalArgumentException(
+                    "The calling method=" + method + " does not allow null bundle");
+        }
+        extras.setClassLoader(TestJournal.class.getClassLoader());
+        if (DEBUG) {
+            extras.size(); // Trigger unparcel for printing plain text.
+            Log.i(TAG, method + " extras=" + extras);
+        }
+    }
+
+    /** Records the activity is called with the given callback. */
+    public static void putActivityCallback(Activity activity, ActivityCallback callback) {
+        try (TestJournalClient client = TestJournalClient.create(activity,
+                activity.getComponentName())) {
+            client.addCallback(callback);
+        }
+    }
+
+    /** Puts information about the activity. */
+    public static void putExtras(Activity activity, Consumer<Bundle> bundleFiller) {
+        putExtras(activity, activity.getComponentName(), bundleFiller);
+    }
+
+    /** Puts information about the component. */
+    public static void putExtras(Context context, ComponentName owner,
+            Consumer<Bundle> bundleFiller) {
+        putExtras(context, componentNameToKey(owner), bundleFiller);
+    }
+
+    /** Puts information about the keyword. */
+    public static void putExtras(Context context, String owner, Consumer<Bundle> bundleFiller) {
+        try (TestJournalClient client = TestJournalClient.create(context, owner)) {
+            final Bundle extras = new Bundle();
+            bundleFiller.accept(extras);
+            client.putExtras(extras);
+        }
+    }
+
+    @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;
+    }
+
+    private static String componentNameToKey(ComponentName name) {
+        return name.flattenToShortString();
+    }
+
+    /**
+     * Executes from the testing component to put their info to {@link TestJournalProvider}.
+     * The caller can be from any process or package.
+     */
+    public static class TestJournalClient implements AutoCloseable {
+        private static final String EMPTY_ARG = "";
+        private final ContentProviderClient mClient;
+        private final String mOwner;
+
+        public TestJournalClient(ContentProviderClient client, String owner) {
+            mClient = client;
+            mOwner = owner;
+        }
+
+        private void callWithExtras(String method, Bundle extras) {
+            extras.putString(EXTRA_KEY_OWNER, mOwner);
+            try {
+                mClient.call(method, EMPTY_ARG, extras);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void addCallback(ActivityCallback callback) {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(METHOD_ADD_CALLBACK, callback);
+            callWithExtras(METHOD_ADD_CALLBACK, extras);
+        }
+
+        public void setLastConfigInfo(ConfigInfo configInfo) {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(METHOD_SET_LAST_CONFIG_INFO, configInfo);
+            callWithExtras(METHOD_SET_LAST_CONFIG_INFO, extras);
+        }
+
+        public void putExtras(Bundle extras) {
+            callWithExtras(METHOD_PUT_EXTRAS, extras);
+        }
+
+        @Override
+        public void close() {
+            mClient.close();
+        }
+
+        @NonNull
+        static TestJournalClient create(Context context, ComponentName owner) {
+            return create(context, componentNameToKey(owner));
+        }
+
+        @NonNull
+        static TestJournalClient create(Context context, String owner) {
+            final ContentProviderClient client = context.getContentResolver()
+                    .acquireContentProviderClient(URI);
+            if (client == null) {
+                throw new RuntimeException("Unable to acquire " + URI);
+            }
+            return new TestJournalClient(client, owner);
+        }
+    }
+
+    /** The basic unit to store testing information. */
+    public static class TestJournal {
+        @NonNull
+        public final ArrayList<ActivityCallback> callbacks = new ArrayList<>();
+        @NonNull
+        public final Bundle extras = new Bundle();
+        @Nullable
+        public ConfigInfo lastConfigInfo;
+    }
+
+    /**
+     * The container lives in test case side. It stores the information from testing components.
+     * The caller must be in the same process as {@link TestJournalProvider}.
+     */
+    public static class TestJournalContainer {
+        private static TestJournalContainer sInstance;
+        private final ArrayMap<String, TestJournal> mContainer = new ArrayMap<>();
+
+        private TestJournalContainer() {
+        }
+
+        @NonNull
+        public static TestJournal get(ComponentName owner) {
+            return get(componentNameToKey(owner));
+        }
+
+        @NonNull
+        public static TestJournal get(String owner) {
+            return get().getTestJournal(owner);
+        }
+
+        private synchronized TestJournal getTestJournal(String owner) {
+            TestJournal info = mContainer.get(owner);
+            if (info == null) {
+                info = new TestJournal();
+                mContainer.put(owner, info);
+            }
+            return info;
+        }
+
+        synchronized void addCallback(String owner, ActivityCallback callback) {
+            getTestJournal(owner).callbacks.add(callback);
+        }
+
+        synchronized void setLastConfigInfo(String owner, ConfigInfo configInfo) {
+            getTestJournal(owner).lastConfigInfo = configInfo;
+        }
+
+        synchronized void putExtras(String owner, Bundle extras) {
+            getTestJournal(owner).extras.putAll(extras);
+        }
+
+        private synchronized static TestJournalContainer get() {
+            if (!TestJournalProvider.sCrossProcessAccessGuard) {
+                throw new IllegalAccessError(TestJournalProvider.class.getSimpleName()
+                        + " is not alive in this process");
+            }
+            if (sInstance == null) {
+                sInstance = new TestJournalContainer();
+            }
+            return sInstance;
+        }
+
+        /**
+         * The method should be called when we are only interested in the following events. It
+         * actually clears the previous records.
+         */
+        @NonNull
+        public static TestJournalContainer start() {
+            final TestJournalContainer instance = get();
+            synchronized (instance) {
+                instance.mContainer.clear();
+            }
+            return instance;
+        }
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/UiDeviceUtils.java b/tests/framework/base/activitymanager/util/src/android/server/am/UiDeviceUtils.java
index 0283d85..3d89529 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/UiDeviceUtils.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/UiDeviceUtils.java
@@ -66,7 +66,7 @@
         getDevice().pressEnter();
     }
 
-    static void pressHomeButton() {
+    public static void pressHomeButton() {
         if (DEBUG) Log.d(TAG, "pressHomeButton");
         getDevice().pressHome();
     }
@@ -88,14 +88,14 @@
                 "***Waiting for device sleep...");
     }
 
-    static void pressWakeupButton() {
+    public static void pressWakeupButton() {
         if (DEBUG) Log.d(TAG, "pressWakeupButton");
         final PowerManager pm = getContext().getSystemService(PowerManager.class);
         retryPressKeyCode(KEYCODE_WAKEUP, () -> pm != null && pm.isInteractive(),
                 "***Waiting for device wakeup...");
     }
 
-    static void pressUnlockButton() {
+    public static void pressUnlockButton() {
         if (DEBUG) Log.d(TAG, "pressUnlockButton");
         final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
         retryPressKeyCode(KEYCODE_MENU, () -> kgm != null && !kgm.isKeyguardLocked(),
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
index 2a9b351..17ca60b 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
index 3d30f66..459dffb 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
@@ -24,27 +24,32 @@
 import static android.server.am.StateLogger.logE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.view.WindowManager;
+import android.view.nano.DisplayInfoProto;
+import android.view.nano.ViewProtoEnums;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-import android.view.nano.DisplayInfoProto;
 
 import com.android.server.wm.nano.AppTransitionProto;
 import com.android.server.wm.nano.AppWindowTokenProto;
 import com.android.server.wm.nano.ConfigurationContainerProto;
+import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.DisplayFramesProto;
-import com.android.server.wm.nano.DisplayProto;
 import com.android.server.wm.nano.IdentifierProto;
 import com.android.server.wm.nano.PinnedStackControllerProto;
 import com.android.server.wm.nano.StackProto;
 import com.android.server.wm.nano.TaskProto;
 import com.android.server.wm.nano.WindowContainerProto;
+import com.android.server.wm.nano.WindowFramesProto;
 import com.android.server.wm.nano.WindowManagerServiceDumpProto;
 import com.android.server.wm.nano.WindowStateAnimatorProto;
 import com.android.server.wm.nano.WindowStateProto;
@@ -95,6 +100,12 @@
     private static final String STARTING_WINDOW_PREFIX = "Starting ";
     private static final String DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: ";
 
+    /** @see WindowManager.LayoutParams */
+    private static final int TYPE_NAVIGATION_BAR = 2019;
+
+    /** @see WindowManager.LayoutParams */
+    private static final int TYPE_NAVIGATION_BAR_PANEL = 2024;
+
     // Windows in z-order with the top most at the front of the list.
     private List<WindowState> mWindowStates = new ArrayList();
     // Stacks in z-order with the top most at the front of the list, starting with primary display.
@@ -105,8 +116,6 @@
     private List<Display> mDisplays = new ArrayList();
     private String mFocusedWindow = null;
     private String mFocusedApp = null;
-    private String mLastTransition = null;
-    private String mAppTransitionState = null;
     private String mInputMethodWindowAppToken = null;
     private Rect mDefaultPinnedStackBounds = new Rect();
     private Rect mPinnedStackMovementBounds = new Rect();
@@ -185,7 +194,7 @@
         }
         mFocusedApp = state.focusedApp;
         for (int i = 0; i < state.rootWindowContainer.displays.length; i++) {
-            DisplayProto displayProto = state.rootWindowContainer.displays[i];
+            DisplayContentProto displayProto = state.rootWindowContainer.displays[i];
             final Display display = new Display(displayProto);
             mDisplays.add(display);
             allWindows.addAll(display.getWindows());
@@ -226,15 +235,6 @@
         mDisplayFrozen = state.displayFrozen;
         mRotation = state.rotation;
         mLastOrientation = state.lastOrientation;
-        AppTransitionProto appTransitionProto = state.appTransition;
-        int appState = 0;
-        int lastTransition = 0;
-        if (appTransitionProto != null) {
-            appState = appTransitionProto.appTransitionState;
-            lastTransition = appTransitionProto.lastUsedAppTransition;
-        }
-        mAppTransitionState = appStateToString(appState);
-        mLastTransition = appTransitionToString(lastTransition);
     }
 
     static String appStateToString(int appState) {
@@ -255,69 +255,72 @@
 
     static String appTransitionToString(int transition) {
         switch (transition) {
-            case AppTransitionProto.TRANSIT_UNSET: {
+            case ViewProtoEnums.TRANSIT_UNSET: {
                 return "TRANSIT_UNSET";
             }
-            case AppTransitionProto.TRANSIT_NONE: {
+            case ViewProtoEnums.TRANSIT_NONE: {
                 return "TRANSIT_NONE";
             }
-            case AppTransitionProto.TRANSIT_ACTIVITY_OPEN: {
+            case ViewProtoEnums.TRANSIT_ACTIVITY_OPEN: {
                 return TRANSIT_ACTIVITY_OPEN;
             }
-            case AppTransitionProto.TRANSIT_ACTIVITY_CLOSE: {
+            case ViewProtoEnums.TRANSIT_ACTIVITY_CLOSE: {
                 return TRANSIT_ACTIVITY_CLOSE;
             }
-            case AppTransitionProto.TRANSIT_TASK_OPEN: {
+            case ViewProtoEnums.TRANSIT_TASK_OPEN: {
                 return TRANSIT_TASK_OPEN;
             }
-            case AppTransitionProto.TRANSIT_TASK_CLOSE: {
+            case ViewProtoEnums.TRANSIT_TASK_CLOSE: {
                 return TRANSIT_TASK_CLOSE;
             }
-            case AppTransitionProto.TRANSIT_TASK_TO_FRONT: {
+            case ViewProtoEnums.TRANSIT_TASK_TO_FRONT: {
                 return "TRANSIT_TASK_TO_FRONT";
             }
-            case AppTransitionProto.TRANSIT_TASK_TO_BACK: {
+            case ViewProtoEnums.TRANSIT_TASK_TO_BACK: {
                 return "TRANSIT_TASK_TO_BACK";
             }
-            case AppTransitionProto.TRANSIT_WALLPAPER_CLOSE: {
+            case ViewProtoEnums.TRANSIT_WALLPAPER_CLOSE: {
                 return TRANSIT_WALLPAPER_CLOSE;
             }
-            case AppTransitionProto.TRANSIT_WALLPAPER_OPEN: {
+            case ViewProtoEnums.TRANSIT_WALLPAPER_OPEN: {
                 return TRANSIT_WALLPAPER_OPEN;
             }
-            case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_OPEN: {
+            case ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_OPEN: {
                 return TRANSIT_WALLPAPER_INTRA_OPEN;
             }
-            case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_CLOSE: {
+            case ViewProtoEnums.TRANSIT_WALLPAPER_INTRA_CLOSE: {
                 return TRANSIT_WALLPAPER_INTRA_CLOSE;
             }
-            case AppTransitionProto.TRANSIT_TASK_OPEN_BEHIND: {
+            case ViewProtoEnums.TRANSIT_TASK_OPEN_BEHIND: {
                 return "TRANSIT_TASK_OPEN_BEHIND";
             }
-            case AppTransitionProto.TRANSIT_ACTIVITY_RELAUNCH: {
+            case ViewProtoEnums.TRANSIT_ACTIVITY_RELAUNCH: {
                 return "TRANSIT_ACTIVITY_RELAUNCH";
             }
-            case AppTransitionProto.TRANSIT_DOCK_TASK_FROM_RECENTS: {
+            case ViewProtoEnums.TRANSIT_DOCK_TASK_FROM_RECENTS: {
                 return "TRANSIT_DOCK_TASK_FROM_RECENTS";
             }
-            case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY: {
+            case ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY: {
                 return TRANSIT_KEYGUARD_GOING_AWAY;
             }
-            case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
+            case ViewProtoEnums.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
                 return TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
             }
-            case AppTransitionProto.TRANSIT_KEYGUARD_OCCLUDE: {
+            case ViewProtoEnums.TRANSIT_KEYGUARD_OCCLUDE: {
                 return TRANSIT_KEYGUARD_OCCLUDE;
             }
-            case AppTransitionProto.TRANSIT_KEYGUARD_UNOCCLUDE: {
+            case ViewProtoEnums.TRANSIT_KEYGUARD_UNOCCLUDE: {
                 return TRANSIT_KEYGUARD_UNOCCLUDE;
             }
-            case AppTransitionProto.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN: {
+            case ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN: {
                 return TRANSIT_TRANSLUCENT_ACTIVITY_OPEN;
             }
-            case AppTransitionProto.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE: {
+            case ViewProtoEnums.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE: {
                 return TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE;
             }
+            case ViewProtoEnums.TRANSIT_CRASHING_ACTIVITY_CLOSE: {
+                return "TRANSIT_CRASHING_ACTIVITY_CLOSE";
+            }
             default: {
                 fail("Invalid lastUsedAppTransition");
                 return null;
@@ -325,12 +328,38 @@
         }
     }
 
+    List<WindowState> getMatchingWindowType(int type) {
+        return getMatchingWindows(ws -> type == ws.mType).collect(Collectors.toList());
+    }
+
     List<String> getMatchingWindowTokens(final String windowName) {
         return getMatchingWindows(ws -> windowName.equals(ws.getName()))
                 .map(WindowState::getToken)
                 .collect(Collectors.toList());
     }
 
+    List<WindowState> getAllNavigationBarStates() {
+        return getMatchingWindows(WindowManagerState::isValidNavBarType)
+                .collect(Collectors.toList());
+    }
+
+    WindowState getAndAssertSingleNavBarWindowOnDisplay(int displayId) {
+        List<WindowState> navWindow = getMatchingWindows(ws ->
+                isValidNavBarType(ws) && ws.getDisplayId() == displayId)
+                .collect(Collectors.toList());
+
+        // We may need some time to wait for nav bar showing.
+        // It's Ok to get 0 nav bar here.
+        assertTrue("There should be at most one navigation bar on a display",
+                navWindow.size() <= 1);
+
+        return navWindow.isEmpty() ? null : navWindow.get(0);
+    }
+
+    private static boolean isValidNavBarType(WindowState navState) {
+        return TYPE_NAVIGATION_BAR == navState.getType();
+    }
+
     public List<WindowState> getMatchingVisibleWindowState(final String windowName) {
         return getMatchingWindows(ws -> ws.isShown() && windowName.equals(ws.getName()))
                 .collect(Collectors.toList());
@@ -392,12 +421,12 @@
         return mFocusedApp;
     }
 
-    String getLastTransition() {
-        return mLastTransition;
+    String getDefaultDisplayLastTransition() {
+        return getDisplay(DEFAULT_DISPLAY).getLastTransition();
     }
 
-    String getAppTransitionState() {
-        return mAppTransitionState;
+    String getDefaultDisplayAppTransitionState() {
+        return getDisplay(DEFAULT_DISPLAY).getAppTransitionState();
     }
 
     int getFrontStackId(int displayId) {
@@ -443,7 +472,7 @@
     /**
      * Check if there exists a window record with matching windowName.
      */
-    boolean containsWindow(String windowName) {
+    public boolean containsWindow(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
                 return true;
@@ -560,7 +589,6 @@
         mDisplayStacks.clear();
         mFocusedWindow = null;
         mFocusedApp = null;
-        mLastTransition = null;
         mInputMethodWindowAppToken = null;
         mIsDockedStackMinimized = false;
         mDefaultPinnedStackBounds.setEmpty();
@@ -609,8 +637,9 @@
     static class WindowTask extends WindowContainer {
 
         int mTaskId;
-        Rect mTempInsetBounds;
         List<String> mAppTokens = new ArrayList<>();
+        private int mSurfaceWidth;
+        private int mSurfaceHeight;
 
         WindowTask(TaskProto proto) {
             super(proto.windowContainer);
@@ -628,7 +657,16 @@
                     mSubWindows.addAll(window.getWindows());
                 }
             }
-            mTempInsetBounds = extract(proto.tempInsetBounds);
+            mSurfaceWidth = proto.surfaceWidth;
+            mSurfaceHeight = proto.surfaceHeight;
+        }
+
+        int getSurfaceWidth() {
+            return mSurfaceWidth;
+        }
+
+        int getSurfaceHeight() {
+            return mSurfaceHeight;
         }
     }
 
@@ -694,8 +732,12 @@
         private int mDpi;
         private Rect mStableBounds;
         private String mName;
+        private int mSurfaceSize;
+        private String mFocusedApp;
+        private String mLastTransition;
+        private String mAppTransitionState;
 
-        public Display(DisplayProto proto) {
+        public Display(DisplayContentProto proto) {
             super(proto.windowContainer);
             mDisplayId = proto.id;
             for (int i = 0; i < proto.aboveAppWindows.length; i++) {
@@ -718,6 +760,18 @@
             if (displayFramesProto != null) {
                 mStableBounds = extract(displayFramesProto.stableBounds);
             }
+            mSurfaceSize = proto.surfaceSize;
+            mFocusedApp = proto.focusedApp;
+
+            final AppTransitionProto appTransitionProto = proto.appTransition;
+            int appState = 0;
+            int lastTransition = 0;
+            if (appTransitionProto != null) {
+                appState = appTransitionProto.appTransitionState;
+                lastTransition = appTransitionProto.lastUsedAppTransition;
+            }
+            mAppTransitionState = appStateToString(appState);
+            mLastTransition = appTransitionToString(lastTransition);
         }
 
         private void addWindowsFromTokenProto(WindowTokenProto proto) {
@@ -749,6 +803,18 @@
             return mName;
         }
 
+        int getSurfaceSize() {
+            return mSurfaceSize;
+        }
+
+        String getFocusedApp() {
+            return mFocusedApp;
+        }
+
+        String getLastTransition() { return mLastTransition; }
+
+        String getAppTransitionState() { return mAppTransitionState; }
+
         @Override
         public String toString() {
             return "Display #" + mDisplayId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
@@ -771,10 +837,10 @@
         private int mStackId;
         private int mLayer;
         private boolean mShown;
-        private Rect mContainingFrame = new Rect();
-        private Rect mParentFrame = new Rect();
-        private Rect mContentFrame = new Rect();
-        private Rect mFrame = new Rect();
+        private Rect mContainingFrame;
+        private Rect mParentFrame;
+        private Rect mContentFrame;
+        private Rect mFrame;
         private Rect mSurfaceInsets = new Rect();
         private Rect mContentInsets = new Rect();
         private Rect mGivenContentInsets = new Rect();
@@ -800,11 +866,14 @@
                 mCrop = extract(animatorProto.lastClipRect);
             }
             mGivenContentInsets = extract(proto.givenContentInsets);
-            mFrame = extract(proto.frame);
-            mContainingFrame = extract(proto.containingFrame);
-            mParentFrame = extract(proto.parentFrame);
-            mContentFrame = extract(proto.contentFrame);
-            mContentInsets = extract(proto.contentInsets);
+            WindowFramesProto windowFramesProto = proto.windowFrames;
+            if (windowFramesProto != null) {
+                mFrame = extract(windowFramesProto.frame);
+                mContainingFrame = extract(windowFramesProto.containingFrame);
+                mParentFrame = extract(windowFramesProto.parentFrame);
+                mContentFrame = extract(windowFramesProto.contentFrame);
+                mContentInsets = extract(windowFramesProto.contentInsets);
+            }
             mSurfaceInsets = extract(proto.surfaceInsets);
             if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
                 mWindowType = WINDOW_TYPE_STARTING;
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java b/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
index b84995b..4dd800f 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
@@ -7,7 +7,10 @@
 import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -106,13 +109,9 @@
                 Log.i(TAG, "close: uri=" + mUri + " value=" + mInitialValue);
             }
         } else {
-            try {
-                delete(mUri);
-                if (DEBUG) {
-                    Log.i(TAG, "close: uri=" + mUri + " deleted");
-                }
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG, "Can't delete settings " + mUri, e);
+            delete(mUri);
+            if (DEBUG) {
+                Log.i(TAG, "close: uri=" + mUri + " deleted");
             }
         }
         if (DEBUG) {
@@ -122,7 +121,9 @@
 
     private static <T> void put(final Uri uri, final SettingsSetter<T> setter, T value)
             throws SettingNotFoundException {
-        setter.set(getContentResolver(), uri.getLastPathSegment(), value);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            setter.set(getContentResolver(), uri.getLastPathSegment(), value);
+        });
     }
 
     private static <T> T get(final Uri uri, final SettingsGetter<T> getter)
@@ -130,8 +131,18 @@
         return getter.get(getContentResolver(), uri.getLastPathSegment());
     }
 
-    private static void delete(final Uri uri) throws IllegalArgumentException {
-        getContentResolver().delete(uri, null, null);
+    private 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());
+            return;
+        }
+        final String namespace = segments.get(0);
+        final String key = segments.get(1);
+        // SystemUtil.runWithShellPermissionIdentity (only applies to the permission checking in
+        // package manager and appops) does not change calling uid which is enforced in
+        // SettingsProvider for deletion, so it requires shell command to pass the restriction.
+        SystemUtil.runShellCommand("settings delete " + namespace + " " + key);
     }
 
     private static ContentResolver getContentResolver() {
diff --git a/tests/framework/base/windowmanager/Android.mk b/tests/framework/base/windowmanager/Android.mk
index 850e7ad..c432ddf 100644
--- a/tests/framework/base/windowmanager/Android.mk
+++ b/tests/framework/base/windowmanager/Android.mk
@@ -39,7 +39,7 @@
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
 
-LOCAL_SDK_VERSION := test_current
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index aee8390..81f1b9d 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -19,9 +19,7 @@
           package="android.server.cts.wm"
           android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
-    <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
 
     <application android:label="CtsWindowManagerDeviceTestCases">
         <uses-library android:name="android.test.runner"/>
@@ -34,10 +32,18 @@
 
         <activity android:name="android.server.wm.AlertWindowsAppOpsTestsActivity"/>
         <activity android:name="android.server.wm.DialogFrameTestActivity" />
-        <activity android:name="android.server.wm.DisplayCutoutTests$TestActivity" />
+        <activity android:name="android.server.wm.DisplayCutoutTests$TestActivity"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"/>
+        <activity android:name="android.server.wm.LayoutTestsActivity"
+                  android:theme="@style/no_animation" />
         <activity android:name="android.server.wm.LocationOnScreenTests$TestActivity"
-            android:theme="@style/no_starting_window" />
+                  android:theme="@style/no_starting_window" />
         <activity android:name="android.server.wm.LocationInWindowTests$TestActivity" />
+        <activity android:name="android.server.wm.WindowFocusTests$PrimaryActivity" />
+        <activity android:name="android.server.wm.WindowFocusTests$SecondaryActivity"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
+        <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity" />
 
     </application>
 
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 337ada0..f89997c 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS WindowManager 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="instant_app" />
     <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/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml
index 4e59d14..e52bdb0 100644
--- a/tests/framework/base/windowmanager/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/res/values/styles.xml
@@ -18,4 +18,8 @@
     <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
         <item name="android:windowDisablePreview">true</item>
     </style>
+    <style name="no_animation" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:windowDisablePreview">true</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
index 5d087a7..6657446 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
@@ -19,6 +19,9 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
 import static android.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_NOT_FOREGROUND;
@@ -46,7 +49,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.AppOpsUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -170,28 +173,34 @@
     }
 
     private void setAlertWindowPermission(boolean allow) throws Exception {
-        final String cmd = "appops set " + mServicePackageName
-                + " android:system_alert_window " + (allow ? "allow" : "deny");
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
+        final int mode = allow ? MODE_ALLOWED : MODE_ERRORED;
+        AppOpsUtils.setOpMode(mServicePackageName, OPSTR_SYSTEM_ALERT_WINDOW, mode);
     }
 
     private void assertImportance(ToIntFunction<ActivityManager> apiCaller,
             int expectedForO, int expectedForPreO) throws Exception {
-        final long TIMEOUT = SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(30);
-        int actual;
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity();
 
-        do {
-            // TODO: We should try to use ActivityManagerTest.UidImportanceListener here to listen
-            // for changes in the uid importance. However, the way it is currently structured
-            // doesn't really work for this use case right now...
-            Thread.sleep(500);
-            actual = apiCaller.applyAsInt(mAm);
-        } while (actual != expectedForO && (SystemClock.uptimeMillis() < TIMEOUT));
+            final long TIMEOUT = SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(30);
+            int actual;
+            do {
+                // TODO: We should try to use ActivityManagerTest.UidImportanceListener here to
+                // listen for changes in the uid importance. However, the way it is currently
+                // structured doesn't really work for this use case right now...
+                Thread.sleep(500);
+                actual = apiCaller.applyAsInt(mAm);
+            } while (actual != expectedForO && (SystemClock.uptimeMillis() < TIMEOUT));
 
-        assertEquals(expectedForO, actual);
+            assertEquals(expectedForO, actual);
 
-        // Check the result for pre-O apps.
-        assertEquals(expectedForPreO, apiCaller.applyAsInt(mAm25));
+            // Check the result for pre-O apps.
+            assertEquals(expectedForPreO, apiCaller.applyAsInt(mAm25));
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
index 3ab7c19..f83585a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -22,6 +22,12 @@
 import static android.server.wm.alertwindowapp.Components.ALERT_WINDOW_TEST_ACTIVITY;
 import static android.server.wm.alertwindowappsdk25.Components.SDK25_ALERT_WINDOW_TEST_ACTIVITY;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -91,8 +97,8 @@
         super.tearDown();
         resetPermissionState(ALERT_WINDOW_TEST_ACTIVITY);
         resetPermissionState(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
-        stopTestPackage(ALERT_WINDOW_TEST_ACTIVITY);
-        stopTestPackage(SDK25_ALERT_WINDOW_TEST_ACTIVITY);
+        stopTestPackage(ALERT_WINDOW_TEST_ACTIVITY.getPackageName());
+        stopTestPackage(SDK25_ALERT_WINDOW_TEST_ACTIVITY.getPackageName());
     }
 
     @Test
@@ -152,13 +158,12 @@
             // in place for SYSTEM_ALERT_WINDOW, which allows the window
             // to be created, but will be hidden instead.
             if (isUiModeLockedToVrHeadset()) {
-                assertTrue("Should not be empty alertWindows=" + alertWindows,
-                        !alertWindows.isEmpty());
+                assertThat("Should not be empty alertWindows",
+                        alertWindows, hasSize(greaterThan(0)));
                 assertTrue("All alert windows should be hidden",
                         allWindowsHidden(alertWindows));
             } else {
-                assertTrue("Should be empty alertWindows=" + alertWindows,
-                        alertWindows.isEmpty());
+                assertThat("Should be empty alertWindows", alertWindows, empty());
                 assertTrue(AppOpsUtils.rejectedOperationLogged(packageName,
                         OPSTR_SYSTEM_ALERT_WINDOW));
                 return;
@@ -168,8 +173,8 @@
         if (atLeastO) {
             // Assert that only TYPE_APPLICATION_OVERLAY was created.
             for (WindowManagerState.WindowState win : alertWindows) {
-                assertTrue("Can't create win=" + win + " on SDK O or greater",
-                        win.getType() == TYPE_APPLICATION_OVERLAY);
+                assertEquals("Can't create win=" + win + " on SDK O or greater",
+                        win.getType(), TYPE_APPLICATION_OVERLAY);
             }
         }
 
@@ -183,17 +188,17 @@
                 alertWindows.get(alertWindows.size() - 1);
 
         // Assert that the alert windows have higher z-order than the main app window
-        assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
-                + mainAppWindow,
-                wmState.getZOrder(lowestAlertWindow) > wmState.getZOrder(mainAppWindow));
+        assertThat("lowestAlertWindow has higher z-order than mainAppWindow",
+                wmState.getZOrder(lowestAlertWindow),
+                greaterThan(wmState.getZOrder(mainAppWindow)));
 
         // Assert that legacy alert windows have a lower z-order than the new alert window layer.
         final WindowManagerState.WindowState appOverlayWindow =
                 wmState.getWindowByPackageName(packageName, TYPE_APPLICATION_OVERLAY);
         if (appOverlayWindow != null && highestAlertWindow != appOverlayWindow) {
-            assertTrue("highestAlertWindow=" + highestAlertWindow
-                            + " greater than appOverlayWindow=" + appOverlayWindow,
-                    wmState.getZOrder(highestAlertWindow) < wmState.getZOrder(appOverlayWindow));
+            assertThat("highestAlertWindow has lower z-order than appOverlayWindow",
+                    wmState.getZOrder(highestAlertWindow),
+                    lessThan(wmState.getZOrder(appOverlayWindow)));
         }
 
         // Assert that alert windows are below key system windows.
@@ -201,9 +206,9 @@
                 wmState.getWindowsByPackageName(packageName, SYSTEM_WINDOW_TYPES);
         if (!systemWindows.isEmpty()) {
             final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
-            assertTrue("highestAlertWindow=" + highestAlertWindow
-                            + " greater than lowestSystemWindow=" + lowestSystemWindow,
-                    wmState.getZOrder(highestAlertWindow) < wmState.getZOrder(lowestSystemWindow));
+            assertThat("highestAlertWindow has lower z-order than lowestSystemWindow",
+                    wmState.getZOrder(highestAlertWindow),
+                    lessThan(wmState.getZOrder(lowestSystemWindow)));
         }
         assertTrue(AppOpsUtils.allowedOperationLogged(packageName, OPSTR_SYSTEM_ALERT_WINDOW));
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
index 5d26234..7bf57e8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -33,21 +33,32 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.platform.test.annotations.AppModeFull;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Map;
+import java.util.function.BooleanSupplier;
 import java.util.regex.Pattern;
 
 /**
@@ -56,15 +67,11 @@
  */
 @Presubmit
 @AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_STACKS")
+@FlakyTest(bugId = 109874623)
 public class CrossAppDragAndDropTests {
     private static final String TAG = "CrossAppDragAndDrop";
 
-    private static final String AM_FORCE_STOP = "am force-stop ";
-    private static final String AM_MOVE_TASK = "am stack move-task ";
-    private static final String AM_RESIZE_TASK = "am task resize ";
-    private static final String AM_REMOVE_STACK = "am stack remove ";
     private static final String AM_START_N = "am start -n ";
-    private static final String AM_STACK_LIST = "am stack list";
     private static final String TASK_ID_PREFIX = "taskId";
 
     // Regex pattern to match adb shell am stack list output of the form:
@@ -120,6 +127,7 @@
 
     protected Context mContext;
     protected ActivityManager mAm;
+    protected ActivityTaskManager mAtm;
 
     private Map<String, String> mSourceResults;
     private Map<String, String> mTargetResults;
@@ -131,6 +139,49 @@
     private String mSourceLogTag;
     private String mTargetLogTag;
 
+    private LifecycleMonitor mLifecycleMonitor = new LifecycleMonitor();
+
+    private static class LifecycleMonitor implements ActivityLifecycleCallback {
+        Stage mCurrentStage;
+        Activity mCurrentActivity;
+
+        @Override
+        synchronized public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+            mCurrentStage = stage;
+            mCurrentActivity = activity;
+        }
+
+        public void waitForStage(String packageName, String activityName, Stage stage) {
+            final ComponentName component = new ComponentName(packageName, activityName);
+            waitForConditionWithTimeout(() -> {
+                if (mCurrentActivity == null || mCurrentStage == null) {
+                    return false;
+                }
+                return mCurrentActivity.getComponentName().equals(component)
+                        && mCurrentStage.equals(stage);
+            }, 3000);
+        }
+
+        /** Blocking call to wait for a condition to become true with max timeout. */
+        synchronized private boolean waitForConditionWithTimeout(BooleanSupplier waitCondition,
+                long timeoutMs) {
+            final long timeout = System.currentTimeMillis() + timeoutMs;
+            while (!waitCondition.getAsBoolean()) {
+                final long waitMs = timeout - System.currentTimeMillis();
+                if (waitMs <= 0) {
+                    // Timeout expired.
+                    return false;
+                }
+                try {
+                    wait(500);
+                } catch (InterruptedException e) {
+                    // Weird, let's retry.
+                }
+            }
+            return true;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         assumeTrue(supportsDragAndDrop());
@@ -142,10 +193,13 @@
 
         mContext = InstrumentationRegistry.getContext();
         mAm = mContext.getSystemService(ActivityManager.class);
+        mAtm = mContext.getSystemService(ActivityTaskManager.class);
 
         mSourcePackageName = SOURCE_PACKAGE_NAME;
         mTargetPackageName = TARGET_PACKAGE_NAME;
         cleanupState();
+
+        ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleMonitor);
     }
 
     @After
@@ -154,28 +208,17 @@
           return;
         }
 
-        executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
-        executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
-    }
-
-    private void clearLogs() {
-        executeShellCommand("logcat -c");
+        ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleMonitor);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mAm.forceStopPackage(mSourcePackageName);
+            mAm.forceStopPackage(mTargetPackageName);
+        });
     }
 
     private String getStartCommand(String componentName, String modeExtra, String logtag) {
         return AM_START_N + componentName + " -e mode " + modeExtra + " -e logtag " + logtag;
     }
 
-    private String getMoveTaskCommand(int taskId, int stackId) {
-        return AM_MOVE_TASK + taskId + " " + stackId + " true";
-    }
-
-    private String getResizeTaskCommand(int taskId, Point topLeft, Point bottomRight)
-            throws Exception {
-        return AM_RESIZE_TASK + taskId + " " + topLeft.x + " " + topLeft.y + " " + bottomRight.x
-                + " " + bottomRight.y;
-    }
-
     private String getComponentName(String packageName, String activityName) {
         return packageName + "/" + packageName + "." + activityName;
     }
@@ -185,23 +228,23 @@
      * is in a good state.
      */
     private void cleanupState() throws Exception {
-        executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
-        executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
-        executeShellCommand(AM_FORCE_STOP + TARGET_23_PACKAGE_NAME);
-        unlockDevice();
-        clearLogs();
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mAm.forceStopPackage(SOURCE_PACKAGE_NAME);
+            mAm.forceStopPackage(TARGET_PACKAGE_NAME);
+            mAm.forceStopPackage(TARGET_23_PACKAGE_NAME);
+            unlockDevice();
 
-        // Remove special stacks.
-        mAm.removeStacksInWindowingModes(new int[] {
-                WINDOWING_MODE_PINNED,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
-                WINDOWING_MODE_FREEFORM
+            // Remove special stacks.
+            mAtm.removeStacksInWindowingModes(new int[]{
+                    WINDOWING_MODE_PINNED,
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                    WINDOWING_MODE_FREEFORM
+            });
         });
     }
 
     private void launchDockedActivity(String packageName, String activityName, String mode,
             String logtag) throws Exception {
-        clearLogs();
         final String componentName = getComponentName(packageName, activityName);
         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
                 + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -210,7 +253,6 @@
 
     private void launchFullscreenActivity(String packageName, String activityName, String mode,
             String logtag) throws Exception {
-        clearLogs();
         final String componentName = getComponentName(packageName, activityName);
         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
                 + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
@@ -224,31 +266,19 @@
      */
     private void launchFreeformActivity(String packageName, String activityName, String mode,
             String logtag, Point displaySize, boolean leftSide) throws Exception {
-        clearLogs();
         final String componentName = getComponentName(packageName, activityName);
         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
                 + WINDOWING_MODE_FREEFORM);
         waitForResume(packageName, activityName);
         Point topLeft = new Point(leftSide ? 0 : displaySize.x / 2, 0);
         Point bottomRight = new Point(leftSide ? displaySize.x / 2 : displaySize.x, displaySize.y);
-        executeShellCommand(getResizeTaskCommand(getActivityTaskId(componentName), topLeft,
-                bottomRight));
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mAtm.resizeTask(getActivityTaskId(componentName),
+                        new Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)));
     }
 
     private void waitForResume(String packageName, String activityName) throws Exception {
-        final String fullActivityName = packageName + "." + activityName;
-        int retryCount = 3;
-        do {
-            Thread.sleep(500);
-            String logs = executeShellCommand("logcat -d -b events");
-            for (String line : logs.split("\\n")) {
-                if (line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
-                    return;
-                }
-            }
-        } while (retryCount-- > 0);
-
-        throw new Exception(fullActivityName + " has failed to start");
+        mLifecycleMonitor.waitForStage(packageName, activityName, Stage.RESUMED);
     }
 
     private void injectInput(Point from, Point to, int steps) throws Exception {
@@ -256,28 +286,36 @@
     }
 
     private String findTaskInfo(String name) {
-        final String output = executeShellCommand(AM_STACK_LIST);
-        final StringBuilder builder = new StringBuilder();
-        builder.append("Finding task info for task: ");
-        builder.append(name);
-        builder.append("\nParsing adb shell am output: ");
-        builder.append(output);
-        log(builder.toString());
-        final Pattern pattern = Pattern.compile(String.format(TASK_REGEX_PATTERN_STRING, name));
-        for (String line : output.split("\\n")) {
-            final String truncatedLine;
-            // Only look for the activity name before the "topActivity" string.
-            final int pos = line.indexOf("topActivity");
-            if (pos > 0) {
-                truncatedLine = line.substring(0, pos);
-            } else {
-                truncatedLine = line;
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity();
+
+            final String output = mAtm.listAllStacks();
+            final StringBuilder builder = new StringBuilder();
+            builder.append("Finding task info for task: ");
+            builder.append(name);
+            builder.append("\nParsing adb shell am output: ");
+            builder.append(output);
+            log(builder.toString());
+            final Pattern pattern = Pattern.compile(String.format(TASK_REGEX_PATTERN_STRING, name));
+            for (String line : output.split("\\n")) {
+                final String truncatedLine;
+                // Only look for the activity name before the "topActivity" string.
+                final int pos = line.indexOf("topActivity");
+                if (pos > 0) {
+                    truncatedLine = line.substring(0, pos);
+                } else {
+                    truncatedLine = line;
+                }
+                if (pattern.matcher(truncatedLine).find()) {
+                    return truncatedLine;
+                }
             }
-            if (pattern.matcher(truncatedLine).find()) {
-                return truncatedLine;
-            }
+            return "";
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
         }
-        return "";
     }
 
     private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
@@ -427,11 +465,11 @@
     }
 
     private static boolean supportsDragAndDrop() {
-        return ActivityManager.supportsMultiWindow(InstrumentationRegistry.getContext());
+        return ActivityTaskManager.supportsMultiWindow(InstrumentationRegistry.getContext());
     }
 
     private static boolean supportsSplitScreenMultiWindow() {
-        return ActivityManager.supportsSplitScreenMultiWindow(InstrumentationRegistry.getContext());
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(InstrumentationRegistry.getContext());
     }
 
     private static boolean supportsFreeformMultiWindow() {
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 c08dc08..2050928 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 
 import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasItem;
@@ -63,6 +64,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -74,6 +76,7 @@
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class DisplayCutoutTests {
+    static final Rect ZERO_RECT = new Rect();
 
     @Rule
     public final ErrorCollector mErrorCollector = new ErrorCollector();
@@ -107,7 +110,10 @@
     @Test
     public void testDisplayCutout_shortEdges_portrait() {
         runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, (a, insets, displayCutout, which) -> {
-            // common assertions in runTest are enough.
+            if (which == ROOT) {
+                assertThat("Display.getCutout() must equal view root cutout",
+                        a.getDisplay().getCutout(), equalTo(displayCutout));
+            }
         });
     }
 
@@ -133,6 +139,7 @@
 
         if (displayCutout != null) {
             commonAsserts(activity, insets, displayCutout);
+            assertCutoutsAreConsistentWithInsets(insets, displayCutout);
         }
         test.run(activity, insets, displayCutout, ROOT);
 
@@ -148,11 +155,34 @@
                         + "cutout safe insets",
                 safeInsets(cutout), insetsLessThanOrEqualTo(systemWindowInsets(insets)));
         assertOnlyShortEdgeHasInsets(activity, cutout);
+        assertOnlyShortEdgeHasBounds(activity, insets, cutout);
         assertCutoutsAreWithinSafeInsets(activity, cutout);
         assertBoundsAreNonEmpty(cutout);
         assertAtMostOneCutoutPerEdge(activity, cutout);
     }
 
+    private void assertCutoutIsConsistentWithInset(String position, int insetSize, Rect bound) {
+        if (insetSize > 0) {
+            assertThat("cutout must have a bound on the " + position, bound,
+                    not(equalTo(ZERO_RECT)));
+        } else {
+            assertThat("cutout  must have no bound on the " + position, bound,
+                    equalTo(ZERO_RECT));
+        }
+    }
+
+    public void assertCutoutsAreConsistentWithInsets(WindowInsets insets, DisplayCutout cutout) {
+        final Rect systemWindowInsets = systemWindowInsets(insets);
+        assertCutoutIsConsistentWithInset("top", systemWindowInsets.top,
+                cutout.getBoundingRectTop());
+        assertCutoutIsConsistentWithInset("bottom", systemWindowInsets.bottom,
+                cutout.getBoundingRectBottom());
+        assertCutoutIsConsistentWithInset("left", systemWindowInsets.left,
+                cutout.getBoundingRectLeft());
+        assertCutoutIsConsistentWithInset("right", systemWindowInsets.right,
+                cutout.getBoundingRectRight());
+    }
+
     private void assertSafeInsetsValid(DisplayCutout displayCutout) {
         //noinspection unchecked
         assertThat("all safe insets must be non-negative", safeInsets(displayCutout),
@@ -196,13 +226,15 @@
             DisplayCutout displayCutout) {
         final Point displaySize = new Point();
         runOnMainSync(() -> activity.getDecorView().getDisplay().getRealSize(displaySize));
-        if (displaySize.y > displaySize.x) {  // Portrait display
+        if (displaySize.y > displaySize.x) {
+            // Portrait display
             assertThat("left edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetLeft(), is(0));
             assertThat("right edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetRight(), is(0));
         }
-        if (displaySize.y < displaySize.x) {  // Landscape display
+        if (displaySize.y < displaySize.x) {
+            // Landscape display
             assertThat("top edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetTop(), is(0));
             assertThat("bottom edge has a cutout despite being long edge",
@@ -210,6 +242,26 @@
         }
     }
 
+    private void assertOnlyShortEdgeHasBounds(TestActivity activity, WindowInsets insets,
+                                              DisplayCutout cutout) {
+        final Point displaySize = new Point();
+        runOnMainSync(() -> activity.getDecorView().getDisplay().getRealSize(displaySize));
+        if (displaySize.y > displaySize.x) {
+            // Portrait display
+            assertThat("left edge has a cutout despite being long edge",
+                    cutout.getBoundingRectLeft(), is(ZERO_RECT));
+            assertThat("right edge has a cutout despite being long edge",
+                    cutout.getBoundingRectRight(), is(ZERO_RECT));
+        }
+        if (displaySize.y < displaySize.x) {
+            // Landscape display
+            assertThat("top edge has a cutout despite being long edge",
+                    cutout.getBoundingRectTop(), is(ZERO_RECT));
+            assertThat("bottom edge has a cutout despite being long edge",
+                    cutout.getBoundingRectBottom(), is(ZERO_RECT));
+        }
+    }
+
     private List<Rect> boundsWith(DisplayCutout cutout, Predicate<Rect> predicate) {
         return cutout.getBoundingRects().stream().filter(predicate).collect(Collectors.toList());
     }
@@ -303,12 +355,19 @@
             }
             View view = new View(this);
             view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-            view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
             view.setOnApplyWindowInsetsListener((v, insets) -> mDispatchedInsets = insets);
             setContentView(view);
         }
 
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+            if (hasFocus) {
+                getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+            }
+        }
+
         View getDecorView() {
             return getWindow().getDecorView();
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
new file mode 100644
index 0000000..98e9780
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
@@ -0,0 +1,216 @@
+/*
+ * 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.server.wm;
+
+import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.provider.Settings.Global.WINDOW_ANIMATION_SCALE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Test whether WindowManager performs the correct layout after we make some changes to it.
+ *
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:LayoutTests
+ */
+@Presubmit
+@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
+@RunWith(AndroidJUnit4.class)
+public class LayoutTests {
+    private static final long TIMEOUT_LAYOUT = 200; // milliseconds
+    private static final long TIMEOUT_RECEIVE_KEY = 100;
+    private static final long TIMEOUT_SYSTEM_UI_VISIBILITY_CHANGE = 1000;
+    private static final int SYSTEM_UI_FLAG_HIDE_ALL = View.SYSTEM_UI_FLAG_FULLSCREEN
+            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+
+    private Instrumentation mInstrumentation;
+    private LayoutTestsActivity mActivity;
+    private ContentResolver mResolver;
+    private float mWindowAnimationScale;
+    private boolean mSystemUiFlagsGotCleared = false;
+    private boolean mChildWindowHasFocus = false;
+    private boolean mChildWindowGotKeyEvent = false;
+
+    @Rule
+    public final ActivityTestRule<LayoutTestsActivity> mActivityRule =
+            new ActivityTestRule<>(LayoutTestsActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mResolver = mInstrumentation.getContext().getContentResolver();
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            // The layout will be performed at the end of the animation of hiding status/navigation
+            // bar, which will recover the possible issue, so we disable the animation during the
+            // test.
+            mWindowAnimationScale = Settings.Global.getFloat(mResolver, WINDOW_ANIMATION_SCALE, 1f);
+            Settings.Global.putFloat(mResolver, WINDOW_ANIMATION_SCALE, 0);
+        });
+    }
+
+    @After
+    public void tearDown() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            // Restore the animation we disabled previously.
+            Settings.Global.putFloat(mResolver, WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
+        });
+    }
+
+    @Test
+    public void testLayoutAfterRemovingFocus() {
+        mActivity = mActivityRule.getActivity();
+        assertNotNull(mActivity);
+
+        // Get the visible frame of the main activity before adding any window.
+        final Rect visibleFrame = new Rect();
+        mActivity.getWindowVisibleDisplayFrame(visibleFrame);
+
+        doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingStatusBar);
+        doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingNavigationBar);
+        doTestLayoutAfterRemovingFocus(visibleFrame, mActivity::addWindowHidingBothSystemBars);
+    }
+
+    private void doTestLayoutAfterRemovingFocus(Rect visibleFrameBeforeAddingWindow,
+            Runnable toAddWindow) {
+        // Add a window which can affect the global layout.
+        mInstrumentation.runOnMainSync(toAddWindow);
+
+        // Wait a bit for the global layout finish triggered by adding window.
+        SystemClock.sleep(TIMEOUT_LAYOUT);
+
+        // Remove the window we added previously.
+        mInstrumentation.runOnMainSync(mActivity::removeWindow);
+        mInstrumentation.waitForIdleSync();
+
+        // Get the visible frame of the main activity after removing the window we added.
+        final Rect visibleFrameAfterRemovingWindow = new Rect();
+        mActivity.getWindowVisibleDisplayFrame(visibleFrameAfterRemovingWindow);
+
+        // Test whether the visible frame after removing window is the same as one before adding
+        // window. If not, it shows that the layout after removing window has a problem.
+        assertEquals(visibleFrameBeforeAddingWindow, visibleFrameAfterRemovingWindow);
+    }
+
+    private void stopWaiting() {
+        synchronized (this) {
+            notify();
+        }
+    }
+
+    @Test
+    public void testAddingImmersiveWindow() throws InterruptedException {
+        mActivity = mActivityRule.getActivity();
+        assertNotNull(mActivity);
+
+        mInstrumentation.runOnMainSync(this::addImmersiveWindow);
+        synchronized (this) {
+            wait(TIMEOUT_SYSTEM_UI_VISIBILITY_CHANGE);
+        }
+        assertFalse("System UI flags must not be cleared.", mSystemUiFlagsGotCleared);
+    }
+
+    private void addImmersiveWindow() {
+        final View view = new View(mActivity);
+        view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | SYSTEM_UI_FLAG_HIDE_ALL);
+        view.setOnSystemUiVisibilityChangeListener(
+                visibility -> {
+                    if ((visibility & SYSTEM_UI_FLAG_HIDE_ALL) != SYSTEM_UI_FLAG_HIDE_ALL) {
+                        mSystemUiFlagsGotCleared = true;
+                        // Early break because things go wrong already
+                        stopWaiting();
+                    }
+                });
+        mActivity.addWindow(view, new LayoutParams(TYPE_APPLICATION_PANEL));
+    }
+
+    @Test
+    public void testChangingFocusableFlag() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        assertNotNull(mActivity);
+
+        // Add a not-focusable window.
+        mInstrumentation.runOnMainSync(this::addNotFocusableWindow);
+        mInstrumentation.waitForIdleSync();
+
+        // Make the window focusable.
+        mInstrumentation.runOnMainSync(this::makeWindowFocusable);
+        synchronized (this) {
+            wait(TIMEOUT_LAYOUT);
+        }
+
+        // The window must have focus.
+        assertTrue("Child window must have focus.", mChildWindowHasFocus);
+
+        // Ensure the window can receive keys.
+        PollingCheck.check("Child window must get key event.", TIMEOUT_RECEIVE_KEY, () -> {
+            mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_0);
+            synchronized (this) {
+                return mChildWindowGotKeyEvent;
+            }
+        });
+    }
+
+    private void addNotFocusableWindow() {
+        final View view = new View(mActivity) {
+            public void onWindowFocusChanged(boolean hasWindowFocus) {
+                super.onWindowFocusChanged(hasWindowFocus);
+                mChildWindowHasFocus = hasWindowFocus;
+                stopWaiting();
+            }
+
+            public boolean onKeyDown(int keyCode, KeyEvent event) {
+                synchronized (LayoutTests.this) {
+                    mChildWindowGotKeyEvent = true;
+                }
+                return super.onKeyDown(keyCode, event);
+            }
+        };
+        mActivity.addWindow(view, new LayoutParams(TYPE_APPLICATION_PANEL, FLAG_NOT_FOCUSABLE));
+    }
+
+    private void makeWindowFocusable() {
+        mActivity.updateLayoutForAddedWindow(new LayoutParams(TYPE_APPLICATION_PANEL));
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTestsActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTestsActivity.java
new file mode 100644
index 0000000..60d5332
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTestsActivity.java
@@ -0,0 +1,73 @@
+/*
+ * 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.server.wm;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+
+public class LayoutTestsActivity extends Activity {
+    private View mChild;
+
+    public void addWindowHidingStatusBar() {
+        addWindow(View.SYSTEM_UI_FLAG_FULLSCREEN);
+    }
+
+    public void addWindowHidingNavigationBar() {
+        addWindow(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+    }
+
+    public void addWindowHidingBothSystemBars() {
+        addWindow(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+    }
+
+    private void addWindow(int systemUiVisibility) {
+        mChild = new View(this);
+        mChild.setSystemUiVisibility(systemUiVisibility);
+        getWindowManager().addView(mChild, new LayoutParams(TYPE_APPLICATION_PANEL));
+    }
+
+    public void removeWindow() {
+        getWindowManager().removeViewImmediate(mChild);
+    }
+
+    public void getWindowVisibleDisplayFrame(Rect outRect) {
+        getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect);
+    }
+
+    public void addWindow(View view, LayoutParams attrs) {
+        mChild = view;
+        getWindowManager().addView(view, attrs);
+    }
+
+    public void updateLayoutForAddedWindow(LayoutParams attrs) {
+        getWindowManager().updateViewLayout(mChild, attrs);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_KEEP_SCREEN_ON);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
new file mode 100644
index 0000000..fb2f1b6
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -0,0 +1,512 @@
+/*
+ * 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.server.wm;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+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.server.am.UiDeviceUtils.pressHomeButton;
+import static android.server.am.UiDeviceUtils.pressUnlockButton;
+import static android.server.am.UiDeviceUtils.pressWakeupButton;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.ACTION_UP;
+import static android.view.KeyEvent.FLAG_CANCELED;
+import static android.view.KeyEvent.KEYCODE_0;
+import static android.view.KeyEvent.KEYCODE_1;
+import static android.view.KeyEvent.KEYCODE_2;
+import static android.view.KeyEvent.KEYCODE_3;
+import static android.view.KeyEvent.KEYCODE_4;
+import static android.view.KeyEvent.KEYCODE_5;
+import static android.view.KeyEvent.KEYCODE_6;
+import static android.view.KeyEvent.KEYCODE_7;
+import static android.view.KeyEvent.KEYCODE_8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ComponentNameUtils;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Ensure window focus assignment is executed as expected.
+ *
+ * Build/Install/Run:
+ *     atest WindowFocusTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowFocusTests {
+
+    @Before
+    public void setUp() {
+        pressWakeupButton();
+        pressUnlockButton();
+        pressHomeButton();
+    }
+
+    private static <T extends InputTargetActivity> T startActivity(Class<T> cls, int displayId)
+            throws InterruptedException {
+        final Bundle options = (displayId == DEFAULT_DISPLAY
+                ? null : ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle());
+        final T activity = (T) getInstrumentation().startActivitySync(
+                new Intent(getTargetContext(), cls).addFlags(FLAG_ACTIVITY_NEW_TASK), options);
+        activity.waitAndAssertWindowFocusState(true /* hasFocus */);
+        return activity;
+    }
+
+    private static void sendKey(int action, int keyCode, int displayId) {
+        final KeyEvent keyEvent = new KeyEvent(action, keyCode);
+        keyEvent.setDisplayId(displayId);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            getInstrumentation().sendKeySync(keyEvent);
+        });
+    }
+
+    private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int keyCode,
+            int targetDisplayId) {
+        sendAndAssertTargetConsumedKey(target, ACTION_DOWN, keyCode, targetDisplayId);
+        sendAndAssertTargetConsumedKey(target, ACTION_UP, keyCode, targetDisplayId);
+    }
+
+    private static void sendAndAssertTargetConsumedKey(InputTargetActivity target, int action,
+            int keyCode, int targetDisplayId) {
+        final int eventCount = target.getKeyEventCount();
+        sendKey(action, keyCode, targetDisplayId);
+        target.assertAndConsumeKeyEvent(action, keyCode, 0 /* flags */);
+        assertEquals(target.getLogTag() + " must only receive key event sent.", eventCount,
+                target.getKeyEventCount());
+    }
+
+    private static void tapOnCenterOfDisplay(int displayId) {
+        final Point point = new Point();
+        getTargetContext().getSystemService(DisplayManager.class).getDisplay(displayId)
+                .getSize(point);
+        final int x = point.x / 2;
+        final int y = point.y / 2;
+        final long downTime = SystemClock.elapsedRealtime();
+        final MotionEvent downEvent = MotionEvent.obtain(downTime, downTime,
+                MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */);
+        downEvent.setDisplayId(displayId);
+        getInstrumentation().sendPointerSync(downEvent);
+        final MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.elapsedRealtime(),
+                MotionEvent.ACTION_UP, x, y, 0 /* metaState */);
+        upEvent.setDisplayId(displayId);
+        getInstrumentation().sendPointerSync(upEvent);
+    }
+
+    /**
+     * Test the following conditions:
+     * - Each display can have a focused window at the same time.
+     * - Focused windows can receive display-specified key events.
+     * - The top focused window can receive display-unspecified key events.
+     * - Taping on a display will make the focused window on it become top-focused.
+     * - The window which lost top-focus can receive display-unspecified cancel events.
+     */
+    @Test
+    public void testKeyReceiving() throws InterruptedException {
+        final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
+                DEFAULT_DISPLAY);
+        sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, INVALID_DISPLAY);
+        sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
+
+        try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int secondaryDisplayId =
+                    displaySession.createDisplay(getTargetContext()).getDisplayId();
+            final SecondaryActivity secondaryActivity =
+                    startActivity(SecondaryActivity.class, secondaryDisplayId);
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
+
+            final boolean perDisplayFocusEnabled = getTargetContext().getResources().getBoolean(
+                    com.android.internal.R.bool.config_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);
+            sendKey(ACTION_DOWN, KEYCODE_6, secondaryDisplayId);
+            sendKey(ACTION_DOWN, KEYCODE_7, INVALID_DISPLAY);
+            secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_5, 0 /* flags */);
+            secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_6, 0 /* flags */);
+            secondaryActivity.assertAndConsumeKeyEvent(ACTION_DOWN, KEYCODE_7, 0 /* flags */);
+
+            tapOnCenterOfDisplay(DEFAULT_DISPLAY);
+
+            // Assert only display-unspecified key would be cancelled after secondary activity is
+            // not top focused if per-display focus is enabled. Otherwise, assert all non-released
+            // 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);
+            if (!perDisplayFocusEnabled) {
+                secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
+            }
+            assertEquals(secondaryActivity.getLogTag() + " must only receive expected events.",
+                    0 /* expected event count */, secondaryActivity.getKeyEventCount());
+
+            // Assert primary activity become top focused after tapping on default display.
+            sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_8, INVALID_DISPLAY);
+        }
+    }
+
+    /**
+     * Test if a display targeted by a key event can be moved to top in a single-focus system.
+     */
+    @Test
+    public void testMovingDisplayToTopByKeyEvent() throws InterruptedException {
+        if (getTargetContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled)) {
+            return;
+        }
+
+        final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
+                DEFAULT_DISPLAY);
+
+        try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int secondaryDisplayId =
+                    displaySession.createDisplay(getTargetContext()).getDisplayId();
+            final SecondaryActivity secondaryActivity =
+                    startActivity(SecondaryActivity.class, secondaryDisplayId);
+
+            sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_0, DEFAULT_DISPLAY);
+            sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, INVALID_DISPLAY);
+
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, secondaryDisplayId);
+            sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, INVALID_DISPLAY);
+        }
+    }
+
+    /**
+     * Test if the client is notified about window-focus lost after the new focused window is drawn.
+     */
+    @Test
+    public void testDelayLosingFocus() throws InterruptedException {
+        final LosingFocusActivity activity = startActivity(LosingFocusActivity.class,
+                DEFAULT_DISPLAY);
+
+        getInstrumentation().runOnMainSync(activity::addChildWindow);
+        activity.waitAndAssertWindowFocusState(false /* hasFocus */);
+        assertFalse("Activity must lose window focus after new focused window is drawn.",
+                activity.losesFocusWhenNewFocusIsNotDrawn());
+    }
+
+
+    /**
+     * Test the following conditions:
+     * - Only the top focused window can have pointer capture.
+     * - The window which lost top-focus can be notified about pointer-capture lost.
+     */
+    @Test
+    @FlakyTest(bugId = 121122996)
+    public void testPointerCapture() throws InterruptedException {
+        final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
+                DEFAULT_DISPLAY);
+
+        // Assert primary activity can have pointer capture before we have multiple focused windows.
+        getInstrumentation().runOnMainSync(primaryActivity::requestPointerCapture);
+        primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+
+        try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int secondaryDisplayId =
+                    displaySession.createDisplay(getTargetContext()).getDisplayId();
+            final SecondaryActivity secondaryActivity =
+                    startActivity(SecondaryActivity.class, secondaryDisplayId);
+
+            // Assert primary activity lost pointer capture when it is not top focused.
+            primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+
+            // Assert secondary activity can have pointer capture when it is top focused.
+            getInstrumentation().runOnMainSync(secondaryActivity::requestPointerCapture);
+            secondaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+
+            tapOnCenterOfDisplay(DEFAULT_DISPLAY);
+
+            // Assert secondary activity lost pointer capture when it is not top focused.
+            secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+        }
+    }
+
+    /**
+     * Test if the focused window can still have focus after it is moved to another display.
+     */
+    @Test
+    public void testDisplayChanged() throws InterruptedException {
+        final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
+                DEFAULT_DISPLAY);
+
+        final SecondaryActivity secondaryActivity;
+        try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int secondaryDisplayId =
+                    displaySession.createDisplay(getTargetContext()).getDisplayId();
+            secondaryActivity = startActivity(SecondaryActivity.class, secondaryDisplayId);
+        }
+        // Secondary display disconnected.
+
+        assertNotNull("SecondaryActivity must be started.", secondaryActivity);
+        secondaryActivity.waitAndAssertDisplayId(DEFAULT_DISPLAY);
+        secondaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
+
+        primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+    }
+
+    private static class InputTargetActivity extends Activity {
+        private static final long TIMEOUT_DISPLAY_CHANGED = 1000; // milliseconds
+        private static final long TIMEOUT_WINDOW_FOCUS_CHANGED = 1000;
+        private static final long TIMEOUT_POINTER_CAPTURE_CHANGED = 1000;
+        private static final long TIMEOUT_NEXT_KEY_EVENT = 1000;
+
+        private final Object mLockWindowFocus = new Object();
+        private final Object mLockPointerCapture = new Object();
+        private final Object mLockKeyEvent = new Object();
+
+        @GuardedBy("this")
+        private int mDisplayId = INVALID_DISPLAY;
+        @GuardedBy("mLockWindowFocus")
+        private boolean mHasWindowFocus;
+        @GuardedBy("mLockPointerCapture")
+        private boolean mHasPointerCapture;
+        @GuardedBy("mLockKeyEvent")
+        private ArrayList<KeyEvent> mKeyEventList = new ArrayList<>();
+
+        public final String getLogTag() {
+            return ComponentNameUtils.getLogTag(getComponentName());
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            synchronized (this) {
+                mDisplayId = getWindow().getDecorView().getDisplay().getDisplayId();
+                notify();
+            }
+        }
+
+        @Override
+        public void onMovedToDisplay(int displayId, Configuration config) {
+            synchronized (this) {
+                mDisplayId = displayId;
+                notify();
+            }
+        }
+
+        void waitAndAssertDisplayId(int displayId) throws InterruptedException {
+            synchronized (this) {
+                if (mDisplayId != displayId) {
+                    wait(TIMEOUT_DISPLAY_CHANGED);
+                }
+                assertEquals(getLogTag() + " must be moved to the display.",
+                        displayId, mDisplayId);
+            }
+        }
+
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+            synchronized (mLockWindowFocus) {
+                mHasWindowFocus = hasFocus;
+                mLockWindowFocus.notify();
+            }
+        }
+
+        void assertWindowFocusState(boolean hasFocus) {
+            synchronized (mLockWindowFocus) {
+                assertEquals(getLogTag() + " must" + (hasFocus ? "" : " not")
+                        + " have window focus.", hasFocus, mHasWindowFocus);
+            }
+        }
+
+        void waitAndAssertWindowFocusState(boolean hasFocus) throws InterruptedException {
+            synchronized (mLockWindowFocus) {
+                if (mHasWindowFocus != hasFocus) {
+                    mLockWindowFocus.wait(TIMEOUT_WINDOW_FOCUS_CHANGED);
+                }
+            }
+            assertWindowFocusState(hasFocus);
+        }
+
+        @Override
+        public void onPointerCaptureChanged(boolean hasCapture) {
+            synchronized (mLockPointerCapture) {
+                mHasPointerCapture = hasCapture;
+                mLockPointerCapture.notify();
+            }
+        }
+
+        void waitAndAssertPointerCaptureState(boolean hasCapture) throws InterruptedException {
+            synchronized (mLockPointerCapture) {
+                if (mHasPointerCapture != hasCapture) {
+                    mLockPointerCapture.wait(TIMEOUT_POINTER_CAPTURE_CHANGED);
+                }
+                assertEquals(getLogTag() + " must" + (hasCapture ? "" : " not")
+                        + " have pointer capture.", hasCapture, mHasPointerCapture);
+            }
+        }
+
+        // Should be only called from the main thread.
+        void requestPointerCapture() {
+            getWindow().getDecorView().requestPointerCapture();
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            synchronized (mLockKeyEvent) {
+                mKeyEventList.add(event);
+                mLockKeyEvent.notify();
+            }
+            return super.dispatchKeyEvent(event);
+        }
+
+        int getKeyEventCount() {
+            synchronized (mLockKeyEvent) {
+                return mKeyEventList.size();
+            }
+        }
+
+        private KeyEvent consumeKeyEvent(int action, int keyCode, int flags) {
+            synchronized (mLockKeyEvent) {
+                for (int i = mKeyEventList.size() - 1; i >= 0; i--) {
+                    final KeyEvent event = mKeyEventList.get(i);
+                    if (event.getAction() == action && event.getKeyCode() == keyCode
+                            && (event.getFlags() & flags) == flags) {
+                        mKeyEventList.remove(event);
+                        return event;
+                    }
+                }
+            }
+            return null;
+        }
+
+        void assertAndConsumeKeyEvent(int action, int keyCode, int flags) {
+            assertNotNull(getLogTag() + " must receive key event.",
+                    consumeKeyEvent(action, keyCode, flags));
+        }
+
+        void waitAssertAndConsumeKeyEvent(int action, int keyCode, int flags)
+                throws InterruptedException {
+            if (consumeKeyEvent(action, keyCode, flags) == null) {
+                synchronized (mLockKeyEvent) {
+                    mLockKeyEvent.wait(TIMEOUT_NEXT_KEY_EVENT);
+                }
+                assertAndConsumeKeyEvent(action, keyCode, flags);
+            }
+        }
+    }
+
+    public static class PrimaryActivity extends InputTargetActivity { }
+
+    public static class SecondaryActivity extends InputTargetActivity { }
+
+    public static class LosingFocusActivity extends InputTargetActivity {
+        private boolean mChildWindowHasDrawn = false;
+
+        @GuardedBy("this")
+        private boolean mLosesFocusWhenNewFocusIsNotDrawn = false;
+
+        void addChildWindow() {
+            getWindowManager().addView(new View(this) {
+                @Override
+                protected void onDraw(Canvas canvas) {
+                    mChildWindowHasDrawn = true;
+                }
+            }, new LayoutParams());
+        }
+
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+            if (!hasFocus && !mChildWindowHasDrawn) {
+                synchronized (this) {
+                    mLosesFocusWhenNewFocusIsNotDrawn = true;
+                }
+            }
+            super.onWindowFocusChanged(hasFocus);
+        }
+
+        boolean losesFocusWhenNewFocusIsNotDrawn() {
+            synchronized (this) {
+                return mLosesFocusWhenNewFocusIsNotDrawn;
+            }
+        }
+    }
+
+    private static class VirtualDisplaySession implements AutoCloseable {
+        private static final int WIDTH = 800;
+        private static final int HEIGHT = 480;
+        private static final int DENSITY = 160;
+
+        private VirtualDisplay mVirtualDisplay;
+        private ImageReader mReader;
+
+        Display createDisplay(Context context) {
+            if (mReader != null) {
+                throw new IllegalStateException(
+                        "Only one display can be created during this session.");
+            }
+            mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888,
+                    2 /* maxImages */);
+            mVirtualDisplay = context.getSystemService(DisplayManager.class).createVirtualDisplay(
+                    "CtsDisplay", WIDTH, HEIGHT, DENSITY, mReader.getSurface(),
+                    VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+            return mVirtualDisplay.getDisplay();
+        }
+
+        @Override
+        public void close() {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+            }
+            if (mReader != null) {
+                mReader.close();
+            }
+        }
+    }
+}
diff --git a/tests/inputmethod/Android.mk b/tests/inputmethod/Android.mk
index 0d11efa..b6e7dca 100644
--- a/tests/inputmethod/Android.mk
+++ b/tests/inputmethod/Android.mk
@@ -32,7 +32,7 @@
     android-support-test \
     compatibility-device-util \
     ctstestrunner \
-    CtsMockInputMethod
+    CtsMockInputMethodLib
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index b9c0ef4..d59b731 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -53,24 +53,6 @@
             </intent-filter>
         </activity>
 
-        <!-- In order to test typical use cases, let this MockIME run in a separate process -->
-        <!--
-          TODO: Move this sevice definition into MockIME package and let the build system to merge
-          manifests once soong supports to build aar package.
-        -->
-        <service
-            android:name="com.android.cts.mockime.MockIme"
-            android:label="Mock IME"
-            android:permission="android.permission.BIND_INPUT_METHOD"
-            android:process=":mockime">
-            <intent-filter>
-                <action android:name="android.view.InputMethod" />
-            </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/method" />
-        </service>
-
         <!--
           In order to test window-focus-stealing from other process, let this service run in a
           separate process. -->
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index f749065..f64bd17 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -18,19 +18,32 @@
 <configuration description="Config for CTS InputMethod test cases">
     <option name="test-suite-tag" value="cts" />
     <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" />
+    <!--
+        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
+        features, e.g. android.software.input_methods so that we can conditionally install APKs
+        based on the feature supported in the target device.
+    -->
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <!--
-            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
-            features, e.g. android.software.input_methods so that we can conditionally install APKs
-            based on the feature supported in the target device.
+            MockIME always needs to be instaleld as a full package, even when CTS is running
+            for instant apps.
         -->
-        <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
+        <option name="force-install-mode" value="FULL"/>
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="cmd input_method set-bind-instant-service-allowed true" />
-        <option name="teardown-command" value="cmd input_method set-bind-instant-service-allowed false" />
+    <!--
+        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
+        features, e.g. android.software.input_methods so that we can conditionally install APKs
+        based on the feature supported in the target device.
+    -->
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.view.inputmethod.cts" />
diff --git a/tests/inputmethod/mockime/Android.mk b/tests/inputmethod/mockime/Android.mk
index 5cfed53..a439c35 100644
--- a/tests/inputmethod/mockime/Android.mk
+++ b/tests/inputmethod/mockime/Android.mk
@@ -16,11 +16,15 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_MODULE := CtsMockInputMethod
+LOCAL_MODULE := CtsMockInputMethodLib
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_SDK_VERSION := current
 
+# TODO: ideally we should split MockIme source files into three categories
+#       1) common, 2) common + IME-only, and 3) common + client-only.
+#       Currently, both MockIme APK and test APKs that use MockIme contain
+#       all the Java classes, which is inefficient.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_JAVA_LIBRARIES := junit
 LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -28,3 +32,29 @@
    compatibility-device-util
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsMockInputMethod
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 19
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+   androidx.annotation_annotation \
+   CtsMockInputMethodLib
+
+include $(BUILD_PACKAGE)
diff --git a/tests/inputmethod/mockime/AndroidManifest.xml b/tests/inputmethod/mockime/AndroidManifest.xml
new file mode 100644
index 0000000..83d8f3f
--- /dev/null
+++ b/tests/inputmethod/mockime/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.mockime">
+
+    <application
+        android:multiArch="true"
+        android:supportsRtl="true">
+
+        <meta-data android:name="instantapps.clients.allowed" android:value="true" />
+
+        <service
+            android:name="com.android.cts.mockime.MockIme"
+            android:label="Mock IME"
+            android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <meta-data
+                android:name="android.view.im"
+                android:resource="@xml/method" />
+        </service>
+
+        <provider
+            android:authorities="com.android.cts.mockime.provider"
+            android:name="com.android.cts.mockime.SettingsProvider"
+            android:exported="true"
+            android:visibleToInstantApps="true">
+        </provider>
+
+    </application>
+</manifest>
diff --git a/tests/inputmethod/res/xml/method.xml b/tests/inputmethod/mockime/res/xml/method.xml
similarity index 100%
rename from tests/inputmethod/res/xml/method.xml
rename to tests/inputmethod/mockime/res/xml/method.xml
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
index 10559b6..e0a9888 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
@@ -17,6 +17,7 @@
 package com.android.cts.mockime;
 
 import android.os.Bundle;
+
 import androidx.annotation.NonNull;
 
 public final class ImeCommand {
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
index 54732a0..0c7c542 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -18,9 +18,10 @@
 
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
+import android.view.View;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import android.view.View;
 
 /**
  * An immutable object that stores event happened in the {@link MockIme}.
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
index 4ed1782..2930009 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
@@ -17,9 +17,10 @@
 package com.android.cts.mockime;
 
 import android.os.Bundle;
+import android.view.inputmethod.EditorInfo;
+
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
-import android.view.inputmethod.EditorInfo;
 
 import java.time.Instant;
 import java.time.ZoneId;
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index c5159af..f683601 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -17,11 +17,12 @@
 package com.android.cts.mockime;
 
 import android.os.SystemClock;
-import androidx.annotation.NonNull;
 import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 
+import androidx.annotation.NonNull;
+
 import java.util.Optional;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Predicate;
@@ -148,7 +149,7 @@
      * @return true if event occurred.
      */
     public static Predicate<ImeEvent> editorMatcher(
-        @NonNull String eventName, @NonNull String marker) {
+            @NonNull String eventName, @NonNull String marker) {
         return event -> {
             if (!TextUtils.equals(eventName, event.getEventName())) {
                 return false;
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
index cfed448..40bdc3f 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
@@ -19,12 +19,13 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /**
  * A collection of layout-related information when
  * {@link View.OnLayoutChangeListener#onLayoutChange(View, int, int, int, int, int, int, int, int)}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 1769a2f..a7bf781 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -16,8 +16,9 @@
 
 package com.android.cts.mockime;
 
-import android.os.Parcel;
+import android.os.Bundle;
 import android.os.PersistableBundle;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -28,8 +29,14 @@
 public class ImeSettings {
 
     @NonNull
+    private final String mClientPackageName;
+
+    @NonNull
     private final String mEventCallbackActionName;
 
+    private static final String EVENT_CALLBACK_INTENT_ACTION_KEY = "eventCallbackActionName";
+    private static final String DATA_KEY = "data";
+
     private static final String BACKGROUND_COLOR_KEY = "BackgroundColor";
     private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor";
     private static final String INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET =
@@ -44,9 +51,10 @@
     @NonNull
     private final PersistableBundle mBundle;
 
-    ImeSettings(@NonNull Parcel parcel) {
-        mEventCallbackActionName = parcel.readString();
-        mBundle = parcel.readPersistableBundle();
+    ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) {
+        mClientPackageName = clientPackageName;
+        mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY);
+        mBundle = bundle.getParcelable(DATA_KEY);
     }
 
     @Nullable
@@ -54,6 +62,11 @@
         return mEventCallbackActionName;
     }
 
+    @NonNull
+    String getClientPackageName() {
+        return mClientPackageName;
+    }
+
     public boolean fullscreenModeAllowed(boolean defaultValue) {
         return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
     }
@@ -92,14 +105,12 @@
         return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
     }
 
-    static void writeToParcel(@NonNull Parcel parcel, @NonNull String eventCallbackActionName,
+    static Bundle serializeToBundle(@NonNull String eventCallbackActionName,
             @Nullable Builder builder) {
-        parcel.writeString(eventCallbackActionName);
-        if (builder != null) {
-            parcel.writePersistableBundle(builder.mBundle);
-        } else {
-            parcel.writePersistableBundle(PersistableBundle.EMPTY);
-        }
+        final Bundle result = new Bundle();
+        result.putString(EVENT_CALLBACK_INTENT_ACTION_KEY, eventCallbackActionName);
+        result.putParcelable(DATA_KEY, builder != null ? builder.mBundle : PersistableBundle.EMPTY);
+        return result;
     }
 
     /**
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
index 4a39bab..3275995 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
@@ -17,6 +17,7 @@
 package com.android.cts.mockime;
 
 import android.os.Bundle;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index f3a9446..b9c7313 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -18,8 +18,6 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 
-import static com.android.cts.mockime.MockImeSession.MOCK_IME_SETTINGS_FILE;
-
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,20 +25,15 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.inputmethodservice.InputMethodService;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Parcel;
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import androidx.annotation.AnyThread;
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.TypedValue;
@@ -56,8 +49,12 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import java.io.IOException;
-import java.io.InputStream;
+import androidx.annotation.AnyThread;
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -70,12 +67,14 @@
 
     private static final String TAG = "MockIme";
 
-    static ComponentName getComponentName(@NonNull String packageName) {
-        return new ComponentName(packageName, MockIme.class.getName());
+    private static final String PACKAGE_NAME = "com.android.cts.mockime";
+
+    static ComponentName getComponentName() {
+        return new ComponentName(PACKAGE_NAME, MockIme.class.getName());
     }
 
-    static String getImeId(@NonNull String packageName) {
-        return new ComponentName(packageName, MockIme.class.getName()).flattenToShortString();
+    static String getImeId() {
+        return getComponentName().flattenToShortString();
     }
 
     static String getCommandActionName(@NonNull String eventActionName) {
@@ -167,6 +166,13 @@
         return mImeEventActionName.get();
     }
 
+    private final AtomicReference<String> mClientPackageName = new AtomicReference<>();
+
+    @Nullable
+    String getClientPackageName() {
+        return mClientPackageName.get();
+    }
+
     private class MockInputMethodImpl extends InputMethodImpl {
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
@@ -196,40 +202,15 @@
         }
     }
 
-    @Nullable
-    private ImeSettings readSettings() {
-        try (InputStream is = openFileInput(MOCK_IME_SETTINGS_FILE)) {
-            Parcel parcel = null;
-            try {
-                parcel = Parcel.obtain();
-                final byte[] buffer = new byte[4096];
-                while (true) {
-                    final int numRead = is.read(buffer);
-                    if (numRead <= 0) {
-                        break;
-                    }
-                    parcel.unmarshall(buffer, 0, numRead);
-                }
-                parcel.setDataPosition(0);
-                return new ImeSettings(parcel);
-            } finally {
-                if (parcel != null) {
-                    parcel.recycle();
-                }
-            }
-        } catch (IOException e) {
-        }
-        return null;
-    }
-
     @Override
     public void onCreate() {
         // Initialize minimum settings to send events in Tracer#onCreate().
-        mSettings = readSettings();
+        mSettings = SettingsProvider.getSettings();
         if (mSettings == null) {
             throw new IllegalStateException("Settings file is not found. "
                     + "Make sure MockImeSession.create() is used to launch Mock IME.");
         }
+        mClientPackageName.set(mSettings.getClientPackageName());
         mImeEventActionName.set(mSettings.getEventCallbackActionName());
 
         getTracer().onCreate(() -> {
@@ -237,9 +218,14 @@
             mHandlerThread.start();
             final String actionName = getCommandActionName(mSettings.getEventCallbackActionName());
             mCommandReceiver = new CommandReceiver(actionName, this::onReceiveCommand);
-            registerReceiver(mCommandReceiver,
-                    new IntentFilter(actionName), null /* broadcastPermission */,
-                    new Handler(mHandlerThread.getLooper()));
+            final IntentFilter filter = new IntentFilter(actionName);
+            final Handler handler = new Handler(mHandlerThread.getLooper());
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler,
+                        Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+            } else {
+                registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
+            }
 
             final int windowFlags = mSettings.getWindowFlags(0);
             final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
@@ -307,7 +293,7 @@
                 textView.setLayoutParams(params);
                 textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
                 textView.setGravity(Gravity.CENTER);
-                textView.setText(getImeId(getContext().getPackageName()));
+                textView.setText(getImeId());
                 layout.addView(textView);
                 addView(layout, LayoutParams.MATCH_PARENT, mainSpacerHeight);
             }
@@ -499,22 +485,29 @@
 
         private String mImeEventActionName;
 
+        private String mClientPackageName;
+
         Tracer(@NonNull MockIme mockIme) {
             mIme = mockIme;
         }
 
         private void sendEventInternal(@NonNull ImeEvent event) {
-            final Intent intent = new Intent();
-            intent.setPackage(mIme.getPackageName());
             if (mImeEventActionName == null) {
                 mImeEventActionName = mIme.getImeEventActionName();
             }
-            if (mImeEventActionName == null) {
+            if (mClientPackageName == null) {
+                mClientPackageName = mIme.getClientPackageName();
+            }
+            if (mImeEventActionName == null || mClientPackageName == null) {
                 Log.e(TAG, "Tracer cannot be used before onCreate()");
                 return;
             }
-            intent.setAction(mImeEventActionName);
-            intent.putExtras(event.toBundle());
+            final Intent intent = new Intent()
+                    .setAction(mImeEventActionName)
+                    .setPackage(mClientPackageName)
+                    .putExtras(event.toBundle())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             mIme.sendBroadcast(intent);
         }
 
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 82765d8..2824269 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.mockime;
 
-import static android.content.Context.MODE_PRIVATE;
-
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -27,20 +25,19 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodManager;
+
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.inputmethod.InputMethodManager;
 
 import com.android.compatibility.common.util.PollingCheck;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -54,9 +51,6 @@
     private final String mImeEventActionName =
             "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
 
-    /** Setting file name to store initialization settings for {@link MockIme}. */
-    static final String MOCK_IME_SETTINGS_FILE = "mockimesettings.data";
-
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
 
     @NonNull
@@ -164,28 +158,16 @@
     private static void writeMockImeSettings(@NonNull Context context,
             @NonNull String imeEventActionName,
             @Nullable ImeSettings.Builder imeSettings) throws Exception {
-        context.deleteFile(MOCK_IME_SETTINGS_FILE);
-        try (OutputStream os = context.openFileOutput(MOCK_IME_SETTINGS_FILE, MODE_PRIVATE)) {
-            Parcel parcel = null;
-            try {
-                parcel = Parcel.obtain();
-                ImeSettings.writeToParcel(parcel, imeEventActionName, imeSettings);
-                os.write(parcel.marshall());
-            } finally {
-                if (parcel != null) {
-                    parcel.recycle();
-                }
-            }
-            os.flush();
-        }
+        final Bundle bundle = ImeSettings.serializeToBundle(imeEventActionName, imeSettings);
+        context.getContentResolver().call(SettingsProvider.AUTHORITY, "write", null, bundle);
     }
 
     private ComponentName getMockImeComponentName() {
-        return MockIme.getComponentName(mContext.getPackageName());
+        return MockIme.getComponentName();
     }
 
     private String getMockImeId() {
-        return MockIme.getImeId(mContext.getPackageName());
+        return MockIme.getImeId();
     }
 
     private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
@@ -267,7 +249,7 @@
 
         mContext.unregisterReceiver(mEventReceiver);
         mHandlerThread.quitSafely();
-        mContext.deleteFile(MOCK_IME_SETTINGS_FILE);
+        mContext.getContentResolver().call(SettingsProvider.AUTHORITY, "delete", null, null);
     }
 
     /**
@@ -291,7 +273,7 @@
         final ImeCommand command = new ImeCommand(
                 "commitText", SystemClock.elapsedRealtimeNanos(), true, params);
         final Intent intent = new Intent();
-        intent.setPackage(mContext.getPackageName());
+        intent.setPackage(MockIme.getComponentName().getPackageName());
         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
         intent.putExtras(command.toBundle());
         mContext.sendBroadcast(intent);
@@ -305,7 +287,7 @@
         final ImeCommand command = new ImeCommand(
                 "setBackDisposition", SystemClock.elapsedRealtimeNanos(), true, params);
         final Intent intent = new Intent();
-        intent.setPackage(mContext.getPackageName());
+        intent.setPackage(MockIme.getComponentName().getPackageName());
         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
         intent.putExtras(command.toBundle());
         mContext.sendBroadcast(intent);
@@ -319,7 +301,7 @@
         final ImeCommand command = new ImeCommand(
                 "requestHideSelf", SystemClock.elapsedRealtimeNanos(), true, params);
         final Intent intent = new Intent();
-        intent.setPackage(mContext.getPackageName());
+        intent.setPackage(MockIme.getComponentName().getPackageName());
         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
         intent.putExtras(command.toBundle());
         mContext.sendBroadcast(intent);
@@ -333,7 +315,7 @@
         final ImeCommand command = new ImeCommand(
                 "requestShowSelf", SystemClock.elapsedRealtimeNanos(), true, params);
         final Intent intent = new Intent();
-        intent.setPackage(mContext.getPackageName());
+        intent.setPackage(MockIme.getComponentName().getPackageName());
         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
         intent.putExtras(command.toBundle());
         mContext.sendBroadcast(intent);
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/SettingsProvider.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/SettingsProvider.java
new file mode 100644
index 0000000..24eabe8
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/SettingsProvider.java
@@ -0,0 +1,87 @@
+/*
+ * 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.mockime;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/**
+ * {@link ContentProvider} to receive {@link ImeSettings} via
+ * {@link ContentProvider#call(String, String, String, Bundle)}.
+ */
+public class SettingsProvider extends ContentProvider {
+
+    static final String AUTHORITY = "com.android.cts.mockime.provider";
+
+    @Nullable
+    private static ImeSettings sSettings = null;
+
+    @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) {
+        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;
+    }
+
+    @Override
+    public Bundle call(String authority, String method, String arg, Bundle extras) {
+        if ("write".equals(method)) {
+            sSettings = null;
+            final String callingPackageName = getCallingPackage();
+            if (callingPackageName == null) {
+                throw new SecurityException("Failed to obtain the calling package name.");
+            }
+            sSettings = new ImeSettings(callingPackageName, extras);
+        } else if ("delete".equals(method)) {
+            sSettings = null;
+        }
+        return Bundle.EMPTY;
+    }
+
+    static ImeSettings getSettings() {
+        return sSettings;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 9b3034e..c546bb2 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -70,7 +70,7 @@
     static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
 
     private static final String TEST_MARKER_PREFIX =
-        "android.view.inputmethod.cts.FocusHandlingTest";
+            "android.view.inputmethod.cts.FocusHandlingTest";
 
     public EditText launchTestActivity(String marker) {
         final AtomicReference<EditText> editTextRef = new AtomicReference<>();
@@ -156,7 +156,8 @@
                 // Input shouldn't start
                 notExpectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
                 // There shouldn't be onStartInput because the focused view is not an editor.
-                notExpectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+                notExpectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                        TIMEOUT);
             } else {
                 // Wait until the MockIme gets bound to the TestActivity.
                 expectBindInput(stream, Process.myPid(), TIMEOUT);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
index 8400c53..190dad6 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -28,7 +28,6 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
-import androidx.annotation.NonNull;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -45,6 +44,8 @@
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 300f573..592ac8b 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -124,7 +124,7 @@
                 waitOnMainUntil(() -> testActivity.getOnBackPressedCallCount() > 0,
                         EXPECTED_TIMEOUT);
                 fail("Activity#onBackPressed() should not be called");
-            } catch (TimeoutException e){
+            } catch (TimeoutException e) {
                 // This is fine.  We actually expect timeout.
             }
         }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 5482a41..d11836c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -26,7 +26,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemClock;
-import androidx.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -40,6 +39,8 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeSettings;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
index de3235c..49c37dc 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -40,8 +40,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.Process;
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -56,13 +54,16 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeLayoutInfo;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
-import org.junit.BeforeClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
index 60e809b..93a1f86 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
@@ -20,10 +20,11 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.util.SparseIntArray;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import android.util.SparseIntArray;
 
 import java.util.Arrays;
 import java.util.OptionalDouble;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
index 59c36ad..ea5fceb 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
@@ -21,6 +21,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
index d76f99e..66bb6d6 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
@@ -26,9 +26,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.util.Size;
 import android.view.View;
@@ -36,6 +33,10 @@
 import android.view.WindowInsets;
 import android.widget.TextView;
 
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
index 0eaa3d6..82fe636 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -21,14 +21,15 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.UiThread;
 import android.support.test.InstrumentationRegistry;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.UiThread;
+
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
index 1f86ccd..f978a55 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -17,9 +17,10 @@
 package android.view.inputmethod.cts.util;
 
 import android.app.Instrumentation;
-import androidx.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 
+import androidx.annotation.NonNull;
+
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
index 45648d1..c0b5724 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+
 import androidx.annotation.NonNull;
 
 import java.util.concurrent.ArrayBlockingQueue;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
index 14b8b77..03e3741 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
@@ -25,11 +25,12 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.ResultReceiver;
+import android.view.WindowManager;
+import android.widget.TextView;
+
 import androidx.annotation.BinderThread;
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
-import android.view.WindowManager;
-import android.widget.TextView;
 
 public final class WindowFocusStealerService extends Service {
 
diff --git a/tests/leanbackjank/app/Android.mk b/tests/leanbackjank/app/Android.mk
index b6404fe..f23d99c 100644
--- a/tests/leanbackjank/app/Android.mk
+++ b/tests/leanbackjank/app/Android.mk
@@ -24,6 +24,7 @@
 
 LOCAL_PACKAGE_NAME := CtsLeanbackJankApp
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index 9d3a579..f137d92 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -34,5 +34,6 @@
         <option name="core-expectation" value="/knownfailures.txt" />
         <option name="runtime-hint" value="16m"/>
         <option name="test-timeout" value="3600000" />
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/libcore/wycheproof/AndroidTest.xml b/tests/libcore/wycheproof/AndroidTest.xml
index caf8bd6..4870a52 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -33,5 +33,6 @@
                 value="com.android.cts.core.runner.ExpectationBasedFilter" />
         <option name="core-expectation" value="/knownfailures.txt" />
         <option name="runtime-hint" value="10m"/>
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/netlegacy22.api/Android.mk b/tests/netlegacy22.api/Android.mk
index 5a330e5..f5b178e7 100644
--- a/tests/netlegacy22.api/Android.mk
+++ b/tests/netlegacy22.api/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SDK_VERSION := 22
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java b/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
index de92910..4a8a2ad 100644
--- a/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
+++ b/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
@@ -26,11 +26,12 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.wifi.WifiManager;
 import android.os.ConditionVariable;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.net.DatagramSocket;
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -55,7 +56,6 @@
     private static final int MAX_NETWORK_TYPE = TYPE_VPN;
 
     private ConnectivityManager mCm;
-    private WifiManager mWifiManager;
     private PackageManager mPackageManager;
 
     private final List<Integer>mProtectedNetworks = new ArrayList<Integer>();
@@ -63,7 +63,6 @@
     protected void setUp() throws Exception {
         super.setUp();
         mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
-        mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
         mPackageManager = getContext().getPackageManager();
 
         // Get com.android.internal.R.array.config_protectedNetworks
@@ -257,7 +256,7 @@
                     NetworkInfo.State.DISCONNECTED;
             expectNetworkBroadcast(TYPE_WIFI, desiredState, new Runnable() {
                 public void run() {
-                    mWifiManager.setWifiEnabled(enabled);
+                    SystemUtil.runShellCommand("svc wifi " + (enabled ? "enable" : "disable"));
                 }
             });
         }
diff --git a/tests/openglperf2/Android.mk b/tests/openglperf2/Android.mk
index 8fa4296..02ae698 100644
--- a/tests/openglperf2/Android.mk
+++ b/tests/openglperf2/Android.mk
@@ -33,5 +33,6 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := 16
+#LOCAL_MIN_SDK_VERSION := 16
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/pdf/AndroidTest.xml b/tests/pdf/AndroidTest.xml
index a402e10..475e555 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -19,6 +19,7 @@
 
     <!-- Printing is a large user of PDF, hence run the tests in the same component -->
     <option name="config-descriptor:metadata" key="component" value="print" />
+    <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/providerui/Android.mk b/tests/providerui/Android.mk
new file mode 100644
index 0000000..852e3c6
--- /dev/null
+++ b/tests/providerui/Android.mk
@@ -0,0 +1,45 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
+    ctstestrunner \
+    ub-uiautomator \
+    junit
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    androidx.legacy_legacy-support-v4
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsProviderUiTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml
new file mode 100644
index 0000000..4051b24
--- /dev/null
+++ b/tests/providerui/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.providerui.cts">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <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" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.providerui.cts.GetResultActivity" />
+
+        <provider android:name="androidx.core.content.FileProvider"
+            android:authorities="android.providerui.cts.fileprovider"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.providerui.cts"
+                     android:label="CTS UI tests of android.provider">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/providerui/AndroidTest.xml b/tests/providerui/AndroidTest.xml
new file mode 100644
index 0000000..6a123f6
--- /dev/null
+++ b/tests/providerui/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<configuration description="Config for CTS Provider UI 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="instant_app" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsProviderUiTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.providerui.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/provider/res/xml/file_paths.xml b/tests/providerui/res/xml/file_paths.xml
similarity index 100%
rename from tests/tests/provider/res/xml/file_paths.xml
rename to tests/providerui/res/xml/file_paths.xml
diff --git a/tests/providerui/src/android/providerui/cts/GetResultActivity.java b/tests/providerui/src/android/providerui/cts/GetResultActivity.java
new file mode 100644
index 0000000..08885d5
--- /dev/null
+++ b/tests/providerui/src/android/providerui/cts/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * 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.providerui.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    private static LinkedBlockingQueue<Result> sResult;
+
+    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 onActivityResult(int requestCode, int resultCode, Intent data) {
+        try {
+            sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void clearResult() {
+        sResult = new LinkedBlockingQueue<>();
+    }
+
+    public Result getResult() {
+        final Result result;
+        try {
+            result = sResult.poll(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalStateException("Activity didn't receive a Result in 30 seconds");
+        }
+        return result;
+    }
+
+    public Result getResult(long timeout, TimeUnit unit) {
+        try {
+            return sResult.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
new file mode 100644
index 0000000..6309756
--- /dev/null
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -0,0 +1,375 @@
+/*
+ * 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.providerui.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.RECORD_AUDIO;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.media.ExifInterface;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.providerui.cts.GetResultActivity.Result;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import androidx.core.content.FileProvider;
+
+public class MediaStoreUiTest extends InstrumentationTestCase {
+    private static final String TAG = "MediaStoreUiTest";
+
+    private static final int REQUEST_CODE = 42;
+    private static final String CONTENT = "Test";
+
+    private UiDevice mDevice;
+    private GetResultActivity mActivity;
+
+    private File mFile;
+    private Uri mMediaStoreUri;
+
+    @Override
+    public void setUp() throws Exception {
+        mDevice = UiDevice.getInstance(getInstrumentation());
+
+        final Context context = getInstrumentation().getContext();
+        mActivity = launchActivity(context.getPackageName(), GetResultActivity.class, null);
+        mActivity.clearResult();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mFile != null) {
+            mFile.delete();
+        }
+
+        final ContentResolver resolver = mActivity.getContentResolver();
+        for (UriPermission permission : resolver.getPersistedUriPermissions()) {
+            mActivity.revokeUriPermission(
+                    permission.getUri(),
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        }
+
+        mActivity.finish();
+    }
+
+    public void testGetDocumentUri() throws Exception {
+        if (!supportsHardware()) return;
+
+        prepareFile();
+
+        final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
+        assertNotNull(treeUri);
+
+        final Uri docUri = MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
+        assertNotNull(docUri);
+
+        final ContentResolver resolver = mActivity.getContentResolver();
+        try (ParcelFileDescriptor fd = resolver.openFileDescriptor(docUri, "rw")) {
+            // Test reading
+            try (final BufferedReader reader =
+                         new BufferedReader(new FileReader(fd.getFileDescriptor()))) {
+                assertEquals(CONTENT, reader.readLine());
+            }
+
+            // Test writing
+            try (final OutputStream out = new FileOutputStream(fd.getFileDescriptor())) {
+                out.write(CONTENT.getBytes());
+            }
+        }
+    }
+
+    public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception {
+        if (!supportsHardware()) return;
+
+        prepareFile();
+
+        try {
+            MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
+            fail("Expecting SecurityException.");
+        } catch (SecurityException e) {
+            // Expected
+        }
+    }
+
+    public void testGetDocumentUri_Symmetry() throws Exception {
+        if (!supportsHardware()) return;
+
+        prepareFile();
+
+        final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
+        assertNotNull(treeUri);
+
+        final Uri docUri = MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
+        assertNotNull(docUri);
+
+        final Uri mediaUri = MediaStore.getMediaUri(mActivity, docUri);
+        assertNotNull(mediaUri);
+
+        assertEquals(mMediaStoreUri, mediaUri);
+    }
+
+    private void maybeClick(UiSelector sel) {
+        try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
+    }
+
+    private void maybeClick(BySelector sel) {
+        try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
+    }
+
+    private void maybeGrantRuntimePermission(String pkg, Set<String> requested, String permission)
+            throws NameNotFoundException {
+        // We only need to grant dangerous permissions
+        final Context context = getInstrumentation().getContext();
+        if ((context.getPackageManager().getPermissionInfo(permission, 0).getProtection()
+                & PermissionInfo.PROTECTION_DANGEROUS) == 0) {
+            return;
+        }
+
+        if (requested.contains(permission)) {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .grantRuntimePermission(pkg, permission);
+        }
+    }
+
+    /**
+     * Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
+     * correctly write the contents into a passed {@code content://} Uri.
+     */
+    public void testImageCapture() throws Exception {
+        final Context context = getInstrumentation().getContext();
+        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+            Log.d(TAG, "Skipping due to lack of camera");
+            return;
+        }
+
+        final File targetDir = new File(context.getFilesDir(), "debug");
+        final File target = new File(targetDir, "capture.jpg");
+
+        targetDir.mkdirs();
+        assertFalse(target.exists());
+
+        final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        intent.putExtra(MediaStore.EXTRA_OUTPUT,
+                FileProvider.getUriForFile(context, "android.providerui.cts.fileprovider", target));
+
+        // Figure out who is going to answer the phone
+        final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
+        final String pkg = ri.activityInfo.packageName;
+        Log.d(TAG, "We're probably launching " + ri);
+
+        final PackageInfo pi = context.getPackageManager().getPackageInfo(pkg,
+                PackageManager.GET_PERMISSIONS);
+        final Set<String> req = new HashSet<>();
+        req.addAll(Arrays.asList(pi.requestedPermissions));
+
+        // Grant them all the permissions they might want
+        maybeGrantRuntimePermission(pkg, req, CAMERA);
+        maybeGrantRuntimePermission(pkg, req, ACCESS_COARSE_LOCATION);
+        maybeGrantRuntimePermission(pkg, req, ACCESS_FINE_LOCATION);
+        maybeGrantRuntimePermission(pkg, req, ACCESS_BACKGROUND_LOCATION);
+        maybeGrantRuntimePermission(pkg, req, RECORD_AUDIO);
+        maybeGrantRuntimePermission(pkg, req, READ_EXTERNAL_STORAGE);
+        maybeGrantRuntimePermission(pkg, req, WRITE_EXTERNAL_STORAGE);
+        SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+
+        // To ensure camera app is launched
+        SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+
+        // Try a couple different strategies for taking a photo: first take a
+        // photo and confirm using hardware keys
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
+        mDevice.waitForIdle();
+        SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
+        mDevice.waitForIdle();
+
+        // Maybe that gave us a result?
+        Result result = mActivity.getResult(15, TimeUnit.SECONDS);
+        Log.d(TAG, "First pass result was " + result);
+
+        // Hrm, that didn't work; let's try an alternative approach of digging
+        // around for a shutter button
+        if (result == null) {
+            maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
+            mDevice.waitForIdle();
+            SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+            maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
+            mDevice.waitForIdle();
+            maybeClick(new UiSelector().resourceId(pkg + ":id/done_button"));
+            mDevice.waitForIdle();
+
+            result = mActivity.getResult(15, TimeUnit.SECONDS);
+            Log.d(TAG, "Second pass result was " + result);
+        }
+
+        // Grr, let's try hunting around even more
+        if (result == null) {
+            maybeClick(By.pkg(pkg).descContains("Capture"));
+            mDevice.waitForIdle();
+            SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+            maybeClick(By.pkg(pkg).descContains("Done"));
+            mDevice.waitForIdle();
+
+            result = mActivity.getResult(15, TimeUnit.SECONDS);
+            Log.d(TAG, "Third pass result was " + result);
+        }
+
+        assertNotNull("Expected to get a IMAGE_CAPTURE result; your camera app should "
+                + "respond to the CAMERA and DPAD_CENTER keycodes", result);
+
+        assertTrue("exists", target.exists());
+        assertTrue("has data", target.length() > 65536);
+
+        // At the very least we expect photos generated by the device to have
+        // sane baseline EXIF data
+        final ExifInterface exif = new ExifInterface(new FileInputStream(target));
+        assertAttribute(exif, ExifInterface.TAG_MAKE);
+        assertAttribute(exif, ExifInterface.TAG_MODEL);
+        assertAttribute(exif, ExifInterface.TAG_DATETIME);
+    }
+
+    private static void assertAttribute(ExifInterface exif, String tag) {
+        final String res = exif.getAttribute(tag);
+        if (res == null || res.length() == 0) {
+            Log.d(TAG, "Expected valid EXIF tag for tag " + tag);
+        }
+    }
+
+    private boolean supportsHardware() {
+        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        return !pm.hasSystemFeature("android.hardware.type.television")
+                && !pm.hasSystemFeature("android.hardware.type.watch");
+    }
+
+    private void prepareFile() throws Exception {
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+
+        final File documents =
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+        documents.mkdirs();
+        assertTrue(documents.isDirectory());
+
+        mFile = new File(documents, "test.jpg");
+        try (OutputStream os = new FileOutputStream(mFile)) {
+            os.write(CONTENT.getBytes());
+        }
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        MediaScannerConnection.scanFile(
+                mActivity,
+                new String[]{ mFile.getAbsolutePath() },
+                new String[]{ "image/jpeg" },
+                (String path, Uri uri) -> onScanCompleted(uri, latch)
+        );
+        assertTrue(
+                "MediaScanner didn't finish scanning in 30s.", latch.await(30, TimeUnit.SECONDS));
+    }
+
+    private void onScanCompleted(Uri uri, CountDownLatch latch) {
+        final String volumeName = MediaStore.getVolumeName(uri);
+        mMediaStoreUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri(volumeName),
+                ContentUris.parseId(uri));
+        latch.countDown();
+    }
+
+    private Uri acquireAccess(File file, String directoryName) {
+        StorageManager storageManager =
+                (StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
+
+        // Request access from DocumentsUI
+        final StorageVolume volume = storageManager.getStorageVolume(file);
+        final Intent intent = volume.createOpenDocumentTreeIntent();
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // Granting the access
+        BySelector buttonPanelSelector = By.pkg("com.android.documentsui")
+                .res("com.android.documentsui:id/container_save");
+        mDevice.wait(Until.hasObject(buttonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+        final UiObject2 buttonPanel = mDevice.findObject(buttonPanelSelector);
+        final UiObject2 allowButton = buttonPanel.findObject(By.res("android:id/button1"));
+        allowButton.click();
+        mDevice.waitForIdle();
+
+        // Granting the access by click "allow" in confirm dialog
+        final BySelector dialogButtonPanelSelector = By.pkg("com.android.documentsui")
+                .res("com.android.documentsui:id/buttonPanel");
+        mDevice.wait(Until.hasObject(dialogButtonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+        final UiObject2 positiveButton = mDevice.findObject(dialogButtonPanelSelector)
+                .findObject(By.res("android:id/button1"));
+        positiveButton.click();
+        mDevice.waitForIdle();
+
+        // Check granting result and take persistent permission
+        final Result result = mActivity.getResult();
+        assertEquals(Activity.RESULT_OK, result.resultCode);
+
+        final Intent resultIntent = result.data;
+        final Uri resultUri = resultIntent.getData();
+        final int flags = resultIntent.getFlags()
+                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        mActivity.getContentResolver().takePersistableUriPermission(resultUri, flags);
+        return resultUri;
+    }
+}
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index 8a7d2d9..c398ab05 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Sample test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="misc" />
+    <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" />
         <option name="test-file-name" value="CtsSampleDeviceTestCases.apk" />
diff --git a/tests/security/Android.mk b/tests/security/Android.mk
index 5bee990..5b0cf00 100755
--- a/tests/security/Android.mk
+++ b/tests/security/Android.mk
@@ -19,7 +19,7 @@
 # Don't include this package in any target.
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-unbundled bouncycastle-bcpkix-unbundled guava
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-unbundled bouncycastle-bcpkix-unbundled guava truth-prebuilt testng
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_MODULE := cts-security-test-support-library
 
diff --git a/tests/security/src/android/keystore/cts/KeyGenerationUtils.java b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
new file mode 100644
index 0000000..27c9a8c
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.keystore.cts;
+
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.security.AttestedKeyPair;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+public class KeyGenerationUtils {
+    private static final String ALIAS = "com.android.test.generated-rsa-1";
+
+    private static KeyGenParameterSpec buildRsaKeySpecWithKeyAttestation(String alias) {
+        return new KeyGenParameterSpec.Builder(
+                alias,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setKeySize(2048)
+                .setDigests(KeyProperties.DIGEST_SHA256)
+                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                .setIsStrongBoxBacked(false)
+                .setAttestationChallenge(new byte[]{'a', 'b', 'c'})
+                .build();
+    }
+
+    private static void generateKeyWithIdFlagsExpectingSuccess(DevicePolicyManager dpm,
+            ComponentName admin, int deviceIdAttestationFlags) {
+        try {
+            AttestedKeyPair generated = dpm.generateKeyPair(
+                    admin, "RSA", buildRsaKeySpecWithKeyAttestation(ALIAS),
+                    deviceIdAttestationFlags);
+            assertThat(generated).isNotNull();
+        } finally {
+            assertThat(dpm.removeKeyPair(admin, ALIAS)).isTrue();
+        }
+    }
+
+    public static void generateKeyWithDeviceIdAttestationExpectingSuccess(DevicePolicyManager dpm,
+            ComponentName admin) {
+        generateKeyWithIdFlagsExpectingSuccess(dpm, admin, ID_TYPE_SERIAL);
+    }
+
+    public static void generateKeyWithDeviceIdAttestationExpectingFailure(DevicePolicyManager dpm,
+            ComponentName admin) {
+        assertThrows(SecurityException.class,
+                () -> dpm.generateKeyPair(admin, "RSA", buildRsaKeySpecWithKeyAttestation(ALIAS),
+                        ID_TYPE_SERIAL));
+    }
+}
diff --git a/tests/sensor/src/android/hardware/cts/SensorIntegrationTests.java b/tests/sensor/src/android/hardware/cts/SensorIntegrationTests.java
index bcd1734..d53034c 100644
--- a/tests/sensor/src/android/hardware/cts/SensorIntegrationTests.java
+++ b/tests/sensor/src/android/hardware/cts/SensorIntegrationTests.java
@@ -19,12 +19,15 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorNotSupportedException;
 import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.sensoroperations.DelaySensorOperation;
 import android.hardware.cts.helpers.sensoroperations.ParallelSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.RepeatingSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.SequentialSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
 import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
+import android.hardware.cts.helpers.sensorverification.FrequencyVerification;
 
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
@@ -160,6 +163,124 @@
         operation.getStats().log(TAG);
     }
 
+    public void testAccelerometerReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void testUncalibratedAccelerometerReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED);
+    }
+
+    public void testMagneticFieldReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_MAGNETIC_FIELD);
+    }
+
+    public void testUncalibratedMagneticFieldReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
+    }
+
+    public void testOrientationReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_ORIENTATION);
+    }
+
+    public void testGyroscopeReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_GYROSCOPE);
+    }
+
+    public void testUncalibratedGyroscopeReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
+    }
+
+    public void testPressureReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_PRESSURE);
+    }
+
+    public void testGravityReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_GRAVITY);
+    }
+
+    public void testLinearAccelerationReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_LINEAR_ACCELERATION);
+    }
+
+    public void testRotationVectorReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_ROTATION_VECTOR);
+    }
+
+    public void testGameRotationVectorReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_GAME_ROTATION_VECTOR);
+    }
+
+    public void testGeomagneticRotationVectorReconfigureWhileActive() throws Throwable {
+        verifySensorReconfigureWhileActive(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);
+    }
+
+    /**
+     * This test focuses on ensuring that an active sensor is able to be reconfigured when a new
+     * client requests a different sampling rate.
+     *
+     * The test verifies that if a sensor is active with a slow sampling rate and a new client
+     * requests a faster sampling rate, the sensor begins returning data at the faster sampling
+     * rate.
+     *
+     * The assertion associated with the test failure provides:
+     * - the thread id on which the failure occurred
+     * - the sensor type and sensor handle that caused the failure
+     * - the event that caused the issue
+     * It is important to look at the internals of the Sensor HAL to identify how the interaction
+     * of several clients can lead to the failing state.
+     */
+    public void verifySensorReconfigureWhileActive(int sensorType) throws Throwable {
+        SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
+
+        final int DELAY_BEFORE_CHANGING_RATE_SEC = 2;
+        final int EVENTS_FOR_VERIFICATION = 200;
+        Context context = getContext();
+        SensorManager sensorManager =
+                (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        assertNotNull("SensorService is not present in the system", sensorManager);
+
+        Sensor sensor = sensorManager.getDefaultSensor(sensorType);
+        if(sensor == null) {
+            throw new SensorNotSupportedException(sensorType);
+        }
+
+        // Request for the sensor rate to be set to the slowest rate.
+        ParallelSensorOperation operation = new ParallelSensorOperation();
+        TestSensorEnvironment environment = new TestSensorEnvironment(
+                context,
+                sensor,
+                shouldEmulateSensorUnderLoad(),
+                true, /* isIntegrationTest */
+                sensor.getMaxDelay(),
+                (int)TimeUnit.SECONDS.toMicros(20));
+        TestSensorOperation sensorOperationSlow = TestSensorOperation.createOperation(
+                environment, 2 * DELAY_BEFORE_CHANGING_RATE_SEC, TimeUnit.SECONDS);
+        operation.add(sensorOperationSlow);
+
+        // Create a second operation that will run in parallel and request the fastest rate after
+        // an initial delay. The delay is to ensure that the first operation has enabled the sensor.
+        // The sensor should begin reporting at the newly requested rate.
+        environment = new TestSensorEnvironment(
+                context,
+                sensor,
+                shouldEmulateSensorUnderLoad(),
+                true, /* isIntegrationTest */
+                sensor.getMinDelay(),
+                0 /* max reporting latency */);
+        TestSensorOperation sensorOperationFast = TestSensorOperation.createOperation(
+                environment, EVENTS_FOR_VERIFICATION);
+        sensorOperationFast.addVerification(FrequencyVerification.getDefault(environment));
+        DelaySensorOperation delaySensorOperation = new DelaySensorOperation(
+                sensorOperationFast,
+                DELAY_BEFORE_CHANGING_RATE_SEC,
+                TimeUnit.SECONDS);
+        operation.add(delaySensorOperation);
+
+        operation.execute(getCurrentTestNode());
+        operation.getStats().log(TAG);
+    }
+
     /**
      * Regress:
      * - b/10641388
diff --git a/tests/sensor/src/android/hardware/cts/SensorParameterRangeTest.java b/tests/sensor/src/android/hardware/cts/SensorParameterRangeTest.java
index c1c9fab..94d7de8 100644
--- a/tests/sensor/src/android/hardware/cts/SensorParameterRangeTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorParameterRangeTest.java
@@ -21,8 +21,11 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.SensorCtsHelper;
+import android.os.Build;
 import android.text.TextUtils;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -38,21 +41,31 @@
  */
 public class SensorParameterRangeTest extends SensorTestCase {
 
-    private static final double ACCELEROMETER_MAX_RANGE = 8 * 9.80; // 8G minus a slop
-    private static final double ACCELEROMETER_MIN_FREQUENCY = 12.50;
-    private static final int ACCELEROMETER_MAX_FREQUENCY = 200;
+    private static final double ACCELEROMETER_MAX_RANGE = 4 * 9.80; // 4g - ε
+    private static final double ACCELEROMETER_MAX_FREQUENCY = 50.0;
+    private static final double ACCELEROMETER_HIFI_MAX_RANGE = 8 * 9.80; // 8g - ε
+    private static final double ACCELEROMETER_HIFI_MIN_FREQUENCY = 12.50;
+    private static final double ACCELEROMETER_HIFI_MAX_FREQUENCY = 400.0;
+    private static final double ACCELEROMETER_HIFI_MAX_FREQUENCY_BEFORE_N = 200.0;
 
-    private static final double GYRO_MAX_RANGE = 1000/57.295 - 1.0; // 1000 degrees per sec minus a slop
-    private static final double GYRO_MIN_FREQUENCY = 12.50;
-    private static final double GYRO_MAX_FREQUENCY = 200.0;
+    private static final double GYRO_MAX_RANGE = 1000 / 57.295 - 1.0; // 1000 degrees/sec - ε
+    private static final double GYRO_MAX_FREQUENCY = 50.0;
+    private static final double GYRO_HIFI_MAX_RANGE = 1000 / 57.295 - 1.0; // 1000 degrees/sec - ε
+    private static final double GYRO_HIFI_MIN_FREQUENCY = 12.50;
+    private static final double GYRO_HIFI_MAX_FREQUENCY = 400.0;
+    private static final double GYRO_HIFI_MAX_FREQUENCY_BEFORE_N = 200.0;
 
-    private static final int MAGNETOMETER_MAX_RANGE = 900;   // micro telsa
-    private static final double MAGNETOMETER_MIN_FREQUENCY = 5.0;
+    private static final double MAGNETOMETER_MAX_RANGE = 900.0;   // micro telsa
     private static final double MAGNETOMETER_MAX_FREQUENCY = 50.0;
+    private static final double MAGNETOMETER_HIFI_MAX_RANGE = 900.0;   // micro telsa
+    private static final double MAGNETOMETER_HIFI_MIN_FREQUENCY = 5.0;
+    private static final double MAGNETOMETER_HIFI_MAX_FREQUENCY = 50.0;
 
-    private static final double PRESSURE_MAX_RANGE = 1100.0;     // hecto-pascal
-    private static final double PRESSURE_MIN_FREQUENCY = 1.0;
-    private static final double PRESSURE_MAX_FREQUENCY = 10.0;
+    private static final double PRESSURE_MAX_RANGE = 0.0;     // hecto-pascal
+    private static final double PRESSURE_MAX_FREQUENCY = 5.0;
+    private static final double PRESSURE_HIFI_MAX_RANGE = 1100.0;     // hecto-pascal
+    private static final double PRESSURE_HIFI_MIN_FREQUENCY = 1.0;
+    private static final double PRESSURE_HIFI_MAX_FREQUENCY = 10.0;
 
     // Note these FIFO minimum constants come from the CCD.  In version
     // 6.0 of the CCD, these are in section 7.3.9.
@@ -76,63 +89,95 @@
     }
 
     public void testAccelerometerRange() {
+        double hifiMaxFrequency = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.N) ?
+                ACCELEROMETER_HIFI_MAX_FREQUENCY :
+                ACCELEROMETER_HIFI_MAX_FREQUENCY_BEFORE_N;
+
         checkSensorRangeAndFrequency(
                 Sensor.TYPE_ACCELEROMETER,
                 ACCELEROMETER_MAX_RANGE,
-                ACCELEROMETER_MIN_FREQUENCY,
-                ACCELEROMETER_MAX_FREQUENCY);
+                ACCELEROMETER_MAX_FREQUENCY,
+                ACCELEROMETER_HIFI_MAX_RANGE,
+                ACCELEROMETER_HIFI_MIN_FREQUENCY,
+                hifiMaxFrequency);
     }
 
     public void testGyroscopeRange() {
+        double hifiMaxFrequency = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.N) ?
+                GYRO_HIFI_MAX_FREQUENCY :
+                GYRO_HIFI_MAX_FREQUENCY_BEFORE_N;
+
         checkSensorRangeAndFrequency(
                 Sensor.TYPE_GYROSCOPE,
                 GYRO_MAX_RANGE,
-                GYRO_MIN_FREQUENCY,
-                GYRO_MAX_FREQUENCY);
+                GYRO_MAX_FREQUENCY,
+                GYRO_HIFI_MAX_RANGE,
+                GYRO_HIFI_MIN_FREQUENCY,
+                hifiMaxFrequency);
     }
 
     public void testMagnetometerRange() {
         checkSensorRangeAndFrequency(
                 Sensor.TYPE_MAGNETIC_FIELD,
                 MAGNETOMETER_MAX_RANGE,
-                MAGNETOMETER_MIN_FREQUENCY,
-                MAGNETOMETER_MAX_FREQUENCY);
+                MAGNETOMETER_MAX_FREQUENCY,
+                MAGNETOMETER_HIFI_MAX_RANGE,
+                MAGNETOMETER_HIFI_MIN_FREQUENCY,
+                MAGNETOMETER_HIFI_MAX_FREQUENCY);
     }
 
     public void testPressureRange() {
-        if (mHasHifiSensors) {
-            checkSensorRangeAndFrequency(
-                    Sensor.TYPE_PRESSURE,
-                    PRESSURE_MAX_RANGE,
-                    PRESSURE_MIN_FREQUENCY,
-                    PRESSURE_MAX_FREQUENCY);
-        }
+        checkSensorRangeAndFrequency(
+                Sensor.TYPE_PRESSURE,
+                PRESSURE_MAX_RANGE,
+                PRESSURE_MAX_FREQUENCY,
+                PRESSURE_HIFI_MAX_RANGE,
+                PRESSURE_HIFI_MIN_FREQUENCY,
+                PRESSURE_HIFI_MAX_FREQUENCY);
     }
 
     private void checkSensorRangeAndFrequency(
-          int sensorType, double maxRange, double minFrequency, double maxFrequency) {
-        if (!mHasHifiSensors && !mVrModeHighPerformance) return;
+            int sensorType, double maxRange, double maxFrequency, double hifiMaxRange,
+            double hifiMinFrequency, double hifiMaxFrequency) {
+        boolean mustMeetHiFi = mHasHifiSensors;
+
+        // CDD 7.9.2/C-1-21: High Performance VR must meet accel, gyro, and mag HiFi requirements
+        if (mVrModeHighPerformance && (sensorType == Sensor.TYPE_ACCELEROMETER ||
+                sensorType == Sensor.TYPE_GYROSCOPE || sensorType == Sensor.TYPE_MAGNETIC_FIELD)) {
+            mustMeetHiFi = true;
+        }
 
         Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
         if (sensor == null) {
-            fail(String.format("Must support sensor type %d", sensorType));
+            if (mustMeetHiFi) {
+                fail(String.format("Must support sensor type %d", sensorType));
+            } else {
+                // Sensor is not required
+                return;
+            }
         }
 
+        double range = mustMeetHiFi ? hifiMaxRange : maxRange;
+        double frequency = mustMeetHiFi ? hifiMaxFrequency : maxFrequency;
+
         assertTrue(String.format("%s Range actual=%.2f expected=%.2f %s",
-                    sensor.getName(), sensor.getMaximumRange(), maxRange,
+                    sensor.getName(), sensor.getMaximumRange(), range,
                     SensorCtsHelper.getUnitsForSensor(sensor)),
-                sensor.getMaximumRange() >= maxRange);
-        double actualMinFrequency = SensorCtsHelper.getFrequency(sensor.getMaxDelay(),
-                TimeUnit.MICROSECONDS);
-        assertTrue(String.format("%s Min Frequency actual=%.2f expected=%.2fHz",
-                    sensor.getName(), actualMinFrequency, minFrequency), actualMinFrequency <=
-                minFrequency + 0.1);
+                sensor.getMaximumRange() >= range);
 
         double actualMaxFrequency = SensorCtsHelper.getFrequency(sensor.getMinDelay(),
                 TimeUnit.MICROSECONDS);
         assertTrue(String.format("%s Max Frequency actual=%.2f expected=%.2fHz",
-                    sensor.getName(), actualMaxFrequency, maxFrequency), actualMaxFrequency >=
-                maxFrequency - 0.1);
+                    sensor.getName(), actualMaxFrequency, frequency), actualMaxFrequency >=
+                frequency - 0.1);
+
+        if (mustMeetHiFi) {
+            double actualMinFrequency = SensorCtsHelper.getFrequency(sensor.getMaxDelay(),
+                    TimeUnit.MICROSECONDS);
+            assertTrue(String.format("%s Min Frequency actual=%.2f expected=%.2fHz",
+                        sensor.getName(), actualMinFrequency, hifiMinFrequency),
+                    actualMinFrequency <=  hifiMinFrequency + 0.1);
+        }
     }
 
     public void testAccelerometerFifoLength() throws Throwable {
diff --git a/tests/sensor/src/android/hardware/cts/SensorTest.java b/tests/sensor/src/android/hardware/cts/SensorTest.java
index 1751a8b..4635df8 100644
--- a/tests/sensor/src/android/hardware/cts/SensorTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorTest.java
@@ -177,6 +177,17 @@
             assertNull(sensor);
         }
 
+        sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+        boolean hasGyroscope = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_SENSOR_GYROSCOPE);
+        // gyroscope sensor is optional
+        if (hasGyroscope) {
+            assertEquals(Sensor.TYPE_GYROSCOPE, sensor.getType());
+            assertSensorValues(sensor);
+        } else {
+            assertNull(sensor);
+        }
+
         sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
         // Note: orientation sensor is deprecated.
         if (sensor != null) {
@@ -509,6 +520,12 @@
                 sensor.getName(), sensor.getPower() >= 0);
         assertTrue("Max resolution must be positive. Resolution=" + sensor.getResolution() +
                 " " + sensor.getName(), sensor.getResolution() >= 0);
+        if (SensorCtsHelper.hasResolutionRequirement(sensor)) {
+            float requiredResolution = SensorCtsHelper.getRequiredResolutionForSensor(sensor);
+            assertTrue("Resolution must be <= " + requiredResolution + ". Resolution=" +
+                    sensor.getResolution() + " " + sensor.getName(),
+                    sensor.getResolution() <= requiredResolution);
+        }
         assertNotNull("Vendor name must not be null " + sensor.getName(), sensor.getVendor());
         assertTrue("Version must be positive version=" + sensor.getVersion() + " " +
                 sensor.getName(), sensor.getVersion() > 0);
diff --git a/tests/sensor/src/android/hardware/cts/helpers/FrameworkUnitTests.java b/tests/sensor/src/android/hardware/cts/helpers/FrameworkUnitTests.java
index 6480f55..763b5e1 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/FrameworkUnitTests.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/FrameworkUnitTests.java
@@ -27,6 +27,7 @@
 import android.hardware.cts.helpers.sensorverification.JitterVerificationTest;
 import android.hardware.cts.helpers.sensorverification.MagnitudeVerificationTest;
 import android.hardware.cts.helpers.sensorverification.MeanVerificationTest;
+import android.hardware.cts.helpers.sensorverification.OffsetVerificationTest;
 import android.hardware.cts.helpers.sensorverification.StandardDeviationVerificationTest;
 import android.hardware.cts.helpers.sensorverification.TimestampClockSourceVerificationTest;
 
@@ -50,6 +51,7 @@
         addTestSuite(JitterVerificationTest.class);
         addTestSuite(MagnitudeVerificationTest.class);
         addTestSuite(MeanVerificationTest.class);
+        addTestSuite(OffsetVerificationTest.class);
         addTestSuite(StandardDeviationVerificationTest.class);
         addTestSuite(TimestampClockSourceVerificationTest.class);
 
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
index bbe2006..d9a1a75 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -327,6 +327,41 @@
         return "";
     }
 
+    public static boolean hasResolutionRequirement(Sensor sensor) {
+        switch (sensor.getType()) {
+            case Sensor.TYPE_ACCELEROMETER:
+            case Sensor.TYPE_ACCELEROMETER_UNCALIBRATED:
+            case Sensor.TYPE_GYROSCOPE:
+            case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
+            case Sensor.TYPE_MAGNETIC_FIELD:
+            case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
+                return true;
+        }
+        return false;
+    }
+
+    public static float getRequiredResolutionForSensor(Sensor sensor) {
+        switch (sensor.getType()) {
+            case Sensor.TYPE_ACCELEROMETER:
+            case Sensor.TYPE_ACCELEROMETER_UNCALIBRATED:
+            case Sensor.TYPE_GYROSCOPE:
+            case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
+                // Accelerometer and gyroscope must have at least 12 bits
+                // of resolution. The maximum resolution calculation uses
+                // slightly more than twice the maximum range because
+                //   1) the sensor must be able to report values from
+                //      [-maxRange, maxRange] without saturating
+                //   2) to allow for slight rounding errors
+                return (float)(2.001f * sensor.getMaximumRange() / Math.pow(2, 12));
+            case Sensor.TYPE_MAGNETIC_FIELD:
+            case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
+                // Magnetometer must have a resolution equal to or denser
+                // than 0.6 uT
+                return 0.6f;
+        }
+        return 0.0f;
+    }
+
     public static String sensorTypeShortString(int type) {
         switch (type) {
             case Sensor.TYPE_ACCELEROMETER:
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerification.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerification.java
new file mode 100644
index 0000000..57426f5
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerification.java
@@ -0,0 +1,125 @@
+/*
+ * 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.hardware.cts.helpers.sensorverification;
+
+import android.hardware.Sensor;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import junit.framework.Assert;
+
+/**
+ * A {@link ISensorVerification} which verifies that the offset for a sensor event is
+ * within range.
+ */
+public class OffsetVerification extends AbstractSensorVerification {
+    public static final String PASSED_KEY = "offset_passed";
+
+    // Number of indices to print in assert message before truncating
+    private static final int TRUNCATE_MESSAGE_LENGTH = 3;
+
+    // CDD 7.3.2/C1-5: Magnetometer must have a hard iron offset value less than 700uT
+    private static final float MAGNETOMETER_MAXIMUM_OFFSET_UT = 700.0f;
+
+    // Threshold to allow for the actual offset magnitudes to vary slightly from the CDD limit
+    // in order to account for rounding errors.
+    private static final float ALLOWED_ERROR_PERCENT = 0.0001f;
+
+    private ArrayList<Float> mOffsetMagnitudes;
+    private float mMaximumOffset;
+
+    /**
+     * Construct a {@link OffsetVerification}
+     *
+     * @param maximumOffset the maximum allowed magnitude for the sensor event offset
+     *                      in units defined by the CDD
+     */
+    public OffsetVerification(float maximumOffset) {
+        mOffsetMagnitudes = new ArrayList<Float>();
+        mMaximumOffset = maximumOffset;
+    }
+
+    /**
+     * Get the default {@link OffsetVerification}.
+     *
+     * @param environment the test environment
+     * @return the verification or null if there is no offset requirement
+     */
+    public static OffsetVerification getDefault(TestSensorEnvironment environment) {
+        if (environment.getSensor().getType() == Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) {
+            return new OffsetVerification(MAGNETOMETER_MAXIMUM_OFFSET_UT);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void verify(TestSensorEnvironment environment, SensorStats stats) {
+        verify(stats);
+    }
+
+    /**
+     * Visible for unit tests only.
+     */
+    protected void verify(SensorStats stats) {
+        final int count = mOffsetMagnitudes.size();
+        float maxOffset = Collections.max(mOffsetMagnitudes);
+        boolean pass = maxOffset < (mMaximumOffset * (1.0 + ALLOWED_ERROR_PERCENT));
+
+        stats.addValue(PASSED_KEY, pass);
+
+        if (!pass) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(count).append(" offsets: ");
+            for (int i = 0; i < Math.min(count, TRUNCATE_MESSAGE_LENGTH); i++) {
+                sb.append(String.format("position=%d, offset_magnitude=%f; ",
+                        i, mOffsetMagnitudes.get(i)));
+            }
+            if (count > TRUNCATE_MESSAGE_LENGTH) {
+                sb.append(count - TRUNCATE_MESSAGE_LENGTH).append(" more; ");
+            }
+            sb.append(String.format("(expected <%f)", mMaximumOffset));
+            Assert.fail(sb.toString());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public OffsetVerification clone() {
+        return new OffsetVerification(mMaximumOffset);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void addSensorEventInternal(TestSensorEvent event) {
+        // Calculate the magnitude of the bias/offsets from the SensorEvent. Uncalibrated
+        // SensorEvent objects contain the bias in values[3], values[4] and values[5].
+        mOffsetMagnitudes.add((float)SensorCtsHelper.getMagnitude(
+                Arrays.copyOfRange(event.values, 3, 6)));
+    }
+}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerificationTest.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerificationTest.java
new file mode 100644
index 0000000..128a8a5
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/OffsetVerificationTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.hardware.cts.helpers.sensorverification;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link OffsetVerification}.
+ */
+public class OffsetVerificationTest extends TestCase {
+
+    /**
+     * Test {@link OffsetVerification#verify(TestSensorEnvironment, SensorStats)}.
+     * The test verifies that the indices of the offsets within a sensor event are
+     * correctly used to determine if the offsets exceed the allowed limit.
+     * The format of the test array mirrors that of a {@link SensorEvent}:
+     *  v[0] : value 1
+     *  v[1] : value 2
+     *  v[2] : value 3
+     *  v[3] : value 1 offset
+     *  v[4] : value 2 offset
+     *  v[5] : value 3 offset
+     */
+    public void testSingleEventVerify() {
+        float[][] values = null;
+
+        // Verify that the norm is calculated correctly
+        values = new float[][]{ {10, 10, 10, 2, 2, 2} };
+        runStats(3.5f /* threshold */, values, true /* pass */);
+        runStats(3.4f /* threshold */, values, false /* pass */);
+
+        // Verify that the first value from the offsets is used
+        values = new float[][]{ {10, 10, 10, 2, 0, 0} };
+        runStats(2.0f /* threshold */, values, true /* pass */);
+        runStats(1.9f /* threshold */, values, false /* pass */);
+
+        // Verify that the last value from the offsets is used
+        values = new float[][]{ {10, 10, 10, 0, 0, 2} };
+        runStats(2.0f /* threshold */, values, true /* pass */);
+        runStats(1.9f /* threshold */, values, false /* pass */);
+
+        // Verify that no values outside the offsets is used
+        values = new float[][]{ {10, 10, 10, 0, 0, 0, 1} };
+        runStats(0.1f /* threshold */, values, true /* pass */);
+    }
+
+    /**
+     * Test {@link OffsetVerification#verify(TestSensorEnvironment, SensorStats)}.
+     * This test verifies that multiple sensor events are correctly recorded and
+     * verified.
+     */
+    public void testMultipleEventVerify() {
+        float[][] values = null;
+
+        values = new float[][] {
+                {10, 10, 10, 2, 2, 2},
+                {10, 10, 10, 2, 2, 2}
+        };
+        runStats(3.5f /* threshold */, values, true /* pass */);
+        runStats(3.4f /* threshold */, values, false /* pass */);
+
+        // Verify when the first event exceeds the threshold and the second does not
+        values = new float[][] {
+                {0, 0, 0, 0, 0, 0},
+                {0, 0, 0, 2, 2, 2}
+        };
+        runStats(3.0f /* threshold */, values, false /* pass */);
+
+        // Verify when the second event exceeds the threshold and the first does not
+        values = new float[][] {
+                {0, 0, 0, 0, 0, 0},
+                {0, 0, 0, 10, 10, 10}
+        };
+        runStats(3.0f /* threshold */, values, false /* pass */);
+    }
+
+    private void runStats(float threshold, float[][] values, boolean pass) {
+        SensorStats stats = new SensorStats();
+        OffsetVerification verification = getVerification(threshold, values);
+        if (pass) {
+            verification.verify(stats);
+        } else {
+            try {
+                verification.verify(stats);
+                throw new Error("Expected an AssertionError");
+            } catch (AssertionError e) {
+                // Expected;
+            }
+        }
+        assertEquals(pass, stats.getValue(OffsetVerification.PASSED_KEY));
+    }
+
+    private static OffsetVerification getVerification(
+            float threshold, float[] ... values) {
+        Collection<TestSensorEvent> events = new ArrayList<>(values.length);
+        for (float[] value : values) {
+            events.add(new TestSensorEvent(null /* sensor */, 0 /* timestamp */,
+                    0 /* receivedTimestamp */, value));
+        }
+        OffsetVerification verification = new OffsetVerification(threshold);
+        verification.addSensorEvents(events);
+        return verification;
+    }
+}
diff --git a/tests/signature/api-check/android-test-base-27-api/Android.mk b/tests/signature/api-check/android-test-base-27-api/Android.mk
index 1adcb12c..dc8dea0 100644
--- a/tests/signature/api-check/android-test-base-27-api/Android.mk
+++ b/tests/signature/api-check/android-test-base-27-api/Android.mk
@@ -21,4 +21,6 @@
 LOCAL_SIGNATURE_API_FILES := \
     android-test-base-current.api \
 
+LOCAL_MIN_SDK_VERSION := 25
+
 include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/Android.mk b/tests/signature/api-check/apache-http-legacy-27-api/Android.mk
index 0f2161a..4e17901 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/Android.mk
+++ b/tests/signature/api-check/apache-http-legacy-27-api/Android.mk
@@ -22,4 +22,6 @@
     current.api \
     apache-http-legacy-minus-current.api \
 
+LOCAL_MIN_SDK_VERSION := 22
+
 include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/tests/accounts/AndroidTest.xml b/tests/tests/accounts/AndroidTest.xml
index e0a3ae9..7ca519c 100644
--- a/tests/tests/accounts/AndroidTest.xml
+++ b/tests/tests/accounts/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Accounts 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="instant_app" />
     <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/alarmclock/Android.mk b/tests/tests/alarmclock/Android.mk
index 65b9f3c..5d5c4e4 100644
--- a/tests/tests/alarmclock/Android.mk
+++ b/tests/tests/alarmclock/Android.mk
@@ -21,7 +21,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner compatibility-device-util androidx.test.rules
+LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner compatibility-device-util
 
 LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
 
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
index 93fca7a..a4c0d0a 100644
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
@@ -4,8 +4,6 @@
 import android.content.Context;
 import android.media.AudioManager;
 
-import androidx.test.filters.Suppress;
-
 public class DismissTimerTest extends AlarmClockTestBase {
 
     private int mSavedVolume;
@@ -29,7 +27,6 @@
         audioManager.setStreamVolume(AudioManager.STREAM_ALARM, mSavedVolume, 0);
     }
 
-    @Suppress // b/120982312 - Flaky on AOSP
     public void testAll() throws Exception {
         assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.SET_TIMER_FOR_DISMISSAL));
         try {
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
index ff31ba4..a95a1dd 100644
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
@@ -19,14 +19,11 @@
 import android.alarmclock.common.Utils;
 import android.alarmclock.common.Utils.TestcaseType;
 
-import androidx.test.filters.Suppress;
-
 public class SetAlarmTest extends AlarmClockTestBase {
     public SetAlarmTest() {
         super();
     }
 
-    @Suppress // b/120982312 - Flaky on AOSP
     public void testAll() throws Exception {
         assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.SET_ALARM));
     }
diff --git a/tests/tests/animation/AndroidTest.xml b/tests/tests/animation/AndroidTest.xml
index 9453183..13b2a02 100644
--- a/tests/tests/animation/AndroidTest.xml
+++ b/tests/tests/animation/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Animation test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsAnimationTestCases.apk" />
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 8a5e974..d4f844f 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -368,6 +368,33 @@
     }
 
     @Test
+    public void testSeekAfterPause() throws Throwable {
+        final AnimatorSet set = new AnimatorSet();
+        ValueAnimator a1 = ValueAnimator.ofFloat(0f, 50f);
+        a1.setDuration(50);
+        ValueAnimator a2 = ValueAnimator.ofFloat(50, 100f);
+        a2.setDuration(50);
+        set.playSequentially(a1, a2);
+        set.setInterpolator(new LinearInterpolator());
+
+        mActivityRule.runOnUiThread(() -> {
+            set.start();
+            set.pause();
+            set.setCurrentPlayTime(60);
+            assertEquals((long) set.getCurrentPlayTime(), 60);
+            assertEquals((float) a1.getAnimatedValue(), 50f, EPSILON);
+            assertEquals((float) a2.getAnimatedValue(), 60f, EPSILON);
+
+            set.setCurrentPlayTime(40);
+            assertEquals((long) set.getCurrentPlayTime(), 40);
+            assertEquals((float) a1.getAnimatedValue(), 40f, EPSILON);
+            assertEquals((float) a2.getAnimatedValue(), 50f, EPSILON);
+
+            set.cancel();
+        });
+    }
+
+    @Test
     public void testDuration() throws Throwable {
         xAnimator.setRepeatCount(ValueAnimator.INFINITE);
         Animator[] animatorArray = { xAnimator, yAnimator };
diff --git a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
index f19b177..8650b7a 100644
--- a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
@@ -254,12 +254,11 @@
 
         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
         final ObjectAnimator animator = ObjectAnimator.ofArgb(object, property, start, end);
-        animator.setDuration(200);
+        animator.setDuration(50);
         animator.addListener(mockListener);
         animator.addUpdateListener(updateListener);
 
         mActivityRule.runOnUiThread(animator::start);
-        assertTrue(animator.isRunning());
 
         verify(mockListener, timeout(400)).onAnimationEnd(animator, false);
     }
@@ -682,7 +681,7 @@
         });
 
         mActivityRule.runOnUiThread(anim::start);
-        assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
+        assertTrue(endLatch.await(400, TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -788,7 +787,6 @@
         startAnimation(objAnimator);
         SystemClock.sleep(100);
         assertTrue(objAnimator.isStarted());
-        SystemClock.sleep(100);
     }
 
     @Test
@@ -858,14 +856,16 @@
                 public void run() {
                     if (twoFramesLatch.getCount() > 0) {
                         twoFramesLatch.countDown();
-                        decor.postOnAnimation(this);
+                        if (twoFramesLatch.getCount() > 0) {
+                            decor.postOnAnimation(this);
+                        }
                     }
                 }
             });
         });
 
         assertTrue("Animation didn't start in a reasonable time",
-                twoFramesLatch.await(100, TimeUnit.MILLISECONDS));
+                twoFramesLatch.await(200, TimeUnit.MILLISECONDS));
 
         mActivityRule.runOnUiThread(() -> {
             assertTrue("Start value should readjust to current position",
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index 59b8e5db..e7d939f 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -481,10 +481,11 @@
     @Test
     public void testGetAnimatedFraction() throws Throwable {
         ValueAnimator objAnimator = getAnimator();
+        objAnimator.setRepeatCount(0);
         startAnimation(objAnimator);
         assertNotNull(objAnimator);
-        float[] fractions = getValue(objAnimator, 10, "getAnimatedFraction()", 200l, null);
-        for(int j = 0; j < 9; j++){
+        float[] fractions = getValue(objAnimator, 5, "getAnimatedFraction()", 100L, null);
+        for (int j = 0; j < fractions.length - 1; j++) {
             assertTrue(fractions[j] >= 0.0);
             assertTrue(fractions[j] <= 1.0);
             assertTrue(errorMessage(fractions), fractions[j + 1] >= fractions[j]);
@@ -494,11 +495,12 @@
     @Test
     public void testGetAnimatedValue() throws Throwable {
         ValueAnimator objAnimator = getAnimator();
+        objAnimator.setRepeatCount(0);
         startAnimation(objAnimator);
         assertNotNull(objAnimator);
-        float[] animatedValues = getValue(objAnimator, 10, "getAnimatedValue()", 200l, null);
+        float[] animatedValues = getValue(objAnimator, 5, "getAnimatedValue()", 100L, null);
 
-        for(int j = 0; j < 9; j++){
+        for (int j = 0; j < animatedValues.length - 1; j++) {
             assertTrue(errorMessage(animatedValues), animatedValues[j + 1] >= animatedValues[j]);
         }
     }
@@ -508,11 +510,12 @@
         String property = "y";
 
         ValueAnimator objAnimator = getAnimator();
+        objAnimator.setRepeatCount(0);
         startAnimation(objAnimator);
         assertNotNull(objAnimator);
-        float[] animatedValues = getValue(objAnimator, 10, "getAnimatedValue(property)", 200l,
+        float[] animatedValues = getValue(objAnimator, 5, "getAnimatedValue(property)", 100L,
             property);
-        for(int j = 0; j < 9; j++){
+        for (int j = 0; j < animatedValues.length - 1; j++) {
             assertTrue(errorMessage(animatedValues), animatedValues[j + 1] >= animatedValues[j]);
         }
     }
@@ -651,6 +654,7 @@
     }
 
     private void testAnimatorsEnabledImpl(boolean enabled) throws Throwable {
+        final CountDownLatch startLatch = new CountDownLatch(1);
         final CountDownLatch endLatch = new CountDownLatch(1);
         final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
         animator.setDuration(1000);
@@ -662,8 +666,11 @@
         });
         mActivityRule.runOnUiThread(() -> {
             animator.start();
+            startLatch.countDown();
         });
 
+        assertTrue(startLatch.await(200, TimeUnit.MILLISECONDS));
+
         float durationScale = enabled ? 1 : 0;
         ValueAnimator.setDurationScale(durationScale);
 
@@ -671,12 +678,12 @@
             assertTrue("Animators not enabled with duration scale 1",
                     ValueAnimator.areAnimatorsEnabled());
             assertFalse("Animator ended too early when animators enabled = ",
-                    endLatch.await(50, TimeUnit.MILLISECONDS));
+                    endLatch.await(100, TimeUnit.MILLISECONDS));
         } else {
             assertFalse("Animators enabled with duration scale 0",
                     ValueAnimator.areAnimatorsEnabled());
             assertTrue("Animator did not end when animators enabled = ",
-                    endLatch.await(50, TimeUnit.MILLISECONDS));
+                    endLatch.await(100, TimeUnit.MILLISECONDS));
         }
         mActivityRule.runOnUiThread(() -> {
             animator.end();
diff --git a/tests/tests/app.usage/Android.mk b/tests/tests/app.usage/Android.mk
index eb22828..2f1513b 100644
--- a/tests/tests/app.usage/Android.mk
+++ b/tests/tests/app.usage/Android.mk
@@ -29,6 +29,7 @@
     android-support-test \
     compatibility-device-util \
     ctstestrunner \
+    cts-amwm-util \
     junit \
     ub-uiautomator
 
@@ -40,3 +41,4 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
 
 include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/app.usage/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 9c4342f..3d650fc 100644
--- a/tests/tests/app.usage/AndroidManifest.xml
+++ b/tests/tests/app.usage/AndroidManifest.xml
@@ -17,6 +17,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.usage.cts" android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <!-- We can't have the test framework turn off the keyguard, because that will
          prevent us from testing interactions with it.
@@ -33,12 +34,24 @@
             android:networkSecurityConfig="@xml/network_security_config">
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".Activities$ActivityOne" />
-        <activity android:name=".Activities$ActivityTwo" />
-        <activity android:name=".Activities$ActivityThree" />
+        <activity android:name=".Activities$ActivityOne"
+                  android:resizeableActivity="true"
+                  android:supportsPictureInPicture="true"
+                  android:exported="true"
+        />
+        <activity android:name=".Activities$ActivityTwo"
+                  android:resizeableActivity="true"
+                  android:supportsPictureInPicture="true"
+                  android:exported="true"
+        />
+        <activity android:name=".Activities$ActivityThree"/>
         <activity android:name=".Activities$ActivityFour" />
         <activity android:name=".ActivityTransitionActivity" />
+        <activity android:name=".ActivityTransitionActivity2"
+            android:taskAffinity="android.app.usage.cts.other_task"/>
         <activity android:name=".FragmentTestActivity" />
+        <activity android:name=".TaskRootActivity" />
+        <service android:name=".TestService" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index c1e9b7d..f611fbe 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -16,11 +16,13 @@
 <configuration description="Configuration for app.usage Tests">
     <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="token" value="SIM_CARD" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUsageStatsTestCases.apk" />
+        <option name="test-file-name" value="CtsUsageStatsTestApp1.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.usage.cts" />
diff --git a/tests/tests/app.usage/TestApp1/Android.mk b/tests/tests/app.usage/TestApp1/Android.mk
new file mode 100644
index 0000000..76d2f47
--- /dev/null
+++ b/tests/tests/app.usage/TestApp1/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsUsageStatsTestApp1
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctstestrunner \
+    cts-amwm-util \
+    junit \
+    ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs android.test.runner.stubs
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/app.usage/TestApp1/AndroidManifest.xml b/tests/tests/app.usage/TestApp1/AndroidManifest.xml
new file mode 100644
index 0000000..d4306e5
--- /dev/null
+++ b/tests/tests/app.usage/TestApp1/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.usage.cts.test1">
+
+    <application>
+        <activity android:name=".SomeActivity"
+                  android:exported="true"
+        />
+    </application>
+</manifest>
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java
new file mode 100644
index 0000000..9d2ca8e
--- /dev/null
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.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.app.usage.cts.test1;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public final class SomeActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/Activities.java b/tests/tests/app.usage/src/android/app/usage/cts/Activities.java
index 35c7904..60fd19c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/Activities.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/Activities.java
@@ -18,15 +18,34 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
 import android.view.WindowManager;
 
 public final class Activities {
+
+    public static final ArraySet<Activity> startedActivities = new ArraySet();
+
     private static class KeepScreenOnActivity extends Activity {
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
+        @Override
+        protected void onStart() {
+            super.onStart();
+            synchronized (startedActivities) {
+                startedActivities.add(this);
+            }
+        }
+        @Override
+        protected void onStop() {
+            super.onStop();
+            synchronized (startedActivities) {
+                startedActivities.remove(this);
+            }
+        }
     }
 
     public static class ActivityOne extends KeepScreenOnActivity {}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
index 2396df3..4bfaeb2 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
@@ -62,6 +62,7 @@
 
     public static final String ENTER_VISIBILITY = "enterVisibility";
     public static final String RETURN_VISIBILITY = "returnVisibility";
+    public static final String ACTIVITY_FINISHED = "activityFinished";
 
     private int mLayoutId;
     private int mTest;
@@ -78,6 +79,7 @@
     public boolean mQuickFinish;
     public boolean mAllowOverlap;
     public boolean mNoReturnTransition;
+    public boolean mIsResumed;
 
     public CountDownLatch returnLatch = new CountDownLatch(1);
     public CountDownLatch reenterLatch = new CountDownLatch(1);
@@ -152,6 +154,12 @@
         outState.putBoolean(PAUSE_ON_RESTART, mPauseOnRestart);
     }
 
+    private void sendResult() {
+        if (mResultReceiver != null) {
+            mResultReceiver.send(RESULT_OK, result);
+        }
+    }
+
     private void startTest() {
         if (mTest == TEST_ARRIVE) {
             setEnterSharedElementCallback(new SharedElementCallback() {
@@ -167,6 +175,7 @@
                     } else {
                         result.putLong(ARRIVE_RETURN_TIME, SystemClock.uptimeMillis());
                     }
+                    sendResult();
 
                     getWindow().getDecorView().postDelayed(new Runnable() {
                         @Override
@@ -178,8 +187,8 @@
                             } else {
                                 result.putLong(ARRIVE_RETURN_TIME_READY,
                                         SystemClock.uptimeMillis());
-                                mResultReceiver.send(RESULT_OK, result);
                             }
+                            sendResult();
                             listener.onSharedElementsReady();
                         }
                     }, SHARED_ELEMENT_READY_DELAY);
@@ -254,6 +263,29 @@
         }
     }
 
+    @Override
+    protected synchronized void onPause() {
+        super.onPause();
+        mIsResumed = false;
+    }
+
+    @Override
+    protected synchronized void onResume() {
+        super.onResume();
+        mIsResumed = true;
+        notifyAll();
+    }
+
+    @Override
+    public void finish() {
+        if (mResultReceiver != null) {
+            Bundle result = new Bundle();
+            result.putBoolean(ACTIVITY_FINISHED, true);
+            mResultReceiver.send(RESULT_OK, result);
+        }
+        super.finish();
+    }
+
     private class VisibilityCheck extends TransitionListenerAdapter {
         private final int mViewId;
         private final OptionalVisibility mVisibility;
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity2.java b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity2.java
new file mode 100644
index 0000000..47ab229
--- /dev/null
+++ b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity2.java
@@ -0,0 +1,19 @@
+/*
+ * 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.app.usage.cts;
+
+public class ActivityTransitionActivity2 extends ActivityTransitionActivity {
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionTest.java b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionTest.java
index fd8a230..1af76e2 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionTest.java
@@ -127,6 +127,69 @@
         checkNormalTransitionVisibility();
     }
 
+    public void testOnSharedElementsArrived_crossTask() throws Throwable {
+        getInstrumentation().waitForIdleSync();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mReceiver = new PassInfo(new Handler());
+                mActivity.setExitSharedElementCallback(new SharedElementCallback() {
+                    @Override
+                    public void onSharedElementsArrived(List<String> sharedElementNames,
+                            List<View> sharedElements,
+                            final OnSharedElementsReadyListener listener) {
+                        mNumArrivedCalls++;
+                        final boolean isExiting = mExitTimeReady == 0;
+                        if (isExiting) {
+                            mExitTime = SystemClock.uptimeMillis();
+                        } else {
+                            mReenterTime = SystemClock.uptimeMillis();
+                        }
+                        mActivity.getWindow().getDecorView().postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (isExiting) {
+                                    mExitTimeReady = SystemClock.uptimeMillis();
+                                } else {
+                                    mReenterTimeReady = SystemClock.uptimeMillis();
+                                }
+                                listener.onSharedElementsReady();
+                            }
+                        }, 60);
+                    }
+                });
+
+                Bundle options = ActivityOptions.makeSceneTransitionAnimation(mActivity,
+                        mActivity.findViewById(R.id.hello), "target").toBundle();
+                Intent intent = new Intent(mActivity, ActivityTransitionActivity2.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.putExtra(ActivityTransitionActivity.TEST,
+                        ActivityTransitionActivity.TEST_ARRIVE);
+                intent.putExtra(ActivityTransitionActivity.LAYOUT_ID, R.layout.end);
+                intent.putExtra(ActivityTransitionActivity.RESULT_RECEIVER, mReceiver);
+                mActivity.startActivity(intent, options);
+            }
+        });
+
+        assertTrue("Activity didn't finish!",
+                mReceiver.finishCountDown.await(3000, TimeUnit.MILLISECONDS));
+        synchronized (mActivity) {
+            while (!mActivity.mIsResumed) {
+                mActivity.wait();
+            }
+        }
+
+        assertEquals("Reenter animation shouldn't run", 1, mActivity.reenterLatch.getCount());
+        assertNotNull(mReceiver.resultData);
+        assertEquals(1, mReceiver.resultData.getInt(
+                ActivityTransitionActivity.ARRIVE_COUNT, -1));
+        assertEquals(1, mNumArrivedCalls);
+        assertNotSame(View.VISIBLE, mReceiver.resultData.getInt(
+                ActivityTransitionActivity.ARRIVE_ENTER_START_VISIBILITY));
+        assertNotSame(View.VISIBLE, mReceiver.resultData.getInt(
+                ActivityTransitionActivity.ARRIVE_ENTER_DELAY_VISIBILITY));
+    }
+
     public void testFinishPostponed() throws Throwable {
         getInstrumentation().waitForIdleSync();
         runTestOnUiThread(new Runnable() {
@@ -311,6 +374,7 @@
     public static class PassInfo extends ResultReceiver {
         public int resultCode;
         public Bundle resultData = new Bundle();
+        public CountDownLatch finishCountDown = new CountDownLatch(1);
 
         public PassInfo(Handler handler) {
             super(handler);
@@ -321,6 +385,9 @@
             this.resultCode = resultCode;
             if (resultData != null) {
                 this.resultData.putAll(resultData);
+                if (resultData.getBoolean(ActivityTransitionActivity.ACTIVITY_FINISHED)) {
+                    finishCountDown.countDown();
+                }
             }
         }
     }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java
index fe8cd6e..6b03e91 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java
@@ -36,6 +36,8 @@
 import android.telephony.TelephonyManager;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.io.FileInputStream;
@@ -337,7 +339,8 @@
         if (ConnectivityManager.TYPE_MOBILE == networkType) {
             TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
                     .getSystemService(Context.TELEPHONY_SERVICE);
-            return tm.getSubscriberId();
+            return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+                    (telephonyManager) -> telephonyManager.getSubscriberId());
         }
         return "";
     }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TaskRootActivity.java b/tests/tests/app.usage/src/android/app/usage/cts/TaskRootActivity.java
new file mode 100644
index 0000000..e0f7fc6
--- /dev/null
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TaskRootActivity.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.usage.cts;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.view.WindowManager;
+
+public class TaskRootActivity extends Activity  {
+    public static final String TEST_APP_PKG = "android.app.usage.cts.test1";
+    public static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
+    private static final long TIMEOUT = 5000;
+    private UiDevice mUiDevice;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Intent intent = new Intent();
+        intent.setClassName(TEST_APP_PKG, TEST_APP_CLASS);
+        startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
new file mode 100644
index 0000000..5bd7dc1
--- /dev/null
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
@@ -0,0 +1,55 @@
+/**
+ * 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.app.usage.cts;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class TestService extends Service {
+    private static final String NOTIFICATION_CHANNEL_ID = TestService.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)
+                .setSmallIcon(R.drawable.ic_notification)
+                .setWhen(System.currentTimeMillis())
+                .setContentTitle("TestService running")
+                .setContentText("TestService running")
+                .setContentIntent(PendingIntent.getActivity(this, 0,
+                        new Intent(this, Activities.ActivityOne.class)
+                                .setAction(Intent.ACTION_MAIN)
+                                .addCategory(Intent.CATEGORY_LAUNCHER)
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
+                .setOngoing(true)
+                .build();
+        startForeground(1, status);
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageReportingTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageReportingTest.java
new file mode 100644
index 0000000..8b48369
--- /dev/null
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageReportingTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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.app.usage.cts;
+
+import android.app.Activity;
+import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.server.am.ActivityManagerTestBase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the UsageStats API around usage reporting against tokens
+ * Run test: atest CtsUsageStatsTestCases:UsageReportingTest
+ */
+public class UsageReportingTest extends ActivityManagerTestBase {
+
+    private UiDevice mUiDevice;
+    private UsageStatsManager mUsageStatsManager;
+    private String mTargetPackage;
+
+    private String mFullToken0;
+    private String mFullToken1;
+
+    private static final String TOKEN_0 = "SuperSecretToken";
+    private static final String TOKEN_1 = "AnotherSecretToken";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUsageStatsManager = (UsageStatsManager) InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(Context.USAGE_STATS_SERVICE);
+        mTargetPackage = InstrumentationRegistry.getTargetContext().getPackageName();
+
+        mFullToken0 = mTargetPackage + "/" + TOKEN_0;
+        mFullToken1 = mTargetPackage + "/" + TOKEN_1;
+    }
+
+    @Test
+    public void testUsageStartAndStopReporting() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testUsagePastReporting() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0, 100);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testUsageReportingMissingStop() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        // Send the device to sleep to get onStop called for the token reporting activities.
+        mUiDevice.sleep();
+        Thread.sleep(1000);
+
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testExceptionOnRepeatReport() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        try {
+            mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+            fail("Should have thrown an IllegalArgumentException for double reporting start");
+        } catch (IllegalArgumentException iae) {
+            //Expected exception
+        }
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+
+
+        try {
+            mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+            fail("Should have thrown an IllegalArgumentException for double reporting stop");
+        } catch (IllegalArgumentException iae) {
+            //Expected exception
+        }
+
+        // One more cycle of reporting just to make sure there was no underflow
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testMultipleTokenUsageReporting() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_1);
+        assertAppOrTokenUsed(mFullToken0, true);
+        assertAppOrTokenUsed(mFullToken1, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+        assertAppOrTokenUsed(mFullToken1, true);
+
+        mUsageStatsManager.reportUsageStop(activity, TOKEN_1);
+        assertAppOrTokenUsed(mFullToken0, false);
+        assertAppOrTokenUsed(mFullToken1, false);
+    }
+
+    @Test
+    public void testMultipleTokenMissingStop() throws Exception {
+        launchActivity(new ComponentName(mTargetPackage, Activities.ActivityOne.class.getName()));
+
+        Activity activity;
+        synchronized ( Activities.startedActivities) {
+            activity = Activities.startedActivities.valueAt(0);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity, TOKEN_1);
+        assertAppOrTokenUsed(mFullToken0, true);
+        assertAppOrTokenUsed(mFullToken1, true);
+
+
+        // Send the device to sleep to get onStop called for the token reporting activities.
+        mUiDevice.sleep();
+        Thread.sleep(1000);
+
+        assertAppOrTokenUsed(mFullToken0, false);
+        assertAppOrTokenUsed(mFullToken1, false);
+    }
+
+    @Test
+    public void testSplitscreenUsageReporting() throws Exception {
+        if (!supportsSplitScreenMultiWindow()) {
+            // Skipping test: no multi-window support
+            return;
+        }
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityOne.class.getName())),
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityTwo.class.getName())));
+        Thread.sleep(500);
+
+        Activity activity0;
+        Activity activity1;
+        synchronized ( Activities.startedActivities) {
+            activity0 = Activities.startedActivities.valueAt(0);
+            activity1 = Activities.startedActivities.valueAt(1);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity0, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity1, TOKEN_1);
+        assertAppOrTokenUsed(mFullToken0, true);
+        assertAppOrTokenUsed(mFullToken1, true);
+
+        mUsageStatsManager.reportUsageStop(activity0, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+        assertAppOrTokenUsed(mFullToken1, true);
+
+        mUsageStatsManager.reportUsageStop(activity1, TOKEN_1);
+        assertAppOrTokenUsed(mFullToken0, false);
+        assertAppOrTokenUsed(mFullToken1, false);
+    }
+
+    @Test
+    public void testSplitscreenSameToken() throws Exception {
+        if (!supportsSplitScreenMultiWindow()) {
+            // Skipping test: no multi-window support
+            return;
+        }
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityOne.class.getName())),
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityTwo.class.getName())));
+        Thread.sleep(500);
+
+        Activity activity0;
+        Activity activity1;
+        synchronized ( Activities.startedActivities) {
+            activity0 = Activities.startedActivities.valueAt(0);
+            activity1 = Activities.startedActivities.valueAt(1);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity0, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity1, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity0, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity1, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testSplitscreenSameTokenOneMissedStop() throws Exception {
+        if (!supportsSplitScreenMultiWindow()) {
+            // Skipping test: no multi-window support
+            return;
+        }
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityOne.class.getName())),
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityTwo.class.getName())));
+        Thread.sleep(500);
+
+        Activity activity0;
+        Activity activity1;
+        synchronized ( Activities.startedActivities) {
+            activity0 = Activities.startedActivities.valueAt(0);
+            activity1 = Activities.startedActivities.valueAt(1);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity0, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity1, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        mUsageStatsManager.reportUsageStop(activity0, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        // Send the device to sleep to get onStop called for the token reporting activities.
+        mUiDevice.sleep();
+        Thread.sleep(1000);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    @Test
+    public void testSplitscreenSameTokenTwoMissedStop() throws Exception {
+        if (!supportsSplitScreenMultiWindow()) {
+            // Skipping test: no multi-window support
+            return;
+        }
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityOne.class.getName())),
+                getLaunchActivityBuilder().setTargetActivity(
+                        new ComponentName(mTargetPackage,
+                                Activities.ActivityTwo.class.getName())));
+        Thread.sleep(500);
+
+        Activity activity0;
+        Activity activity1;
+        synchronized ( Activities.startedActivities) {
+            activity0 = Activities.startedActivities.valueAt(0);
+            activity1 = Activities.startedActivities.valueAt(1);
+        }
+
+        mUsageStatsManager.reportUsageStart(activity0, TOKEN_0);
+        mUsageStatsManager.reportUsageStart(activity1, TOKEN_0);
+        assertAppOrTokenUsed(mFullToken0, true);
+
+        // Send the device to sleep to get onStop called for the token reporting activities.
+        mUiDevice.sleep();
+        Thread.sleep(1000);
+        assertAppOrTokenUsed(mFullToken0, false);
+    }
+
+    private void assertAppOrTokenUsed(String entity, boolean expected) throws Exception {
+        final String activeUsages =
+                mUiDevice.executeShellCommand("dumpsys usagestats apptimelimit actives");
+        final String[] actives = activeUsages.split("\n");
+        boolean found = false;
+
+        for (String active: actives) {
+            if (active.equals(entity)) {
+                found = true;
+                break;
+            }
+        }
+        if (expected) {
+            assertTrue(entity +" not found in list of active activities and tokens\n"
+                    + activeUsages, found);
+        } else {
+            assertFalse(entity + " found in list of active activities and tokens\n"
+                    + activeUsages, found);
+        }
+    }
+}
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 a045488..99fbff7 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
@@ -54,11 +54,13 @@
 
 import com.android.compatibility.common.util.AppStandbyUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -88,6 +90,15 @@
     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
             AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
 
+    private static final String USAGE_SOURCE_GET_SHELL_COMMAND = "settings get global " +
+            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE;
+
+    private static final String USAGE_SOURCE_SET_SHELL_COMMAND = "settings put global " +
+            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE + " {0}";
+
+    private static final String USAGE_SOURCE_DELETE_SHELL_COMMAND = "settings delete global " +
+            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE;
+
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
     private static final long DAY = TimeUnit.DAYS.toMillis(1);
@@ -101,6 +112,7 @@
     private UiDevice mUiDevice;
     private UsageStatsManager mUsageStatsManager;
     private String mTargetPackage;
+    private String mCachedUsageSourceSetting;
 
     @Before
     public void setUp() throws Exception {
@@ -110,6 +122,16 @@
         mTargetPackage = InstrumentationRegistry.getContext().getPackageName();
         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
         setAppOpsMode("allow");
+        mCachedUsageSourceSetting = getUsageSourceSetting();
+    }
+
+
+    @After
+    public void cleanUp() throws Exception {
+        if (!mCachedUsageSourceSetting.equals(getUsageSourceSetting())) {
+            setUsageSourceSetting(mCachedUsageSourceSetting);
+            mUsageStatsManager.forceUsageSourceSettingRead();
+        }
     }
 
     private static void assertLessThan(long left, long right) {
@@ -126,6 +148,22 @@
         mUiDevice.executeShellCommand(command);
     }
 
+
+    private String getUsageSourceSetting() throws Exception {
+        return mUiDevice.executeShellCommand(USAGE_SOURCE_GET_SHELL_COMMAND);
+    }
+
+    private void setUsageSourceSetting(String usageSource) throws Exception {
+        if (usageSource.equals("null")) {
+            mUiDevice.executeShellCommand(USAGE_SOURCE_DELETE_SHELL_COMMAND);
+        } else {
+            final String command = MessageFormat.format(USAGE_SOURCE_SET_SHELL_COMMAND,
+                                                        usageSource);
+            mUiDevice.executeShellCommand(command);
+        }
+        mUsageStatsManager.forceUsageSourceSettingRead();
+    }
+
     private void launchSubActivity(Class<? extends Activity> clazz) {
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         final Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -150,61 +188,86 @@
                 Activities.ActivityTwo.class,
                 Activities.ActivityThree.class,
         };
+        mUiDevice.wakeUp();
 
-        final long startTime = System.currentTimeMillis() - MINUTE;
-
+        final long startTime = System.currentTimeMillis();
         // Launch the series of Activities.
         launchSubActivities(activitySequence);
-
         final long endTime = System.currentTimeMillis();
         UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
 
-        // Consume all the events.
+        // Only look at events belongs to mTargetPackage.
         ArrayList<UsageEvents.Event> eventList = new ArrayList<>();
         while (events.hasNextEvent()) {
             UsageEvents.Event event = new UsageEvents.Event();
             assertTrue(events.getNextEvent(event));
-            eventList.add(event);
-        }
-
-        // Find the last Activity's MOVE_TO_FOREGROUND event.
-        int end = eventList.size();
-        while (end > 0) {
-            UsageEvents.Event event = eventList.get(end - 1);
-            if (event.getClassName().equals(activitySequence[activitySequence.length - 1].getName())
-                    && event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
-                break;
+            if (mTargetPackage.equals(event.getPackageName())) {
+                eventList.add(event);
             }
-            end--;
         }
 
-        // We expect 2 events per Activity launched (foreground + background)
-        // except for the last Activity, which was in the foreground when
-        // we queried the event log.
-        final int start = end - ((activitySequence.length * 2) - 1);
-        assertTrue("Not enough events", start >= 0);
-
         final int activityCount = activitySequence.length;
         for (int i = 0; i < activityCount; i++) {
-            final int index = start + (i * 2);
-
-            // Check for foreground event.
-            UsageEvents.Event event = eventList.get(index);
-            assertEquals(mTargetPackage, event.getPackageName());
-            assertEquals(activitySequence[i].getName(), event.getClassName());
-            assertEquals(UsageEvents.Event.MOVE_TO_FOREGROUND, event.getEventType());
-
-            // Only check for the background event if this is not the
-            // last activity.
+            String className = activitySequence[i].getName();
+            ArrayList<UsageEvents.Event> activityEvents = new ArrayList<>();
+            final int size = eventList.size();
+            for (int j = 0; j < size; j++) {
+                Event evt = eventList.get(j);
+                if (className.equals(evt.getClassName())) {
+                    activityEvents.add(evt);
+                }
+            }
+            // We expect 3 events per Activity launched (ACTIVITY_RESUMED + ACTIVITY_PAUSED
+            // + ACTIVITY_STOPPED) except for the last Activity, which only has
+            // ACTIVITY_RESUMED event.
             if (i < activityCount - 1) {
-                event = eventList.get(index + 1);
-                assertEquals(mTargetPackage, event.getPackageName());
-                assertEquals(activitySequence[i].getName(), event.getClassName());
-                assertEquals(UsageEvents.Event.MOVE_TO_BACKGROUND, event.getEventType());
+                assertEquals(3, activityEvents.size());
+                assertEquals(Event.ACTIVITY_RESUMED, activityEvents.get(0).getEventType());
+                assertEquals(Event.ACTIVITY_PAUSED, activityEvents.get(1).getEventType());
+                assertEquals(Event.ACTIVITY_STOPPED, activityEvents.get(2).getEventType());
+            } else {
+                // The last activity
+                assertEquals(1, activityEvents.size());
+                assertEquals(Event.ACTIVITY_RESUMED, activityEvents.get(0).getEventType());
             }
         }
     }
 
+    @Test
+    public void testActivityOnBackButton() throws Exception {
+        testActivityOnButton(mUiDevice::pressBack);
+    }
+
+    @Test
+    public void testActivityOnHomeButton() throws Exception {
+        testActivityOnButton(mUiDevice::pressHome);
+    }
+
+    private void testActivityOnButton(Runnable pressButton) throws Exception {
+        mUiDevice.wakeUp();
+        final long startTime = System.currentTimeMillis();
+        final Class clazz = Activities.ActivityOne.class;
+        launchSubActivity(clazz);
+        pressButton.run();
+        Thread.sleep(1000);
+        final long endTime = System.currentTimeMillis();
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
+
+        ArrayList<UsageEvents.Event> eventList = new ArrayList<>();
+        while (events.hasNextEvent()) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            assertTrue(events.getNextEvent(event));
+            if (mTargetPackage.equals(event.getPackageName())
+                && clazz.getName().equals(event.getClassName())) {
+                eventList.add(event);
+            }
+        }
+        assertEquals(3, eventList.size());
+        assertEquals(Event.ACTIVITY_RESUMED, eventList.get(0).getEventType());
+        assertEquals(Event.ACTIVITY_PAUSED, eventList.get(1).getEventType());
+        assertEquals(Event.ACTIVITY_STOPPED, eventList.get(2).getEventType());
+    }
+
     @AppModeFull // No usage events access in instant apps
     @Test
     public void testAppLaunchCount() throws Exception {
@@ -844,14 +907,161 @@
     }
 
     @Test
-    public void testObserveUsagePermission() {
+    public void testObserveUsagePermissionForRegisterObserver() {
+        final int observerId = 0;
+        final String[] packages = new String[] {"com.android.settings"};
+
         try {
-            mUsageStatsManager.registerAppUsageObserver(0, new String[] {"com.android.settings"},
+            mUsageStatsManager.registerAppUsageObserver(observerId, packages,
                     1, java.util.concurrent.TimeUnit.HOURS, null);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
         } catch (SecurityException e) {
-            return;
+            // Exception expected
         }
-        fail("Should throw SecurityException");
+
+        try {
+            mUsageStatsManager.registerUsageSessionObserver(observerId, packages,
+                    1, java.util.concurrent.TimeUnit.HOURS, 10,
+                    java.util.concurrent.TimeUnit.SECONDS, null, null);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+
+        try {
+            mUsageStatsManager.registerAppUsageLimitObserver(observerId, packages,
+                    1, java.util.concurrent.TimeUnit.HOURS, null);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+    }
+
+    @Test
+    public void testObserveUsagePermissionForUnregisterObserver() {
+        final int observerId = 0;
+
+        try {
+            mUsageStatsManager.unregisterAppUsageObserver(observerId);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+
+        try {
+            mUsageStatsManager.unregisterUsageSessionObserver(observerId);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+
+        try {
+            mUsageStatsManager.unregisterAppUsageLimitObserver(observerId);
+            fail("Expected SecurityException for an app not holding OBSERVE_APP_USAGE permission.");
+        } catch (SecurityException e) {
+            // Exception expected
+        }
+    }
+
+    @Test
+    public void testForegroundService() throws Exception {
+        // This test start a foreground service then stop it. The event list should have one
+        // FOREGROUND_SERVICE_START and one FOREGROUND_SERVICE_STOP event.
+        final long startTime = System.currentTimeMillis();
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        context.startService(new Intent(context, TestService.class));
+        mUiDevice.wait(Until.hasObject(By.clazz(TestService.class)), TIMEOUT);
+        context.stopService(new Intent(context, TestService.class));
+        mUiDevice.wait(Until.gone(By.clazz(TestService.class)), TIMEOUT);
+        final long endTime = System.currentTimeMillis();
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
+
+        int numStarts = 0;
+        int numStops = 0;
+        int startIdx = -1;
+        int stopIdx = -1;
+        int i = 0;
+        while (events.hasNextEvent()) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            assertTrue(events.getNextEvent(event));
+            if (mTargetPackage.equals(event.getPackageName())
+                    || TestService.class.getName().equals(event.getClassName())) {
+                if (event.getEventType() == Event.FOREGROUND_SERVICE_START) {
+                    numStarts++;
+                    startIdx = i;
+                } else if (event.getEventType() == Event.FOREGROUND_SERVICE_STOP) {
+                    numStops++;
+                    stopIdx = i;
+                }
+                i++;
+            }
+        }
+        // One FOREGROUND_SERVICE_START event followed by one FOREGROUND_SERVICE_STOP event.
+        assertEquals(numStarts, 1);
+        assertEquals(numStops, 1);
+        assertLessThan(startIdx, stopIdx);
+    }
+
+    @AppModeFull // No usage events access in instant apps
+    @Test
+    public void testTaskRootEventField() throws Exception {
+        final KeyguardManager kmgr = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(KeyguardManager.class);
+        mUiDevice.wakeUp();
+        // Also want to start out with the keyguard dismissed.
+        if (kmgr.isKeyguardLocked()) {
+            final long startTime = getEvents(KEYGUARD_EVENTS, 0, null) + 1;
+            mUiDevice.executeShellCommand("wm dismiss-keyguard");
+            ArrayList<Event> events = waitForEventCount(KEYGUARD_EVENTS, startTime, 1);
+            assertEquals(Event.KEYGUARD_HIDDEN, events.get(0).getEventType());
+            SystemClock.sleep(500);
+        }
+
+        final long startTime = System.currentTimeMillis();
+        launchSubActivity(TaskRootActivity.class);
+        final long endTime = System.currentTimeMillis();
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
+
+        while (events.hasNextEvent()) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            assertTrue(events.getNextEvent(event));
+            if (TaskRootActivity.TEST_APP_PKG.equals(event.getPackageName())
+                    && TaskRootActivity.TEST_APP_CLASS.equals(event.getClassName())) {
+                assertEquals(mTargetPackage, event.getTaskRootPackageName());
+                assertEquals(TaskRootActivity.class.getCanonicalName(),
+                        event.getTaskRootClassName());
+                return;
+            }
+        }
+        fail("Did not find nested activity name in usage events");
+    }
+
+    @Test
+    public void testUsageSourceAttribution() throws Exception {
+        final KeyguardManager kmgr = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(KeyguardManager.class);
+        mUiDevice.wakeUp();
+        // Also want to start out with the keyguard dismissed.
+        if (kmgr.isKeyguardLocked()) {
+            final long startTime = getEvents(KEYGUARD_EVENTS, 0, null) + 1;
+            mUiDevice.executeShellCommand("wm dismiss-keyguard");
+            ArrayList<Event> events = waitForEventCount(KEYGUARD_EVENTS, startTime, 1);
+            assertEquals(Event.KEYGUARD_HIDDEN, events.get(0).getEventType());
+            SystemClock.sleep(500);
+        }
+
+        setUsageSourceSetting(Integer.toString(mUsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY));
+        launchSubActivity(TaskRootActivity.class);
+        // Usage should be attributed to the test app package
+        assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true);
+
+        mUiDevice.pressHome();
+
+        setUsageSourceSetting(Integer.toString(
+                mUsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
+        launchSubActivity(TaskRootActivity.class);
+        // Usage should be attributed to this package
+        assertAppOrTokenUsed(mTargetPackage, true);
     }
 
     private void pressWakeUp() {
@@ -861,4 +1071,30 @@
     private void pressSleep() {
         mUiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP);
     }
+
+    /**
+     * Assert on an app or token's usage state.
+     * @param entity name of the app or token
+     * @param expected expected usage state, true for in use, false for not in use
+     */
+    private void assertAppOrTokenUsed(String entity, boolean expected) throws IOException {
+        final String activeUsages =
+                mUiDevice.executeShellCommand("dumpsys usagestats apptimelimit actives");
+        final String[] actives = activeUsages.split("\n");
+        boolean found = false;
+
+        for (String active : actives) {
+            if (active.equals(entity)) {
+                found = true;
+                break;
+            }
+        }
+        if (expected) {
+            assertTrue(entity + " not found in list of active activities and tokens\n"
+                    + activeUsages, found);
+        } else {
+            assertFalse(entity + " found in list of active activities and tokens\n"
+                    + activeUsages, found);
+        }
+    }
 }
diff --git a/tests/tests/app/AndroidTest.xml b/tests/tests/app/AndroidTest.xml
index 9f74210..adebf03 100644
--- a/tests/tests/app/AndroidTest.xml
+++ b/tests/tests/app/AndroidTest.xml
@@ -24,6 +24,5 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.cts" />
         <option name="runtime-hint" value="1s" />
-        <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
new file mode 100644
index 0000000..fa1f556
--- /dev/null
+++ b/tests/tests/appop/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+android_test {
+    name: "CtsAppOpsTestCases",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "android-support-test",
+        "compatibility-device-util",
+        "androidx.legacy_legacy-support-v4",
+        "platform-test-annotations",
+        "truth-prebuilt"
+    ],
+
+    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
new file mode 100644
index 0000000..4780da8
--- /dev/null
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.app.appops.cts"
+    android:targetSandboxVersion="2">
+
+  <application>
+      <uses-library android:name="android.test.runner"/>
+  </application>
+
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+      android:functionalTest="true"
+      android:targetPackage="android.app.appops.cts"
+      android:label="Tests for the app ops API."/>
+
+</manifest>
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
new file mode 100644
index 0000000..220030e
--- /dev/null
+++ b/tests/tests/appop/AndroidTest.xml
@@ -0,0 +1,28 @@
+<!-- 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.
+-->
+<configuration description="Config for CTS app ops 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="instant_app" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAppOpsTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="hidden-api-checks" value="true" />
+        <option name="package" value="android.app.appops.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
new file mode 100644
index 0000000..8c48f25
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -0,0 +1,370 @@
+package android.app.appops.cts
+
+/*
+ * 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.
+ */
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+
+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.app.AppOpsManager.OPSTR_READ_CALENDAR
+import android.app.AppOpsManager.OPSTR_READ_SMS
+import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
+
+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
+
+import android.Manifest.permission
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OnOpChangedListener
+import android.app.Instrumentation
+import android.content.Context
+import android.os.Process
+import android.support.test.runner.AndroidJUnit4
+import android.support.test.InstrumentationRegistry
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.util.HashMap
+import java.util.HashSet
+
+@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 lateinit var mAppOps: AppOpsManager
+    private lateinit var mContext: Context
+    private lateinit var mOpPackageName: String
+
+    companion object {
+        // These permissions and opStrs must map to the same op codes.
+        val permissionToOpStr = HashMap<String, String>()
+
+        init {
+            permissionToOpStr[permission.ACCESS_COARSE_LOCATION] =
+                    AppOpsManager.OPSTR_COARSE_LOCATION
+            permissionToOpStr[permission.ACCESS_FINE_LOCATION] =
+                    AppOpsManager.OPSTR_FINE_LOCATION
+            permissionToOpStr[permission.READ_CONTACTS] =
+                    AppOpsManager.OPSTR_READ_CONTACTS
+            permissionToOpStr[permission.WRITE_CONTACTS] =
+                    AppOpsManager.OPSTR_WRITE_CONTACTS
+            permissionToOpStr[permission.READ_CALL_LOG] =
+                    AppOpsManager.OPSTR_READ_CALL_LOG
+            permissionToOpStr[permission.WRITE_CALL_LOG] =
+                   AppOpsManager.OPSTR_WRITE_CALL_LOG
+            permissionToOpStr[permission.READ_CALENDAR] =
+                   AppOpsManager.OPSTR_READ_CALENDAR
+            permissionToOpStr[permission.WRITE_CALENDAR] =
+                   AppOpsManager.OPSTR_WRITE_CALENDAR
+            permissionToOpStr[permission.CALL_PHONE] =
+                   AppOpsManager.OPSTR_CALL_PHONE
+            permissionToOpStr[permission.READ_SMS] =
+                    AppOpsManager.OPSTR_READ_SMS
+            permissionToOpStr[permission.RECEIVE_SMS] =
+                    AppOpsManager.OPSTR_RECEIVE_SMS
+            permissionToOpStr[permission.RECEIVE_MMS] =
+                    AppOpsManager.OPSTR_RECEIVE_MMS
+            permissionToOpStr[permission.RECEIVE_WAP_PUSH] =
+                    AppOpsManager.OPSTR_RECEIVE_WAP_PUSH
+            permissionToOpStr[permission.SEND_SMS] =
+                    AppOpsManager.OPSTR_SEND_SMS
+            permissionToOpStr[permission.READ_SMS] =
+                    AppOpsManager.OPSTR_READ_SMS
+            permissionToOpStr[permission.WRITE_SETTINGS] =
+                    AppOpsManager.OPSTR_WRITE_SETTINGS
+            permissionToOpStr[permission.SYSTEM_ALERT_WINDOW] =
+                    AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW
+            permissionToOpStr[permission.ACCESS_NOTIFICATIONS] =
+                    AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS
+            permissionToOpStr[permission.CAMERA] =
+                    AppOpsManager.OPSTR_CAMERA
+            permissionToOpStr[permission.RECORD_AUDIO] =
+                    AppOpsManager.OPSTR_RECORD_AUDIO
+            permissionToOpStr[permission.READ_PHONE_STATE] =
+                    AppOpsManager.OPSTR_READ_PHONE_STATE
+            permissionToOpStr[permission.ADD_VOICEMAIL] =
+                    AppOpsManager.OPSTR_ADD_VOICEMAIL
+            permissionToOpStr[permission.USE_SIP] =
+                    AppOpsManager.OPSTR_USE_SIP
+            permissionToOpStr[permission.PROCESS_OUTGOING_CALLS] =
+                    AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS
+            permissionToOpStr[permission.BODY_SENSORS] =
+                    AppOpsManager.OPSTR_BODY_SENSORS
+            permissionToOpStr[permission.READ_CELL_BROADCASTS] =
+                    AppOpsManager.OPSTR_READ_CELL_BROADCASTS
+            permissionToOpStr[permission.READ_EXTERNAL_STORAGE] =
+                    AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
+            permissionToOpStr[permission.WRITE_EXTERNAL_STORAGE] =
+                    AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE
+        }
+    }
+
+    @Before
+    fun setUp() {
+        mContext = InstrumentationRegistry.getContext()
+        mAppOps = mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+        mOpPackageName = mContext.opPackageName
+        assertNotNull(mAppOps)
+        // Reset app ops state for this test package to the system default.
+        AppOpsUtils.reset(mOpPackageName)
+    }
+
+    @Test
+    fun testNoteOpAndCheckOp() {
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
+        assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_ALLOWED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_ALLOWED, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_ALLOWED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED)
+        assertEquals(MODE_IGNORED, mAppOps.noteOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_IGNORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_IGNORED, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_IGNORED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT)
+        assertEquals(MODE_DEFAULT, mAppOps.noteOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_DEFAULT, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_DEFAULT, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_DEFAULT, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
+        assertEquals(MODE_ERRORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_ERRORED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        try {
+            mAppOps.noteOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
+            fail("SecurityException expected")
+        } catch (expected: SecurityException) {
+        }
+        try {
+            mAppOps.unsafeCheckOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
+            fail("SecurityException expected")
+        } catch (expected: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testStartOpAndFinishOp() {
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
+        assertEquals(MODE_ALLOWED, mAppOps.startOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        mAppOps.finishOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
+        assertEquals(MODE_ALLOWED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        mAppOps.finishOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName)
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED)
+        assertEquals(MODE_IGNORED, mAppOps.startOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_IGNORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT)
+        assertEquals(MODE_DEFAULT, mAppOps.startOp(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        assertEquals(MODE_DEFAULT, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
+        assertEquals(MODE_ERRORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
+                Process.myUid(), mOpPackageName))
+        try {
+            mAppOps.startOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
+            fail("SecurityException expected")
+        } catch (expected: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testCheckPackagePassesCheck() {
+        mAppOps.checkPackage(Process.myUid(), mOpPackageName)
+        mAppOps.checkPackage(Process.SYSTEM_UID, "android")
+    }
+
+    @Test
+    fun testCheckPackageDoesntPassCheck() {
+        try {
+            // Package name doesn't match UID.
+            mAppOps.checkPackage(Process.SYSTEM_UID, mOpPackageName)
+            fail("SecurityException expected")
+        } catch (expected: SecurityException ) {
+        }
+
+        try {
+            // Package name doesn't match UID.
+            mAppOps.checkPackage(Process.myUid(), "android")
+            fail("SecurityException expected")
+        } catch (expected: SecurityException) {
+        }
+
+        try {
+            // Package name missing
+            mAppOps.checkPackage(Process.myUid(), "")
+            fail("SecurityException expected")
+        } catch (expected: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testWatchingMode() {
+        val watcher = mock(OnOpChangedListener::class.java)
+        try {
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
+
+            mAppOps.startWatchingMode(OPSTR_READ_SMS, mOpPackageName, watcher)
+
+            // Make a change to the app op's mode.
+            reset(watcher)
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
+            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+                    .onOpChanged(OPSTR_READ_SMS, mOpPackageName)
+
+            // Make another change to the app op's mode.
+            reset(watcher)
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
+            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+                    .onOpChanged(OPSTR_READ_SMS, mOpPackageName)
+
+            // Set mode to the same value as before - expect no call to the listener.
+            reset(watcher)
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
+            verifyZeroInteractions(watcher)
+
+            mAppOps.stopWatchingMode(watcher)
+
+            // Make a change to the app op's mode. Since we already stopped watching the mode, the
+            // listener shouldn't be called.
+            reset(watcher)
+            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
+            verifyZeroInteractions(watcher)
+        } finally {
+            // Clean up registered watcher.
+            mAppOps.stopWatchingMode(watcher)
+        }
+    }
+
+    @Test
+    fun testAllOpsHaveOpString() {
+        val opStrs = HashSet<String>()
+        for (opStr in AppOpsManager.getOpStrs()) {
+            assertNotNull("Each app op must have an operation string defined", opStr)
+            opStrs.add(opStr)
+        }
+        assertEquals("Not all op strings are unique", AppOpsManager.getNumOps(), opStrs.size)
+    }
+
+    @Test
+    fun testOpCodesUnique() {
+        val opStrs = AppOpsManager.getOpStrs()
+        val opCodes = HashSet<Int>()
+        for (opStr in opStrs) {
+            opCodes.add(AppOpsManager.strOpToOp(opStr))
+        }
+        assertEquals("Not all app op codes are unique", opStrs.size, opCodes.size)
+    }
+
+    @Test
+    fun testPermissionMapping() {
+        for (entry in permissionToOpStr) {
+            testPermissionMapping(entry.key, permissionToOpStr[entry.key])
+        }
+    }
+
+    private fun testPermissionMapping(permission: String, opStr: String?) {
+        // Do the public value => internal op code lookups.
+        val mappedOpStr = AppOpsManager.permissionToOp(permission)
+        assertEquals(mappedOpStr, opStr)
+        val mappedOpCode = AppOpsManager.permissionToOpCode(permission)
+        val mappedOpCode2 = AppOpsManager.strOpToOp(opStr)
+        assertEquals(mappedOpCode, mappedOpCode2)
+
+        // Do the internal op code => public value lookup (reverse lookup).
+        val permissionMappedBack = AppOpsManager.opToPermission(mappedOpCode)
+        assertEquals(permission, permissionMappedBack)
+    }
+
+    /**
+     * Test that the app can not change the app op mode for itself.
+     */
+    @Test
+    fun testCantSetModeForSelf() {
+        try {
+            val writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS")
+            mAppOps.setMode(writeSmsOp, Process.myUid(), mOpPackageName, AppOpsManager.MODE_ALLOWED)
+            fail("Was able to set mode for self")
+        } catch (expected: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testGetOpsForPackageOpsAreLogged() {
+        // This test checks if operations get logged by the system. It needs to start with a clean
+        // slate, i.e. these ops can't have been logged previously for this test package. The reason
+        // is that there's no API for clearing the app op logs before a test run. However, the op
+        // logs are cleared when this test package is reinstalled between test runs. To make sure
+        // that other test methods in this class don't affect this test method, here we use
+        // operations that are not used by any other test cases.
+        val mustNotBeLogged = "Operation mustn't be logged before the test runs"
+        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
+        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR))
+
+        setOpMode(mOpPackageName, OPSTR_RECORD_AUDIO, MODE_ALLOWED)
+        setOpMode(mOpPackageName, OPSTR_READ_CALENDAR, MODE_ERRORED)
+
+        // Note an op that's allowed.
+        mAppOps.noteOp(OPSTR_RECORD_AUDIO, Process.myUid(), mOpPackageName)
+        val mustBeLogged = "Operation must be logged"
+        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
+
+        // Note another op that's not allowed.
+        mAppOps.noteOpNoThrow(OPSTR_READ_CALENDAR, Process.myUid(), mOpPackageName)
+        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
+        assertTrue(mustBeLogged, rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR))
+    }
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
new file mode 100644
index 0000000..4ca65a5
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.support.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
new file mode 100644
index 0000000..1a749b0
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
@@ -0,0 +1,419 @@
+/*
+ * 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.app.appops.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.HistoricalOps
+import android.app.Instrumentation
+import android.content.Context
+import android.os.Process
+import android.os.SystemClock
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import java.util.function.Consumer
+
+@RunWith(AndroidJUnit4::class)
+class HistoricalAppopsTest {
+    private val uid = Process.myUid()
+    private var appOpsManager: AppOpsManager? = null
+    private var packageName: String? = null
+
+    @Before
+    fun setUpTest() {
+        appOpsManager = getContext().getSystemService(AppOpsManager::class.java)
+        packageName = getContext().packageName
+        val uiAutomation = getInstrumentation().getUiAutomation()
+        uiAutomation.adoptShellPermissionIdentity()
+        appOpsManager!!.clearHistory()
+        appOpsManager!!.resetHistoryParameters()
+    }
+
+    @After
+    fun tearDownTest() {
+        appOpsManager!!.clearHistory()
+        appOpsManager!!.resetHistoryParameters()
+        val uiAutomation = getInstrumentation().getUiAutomation()
+        uiAutomation.dropShellPermissionIdentity()
+    }
+
+    @Test
+    fun testGetHistoricalPackageOpsForegroundAccessInMemoryBucket() {
+        testGetHistoricalPackageOpsForegroundAtDepth(0)
+    }
+
+    @Test
+    fun testGetHistoricalPackageOpsForegroundAccessFirstOnDiskBucket() {
+        testGetHistoricalPackageOpsForegroundAtDepth(1)
+    }
+
+    @Test
+    fun testHistoricalAggregationOneLevelsDeep() {
+        testHistoricalAggregationSomeLevelsDeep(0)
+    }
+
+    @Test
+    fun testHistoricalAggregationTwoLevelsDeep() {
+        testHistoricalAggregationSomeLevelsDeep(1)
+    }
+
+    @Test
+    fun testHistoricalAggregationOverflow() {
+        // Configure historical registry behavior.
+        appOpsManager!!.setHistoryParameters(
+                AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
+                SNAPSHOT_INTERVAL_MILLIS,
+                INTERVAL_COMPRESSION_MULTIPLIER)
+
+        // Add the data to the history
+        val chunk = createDataChunk()
+        val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
+        for (i in 0 until chunkCount) {
+            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)
+        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)
+        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)
+        assertHasCounts(thirdOps!!, 33)
+    }
+
+    @Test
+    fun testHistoryTimeTravel() {
+        // Configure historical registry behavior.
+        appOpsManager!!.setHistoryParameters(
+                AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
+                SNAPSHOT_INTERVAL_MILLIS,
+                INTERVAL_COMPRESSION_MULTIPLIER)
+
+        // Fill the first two intervals with data
+        val chunk = createDataChunk()
+        val chunkCount = computeSlotCount(2) * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis
+        for (i in 0 until chunkCount) {
+            appOpsManager!!.addHistoricalOps(chunk)
+        }
+
+        // Move history in past with the first interval duration
+        val firstIntervalDurationMillis = computeIntervalDurationMillis(0)
+        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)
+        assertThat(firstOps).isNotNull()
+        assertThat(firstOps!!.uidCount).isEqualTo(0)
+
+        // Validate the data for the second interval
+        val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
+        val secondIntervalDurationMillis = computeIntervalDurationMillis(1)
+        val secondIntervalEndMillis = secondIntervalBeginMillis + secondIntervalDurationMillis
+        val secondOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
+                null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+        val secondChunkCount = ((computeSlotCount(2) - computeSlotCount(1))
+                * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis)
+        assertHasCounts(secondOps!!, 10 * secondChunkCount)
+
+        // Validate the data for the third interval
+        val thirdIntervalBeginMillis = computeIntervalBeginRawMillis(2)
+        val thirdIntervalDurationMillis = computeIntervalDurationMillis(2)
+        val thirdIntervalEndMillis = thirdIntervalBeginMillis + thirdIntervalDurationMillis
+        val thirdOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, 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())
+
+        // Validate the data for the fourth interval
+        val fourthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
+                null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+        assertHasCounts(fourthOps!!, 194)
+
+        // Validate the data for the fourth interval
+        val fifthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
+                null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+
+        assertThat(fifthOps).isNotNull()
+        assertHasCounts(fifthOps!!, 1703)
+    }
+
+    private fun testHistoricalAggregationSomeLevelsDeep(depth: Int) {
+        // Configure historical registry behavior.
+        appOpsManager!!.setHistoryParameters(
+                AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
+                SNAPSHOT_INTERVAL_MILLIS,
+                INTERVAL_COMPRESSION_MULTIPLIER)
+
+        // Add the data to the history
+        val chunk = createDataChunk()
+        val chunkCount = (computeSlotCount(depth + 1)
+                * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis)
+        for (i in 0 until chunkCount) {
+            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 expectedOpCount = ((computeSlotCount(depth + 1) - computeSlotCount(depth))
+                * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis) * 10
+        assertHasCounts(ops!!, expectedOpCount)
+    }
+
+    private fun testGetHistoricalPackageOpsForegroundAtDepth(depth: Int) {
+        // Configure historical registry behavior.
+        appOpsManager!!.setHistoryParameters(
+                AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
+                SNAPSHOT_INTERVAL_MILLIS,
+                INTERVAL_COMPRESSION_MULTIPLIER)
+
+        appOpsManager!!.setUidMode(AppOpsManager.OPSTR_FINE_LOCATION, uid,
+                AppOpsManager.MODE_ALLOWED)
+        appOpsManager!!.setUidMode(AppOpsManager.OPSTR_FINE_LOCATION, 2000,
+                AppOpsManager.MODE_ALLOWED)
+
+        try {
+            val noteCount = 5
+
+            // 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_FINE_LOCATION, uid, packageName)
+                }
+                if (d > 0) {
+                    val previousIntervalDuration = computeIntervalDurationMillis(d - 1)
+                    val currentIntervalDuration = computeIntervalDurationMillis(d)
+                    val sleepDurationMillis = (previousIntervalDuration
+                            + currentIntervalDuration / 2)
+                    SystemClock.sleep(sleepDurationMillis)
+                }
+            }
+
+            // Pick up ops for the whole interval
+            val durationMillis = (SNAPSHOT_INTERVAL_MILLIS
+                    * Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
+                    (depth).toDouble()) * (INTERVAL_COMPRESSION_MULTIPLIER - 1)).toLong()
+            val endTimeMillis = System.currentTimeMillis()
+            val beginTimeMillis = endTimeMillis - durationMillis
+
+            // Get all ops for the package
+            val allOps = getHistoricalOps(appOpsManager!!, uid, packageName!!,
+                    null, beginTimeMillis, Long.MAX_VALUE)
+
+            assertThat(allOps).isNotNull()
+            assertThat(allOps!!.uidCount).isEqualTo(1)
+            assertThat(allOps.beginTimeMillis).isEqualTo(beginTimeMillis)
+            assertThat(allOps.endTimeMillis).isAtLeast(beginTimeMillis + durationMillis)
+
+            val uidOps = allOps.getUidOpsAt(0)
+            assertThat(uidOps).isNotNull()
+            assertThat(uidOps.uid).isEqualTo(Process.myUid())
+            assertThat(uidOps.packageCount).isEqualTo(1)
+
+            val packageOps = uidOps.getPackageOpsAt(0)
+            assertThat(packageOps).isNotNull()
+            assertThat(packageOps.packageName).isEqualTo(getContext().packageName)
+            assertThat(packageOps.opCount).isEqualTo(1)
+
+            val op = packageOps.getOpAt(0)
+            assertThat(op).isNotNull()
+            assertThat(op.opName).isEqualTo(AppOpsManager.OPSTR_FINE_LOCATION)
+
+            assertThat(op.foregroundAccessCount).isEqualTo(noteCount)
+            assertThat(op.backgroundAccessCount).isEqualTo(0)
+
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_PERSISTENT)).isEqualTo(0)
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_TOP)).isEqualTo(0)
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_FOREGROUND)).isEqualTo(0)
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_FOREGROUND_SERVICE))
+                    .isEqualTo(noteCount)
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
+            assertThat(op.getAccessCount(AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
+
+            assertThat(op.foregroundAccessDuration).isEqualTo(0)
+            assertThat(op.backgroundAccessDuration).isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_PERSISTENT)).isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_TOP)).isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_FOREGROUND)).isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_FOREGROUND_SERVICE))
+                    .isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
+            assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
+
+            assertThat(op.foregroundRejectCount).isEqualTo(0)
+            assertThat(op.backgroundRejectCount).isEqualTo(0)
+        } finally {
+            appOpsManager!!.setUidMode(AppOpsManager.OPSTR_FINE_LOCATION, uid,
+                    AppOpsManager.MODE_FOREGROUND)
+            appOpsManager!!.setUidMode(AppOpsManager.OPSTR_FINE_LOCATION, 2000,
+                    AppOpsManager.MODE_FOREGROUND)
+        }
+    }
+
+    private fun createDataChunk() : HistoricalOps {
+        val chunk = HistoricalOps(SNAPSHOT_INTERVAL_MILLIS / 4,
+                SNAPSHOT_INTERVAL_MILLIS / 2)
+        chunk.increaseAccessCount(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_TOP, 10)
+        chunk.increaseAccessCount(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, 10)
+        chunk.increaseRejectCount(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_TOP, 10)
+        chunk.increaseRejectCount(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, 10)
+        chunk.increaseAccessDuration(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_TOP, 10)
+        chunk.increaseAccessDuration(AppOpsManager.OP_COARSE_LOCATION, uid,
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, 10)
+        return chunk
+    }
+
+    private fun getHistoricalOps(appOpsManager: AppOpsManager, uid: Int,
+            packageName: String, opNames: Array<String>?, beginTimeMillis: Long,
+            endTimeMillis: Long) : HistoricalOps? {
+        val array = arrayOfNulls<HistoricalOps>(1)
+        val lock = ReentrantLock()
+        val condition = lock.newCondition()
+        try {
+            lock.lock()
+            appOpsManager.getHistoricalOps(uid, packageName, opNames,
+                    beginTimeMillis, endTimeMillis, getContext().getMainExecutor(),
+                    Consumer { ops ->
+                array[0] = ops
+                try {
+                    lock.lock()
+                    condition.signalAll()
+                } finally {
+                    lock.unlock()
+                }
+            })
+            condition.await(5, TimeUnit.SECONDS)
+            return array[0]
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    private fun assertHasCounts(ops: HistoricalOps, count: Long) {
+        assertThat(ops).isNotNull()
+        assertThat(ops.uidCount).isEqualTo(1)
+
+        val uidOps = ops.getUidOpsAt(0)
+        assertThat(uidOps).isNotNull()
+
+        val packageOps = uidOps.getPackageOpsAt(0)
+        assertThat(packageOps).isNotNull()
+
+        val op = packageOps.getOpAt(0)
+        assertThat(op).isNotNull()
+
+        assertThat(op.foregroundAccessCount).isEqualTo(count)
+        assertThat(op.backgroundAccessCount).isEqualTo(count)
+        assertThat(op.foregroundRejectCount).isEqualTo(count)
+        assertThat(op.backgroundRejectCount).isEqualTo(count)
+        assertThat(op.foregroundAccessDuration).isEqualTo(count)
+        assertThat(op.backgroundAccessDuration).isEqualTo(count)
+    }
+
+    private fun getHistoricalOpsFromDiskRaw(appOpsManager: AppOpsManager, uid: Int,
+            packageName: String, opNames: Array<String>?, beginTimeMillis: Long,
+            endTimeMillis: Long) : HistoricalOps? {
+        val array = arrayOfNulls<HistoricalOps>(1)
+        val lock = ReentrantLock()
+        val condition = lock.newCondition()
+        try {
+            lock.lock()
+            appOpsManager.getHistoricalOpsFromDiskRaw(uid, packageName, opNames,
+                    beginTimeMillis, endTimeMillis, getContext().getMainExecutor(),
+                Consumer { ops ->
+                  array[0] = ops
+                  try {
+                      lock.lock()
+                      condition.signalAll()
+                  } finally {
+                      lock.unlock()
+                  }
+              })
+            condition.await(5, TimeUnit.SECONDS)
+            return array[0]
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    companion object {
+        const val INTERVAL_COMPRESSION_MULTIPLIER = 10
+        const val SNAPSHOT_INTERVAL_MILLIS = 1000L
+
+        private fun computeIntervalDurationMillis(depth: Int): Long {
+            return Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
+                    (depth + 1).toDouble()).toLong() * SNAPSHOT_INTERVAL_MILLIS
+        }
+
+        private fun computeSlotCount(depth: Int) : Int {
+            var count = 0
+            for (i in 1..depth) {
+                count += Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(), i.toDouble()).toInt()
+            }
+            return count
+        }
+
+        private fun computeIntervalBeginRawMillis(depth: Int): Long {
+            var beginTimeMillis: Long = 0
+            for (i in 0 until depth + 1) {
+                beginTimeMillis += Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
+                        i.toDouble()).toLong()
+            }
+            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/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
index 46daf85..1e533f4 100644
--- a/tests/tests/appwidget/AndroidManifest.xml
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -63,10 +63,28 @@
               android:resource="@xml/appwidget_info_with_feature3" />
       </receiver>
 
+      <receiver android:name="android.appwidget.cts.provider.CollectionAppWidgetProvider" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/collection_appwidget_info" />
+      </receiver>
+
       <service android:name="android.appwidget.cts.service.MyAppWidgetService"
           android:permission="android.permission.BIND_REMOTEVIEWS">
       </service>
 
+      <activity android:name="android.appwidget.cts.activity.EmptyActivity"
+                android:label="EmptyActivity">
+          <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.appwidget.cts.activity.TransitionActivity"
+          android:label="TransitionActivity" />
   </application>
 
   <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/appwidget/AndroidTest.xml b/tests/tests/appwidget/AndroidTest.xml
index e64123c..4b19277 100644
--- a/tests/tests/appwidget/AndroidTest.xml
+++ b/tests/tests/appwidget/AndroidTest.xml
@@ -15,6 +15,7 @@
 <configuration description="Config for CTS App Widget 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAppWidgetLauncher1.apk" />
diff --git a/tests/tests/appwidget/res/layout/activity_transition.xml b/tests/tests/appwidget/res/layout/activity_transition.xml
new file mode 100644
index 0000000..2d6cb4c
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/activity_transition.xml
@@ -0,0 +1,38 @@
+<?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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <FrameLayout
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#0F0"
+        android:id="@+id/greenSquare"/>
+    <FrameLayout
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#F00"
+        android:id="@+id/redSquare"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:transitionName="text_foo"
+        android:id="@+id/hello"
+        android:text="@string/foo"/>
+</LinearLayout>
diff --git a/tests/tests/appwidget/res/layout/activity_transition_end.xml b/tests/tests/appwidget/res/layout/activity_transition_end.xml
new file mode 100644
index 0000000..374ba33
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/activity_transition_end.xml
@@ -0,0 +1,38 @@
+<?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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#F00"
+        android:id="@+id/redSquare"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:transitionName="text_foo"
+        android:text="@string/foo" />
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#0F0"
+        android:transitionName="green_square"
+        android:id="@+id/greenSquare"/>
+</LinearLayout>
diff --git a/tests/tests/appwidget/res/layout/remoteviews_adapter.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter.xml
new file mode 100644
index 0000000..1cd50c3
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/remoteviews_adapter.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <StackView
+        android:id="@+id/remoteViews_stack"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:loopViews="true" />
+    <TextView
+        android:id="@+id/remoteViews_empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center" />
+    <ListView
+        android:id="@+id/remoteViews_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+</FrameLayout>
diff --git a/tests/tests/widget/res/layout/remoteviews_adapter_item.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
similarity index 100%
rename from tests/tests/widget/res/layout/remoteviews_adapter_item.xml
rename to tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
diff --git a/tests/tests/appwidget/res/layout/simple_black_layout.xml b/tests/tests/appwidget/res/layout/simple_black_layout.xml
new file mode 100644
index 0000000..f1adbe9
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/simple_black_layout.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FF000000"
+    android:id="@+id/hello" />
diff --git a/tests/tests/appwidget/res/layout/simple_white_layout.xml b/tests/tests/appwidget/res/layout/simple_white_layout.xml
new file mode 100644
index 0000000..314fd30
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/simple_white_layout.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFFFFFFF"
+    android:id="@+id/hello" />
diff --git a/tests/tests/appwidget/res/xml/collection_appwidget_info.xml b/tests/tests/appwidget/res/xml/collection_appwidget_info.xml
new file mode 100644
index 0000000..9e4baa0
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/collection_appwidget_info.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="200dp"
+    android:minHeight="200dp"
+    android:minResizeWidth="300dp"
+    android:minResizeHeight="300dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/remoteviews_adapter"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen|keyguard">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 2bb7dee..0e84505 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -89,12 +89,6 @@
                 .getTargetContext().getCacheDir().getPath());
     }
 
-    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
-            "appwidget grantbind --package android.appwidget.cts --user 0";
-
-    private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
-            "appwidget revokebind --package android.appwidget.cts --user 0";
-
     @AppModeInstant(reason = "Instant apps cannot provide or host app widgets")
     @Test
     public void testInstantAppsCannotProvideAppWidgets() {
@@ -1343,14 +1337,6 @@
         assertTrue(verifiedWidgets[1]);
     }
 
-    private void grantBindAppWidgetPermission() throws Exception {
-        runShellCommand(GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
-    }
-
-    private void revokeBindAppWidgetPermission() throws Exception {
-        runShellCommand(REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
-    }
-
     private AppWidgetProviderInfo getFirstAppWidgetProviderInfo() {
         return getProviderInfo(getFirstWidgetComponent());
     }
@@ -1359,27 +1345,6 @@
         return getProviderInfo(getSecondWidgetComponent());
     }
 
-    private AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
-        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
-
-        final int providerCount = providers.size();
-        for (int i = 0; i < providerCount; i++) {
-            AppWidgetProviderInfo provider = providers.get(i);
-            if (componentName.equals(provider.provider)
-                    && Process.myUserHandle().equals(provider.getProfile())) {
-                return provider;
-
-            }
-        }
-
-        return null;
-    }
-
-    private AppWidgetManager getAppWidgetManager() {
-        return (AppWidgetManager) getInstrumentation().getTargetContext()
-                .getSystemService(Context.APPWIDGET_SERVICE);
-    }
-
     private AppWidgetProviderCallbacks createAppWidgetProviderCallbacks(
             final AtomicInteger callCounter) {
         // Set a mock to intercept provider callbacks.
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
index 3352d12..e67a01d 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
@@ -20,12 +20,15 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
+import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.appwidget.cts.provider.FirstAppWidgetProvider;
 import android.appwidget.cts.provider.SecondAppWidgetProvider;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -46,6 +49,12 @@
     private static final String SECOND_APP_WIDGET_CONFIGURE_ACTIVITY =
             "android.appwidget.cts.provider.SecondAppWidgetConfigureActivity";
 
+    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget grantbind --package android.appwidget.cts --user 0";
+
+    private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget revokebind --package android.appwidget.cts --user 0";
+
     @Before
     public void assumeHasWidgets() {
         assumeTrue(hasAppWidgets());
@@ -156,6 +165,27 @@
                 SecondAppWidgetProvider.class.getName());
     }
 
+    public AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+
+        final int providerCount = providers.size();
+        for (int i = 0; i < providerCount; i++) {
+            AppWidgetProviderInfo provider = providers.get(i);
+            if (componentName.equals(provider.provider)
+                    && Process.myUserHandle().equals(provider.getProfile())) {
+                return provider;
+
+            }
+        }
+
+        return null;
+    }
+
+    public AppWidgetManager getAppWidgetManager() {
+        return (AppWidgetManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.APPWIDGET_SERVICE);
+    }
+
     public ArrayList<String> runShellCommand(String command) throws Exception {
         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
                 .executeShellCommand(command);
@@ -171,4 +201,12 @@
         }
         return ret;
     }
+
+    protected void grantBindAppWidgetPermission() throws Exception {
+        runShellCommand(GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+    protected void revokeBindAppWidgetPermission() throws Exception {
+        runShellCommand(REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
 }
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/BlockingBroadcastReceiver.java b/tests/tests/appwidget/src/android/appwidget/cts/BlockingBroadcastReceiver.java
new file mode 100644
index 0000000..d7d8a40
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/BlockingBroadcastReceiver.java
@@ -0,0 +1,60 @@
+/*
+ * 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.appwidget.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class BlockingBroadcastReceiver extends BroadcastReceiver {
+
+    public static final String KEY_PARAM = "BlockingBroadcastReceiver.param";
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    public Intent result;
+    private String mParams;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        result = intent;
+        mParams = intent.getStringExtra(KEY_PARAM);
+        InstrumentationRegistry.getTargetContext().unregisterReceiver(this);
+        latch.countDown();
+    }
+
+    public String getParam(long timeout, TimeUnit unit) throws InterruptedException {
+        latch.await(timeout, unit);
+        return mParams;
+    }
+
+    public void await() throws Exception {
+        assertTrue(latch.await(20, TimeUnit.SECONDS));
+    }
+
+    public BlockingBroadcastReceiver register(String action) {
+        InstrumentationRegistry.getTargetContext().registerReceiver(
+                this, new IntentFilter(action));
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
new file mode 100644
index 0000000..6ac7cb5
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
@@ -0,0 +1,459 @@
+/*
+ * 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.appwidget.cts;
+
+import static android.appwidget.cts.provider.CollectionAppWidgetProvider.BROADCAST_ACTION;
+
+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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.activity.EmptyActivity;
+import android.appwidget.cts.provider.CollectionAppWidgetProvider;
+import android.appwidget.cts.service.MyAppWidgetService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+import android.widget.StackView;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Test AppWidgets which host collection widgets.
+ */
+@LargeTest
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class CollectionAppWidgetTest extends AppWidgetTestCase {
+    public static final String[] COUNTRY_LIST = new String[] {
+        "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus",
+        "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
+        "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
+        "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
+        "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
+        "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
+        "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
+    };
+
+    private static final long TEST_TIMEOUT_MS = 5000;
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    private Instrumentation mInstrumentation;
+
+    private Context mContext;
+
+    private boolean mHasAppWidgets;
+
+    private AppWidgetHostView mAppWidgetHostView;
+
+    private int mAppWidgetId;
+
+    private StackView mStackView;
+
+    private ListView mListView;
+
+    private AppWidgetHost mAppWidgetHost;
+
+    @Before
+    public void setup() throws Throwable {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+
+        mHasAppWidgets = hasAppWidgets();
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        // We want to bind widgets - run a shell command to grant bind permission to our
+        // package.
+        grantBindAppWidgetPermission();
+
+        mAppWidgetHost = new AppWidgetHost(mContext, 0);
+
+        mAppWidgetHost.deleteHost();
+        mAppWidgetHost.startListening();
+
+        // Configure the app widget provider behavior
+        final CountDownLatch providerCountDownLatch = new CountDownLatch(2);
+        CollectionAppWidgetProvider.configure(providerCountDownLatch, null, null);
+
+        // Grab the provider to be bound
+        final AppWidgetProviderInfo providerInfo = getAppWidgetProviderInfo();
+
+        // Allocate a widget id to bind
+        mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+        // Bind the app widget
+        boolean isBinding = getAppWidgetManager().bindAppWidgetIdIfAllowed(mAppWidgetId,
+                providerInfo.getProfile(), providerInfo.provider, null);
+        assertTrue(isBinding);
+
+        // Wait for onEnabled and onUpdate calls on our provider
+        try {
+            assertTrue(providerCountDownLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ie) {
+            fail(ie.getMessage());
+        }
+
+        // Configure the app widget service behavior
+        final CountDownLatch factoryCountDownLatch = new CountDownLatch(2);
+        RemoteViewsService.RemoteViewsFactory factory =
+                mock(RemoteViewsService.RemoteViewsFactory.class);
+        when(factory.getCount()).thenReturn(COUNTRY_LIST.length);
+        doAnswer(new Answer<RemoteViews>() {
+            @Override
+            public RemoteViews answer(InvocationOnMock invocation) throws Throwable {
+                final int position = (Integer) invocation.getArguments()[0];
+                RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
+                        R.layout.remoteviews_adapter_item);
+                remoteViews.setTextViewText(R.id.item, COUNTRY_LIST[position]);
+
+                // Set a fill-intent which will be used to fill-in the pending intent template
+                // which is set on the collection view in CollectionAppWidgetProvider.
+                Bundle extras = new Bundle();
+                extras.putString(BlockingBroadcastReceiver.KEY_PARAM, COUNTRY_LIST[position]);
+                Intent fillInIntent = new Intent();
+                fillInIntent.putExtras(extras);
+                remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
+
+                if (position == 0) {
+                    factoryCountDownLatch.countDown();
+                }
+                return remoteViews;
+            }
+        }).when(factory).getViewAt(any(int.class));
+        when(factory.getViewTypeCount()).thenReturn(1);
+        MyAppWidgetService.setFactory(factory);
+
+        mActivityRule.runOnUiThread(
+                () -> mAppWidgetHostView = mAppWidgetHost.createView(
+                        mContext, mAppWidgetId, providerInfo));
+
+        // Wait our factory to be called to create the first item
+        try {
+            assertTrue(factoryCountDownLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ie) {
+            fail(ie.getMessage());
+        }
+
+        // Add our host view to the activity behind this test. This is similar to how launchers
+        // add widgets to the on-screen UI.
+        mActivityRule.runOnUiThread(() -> {
+            EmptyActivity activity = mActivityRule.getActivity();
+            activity.setContentView(mAppWidgetHostView);
+        });
+    }
+
+    @After
+    public void teardown() throws Exception {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        mAppWidgetHost.deleteHost();
+        revokeBindAppWidgetPermission();
+    }
+
+    private AppWidgetProviderInfo getAppWidgetProviderInfo() {
+        ComponentName firstComponentName = new ComponentName(mContext.getPackageName(),
+                CollectionAppWidgetProvider.class.getName());
+
+        return getProviderInfo(firstComponentName);
+    }
+
+    @Test
+    public void testInitialState() {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        assertNotNull(mAppWidgetHostView);
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+        assertNotNull(mStackView);
+
+        assertEquals(COUNTRY_LIST.length, mStackView.getCount());
+        assertEquals(0, mStackView.getDisplayedChild());
+        assertEquals(R.id.remoteViews_empty, mStackView.getEmptyView().getId());
+    }
+
+    private void verifySetDisplayedChild(int displayedChildIndex) {
+        final CountDownLatch updateLatch = new CountDownLatch(1);
+        CollectionAppWidgetProvider.configure(updateLatch, null, null);
+
+        // Create the intent to update the widget. Note that we're passing the value
+        // for displayed child index in the intent
+        Intent intent = new Intent(mContext, CollectionAppWidgetProvider.class);
+        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
+        intent.putExtra(CollectionAppWidgetProvider.KEY_DISPLAYED_CHILD_INDEX, displayedChildIndex);
+        mContext.sendBroadcast(intent);
+
+        // Wait until the update request has been processed
+        try {
+            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ie) {
+            fail(ie.getMessage());
+        }
+        // And wait until the underlying StackView has been updated to switch to the requested
+        // child
+        PollingCheck.waitFor(TEST_TIMEOUT_MS,
+                () -> mStackView.getDisplayedChild() == displayedChildIndex);
+    }
+
+    @Test
+    public void testSetDisplayedChild() {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+
+        verifySetDisplayedChild(4);
+        verifySetDisplayedChild(2);
+        verifySetDisplayedChild(6);
+    }
+
+    private void verifyShowCommand(String intentShowKey, int expectedDisplayedChild) {
+        final CountDownLatch updateLatch = new CountDownLatch(1);
+        CollectionAppWidgetProvider.configure(updateLatch, null, null);
+
+        // Create the intent to update the widget. Note that we're passing the "indication"
+        // which one of showNext / showPrevious APIs to execute in the intent that we're
+        // creating.
+        Intent intent = new Intent(mContext, CollectionAppWidgetProvider.class);
+        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
+        intent.putExtra(intentShowKey, true);
+        mContext.sendBroadcast(intent);
+
+        // Wait until the update request has been processed
+        try {
+            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ie) {
+            fail(ie.getMessage());
+        }
+        // And wait until the underlying StackView has been updated to switch to the expected
+        // child
+        PollingCheck.waitFor(TEST_TIMEOUT_MS,
+                () -> mStackView.getDisplayedChild() == expectedDisplayedChild);
+    }
+
+    @Test
+    public void testShowNextPrevious() {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+
+        // Two forward
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 1);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 2);
+        // Four back (looping to the end of the adapter data)
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_PREVIOUS, 1);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_PREVIOUS, 0);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_PREVIOUS, COUNTRY_LIST.length - 1);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_PREVIOUS, COUNTRY_LIST.length - 2);
+        // And three forward (looping to the start of the adapter data)
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, COUNTRY_LIST.length - 1);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 0);
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 1);
+    }
+
+    private void verifyItemClickIntents(int indexToClick) throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver();
+        mActivityRule.runOnUiThread(() -> receiver.register(BROADCAST_ACTION));
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+        PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
+        final View initialView = mStackView.getCurrentView();
+        mActivityRule.runOnUiThread(
+                () -> mStackView.performItemClick(initialView, indexToClick, 0L));
+        assertEquals(COUNTRY_LIST[indexToClick],
+                receiver.getParam(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSetOnClickPendingIntent() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        verifyItemClickIntents(0);
+
+        // Switch to another child
+        verifySetDisplayedChild(2);
+        verifyItemClickIntents(2);
+
+        // And one more
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 3);
+        verifyItemClickIntents(3);
+    }
+
+    private class ListScrollListener implements AbsListView.OnScrollListener {
+        private CountDownLatch mLatchToNotify;
+
+        private int mTargetPosition;
+
+        public ListScrollListener(CountDownLatch latchToNotify, int targetPosition) {
+            mLatchToNotify = latchToNotify;
+            mTargetPosition = targetPosition;
+        }
+
+        @Override
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+        }
+
+        @Override
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                int totalItemCount) {
+            if ((mTargetPosition >= firstVisibleItem) &&
+                    (mTargetPosition <= (firstVisibleItem + visibleItemCount))) {
+                mLatchToNotify.countDown();
+            }
+        }
+    }
+
+    @Test
+    public void testSetScrollPosition() {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        mListView = (ListView) mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+
+        final CountDownLatch updateLatch = new CountDownLatch(1);
+        final AtomicBoolean scrollToPositionIsComplete = new AtomicBoolean(false);
+        // We're configuring our provider with three parameters:
+        // 1. The CountDownLatch to be notified when the provider has been enabled
+        // 2. The gating condition that waits until ListView has populated its content
+        //    so that we can proceed to call setScrollPosition on it
+        // 3. The gating condition that waits until the setScrollPosition has completed
+        //    its processing / scrolling so that we can proceed to call
+        //    setRelativeScrollPosition on it
+        CollectionAppWidgetProvider.configure(updateLatch, () -> mListView.getChildCount() > 0,
+                scrollToPositionIsComplete::get);
+
+        final int positionToScrollTo = COUNTRY_LIST.length - 10;
+        final int scrollByAmount = COUNTRY_LIST.length / 2;
+        final int offsetScrollTarget = positionToScrollTo - scrollByAmount;
+
+        // Register the first scroll listener on our ListView. The listener will notify our latch
+        // when the "target" item comes into view. If that never happens, the latch will
+        // time out and fail the test.
+        final CountDownLatch scrollToPositionLatch = new CountDownLatch(1);
+        mListView.setOnScrollListener(
+                new ListScrollListener(scrollToPositionLatch, positionToScrollTo));
+
+        // Create the intent to update the widget. Note that we're passing the "indication"
+        // to switch to our ListView in the intent that we're creating.
+        Intent intent = new Intent(mContext, CollectionAppWidgetProvider.class);
+        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
+        intent.putExtra(CollectionAppWidgetProvider.KEY_SWITCH_TO_LIST, true);
+        intent.putExtra(CollectionAppWidgetProvider.KEY_SCROLL_POSITION, positionToScrollTo);
+        intent.putExtra(CollectionAppWidgetProvider.KEY_SCROLL_OFFSET, -scrollByAmount);
+        mContext.sendBroadcast(intent);
+
+        // Wait until the update request has been processed
+        try {
+            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ie) {
+            fail(ie.getMessage());
+        }
+        // And wait until the underlying ListView has been updated to be visible
+        PollingCheck.waitFor(TEST_TIMEOUT_MS, () -> mListView.getVisibility() == View.VISIBLE);
+
+        // Wait until our ListView has at least one visible child view. At that point we know
+        // that not only the host view is on screen, but also that the list view has completed
+        // its layout pass after having asked its adapter to populate the list content.
+        PollingCheck.waitFor(TEST_TIMEOUT_MS, () -> mListView.getChildCount() > 0);
+
+        // If we're on a really big display, we might be in a situation where the position
+        // we're going to scroll to is already visible. In that case the logic in the rest
+        // of this test will never fire off a listener callback and then fail the test.
+        final int lastVisiblePosition = mListView.getLastVisiblePosition();
+        if (positionToScrollTo <= lastVisiblePosition) {
+            return;
+        }
+
+        boolean result = false;
+        try {
+            result = scrollToPositionLatch.await(20, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+        assertTrue("Timed out while waiting for the target view to be scrolled into view", result);
+
+        if ((offsetScrollTarget < 0) ||
+                (offsetScrollTarget >= mListView.getFirstVisiblePosition())) {
+            // We can't scroll up because the target is either already visible or negative
+            return;
+        }
+
+        // Now register another scroll listener on our ListView. The listener will notify our latch
+        // when our new "target" item comes into view. If that never happens, the latch will
+        // time out and fail the test.
+        final CountDownLatch scrollByOffsetLatch = new CountDownLatch(1);
+        mListView.setOnScrollListener(
+                new ListScrollListener(scrollByOffsetLatch, offsetScrollTarget));
+
+        // Update our atomic boolean to "kick off" the widget provider request to call
+        // setRelativeScrollPosition on our RemoteViews
+        scrollToPositionIsComplete.set(true);
+        try {
+            result = scrollByOffsetLatch.await(20, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+        assertTrue("Timed out while waiting for the target view to be scrolled into view", result);
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/DarkTextThemeTest.java b/tests/tests/appwidget/src/android/appwidget/cts/DarkTextThemeTest.java
new file mode 100644
index 0000000..924f272
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/DarkTextThemeTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.appwidget.cts;
+
+import static android.view.View.FIND_VIEWS_WITH_TEXT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.activity.EmptyActivity;
+import android.appwidget.cts.service.MyAppWidgetService;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+
+/**
+ * Test AppWidgets dark text theme
+ */
+@LargeTest
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class DarkTextThemeTest extends AppWidgetTestCase {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    private boolean mHasAppWidgets;
+
+    private EmptyActivity mActivity;
+
+    private AppWidgetHost mAppWidgetHost;
+
+    private MyHostView mAppWidgetHostView;
+    private int mAppWidgetId;
+
+    @Before
+    public void setup() throws Throwable {
+        mHasAppWidgets = hasAppWidgets();
+        if (!mHasAppWidgets) {
+            return;
+        }
+        // We want to bind widgets - run a shell command to grant bind permission to our
+        // package.
+        grantBindAppWidgetPermission();
+
+        mActivity = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(this::bindNewWidget);
+    }
+
+    @After
+    public void teardown() throws Exception {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        mAppWidgetHost.deleteHost();
+        revokeBindAppWidgetPermission();
+    }
+
+    private void bindNewWidget() {
+        mAppWidgetHost = new AppWidgetHost(mActivity, 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyHostView(context);
+            }
+        };
+        mAppWidgetHost.deleteHost();
+        mAppWidgetHost.startListening();
+
+        // Allocate a widget id to bind
+        mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+        // Bind the app widget
+        final AppWidgetProviderInfo providerInfo = getProviderInfo(getFirstWidgetComponent());
+        boolean isBinding = getAppWidgetManager().bindAppWidgetIdIfAllowed(mAppWidgetId,
+                providerInfo.getProfile(), providerInfo.provider, null);
+        assertTrue(isBinding);
+
+        // Create host view
+        mAppWidgetHostView = (MyHostView) mAppWidgetHost
+                .createView(mActivity, mAppWidgetId, providerInfo);
+        mActivity.setContentView(mAppWidgetHostView);
+    }
+
+    @Test
+    public void testWidget_light() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        // Push update
+        RemoteViews views = getViewsForResponse();
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+
+        // Await until update
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mAppWidgetHostView.mCommands.put(
+                (v) -> v.findViewById(R.id.hello) != null, updateLatch::countDown);
+        updateLatch.await();
+
+        // Perform click
+        verifyColor(mAppWidgetHostView, Color.WHITE);
+    }
+
+    @Test
+    public void testWidget_dark() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        mActivity.runOnUiThread(() -> mAppWidgetHostView.setOnLightBackground(true));
+
+        // Push update
+        RemoteViews views = getViewsForResponse();
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+
+        // Await until update
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mAppWidgetHostView.mCommands.put(
+                (v) -> v.findViewById(R.id.hello) != null, updateLatch::countDown);
+        updateLatch.await();
+
+        // Perform click
+        verifyColor(mAppWidgetHostView, Color.BLACK);
+    }
+
+    @Test
+    public void testCollection_light() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        setupAndAwaitCollectionWidget();
+
+        // Perform click on various elements
+        ListView listView = mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+        verifyColor(listView.getChildAt(0), Color.WHITE);
+        verifyColor(listView.getChildAt(1), Color.WHITE);
+        verifyColor(listView.getChildAt(2), Color.WHITE);
+    }
+
+    @Test
+    public void testCollection_dark() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        mActivity.runOnUiThread(() -> mAppWidgetHostView.setOnLightBackground(true));
+
+        setupAndAwaitCollectionWidget();
+
+        // Perform click on various elements
+        ListView listView = mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+        verifyColor(listView.getChildAt(0), Color.BLACK);
+        verifyColor(listView.getChildAt(1), Color.BLACK);
+        verifyColor(listView.getChildAt(2), Color.BLACK);
+    }
+
+    private void setupAndAwaitCollectionWidget() throws Throwable {
+        // Configure the app widget service behavior
+        RemoteViewsService.RemoteViewsFactory factory =
+                mock(RemoteViewsService.RemoteViewsFactory.class);
+        when(factory.getCount()).thenReturn(3);
+        doAnswer(invocation -> {
+            final int position = (Integer) invocation.getArguments()[0];
+            RemoteViews remoteViews = getViewsForResponse();
+            remoteViews.setTextViewText(R.id.hello, "Text " + position);
+            return remoteViews;
+        }).when(factory).getViewAt(any(int.class));
+        when(factory.getViewTypeCount()).thenReturn(1);
+        MyAppWidgetService.setFactory(factory);
+
+        // Push update
+        RemoteViews views = new RemoteViews(mActivity.getPackageName(),
+                R.layout.remoteviews_adapter);
+        Intent listIntent = new Intent(mActivity, MyAppWidgetService.class)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+        listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
+        views.setRemoteAdapter(R.id.remoteViews_list, listIntent);
+        views.setViewVisibility(R.id.remoteViews_stack, View.GONE);
+        views.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
+
+        // Await until update
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> mAppWidgetHostView.getViewTreeObserver()
+                .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mAppWidgetHostView.post(this::verifyChildrenAdded);
+                    }
+
+                    private void verifyChildrenAdded() {
+                        ListView listView = mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+                        if (listView == null || listView.getChildCount() != 3) {
+                            return;
+                        }
+                        if (hasText("Text 0", listView.getChildAt(0))
+                                && hasText("Text 1", listView.getChildAt(1))
+                                && hasText("Text 2", listView.getChildAt(2))) {
+                            updateLatch.countDown();
+                        }
+                    }
+
+                    private boolean hasText(String text, View parent) {
+                        ArrayList<View> out = new ArrayList<>();
+                        parent.findViewsWithText(out, text, FIND_VIEWS_WITH_TEXT);
+                        return !out.isEmpty();
+                    }
+                }));
+        updateLatch.await();
+    }
+
+    private RemoteViews getViewsForResponse() {
+        RemoteViews views = new RemoteViews(mActivity.getPackageName(),
+                R.layout.simple_white_layout);
+        views.setLightBackgroundLayoutId(R.layout.simple_black_layout);
+        return views;
+    }
+
+    private void verifyColor(View parent, int color) {
+        Drawable bg = parent.findViewById(R.id.hello).getBackground();
+        assertTrue(bg instanceof ColorDrawable);
+        assertEquals(color, ((ColorDrawable) bg).getColor());
+    }
+
+    /**
+     * Host view which supports waiting for a update to happen.
+     */
+    private static class MyHostView extends AppWidgetHostView {
+
+        final ArrayMap<Predicate<MyHostView>, Runnable> mCommands = new ArrayMap<>();
+
+        MyHostView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void updateAppWidget(RemoteViews remoteViews) {
+            super.updateAppWidget(remoteViews);
+
+            for (int i = mCommands.size() - 1; i >= 0; i--) {
+                if (mCommands.keyAt(i).test(this)) {
+                    Runnable action = mCommands.valueAt(i);
+                    mCommands.removeAt(i);
+                    action.run();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index 6a54e57..f255454 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -23,24 +23,19 @@
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.cts.common.Constants;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.LauncherApps;
 import android.os.Bundle;
-import android.os.Handler;
 import android.platform.test.annotations.AppModeFull;
 
+import com.android.compatibility.common.util.CddTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import com.android.compatibility.common.util.CddTest;
 
 @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
 public class RequestPinAppWidgetTest extends AppWidgetTestCase {
@@ -68,7 +63,7 @@
         Context context = getInstrumentation().getContext();
 
         // Request to pin widget
-        BlockingReceiver setupReceiver = new BlockingReceiver()
+        BlockingBroadcastReceiver setupReceiver = new BlockingBroadcastReceiver()
                 .register(Constants.ACTION_SETUP_REPLY);
 
         Bundle extras = new Bundle();
@@ -81,12 +76,11 @@
 
         setupReceiver.await();
         // Verify that the confirmation dialog was opened
-        assertTrue(setupReceiver.mResult.getBooleanExtra(Constants.EXTRA_SUCCESS, false));
-        assertEquals(launcherPkg, setupReceiver.mResult.getStringExtra(Constants.EXTRA_PACKAGE));
-        setupReceiver.unregister();
+        assertTrue(setupReceiver.result.getBooleanExtra(Constants.EXTRA_SUCCESS, false));
+        assertEquals(launcherPkg, setupReceiver.result.getStringExtra(Constants.EXTRA_PACKAGE));
 
         LauncherApps.PinItemRequest req =
-                setupReceiver.mResult.getParcelableExtra(Constants.EXTRA_REQUEST);
+                setupReceiver.result.getParcelableExtra(Constants.EXTRA_REQUEST);
         assertNotNull(req);
         // Verify that multiple calls to getAppWidgetProviderInfo have proper dimension.
         boolean[] providerInfo = verifyInstalledProviders(Arrays.asList(
@@ -96,15 +90,15 @@
         assertEquals(launcherPkg + "-dummy", req.getExtras().getString("dummy"));
 
         // Accept the request
-        BlockingReceiver resultReceiver = new BlockingReceiver().register(ACTION_PIN_RESULT);
+        BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver()
+                .register(ACTION_PIN_RESULT);
         context.sendBroadcast(new Intent(Constants.ACTION_CONFIRM_PIN)
                 .setPackage(launcherPkg)
                 .putExtra("dummy", "dummy-2"));
         resultReceiver.await();
 
         // Verify that the result contain the extras
-        assertEquals("dummy-2", resultReceiver.mResult.getStringExtra("dummy"));
-        resultReceiver.unregister();
+        assertEquals("dummy-2", resultReceiver.result.getStringExtra("dummy"));
     }
 
     @Test
@@ -157,31 +151,4 @@
         runShellCommand("cmd package set-home-activity --user "
                 + getInstrumentation().getContext().getUserId() + " " + component);
     }
-
-    private class BlockingReceiver extends BroadcastReceiver {
-        private final CountDownLatch notifier = new CountDownLatch(1);
-
-        private Intent mResult;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mResult = new Intent(intent);
-            notifier.countDown();
-        }
-
-        public BlockingReceiver register(String action) {
-            Context context = getInstrumentation().getContext();
-            context.registerReceiver(this, new IntentFilter(action),
-                    null, new Handler(context.getMainLooper()));
-            return this;
-        }
-
-        public void await() throws Exception {
-            assertTrue(notifier.await(20, TimeUnit.SECONDS));
-        }
-
-        public void unregister() {
-            getInstrumentation().getContext().unregisterReceiver(this);
-        }
-    }
 }
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
new file mode 100644
index 0000000..5f160ad
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.appwidget.cts;
+
+import static android.view.View.FIND_VIEWS_WITH_TEXT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.app.SharedElementCallback;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.activity.EmptyActivity;
+import android.appwidget.cts.activity.TransitionActivity;
+import android.appwidget.cts.service.MyAppWidgetService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * Test AppWidgets transitions.
+ */
+@LargeTest
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class WidgetTransitionTest extends AppWidgetTestCase {
+
+    private static final String CLICK_ACTION = WidgetTransitionTest.class.getSimpleName();
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    private boolean mHasAppWidgets;
+
+    private EmptyActivity mActivity;
+
+    private AppWidgetHost mAppWidgetHost;
+
+    private MyHostView mAppWidgetHostView;
+    private int mAppWidgetId;
+
+    @Before
+    public void setup() throws Throwable {
+        mHasAppWidgets = hasAppWidgets();
+        if (!mHasAppWidgets) {
+            return;
+        }
+        // We want to bind widgets - run a shell command to grant bind permission to our
+        // package.
+        grantBindAppWidgetPermission();
+
+        mActivity = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(this::bindNewWidget);
+    }
+
+    @After
+    public void teardown() throws Exception {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        mAppWidgetHost.deleteHost();
+        revokeBindAppWidgetPermission();
+    }
+
+    private void bindNewWidget() {
+        mAppWidgetHost = new AppWidgetHost(mActivity, 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyHostView(context);
+            }
+        };
+        mAppWidgetHost.deleteHost();
+        mAppWidgetHost.startListening();
+
+        // Allocate a widget id to bind
+        mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+        // Bind the app widget
+        final AppWidgetProviderInfo providerInfo = getProviderInfo(getFirstWidgetComponent());
+        boolean isBinding = getAppWidgetManager().bindAppWidgetIdIfAllowed(mAppWidgetId,
+                providerInfo.getProfile(), providerInfo.provider, null);
+        assertTrue(isBinding);
+
+        // Create host view
+        mAppWidgetHostView = (MyHostView) mAppWidgetHost
+                .createView(mActivity, mAppWidgetId, providerInfo);
+        mActivity.setContentView(mAppWidgetHostView);
+    }
+
+    @Test
+    public void testWidget_sendBroadcast() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        // Push update
+        RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
+                PendingIntent.getBroadcast(mActivity, 0,
+                        new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT)));
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+
+        // Await until update
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mAppWidgetHostView.mCommands.put(
+                (v) -> v.findViewById(R.id.hello) != null, updateLatch::countDown);
+        updateLatch.await();
+
+        // Perform click
+        verifyClickBroadcast(mAppWidgetHostView);
+    }
+
+    @Test
+    public void testWidget_startActivity() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+        // Push update
+        RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
+                PendingIntent.getActivity(mActivity, 0,
+                        new Intent(mActivity, TransitionActivity.class),
+                        PendingIntent.FLAG_UPDATE_CURRENT)));
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+
+        // Await until update
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mAppWidgetHostView.mCommands.put(
+                (v) -> v.findViewById(R.id.hello) != null, updateLatch::countDown);
+        updateLatch.await();
+
+        // Perform click
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver()
+                .register(TransitionActivity.BROADCAST_ACTION);
+        CountDownLatch sharedElementCallbackLatch = new CountDownLatch(1);
+        mActivity.setExitSharedElementCallback(new SharedElementCallback() {
+            @Override
+            public void onSharedElementsArrived(
+                    List<String> sharedElementNames,
+                    List<View> sharedElements,
+                    final OnSharedElementsReadyListener listener) {
+                sharedElementCallbackLatch.countDown();
+                mActivity.getWindow().getDecorView().postDelayed(
+                        listener::onSharedElementsReady, 60);
+            }
+        });
+        mActivityRule.runOnUiThread(
+                () -> mAppWidgetHostView.findViewById(R.id.hello).performClick());
+
+        receiver.await();
+        assertTrue(sharedElementCallbackLatch.await(1000, TimeUnit.MILLISECONDS));
+        String[] elements = receiver.result
+                .getStringArrayListExtra(TransitionActivity.EXTRA_ELEMENTS).toArray(new String[0]);
+        assertArrayEquals(new String[] {"green_square"}, elements);
+    }
+
+    @Test
+    public void testCollection_sendBroadcast() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        // Configure the app widget service behavior
+        RemoteViewsService.RemoteViewsFactory factory =
+                mock(RemoteViewsService.RemoteViewsFactory.class);
+        when(factory.getCount()).thenReturn(3);
+        doAnswer(invocation -> {
+            final int position = (Integer) invocation.getArguments()[0];
+            RemoteViews remoteViews = getViewsForResponse(RemoteViews.RemoteResponse
+                    .fromFillInIntent(new Intent().putExtra("item_id", position)));
+            remoteViews.setTextViewText(R.id.hello, "Text " + position);
+            return remoteViews;
+        }).when(factory).getViewAt(any(int.class));
+        when(factory.getViewTypeCount()).thenReturn(1);
+        MyAppWidgetService.setFactory(factory);
+
+        // Push update
+        RemoteViews views = new RemoteViews(mActivity.getPackageName(),
+                R.layout.remoteviews_adapter);
+        Intent listIntent = new Intent(mActivity, MyAppWidgetService.class)
+                .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+        listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
+        views.setRemoteAdapter(R.id.remoteViews_list, listIntent);
+        views.setViewVisibility(R.id.remoteViews_stack, View.GONE);
+        views.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mActivity, 0,
+                new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);
+        views.setPendingIntentTemplate(R.id.remoteViews_list, pendingIntent);
+
+        // Await until update
+        getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
+        CountDownLatch updateLatch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> mAppWidgetHostView.getViewTreeObserver()
+                .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mAppWidgetHostView.post(this::verifyChildrenAdded);
+                    }
+
+                    private void verifyChildrenAdded() {
+                        ListView listView = mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+                        if (listView == null || listView.getChildCount() != 3) {
+                            return;
+                        }
+                        if (hasText("Text 0", listView.getChildAt(0))
+                                && hasText("Text 1", listView.getChildAt(1))
+                                && hasText("Text 2", listView.getChildAt(2))) {
+                            updateLatch.countDown();
+                        }
+                    }
+
+                    private boolean hasText(String text, View parent) {
+                        ArrayList<View> out = new ArrayList<>();
+                        parent.findViewsWithText(out, text, FIND_VIEWS_WITH_TEXT);
+                        return !out.isEmpty();
+                    }
+                }));
+        updateLatch.await();
+
+        // Perform click on various elements
+        ListView listView = mAppWidgetHostView.findViewById(R.id.remoteViews_list);
+        verifyClickBroadcast(listView.getChildAt(0));
+        verifyClickBroadcast(listView.getChildAt(1));
+        verifyClickBroadcast(listView.getChildAt(2));
+    }
+
+    private RemoteViews getViewsForResponse(RemoteViews.RemoteResponse response) {
+        RemoteViews views = new RemoteViews(mActivity.getPackageName(),
+                R.layout.activity_transition);
+        response.addSharedElement(R.id.greenSquare, "green_square");
+        views.setOnClickResponse(R.id.hello, response);
+        return views;
+    }
+
+    private void verifyClickBroadcast(View parent) throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver().register(CLICK_ACTION);
+        mActivityRule.runOnUiThread(() -> parent.findViewById(R.id.hello).performClick());
+
+        receiver.await();
+        assertNotNull(receiver.result);
+
+        assertEquals(getSourceBounds(parent.findViewById(R.id.hello)),
+                receiver.result.getSourceBounds());
+        assertEquals(getSourceBounds(parent.findViewById(R.id.greenSquare)),
+                receiver.result.getBundleExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS)
+                        .getParcelable("green_square"));
+    }
+
+    private static Rect getSourceBounds(View v) {
+        final int[] pos = new int[2];
+        v.getLocationOnScreen(pos);
+        return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
+    }
+
+    /**
+     * Host view which supports waiting for a update to happen.
+     */
+    private static class MyHostView extends AppWidgetHostView {
+
+        final ArrayMap<Predicate<MyHostView>, Runnable> mCommands = new ArrayMap<>();
+
+        MyHostView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void updateAppWidget(RemoteViews remoteViews) {
+            super.updateAppWidget(remoteViews);
+
+            for (int i = mCommands.size() - 1; i >= 0; i--) {
+                if (mCommands.keyAt(i).test(this)) {
+                    Runnable action = mCommands.valueAt(i);
+                    mCommands.removeAt(i);
+                    action.run();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/activity/EmptyActivity.java b/tests/tests/appwidget/src/android/appwidget/cts/activity/EmptyActivity.java
new file mode 100644
index 0000000..5748471
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/activity/EmptyActivity.java
@@ -0,0 +1,20 @@
+/*
+ * 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.appwidget.cts.activity;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity { }
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/activity/TransitionActivity.java b/tests/tests/appwidget/src/android/appwidget/cts/activity/TransitionActivity.java
new file mode 100644
index 0000000..276cc42
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/activity/TransitionActivity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.appwidget.cts.activity;
+
+import android.app.Activity;
+import android.app.SharedElementCallback;
+import android.appwidget.cts.R;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TransitionActivity extends Activity {
+
+    public static final String BROADCAST_ACTION = TransitionActivity.class.getSimpleName();
+    public static final String EXTRA_ELEMENTS = "elements";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_transition_end);
+
+        setEnterSharedElementCallback(new SharedElementCallback() {
+            @Override
+            public void onSharedElementsArrived(List<String> sharedElementNames,
+                    List<View> sharedElements, OnSharedElementsReadyListener listener) {
+                super.onSharedElementsArrived(sharedElementNames, sharedElements, listener);
+                sendBroadcast(new Intent(BROADCAST_ACTION).putStringArrayListExtra(
+                        EXTRA_ELEMENTS, new ArrayList<>(sharedElementNames)));
+            }
+        });
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
new file mode 100644
index 0000000..b1b21b7
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
@@ -0,0 +1,178 @@
+/*
+ * 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.appwidget.cts.provider;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.cts.R;
+import android.appwidget.cts.service.MyAppWidgetService;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.concurrent.CountDownLatch;
+
+public class CollectionAppWidgetProvider  extends AppWidgetProvider {
+    private static final long TIME_SLICE = 100;
+
+    public static final String KEY_DISPLAYED_CHILD_INDEX =
+            "MyAppWidgetProvider.displayedChildIndex";
+    public static final String KEY_SHOW_NEXT = "MyAppWidgetProvider.showNext";
+    public static final String KEY_SHOW_PREVIOUS = "MyAppWidgetProvider.showPrevious";
+    public static final String KEY_SWITCH_TO_LIST = "MyAppWidgetProvider.switchToList";
+    public static final String KEY_SCROLL_POSITION = "MyAppWidgetProvider.scrollPosition";
+    public static final String KEY_SCROLL_OFFSET = "MyAppWidgetProvider.scrollOffset";
+    public static final String BROADCAST_ACTION = CollectionAppWidgetProvider.class.getName();
+
+    // This latch will be notified when onEnabled is called on our provider.
+    private static CountDownLatch sCountDownLatch;
+    // Gating condition to be polled to proceed with setScrollPosition call.
+    private static PollingCheck.PollingCheckCondition sSetScrollCondition;
+    // Gating condition to be polled to proceed with setRelativeScrollPosition call.
+    private static PollingCheck.PollingCheckCondition sSetRelativeScrollCondition;
+
+    private int mDisplayedChildIndex;
+    private boolean mShowNext;
+    private boolean mShowPrevious;
+    private boolean mSwitchToList;
+    private int mScrollPosition;
+    private int mScrollOffset;
+
+    public static void configure(CountDownLatch countDownLatch,
+            PollingCheck.PollingCheckCondition setScrollCondition,
+            PollingCheck.PollingCheckCondition setRelativeScrollCondition) {
+        sCountDownLatch = countDownLatch;
+        sSetScrollCondition = setScrollCondition;
+        sSetRelativeScrollCondition = setRelativeScrollCondition;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mDisplayedChildIndex = intent.getIntExtra(KEY_DISPLAYED_CHILD_INDEX, -1);
+        mShowNext = intent.getBooleanExtra(KEY_SHOW_NEXT, false);
+        mShowPrevious = intent.getBooleanExtra(KEY_SHOW_PREVIOUS, false);
+        mSwitchToList = intent.getBooleanExtra(KEY_SWITCH_TO_LIST, false);
+        mScrollPosition = intent.getIntExtra(KEY_SCROLL_POSITION, -1);
+        mScrollOffset = intent.getIntExtra(KEY_SCROLL_OFFSET, 0);
+
+        super.onReceive(context, intent);
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        final int appWidgetId = appWidgetIds[0];
+        final RemoteViews widgetAdapterView = new RemoteViews(context.getPackageName(),
+                R.layout.remoteviews_adapter);
+
+        final Intent stackIntent = new Intent(context, MyAppWidgetService.class);
+        stackIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        stackIntent.setData(Uri.parse(stackIntent.toUri(Intent.URI_INTENT_SCHEME)));
+
+        widgetAdapterView.setRemoteAdapter(R.id.remoteViews_stack, stackIntent);
+        widgetAdapterView.setEmptyView(R.id.remoteViews_stack, R.id.remoteViews_empty);
+
+        if (mDisplayedChildIndex >= 0) {
+            widgetAdapterView.setDisplayedChild(R.id.remoteViews_stack, mDisplayedChildIndex);
+        }
+        if (mShowNext) {
+            widgetAdapterView.showNext(R.id.remoteViews_stack);
+        }
+        if (mShowPrevious) {
+            widgetAdapterView.showPrevious(R.id.remoteViews_stack);
+        }
+
+        // Here we setup the a pending intent template. Individuals items of a collection
+        // cannot setup their own pending intents, instead, the collection as a whole can
+        // setup a pending intent template, and the individual items can set a fillInIntent
+        // to create unique before on an item to item basis.
+        Intent viewIntent = new Intent(BROADCAST_ACTION);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, viewIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        widgetAdapterView.setPendingIntentTemplate(R.id.remoteViews_stack, pendingIntent);
+
+        if (mSwitchToList) {
+            final Intent listIntent = new Intent(context, MyAppWidgetService.class);
+            listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+            listIntent.setData(Uri.parse(stackIntent.toUri(Intent.URI_INTENT_SCHEME)));
+
+            widgetAdapterView.setRemoteAdapter(R.id.remoteViews_list, listIntent);
+
+            widgetAdapterView.setViewVisibility(R.id.remoteViews_stack, View.GONE);
+            widgetAdapterView.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
+        }
+
+        final Handler handler = new Handler(Looper.myLooper());
+        if (mScrollPosition >= 0) {
+            // We need to schedule the call to setScrollPosition as a separate event that runs
+            // after the underlying ListView has been laid out on the screen. Otherwise calling
+            // that API on a ListView with 0x0 dimension has no effect - the list content is only
+            // populated via the adapter when ListView has "real" bounds.
+            final Runnable setScrollRunnable = new Runnable() {
+                public void run() {
+                    if (sSetScrollCondition.canProceed()) {
+                        // Gating condition has been satisfied. Call setScrollPosition and
+                        // ask the widget manager to update our widget
+                        widgetAdapterView.setScrollPosition(R.id.remoteViews_list, mScrollPosition);
+                        appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widgetAdapterView);
+                    } else {
+                        // Keep on "waiting" until the gating condition is satisfied
+                        handler.postDelayed(this, TIME_SLICE);
+                    }
+                }
+            };
+            handler.postDelayed(setScrollRunnable, TIME_SLICE);
+        }
+
+        if (mScrollOffset != 0) {
+            // We need to schedule the call to setRelativeScrollPosition as a separate event that
+            // runs after the underlying ListView has been laid out on the screen. Otherwise calling
+            // that API on a ListView with 0x0 dimension has no effect - the list content is only
+            // populated via the adapter when ListView has "real" bounds.
+            final Runnable setRelativeScrollRunnable = new Runnable() {
+                public void run() {
+                    if (sSetRelativeScrollCondition.canProceed()) {
+                        // Gating condition has been satisfied. Call setRelativeScrollPosition and
+                        // ask the widget manager to update our widget
+                        widgetAdapterView.setRelativeScrollPosition(
+                                R.id.remoteViews_list, mScrollOffset);
+                        appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widgetAdapterView);
+                    } else {
+                        // Keep on "waiting" until the gating condition is satisfied
+                        handler.postDelayed(this, TIME_SLICE);
+                    }
+                }
+            };
+            handler.postDelayed(setRelativeScrollRunnable, TIME_SLICE);
+        }
+
+        appWidgetManager.updateAppWidget(appWidgetId, widgetAdapterView);
+
+        sCountDownLatch.countDown();
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        sCountDownLatch.countDown();
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java b/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java
index 313855b..7ce4f17 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java
@@ -17,6 +17,7 @@
 package android.appwidget.cts.service;
 
 import android.content.Intent;
+import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
 
 public class MyAppWidgetService extends RemoteViewsService {
@@ -33,7 +34,48 @@
     @Override
     public RemoteViewsFactory onGetViewFactory(Intent intent) {
         synchronized (sLock) {
-            return sFactory;
+            return sFactory == null ? new DefaultFactory() : sFactory;
+        }
+    }
+
+    private static class DefaultFactory implements RemoteViewsFactory {
+        @Override
+        public void onCreate() { }
+
+        @Override
+        public void onDataSetChanged() { }
+
+        @Override
+        public void onDestroy() { }
+
+        @Override
+        public int getCount() {
+            return 0;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int i) {
+            return null;
+        }
+
+        @Override
+        public RemoteViews getLoadingView() {
+            return null;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public long getItemId(int i) {
+            return i;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
         }
     }
 }
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index a41b507..937d8ce 100755
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -16,8 +16,11 @@
 package android.assist.common;
 
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.LocaleList;
+import android.os.Process;
+import android.util.Log;
 
 import org.json.JSONObject;
 
@@ -25,6 +28,7 @@
 import java.util.Locale;
 
 public class Utils {
+    private static final String TAG = Utils.class.getSimpleName();
     public static final String TESTCASE_TYPE = "testcase_type";
     public static final String TESTINFO = "testinfo";
     public static final String ACTION_PREFIX = "android.intent.action.";
@@ -116,6 +120,8 @@
     private static Bundle EXTRA_ASSIST_BUNDLE;
     private static String STRUCTURED_JSON;
 
+    private static String MY_UID_EXTRA = "my_uid";
+
     public static final String getStructuredJSON() throws Exception {
         if (STRUCTURED_JSON == null) {
             STRUCTURED_JSON = new JSONObject()
@@ -139,15 +145,24 @@
     public static final Bundle getExtraAssistBundle() {
         if (EXTRA_ASSIST_BUNDLE == null) {
             EXTRA_ASSIST_BUNDLE = new Bundle();
-            addExtraAssistDataToBundle(EXTRA_ASSIST_BUNDLE);
+            addExtraAssistDataToBundle(EXTRA_ASSIST_BUNDLE, /* addMyUid= */ false);
         }
         return EXTRA_ASSIST_BUNDLE;
     }
 
     public static void addExtraAssistDataToBundle(Bundle data) {
+        addExtraAssistDataToBundle(data, /* addMyUid= */ true);
+
+    }
+
+    private static void addExtraAssistDataToBundle(Bundle data, boolean addMyUid) {
         data.putString("hello", "there");
         data.putBoolean("isthis_true_or_false", true);
         data.putInt("number", 123);
+        if (addMyUid) {
+            Log.i(TAG, "adding " + MY_UID_EXTRA + "=" + Process.myUid());
+            data.putInt(MY_UID_EXTRA, Process.myUid());
+        }
     }
 
     /** The shim activity that starts the service associated with each test. */
@@ -248,4 +263,8 @@
         testinfo.getStringArrayList(testinfo.getString(Utils.TESTCASE_TYPE))
             .add(TEST_ERROR + " " + msg);
     }
+
+    public static int getExpectedUid(Bundle extras) {
+        return extras.getInt(MY_UID_EXTRA);
+    }
 }
diff --git a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
index 1154179..5717fcc 100644
--- a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.os.Process;
 import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
@@ -80,10 +81,9 @@
 
         Log.i(TAG, "assist bundle is: " + Utils.toBundleString(mAssistBundle));
 
-        // tests that the assist content's structured data is the expected
+        // 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());
-        // tests the assist data. EXTRA_ASSIST_CONTEXT is what's expected.
         Bundle extraExpectedBundle = Utils.getExtraAssistBundle();
         Bundle extraAssistBundle = mAssistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
         for (String key : extraExpectedBundle.keySet()) {
@@ -92,6 +92,12 @@
             assertEquals("Extra assist context bundle values do not match for key: " + key,
                     extraExpectedBundle.get(key), extraAssistBundle.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);
+
     }
 
     private void waitForOnResume() throws Exception {
diff --git a/tests/tests/assist/testapp/src/android/assist/testapp/ExtraAssistDataActivity.java b/tests/tests/assist/testapp/src/android/assist/testapp/ExtraAssistDataActivity.java
index 0cb8801..4b4c909 100644
--- a/tests/tests/assist/testapp/src/android/assist/testapp/ExtraAssistDataActivity.java
+++ b/tests/tests/assist/testapp/src/android/assist/testapp/ExtraAssistDataActivity.java
@@ -29,10 +29,6 @@
  */
 public class ExtraAssistDataActivity extends Activity {
     private static final String TAG = "ExtraAssistDataActivity";
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
 
     @Override
     public void onProvideAssistData(Bundle data) {
diff --git a/tests/tests/background/AndroidTest.xml b/tests/tests/background/AndroidTest.xml
index c54940c..f0af389 100644
--- a/tests/tests/background/AndroidTest.xml
+++ b/tests/tests/background/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for background restrictions 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="instant_app" />
     <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/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index d14c61f..1bfb6ce 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -33,6 +33,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="am set-standby-bucket android.os.cts.batterysaving.app_target_api_25      10" />
         <option name="run-command" value="am set-standby-bucket android.os.cts.batterysaving.app_target_api_current 10" />
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/batterysaving/apps/app_target_api_25/Android.mk b/tests/tests/batterysaving/apps/app_target_api_25/Android.mk
index f1ebee6..42c1466 100644
--- a/tests/tests/batterysaving/apps/app_target_api_25/Android.mk
+++ b/tests/tests/batterysaving/apps/app_target_api_25/Android.mk
@@ -33,6 +33,7 @@
     ub-uiautomator
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 23
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
index 5f29008..4f87e43 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -15,16 +15,21 @@
  */
 package android.os.cts.batterysaving;
 
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
 
 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.os.PowerManager;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -68,4 +73,22 @@
         waitUntilJobForceAppStandby(false);
         waitUntilForceBackgroundCheck(false);
     }
+
+    @Test
+    public void testSetBatterySaver_powerManager() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            PowerManager manager = BatteryUtils.getPowerManager();
+            assertFalse(manager.isPowerSaveMode());
+
+            // Unplug the charger.
+            runDumpsysBatteryUnplug();
+
+            // Verify battery saver gets toggled.
+            manager.setPowerSaveMode(true);
+            assertTrue(manager.isPowerSaveMode());
+
+            manager.setPowerSaveMode(false);
+            assertFalse(manager.isPowerSaveMode());
+        });
+    }
 }
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 4aef998..19a34eb 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
@@ -29,6 +29,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
+import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.BeforeAfterRule;
 import com.android.compatibility.common.util.OnFailureRule;
 
@@ -60,9 +61,7 @@
     private final BeforeAfterRule mInitializeAndCleanupRule = new BeforeAfterRule() {
         @Override
         protected void onBefore(Statement base, Description description) throws Throwable {
-            // Don't run any battery saver tests on wear.
-            final PackageManager pm = getPackageManager();
-            Assume.assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
+            BatteryUtils.assumeBatterySaverFeature();
 
             turnOnScreen(true);
         }
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 61b35d5..0d4b701 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -1,10 +1,12 @@
 LOCAL_PATH := $(call my-dir)
 
 cts_bionic_tests_dir := lib32
+lib_or_lib64 := lib
 
 ifeq (true,$(TARGET_IS_64_BIT))
   ifeq (,$(cts_bionic_tests_2nd_arch_prefix))
     cts_bionic_tests_dir := lib64
+    lib_or_lib64 := lib64
   endif
 endif
 
@@ -16,6 +18,7 @@
   dt_runpath_b_c_x/libtest_dt_runpath_b.so \
   dt_runpath_b_c_x/libtest_dt_runpath_c.so \
   dt_runpath_b_c_x/libtest_dt_runpath_x.so \
+  dt_runpath_y/$(lib_or_lib64)/libtest_dt_runpath_y.so \
   elftls_dlopen_ie_error_helper/elftls_dlopen_ie_error_helper \
   exec_linker_helper/exec_linker_helper \
   exec_linker_helper_lib.so \
@@ -165,9 +168,28 @@
     $(my_bionic_testlibs_src_dir)/$(lib):$(my_bionic_testlibs_out_dir)/$(lib))
 endif
 
+# Special casing for libtest_dt_runpath_y.so. Since we use the standard ARM CTS
+# to test ARM-on-x86 devices where ${LIB} is expanded to lib/arm, the lib
+# is installed to ./lib/arm as well as ./lib to make sure that the lib can be
+# found on any device.
+archname := $(TARGET_ARCH)
+ifneq (,$(cts_bionic_tests_2nd_arch_prefix))
+  archname := $(TARGET_2ND_ARCH)
+endif
+
+src := $(my_bionic_testlibs_src_dir)/dt_runpath_y/$(lib_or_lib64)/libtest_dt_runpath_y.so
+dst := $(my_bionic_testlibs_out_dir)/dt_runpath_y/$(lib_or_lib64)/$(archname)/libtest_dt_runpath_y.so
+
+LOCAL_COMPATIBILITY_SUPPORT_FILES += $(src):$(dst)
+
 my_bionic_testlib_files :=
 my_bionic_testlib_files_non_mips :=
 my_bionic_testlibs_src_dir :=
 my_bionic_testlibs_out_dir :=
 cts_bionic_tests_dir :=
 cts_bionic_tests_2nd_arch_prefix :=
+lib_or_lib64 :=
+archname :=
+src :=
+dst :=
+
diff --git a/tests/tests/bluetooth/AndroidTest.xml b/tests/tests/bluetooth/AndroidTest.xml
index 04f7f36..3096bc2 100644
--- a/tests/tests/bluetooth/AndroidTest.xml
+++ b/tests/tests/bluetooth/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Bluetooth test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="bluetooth" />
+    <!-- Instant apps cannot hold android.permission.BLUETOOTH which makes BT tests irrelevant -->
+    <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" />
         <option name="test-file-name" value="CtsBluetoothTestCases.apk" />
diff --git a/tests/tests/calendarcommon/Android.mk b/tests/tests/calendarcommon/Android.mk
index bfd9f26..fd97621 100644
--- a/tests/tests/calendarcommon/Android.mk
+++ b/tests/tests/calendarcommon/Android.mk
@@ -32,6 +32,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 15
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/car/AndroidManifest.xml b/tests/tests/car/AndroidManifest.xml
index 2a52f2e..fe1b64f 100644
--- a/tests/tests/car/AndroidManifest.xml
+++ b/tests/tests/car/AndroidManifest.xml
@@ -19,7 +19,8 @@
     <uses-feature android:name="android.hardware.type.automotive" />
     <uses-permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT" />
     <uses-permission android:name="android.car.permission.CAR_INFO" />
-
+    <uses-permission android:name="android.car.permission.CAR_POWERTRAIN" />
+    <uses-permission android:name="android.car.permission.CAR_SPEED" />
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name=".drivingstate.DistractionOptimizedActivity">
diff --git a/tests/tests/carrierapi/Android.mk b/tests/tests/carrierapi/Android.mk
index 2215b88..e347f72 100644
--- a/tests/tests/carrierapi/Android.mk
+++ b/tests/tests/carrierapi/Android.mk
@@ -30,13 +30,12 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsCarrierApiTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := current
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-LOCAL_JAVA_LIBRARIES += android.test.runner.stubs telephony-common
-LOCAL_JAVA_LIBRARIES += android.test.base.stubs
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs android.test.base.stubs
 
 # This APK must be signed to match the test SIM's cert whitelist.
 # While "testkey" is the default, there are different per-device testkeys, so
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index b3ca3bc..d77f373 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -27,6 +27,5 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.carrierapi.cts" />
-        <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
index ee35eed..853265a 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
@@ -25,11 +25,12 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
-import android.net.ConnectivityManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PersistableBundle;
+import android.provider.Telephony;
 import android.provider.VoicemailContract;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneStateListener;
@@ -39,9 +40,6 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.uicc.IccUtils;
-
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
@@ -108,10 +106,8 @@
      * Checks whether the cellular stack should be running on this device.
      */
     private boolean hasCellular() {
-        ConnectivityManager mgr =
-                (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
-        return mgr.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) &&
-               mTelephonyManager.isVoiceCapable();
+        return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
+                mTelephonyManager.getPhoneCount() > 0;
     }
 
     private boolean isSimCardPresent() {
@@ -124,7 +120,7 @@
             PackageInfo pInfo = mPackageManager.getPackageInfo(pkgName,
                     PackageManager.GET_SIGNATURES | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
             MessageDigest md = MessageDigest.getInstance("SHA-1");
-            return IccUtils.bytesToHexString(md.digest(pInfo.signatures[0].toByteArray()));
+            return bytesToHexString(md.digest(pInfo.signatures[0].toByteArray()));
         } catch (PackageManager.NameNotFoundException ex) {
             Log.e(TAG, pkgName + " not found", ex);
         } catch (NoSuchAlgorithmException ex) {
@@ -184,12 +180,13 @@
         try {
             IntentReceiver intentReceiver = new IntentReceiver();
             final IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(TelephonyIntents.SECRET_CODE_ACTION);
+            intentFilter.addAction(Telephony.Sms.Intents.SECRET_CODE_ACTION);
             intentFilter.addDataScheme("android_secret_code");
             getContext().registerReceiver(intentReceiver, intentFilter);
 
             mTelephonyManager.sendDialerSpecialCode("4636");
-            assertTrue("Did not receive expected Intent: " + TelephonyIntents.SECRET_CODE_ACTION,
+            assertTrue("Did not receive expected Intent: " +
+                    Telephony.Sms.Intents.SECRET_CODE_ACTION,
                     intentReceiver.waitForReceive());
         } catch (SecurityException e) {
             failMessage();
@@ -212,7 +209,7 @@
                 TelephonyManager tm =
                         mTelephonyManager.createForSubscriptionId(info.getSubscriptionId());
                 assertTrue("getActiveSubscriptionInfoList() returned an inaccessible subscription",
-                        tm.hasCarrierPrivileges(info.getSubscriptionId()));
+                        tm.hasCarrierPrivileges());
 
                 // Check other APIs to make sure they are accessible and return consistent info.
                 SubscriptionInfo infoForSlot =
@@ -255,15 +252,14 @@
     public void testTelephonyApisAreAccessible() {
         if (!hasCellular) return;
         // The following methods may return any value depending on the state of the device. Simply
-        // call them to make sure they do not throw any exceptions.
+        // call them to make sure they do not throw any exceptions. Methods that return a device
+        // identifier will be accessible to apps with carrier privileges in Q, but this may change
+        // in a future release.
         try {
-            mTelephonyManager.getDeviceSoftwareVersion();
             mTelephonyManager.getDeviceId();
-            mTelephonyManager.getDeviceId(mTelephonyManager.getSlotIndex());
             mTelephonyManager.getImei();
-            mTelephonyManager.getImei(mTelephonyManager.getSlotIndex());
             mTelephonyManager.getMeid();
-            mTelephonyManager.getMeid(mTelephonyManager.getSlotIndex());
+            mTelephonyManager.getDeviceSoftwareVersion();
             mTelephonyManager.getNai();
             mTelephonyManager.getDataNetworkType();
             mTelephonyManager.getVoiceNetworkType();
@@ -387,4 +383,22 @@
             return mReceiveLatch.await(30, TimeUnit.SECONDS);
         }
     }
+
+
+    // A table mapping from a number to a hex character for fast encoding hex strings.
+    private static final char[] HEX_CHARS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    private static String bytesToHexString(byte[] bytes) {
+        StringBuilder ret = new StringBuilder(2 * bytes.length);
+        for (int i = 0 ; i < bytes.length ; i++) {
+            int b;
+            b = 0x0f & (bytes[i] >> 4);
+            ret.append(HEX_CHARS[b]);
+            b = 0x0f & bytes[i];
+            ret.append(HEX_CHARS[b]);
+        }
+        return ret.toString();
+    }
 }
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
index 9d7bc93..45e28b0 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -148,7 +149,9 @@
                         case EVENT_NETWORK_SCAN_START:
                             Log.d(TAG, "request network scan");
                             mNetworkScan = mTelephonyManager.requestNetworkScan(
-                                    mNetworkScanRequest, mNetworkScanCallback);
+                                    mNetworkScanRequest,
+                                    AsyncTask.SERIAL_EXECUTOR,
+                                    mNetworkScanCallback);
                             break;
                         default:
                             Log.d(TAG, "Unknown Event " + msg.what);
diff --git a/tests/tests/carrierapi2/Android.mk b/tests/tests/carrierapi2/Android.mk
new file mode 100644
index 0000000..81e7029
--- /dev/null
+++ b/tests/tests/carrierapi2/Android.mk
@@ -0,0 +1,48 @@
+# 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.
+
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+    junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsCarrierApi2TestCases
+# The SDK version is set to 28 to test device identifier access for apps with
+# carrier privileges targeting pre-Q.
+LOCAL_SDK_VERSION := 28
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs android.test.base.stubs
+
+# This APK must be signed to match the test SIM's cert whitelist.
+# While "testkey" is the default, there are different per-device testkeys, so
+# hard-code the AOSP default key to ensure it is used regardless of build
+# environment.
+LOCAL_CERTIFICATE := build/make/target/product/security/testkey
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/carrierapi2/AndroidManifest.xml b/tests/tests/carrierapi2/AndroidManifest.xml
new file mode 100644
index 0000000..f039a8d
--- /dev/null
+++ b/tests/tests/carrierapi2/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.carrierapi2.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.carrierapi2.cts">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/carrierapi2/AndroidTest.xml b/tests/tests/carrierapi2/AndroidTest.xml
new file mode 100644
index 0000000..6951847
--- /dev/null
+++ b/tests/tests/carrierapi2/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<configuration description="Config for CTS Carrier APIs test cases targeting pre-Q">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="telecom" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
+        <option name="token" value="sim-card-with-certs" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCarrierApi2TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.carrierapi2.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/carrierapi2/src/android/carrierapi2/cts/CarrierApiTest.java b/tests/tests/carrierapi2/src/android/carrierapi2/cts/CarrierApiTest.java
new file mode 100644
index 0000000..953beda
--- /dev/null
+++ b/tests/tests/carrierapi2/src/android/carrierapi2/cts/CarrierApiTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.carrierapi2.cts;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Verifies the APIs for apps with carrier privileges targeting pre-Q.
+ *
+ * @see android.carrierapi.cts.CarrierApiTest
+ */
+public class CarrierApiTest extends AndroidTestCase {
+    private static final String TAG = "CarrierApi2Test";
+
+    private TelephonyManager mTelephonyManager;
+    private PackageManager mPackageManager;
+    private boolean hasCellular;
+    private String selfPackageName;
+    private String selfCertHash;
+
+    private static final String FiDevCert = "24EB92CBB156B280FA4E1429A6ECEEB6E5C1BFE4";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTelephonyManager = (TelephonyManager)
+                getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        mPackageManager = getContext().getPackageManager();
+        selfPackageName = getContext().getPackageName();
+        selfCertHash = getCertHash(selfPackageName);
+        hasCellular = hasCellular();
+        if (!hasCellular) {
+            Log.e(TAG, "No cellular support, all tests will be skipped.");
+        }
+    }
+
+    /**
+     * Checks whether the cellular stack should be running on this device.
+     */
+    private boolean hasCellular() {
+        return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
+                mTelephonyManager.getPhoneCount() > 0;
+    }
+
+    private boolean isSimCardPresent() {
+        return mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE &&
+                mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
+    }
+
+    private String getCertHash(String pkgName) {
+        try {
+            PackageInfo pInfo = mPackageManager.getPackageInfo(pkgName,
+                    PackageManager.GET_SIGNATURES | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            return bytesToHexString(md.digest(pInfo.signatures[0].toByteArray()));
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.e(TAG, pkgName + " not found", ex);
+        } catch (NoSuchAlgorithmException ex) {
+            Log.e(TAG, "Algorithm SHA1 is not found.");
+        }
+        return "";
+    }
+
+    private void failMessage() {
+        if (FiDevCert.equalsIgnoreCase(selfCertHash)) {
+            fail("This test requires a Project Fi SIM card.");
+        } else {
+            fail("This test requires a SIM card with carrier privilege rule on it.\n" +
+                 "Cert hash: " + selfCertHash + "\n" +
+                 "Visit https://source.android.com/devices/tech/config/uicc.html");
+        }
+    }
+
+    public void testSimCardPresent() {
+        if (!hasCellular) return;
+        assertTrue("This test requires SIM card.", isSimCardPresent());
+    }
+
+    public void testHasCarrierPrivileges() {
+        if (!hasCellular) return;
+        if (!mTelephonyManager.hasCarrierPrivileges()) {
+            failMessage();
+        }
+    }
+
+    public void testTelephonyApisAreAccessible() {
+        if (!hasCellular) return;
+        // The following methods may return any value depending on the state of the device. Simply
+        // call them to make sure they do not throw any exceptions. Methods that return a device
+        // identifier will be accessible to apps with carrier privileges in Q, but this may change
+        // in a future release.
+        try {
+            mTelephonyManager.getDeviceId();
+            mTelephonyManager.getImei();
+            mTelephonyManager.getMeid();
+            mTelephonyManager.getDeviceSoftwareVersion();
+            mTelephonyManager.getNai();
+            mTelephonyManager.getDataNetworkType();
+            mTelephonyManager.getVoiceNetworkType();
+            mTelephonyManager.getSimSerialNumber();
+            mTelephonyManager.getSubscriberId();
+            mTelephonyManager.getGroupIdLevel1();
+            mTelephonyManager.getLine1Number();
+            mTelephonyManager.getVoiceMailNumber();
+            mTelephonyManager.getVisualVoicemailPackageName();
+            mTelephonyManager.getVoiceMailAlphaTag();
+            mTelephonyManager.getForbiddenPlmns();
+            mTelephonyManager.getServiceState();
+        } catch (SecurityException e) {
+            failMessage();
+        }
+    }
+
+    // A table mapping from a number to a hex character for fast encoding hex strings.
+    private static final char[] HEX_CHARS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    private static String bytesToHexString(byte[] bytes) {
+        StringBuilder ret = new StringBuilder(2 * bytes.length);
+        for (int i = 0 ; i < bytes.length ; i++) {
+            int b;
+            b = 0x0f & (bytes[i] >> 4);
+            ret.append(HEX_CHARS[b]);
+            b = 0x0f & bytes[i];
+            ret.append(HEX_CHARS[b]);
+        }
+        return ret.toString();
+    }
+}
diff --git a/tests/tests/colormode/AndroidTest.xml b/tests/tests/colormode/AndroidTest.xml
index eb491c2..4316cc6 100644
--- a/tests/tests/colormode/AndroidTest.xml
+++ b/tests/tests/colormode/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Color Mode test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="graphics" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/contactsproviderwipe/AndroidTest.xml b/tests/tests/contactsproviderwipe/AndroidTest.xml
index 3a03a1e..7bbb519 100644
--- a/tests/tests/contactsproviderwipe/AndroidTest.xml
+++ b/tests/tests/contactsproviderwipe/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Provider test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- This is a test for apps with READ/WRITE_CONTACTS, which instant apps don't have -->
+    <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">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index 5d59af7..5a26d11 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -39,7 +39,8 @@
     services.core \
     junit \
     truth-prebuilt \
-    accountaccesslib
+    accountaccesslib \
+	ub-uiautomator
 
 LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
 
@@ -63,7 +64,8 @@
 	-c tlh \
 	-c xx,xx-rYY
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, BinderPermissionTestService)
 LOCAL_MULTILIB := both
 LOCAL_PACKAGE_NAME := CtsContentTestCases
 LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 20baa2d..79f0cb0 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.content.cts.permission.TEST_GRANTED" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 
     <!-- Used for PackageManager test, don't delete this INTERNET permission -->
     <uses-permission android:name="android.permission.INTERNET" />
@@ -144,9 +145,9 @@
 
         <uses-library android:name="android.test.runner" />
 
-        <service android:name="android.content.cts.MockContextWrapperService" />
-        <activity android:name=".content.ContextWrapperCtsActivity"
-            android:label="ContextWrapperCtsActivity">
+        <service android:name="android.content.cts.MockContextService" />
+        <activity android:name=".content.ContextCtsActivity"
+            android:label="ContextCtsActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
@@ -167,7 +168,7 @@
             <intent-filter android:priority="1">
                 <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_MOCKTEST" />
                 <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
-                <action android:name="android.content.cts.ContextWrapperTest.BROADCAST_TESTORDER" />
+                <action android:name="android.content.cts.ContextTest.BROADCAST_TESTORDER" />
             </intent-filter>
         </receiver>
 
@@ -189,7 +190,8 @@
 
         <!--Test for PackageManager-->
         <activity android:name="android.content.pm.cts.TestPmActivity"
-                android:icon="@drawable/start">
+                android:icon="@drawable/start"
+                android:launchMode="singleTop">
             <intent-filter>
                 <action android:name="android.intent.action.PMTEST" />
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
@@ -203,7 +205,8 @@
             </intent-filter>
         </activity>
         <!--Test for PackageManager-->
-        <service android:name="android.content.pm.cts.TestPmService">
+        <service android:name="android.content.pm.cts.TestPmService"
+            android:permission="android.content.cts.CALL_ABROAD_PERMISSION">
             <intent-filter>
                 <action android:name="android.content.pm.cts.activity.PMTEST_SERVICE" />
             </intent-filter>
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 4b22631..a975b5b 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -20,6 +20,8 @@
     <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"/>
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
     </target_preparer>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
@@ -32,6 +34,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsContentTestCases.apk" />
         <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
+        <option name="test-file-name" value="CtsBinderPermissionTestService.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/BinderPermissionTestService/Android.mk b/tests/tests/content/BinderPermissionTestService/Android.mk
new file mode 100644
index 0000000..697d8f6
--- /dev/null
+++ b/tests/tests/content/BinderPermissionTestService/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, aidl)
+
+LOCAL_PACKAGE_NAME := CtsBinderPermissionTestService
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/content/BinderPermissionTestService/AndroidManifest.xml b/tests/tests/content/BinderPermissionTestService/AndroidManifest.xml
new file mode 100644
index 0000000..c0e4774
--- /dev/null
+++ b/tests/tests/content/BinderPermissionTestService/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts">
+    <application>
+        <service
+            android:name=".BinderPermissionTestService"
+            android:exported="true">
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/content/BinderPermissionTestService/README.txt b/tests/tests/content/BinderPermissionTestService/README.txt
new file mode 100644
index 0000000..1b7c2bd
--- /dev/null
+++ b/tests/tests/content/BinderPermissionTestService/README.txt
@@ -0,0 +1,7 @@
+A test project that publishes a Binder service. The methods of this service
+check if their caller has certain permissions. This service is used by
+Context tests to verify that methods like enforceCallingPermission()
+work correctly.
+
+This service has to be in a separate package so the permissions of the
+caller (the test) and the callee (this service) are different.
diff --git a/tests/tests/content/BinderPermissionTestService/aidl/com/android/cts/IBinderPermissionTestService.aidl b/tests/tests/content/BinderPermissionTestService/aidl/com/android/cts/IBinderPermissionTestService.aidl
new file mode 100644
index 0000000..26e6326
--- /dev/null
+++ b/tests/tests/content/BinderPermissionTestService/aidl/com/android/cts/IBinderPermissionTestService.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+interface IBinderPermissionTestService {
+    // Methods that, when called, invoke one of the permission check methods
+    // to check the caller's permissions.
+    void doEnforceCallingPermission(String permission);
+    int doCheckCallingPermission(String permission);
+    void doEnforceCallingOrSelfPermission(String permission);
+    int doCheckCallingOrSelfPermission(String permission);
+}
diff --git a/tests/tests/content/BinderPermissionTestService/src/com/android/cts/BinderPermissionTestService.java b/tests/tests/content/BinderPermissionTestService/src/com/android/cts/BinderPermissionTestService.java
new file mode 100644
index 0000000..955fb30
--- /dev/null
+++ b/tests/tests/content/BinderPermissionTestService/src/com/android/cts/BinderPermissionTestService.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class BinderPermissionTestService extends Service {
+
+    private static String TEST_NOT_ALLOWED_MESSAGE = "Test: you're not allowed to do this.";
+
+    private final IBinder mBinder = new IBinderPermissionTestService.Stub() {
+        @Override
+        public void doEnforceCallingPermission(String permission) {
+            enforceCallingPermission(permission, TEST_NOT_ALLOWED_MESSAGE);
+        }
+
+        @Override
+        public int doCheckCallingPermission(String permission) {
+            return checkCallingPermission(permission);
+        }
+
+        @Override
+        public void doEnforceCallingOrSelfPermission(String permission) {
+            enforceCallingOrSelfPermission(permission, TEST_NOT_ALLOWED_MESSAGE);
+        }
+
+        @Override
+        public int doCheckCallingOrSelfPermission(String permission) {
+            return checkCallingOrSelfPermission(permission);
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.mk b/tests/tests/content/SyncAccountAccessStubs/Android.mk
index 9f015a3..d176ce7 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.mk
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.mk
@@ -20,10 +20,13 @@
 
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsSyncAccountAccessStubs
-LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_SDK_VERSION := current
 
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
diff --git a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
index 8a21e0d..c97e115 100644
--- a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
+++ b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
@@ -16,12 +16,12 @@
 
 package com.android.cts.stub;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 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 StubProvider extends ContentProvider {
     @Override
diff --git a/tests/tests/content/res/layout/complex_color_drawable_attr_layout.xml b/tests/tests/content/res/layout/complex_color_drawable_attr_layout.xml
new file mode 100644
index 0000000..4d2042a
--- /dev/null
+++ b/tests/tests/content/res/layout/complex_color_drawable_attr_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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="wrap_content"
+              android:background="?android:attr/textColorSecondary">
+
+</LinearLayout>
diff --git a/tests/tests/content/res/values/styles.xml b/tests/tests/content/res/values/styles.xml
index 91191cf..7da5c5e 100644
--- a/tests/tests/content/res/values/styles.xml
+++ b/tests/tests/content/res/values/styles.xml
@@ -39,6 +39,21 @@
         <item name="typeUndefined">@null</item>
     </style>
 
+    <style name="StyleA" parent="StyleB">
+        <item name="type1">true</item>
+    </style>
+    <style name="StyleB" parent="StyleC">
+        <item name="type1">false</item>
+        <item name="type2">false</item>
+        <item name="type4">@color/testcolor1</item>
+    </style>
+
+    <style name="StyleC">
+        <item name="type1">false</item>
+        <item name="type2">true</item>
+        <item name="type3">#ff0000ff</item>
+    </style>
+
     <style name="TextViewWithoutColorAndAppearance">
         <item name="android:textSize">18sp</item>
     </style>
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 4723524..969ee21 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.storage.StorageManager;
 import android.provider.AlarmClock;
 import android.provider.MediaStore;
@@ -40,6 +41,8 @@
 public class AvailableIntentsTest extends AndroidTestCase {
     private static final String NORMAL_URL = "http://www.google.com/";
     private static final String SECURE_URL = "https://www.google.com/";
+    private static final String QRCODE= "DPP:I:SN=4774LH2b4044;M:010203040506;K:MDkwEwYHKoZIzj" +
+            "0CAQYIKoZIzj0DAQcDIgADURzxmttZoIRIPWGoQMV00XHWCAQIhXruVWOz0NjlkIA=;;";
 
     /**
      * Assert target intent can be handled by at least one Activity.
@@ -365,4 +368,39 @@
             assertCanBeHandled(new Intent("android.settings.LOCATION_SCANNING_SETTINGS"));
         }
     }
+
+    public void testSettingsSearchIntent() {
+        if (FeatureUtil.isTV() || FeatureUtil.isAutomotive() || FeatureUtil.isWatch()) {
+            return;
+        }
+        assertCanBeHandled(new Intent(Settings.ACTION_APP_SEARCH_SETTINGS));
+    }
+
+    public void testChangeDefaultDialer() {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            assertCanBeHandled(new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER));
+        }
+    }
+
+    public void testTapAnPaySettings() {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+            assertCanBeHandled(new Intent(Settings.ACTION_NFC_PAYMENT_SETTINGS));
+        }
+    }
+
+    public void testPowerUsageSummarySettings() {
+        assertCanBeHandled(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
+    }
+
+    public void testEasyConnectIntent() {
+        WifiManager manager = mContext.getSystemService(WifiManager.class);
+
+        if (manager.isEasyConnectSupported()) {
+            Intent intent = new Intent(Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE);
+            intent.putExtra(Settings.EXTRA_QR_CODE, QRCODE);
+            assertCanBeHandled(intent);
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
index bd9756e..bbfbd66 100644
--- a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -18,14 +18,20 @@
 
 import static org.junit.Assert.fail;
 
+import android.app.Activity;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.Intent;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,6 +44,17 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ClipDescriptionTest {
+    private UiDevice mUiDevice;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUiDevice.wakeUp();
+        launchActivity(MockActivity.class);
+    }
+
     @UiThreadTest
     @Test
     public void testGetTimestamp() {
@@ -73,4 +90,12 @@
             return Long.toString(millis);
         }
     }
-}
\ No newline at end of file
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(mContext.getPackageName(), clazz.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
index c5a7d7d..92fdd5f 100644
--- a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.app.Activity;
 import android.content.ClipData;
 import android.content.ClipData.Item;
 import android.content.ClipDescription;
@@ -33,6 +34,9 @@
 import android.net.Uri;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,11 +49,15 @@
 public class ClipboardManagerTest {
     private Context mContext;
     private ClipboardManager mClipboardManager;
+    private UiDevice mUiDevice;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mClipboardManager = mContext.getSystemService(ClipboardManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUiDevice.wakeUp();
+        launchActivity(MockActivity.class);
     }
 
     @Test
@@ -232,6 +240,53 @@
         assertNull(mClipboardManager.getPrimaryClipDescription());
     }
 
+    @Test
+    public void testPrimaryClipNotAvailableWithoutFocus() throws Exception {
+        ClipData textData = ClipData.newPlainText("TextLabel", "Text1");
+        assertSetPrimaryClip(textData, "TextLabel",
+                new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN},
+                new ExpectedClipItem("Text1", null, null));
+
+        // Press the home button to unfocus the app.
+        mUiDevice.pressHome();
+        mUiDevice.wait(Until.gone(By.clazz(MockActivity.class)), 5000);
+
+        // We should see an empty clipboard now.
+        assertFalse(mClipboardManager.hasPrimaryClip());
+        assertFalse(mClipboardManager.hasText());
+        assertNull(mClipboardManager.getPrimaryClip());
+        assertNull(mClipboardManager.getPrimaryClipDescription());
+
+        // We should be able to set the clipboard but not see the contents.
+        mClipboardManager.setPrimaryClip(ClipData.newPlainText("TextLabel", "Text2"));
+        assertFalse(mClipboardManager.hasPrimaryClip());
+        assertFalse(mClipboardManager.hasText());
+        assertNull(mClipboardManager.getPrimaryClip());
+        assertNull(mClipboardManager.getPrimaryClipDescription());
+
+        // Launch an activity to get back in focus.
+        launchActivity(MockActivity.class);
+
+
+        // Verify clipboard access is restored.
+        assertNotNull(mClipboardManager.getPrimaryClip());
+        assertNotNull(mClipboardManager.getPrimaryClipDescription());
+
+        // Verify we were unable to change the clipboard while out of focus.
+        assertClipData(mClipboardManager.getPrimaryClip(),
+                "TextLabel",
+                new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN},
+                new ExpectedClipItem("Text2", null, null));
+    }
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(mContext.getPackageName(), clazz.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+    }
+
     private class ExpectedClipItem {
         CharSequence mText;
         Intent mIntent;
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
index 4d5378c..5cfd532 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
@@ -16,7 +16,9 @@
 
 package android.content.cts;
 
+import android.database.Cursor;
 import android.database.CursorWindowAllocationException;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.platform.test.annotations.SecurityTest;
 import android.test.AndroidTestCase;
@@ -30,13 +32,33 @@
     private static final String TAG = "ContentProviderCursorWindowTest";
 
     public void testQuery() {
+        // First check if the system has a patch for enforcing protected Parcel data
+        Cursor cursor;
         try {
-            getContext().getContentResolver().query(
+            cursor = getContext().getContentResolver().query(
                     Uri.parse("content://cursorwindow.provider/hello"),
                     null, null, null, null);
-            fail("Reading from malformed Parcel should fail due to invalid offset used");
         } catch (CursorWindowAllocationException expected) {
             Log.i(TAG, "Expected exception: " + expected);
+            return;
+        }
+
+        // If the system has no patch for protected Parcel data,
+        // it should still fail while reading from the cursor
+        try {
+            cursor.moveToFirst();
+
+            int type = cursor.getType(0);
+            if (type != Cursor.FIELD_TYPE_BLOB) {
+                fail("Unexpected type " + type);
+            }
+            byte[] blob = cursor.getBlob(0);
+            Log.i(TAG,  "Blob length " + blob.length);
+            fail("getBlob should fail due to invalid offset used in the field slot");
+        } catch (SQLiteException expected) {
+            Log.i(TAG, "Expected exception: " + expected);
+        } finally {
+            cursor.close();
         }
     }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index d1c03d2..824d0fe 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -30,6 +30,7 @@
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -42,6 +43,8 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class ContentResolverTest extends AndroidTestCase {
     private final static String COLUMN_ID_NAME = "_id";
@@ -62,10 +65,10 @@
     private static final String REMOTE_AUTHORITY = "remotectstest";
     private static final Uri REMOTE_TABLE1_URI = Uri.parse("content://"
                 + REMOTE_AUTHORITY + "/testtable1/");
-    private static final Uri REMOTE_SELF_URI = Uri.parse("content://"
-                + REMOTE_AUTHORITY + "/self/");
     private static final Uri REMOTE_CRASH_URI = Uri.parse("content://"
             + REMOTE_AUTHORITY + "/crash/");
+    private static final Uri REMOTE_HANG_URI = Uri.parse("content://"
+            + REMOTE_AUTHORITY + "/hang/");
 
     private static final Account ACCOUNT = new Account("cts", "cts");
 
@@ -112,6 +115,9 @@
 
     @Override
     protected void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+
         mContentResolver.delete(TABLE1_URI, null, null);
         if ( null != mCursor && !mCursor.isClosed() ) {
             mCursor.close();
@@ -138,7 +144,7 @@
         // so the act of killing it doesn't kill our own process.
         client.release();
         try {
-            client.delete(REMOTE_SELF_URI, null, null);
+            client.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         // Now make sure the thing is actually gone.
@@ -189,7 +195,7 @@
         try {
             Log.i("ContentResolverTest",
                     "Killing remote client -- if test process goes away, that is why!");
-            uClient.delete(REMOTE_SELF_URI, null, null);
+            uClient.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         // Make sure the remote client is actually gone.
@@ -227,7 +233,7 @@
         try {
             Log.i("ContentResolverTest",
                     "Killing remote client -- if test process goes away, that is why!");
-            uClient.delete(REMOTE_SELF_URI, null, null);
+            uClient.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         // Make sure the remote client is actually gone.
@@ -276,7 +282,7 @@
         try {
             Log.i("ContentResolverTest",
                     "Killing remote client -- if test process goes away, that is why!");
-            client.delete(REMOTE_SELF_URI, null, null);
+            client.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         // Make sure the remote client is actually gone.
@@ -708,7 +714,7 @@
         try {
             Log.i("ContentResolverTest",
                     "Killing remote client -- if test process goes away, that is why!");
-            uClient.delete(REMOTE_SELF_URI, null, null);
+            uClient.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         uClient.release();
@@ -740,7 +746,7 @@
         try {
             Log.i("ContentResolverTest",
                     "Killing remote client -- if test process goes away, that is why!");
-            uClient.delete(REMOTE_SELF_URI, null, null);
+            uClient.delete(REMOTE_CRASH_URI, null, null);
         } catch (RemoteException e) {
         }
         uClient.release();
@@ -1232,6 +1238,30 @@
         }
     }
 
+    public void testHangRecover() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(android.Manifest.permission.REMOVE_TASKS);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        new Thread(() -> {
+            final ContentProviderClient client = mContentResolver
+                    .acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
+            client.setDetectNotResponding(2_000);
+            try {
+                client.query(REMOTE_HANG_URI, null, null, null);
+                fail("Funky, we somehow returned?");
+            } catch (RemoteException e) {
+                latch.countDown();
+            }
+        }).start();
+
+        // The remote process should have been killed after the ANR was detected
+        // above, causing our pending call to return and release our latch above
+        // within 10 seconds; if our Binder thread hasn't been freed, then we
+        // fail with a timeout.
+        latch.await(10, TimeUnit.SECONDS);
+    }
+
     private class MockContentObserver extends ContentObserver {
         private boolean mHadOnChanged = false;
 
diff --git a/tests/tests/content/src/android/content/cts/ContentUrisTest.java b/tests/tests/content/src/android/content/cts/ContentUrisTest.java
index 7bd0084..65ae443 100644
--- a/tests/tests/content/src/android/content/cts/ContentUrisTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentUrisTest.java
@@ -110,4 +110,31 @@
             // expected, test success.
         }
     }
+
+    public void testRemoveId() {
+        assertEquals(Uri.parse("content://auth"),
+                ContentUris.removeId(Uri.parse("content://auth/12")));
+        assertEquals(Uri.parse("content://auth/path"),
+                ContentUris.removeId(Uri.parse("content://auth/path/12")));
+        assertEquals(Uri.parse("content://auth/path/path"),
+                ContentUris.removeId(Uri.parse("content://auth/path/path/12")));
+    }
+
+    public void testRemoveId_MissingId() {
+        try {
+            ContentUris.removeId(Uri.parse("content://auth/"));
+            fail("There should be a IllegalArgumentException thrown out.");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            ContentUris.removeId(Uri.parse("content://auth/path/"));
+            fail("There should be a IllegalArgumentException thrown out.");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            ContentUris.removeId(Uri.parse("content://auth/path/path/"));
+            fail("There should be a IllegalArgumentException thrown out.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContentValuesTest.java b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
index 24a6e36..4159817 100644
--- a/tests/tests/content/src/android/content/cts/ContentValuesTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
@@ -16,24 +16,36 @@
 
 package android.content.cts;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.content.ContentValues;
 import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 
-public class ContentValuesTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentValuesTest {
     ContentValues mContentValues;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mContentValues = new ContentValues();
     }
 
+    @Test
     public void testConstructor() {
         new ContentValues();
         new ContentValues(5);
@@ -54,6 +66,7 @@
         }
     }
 
+    @Test
     public void testValueSet() {
         Set<Map.Entry<String, Object>> map;
         assertNotNull(map = mContentValues.valueSet());
@@ -67,6 +80,7 @@
         assertEquals(2, map.size());
     }
 
+    @Test
     public void testPutNull() {
         mContentValues.putNull("key");
         assertNull(mContentValues.get("key"));
@@ -81,6 +95,7 @@
         mContentValues.putNull(null);
     }
 
+    @Test
     public void testGetAsLong() {
         Long expected = 10L;
         mContentValues.put("Long", expected);
@@ -94,6 +109,7 @@
         assertNull(mContentValues.getAsLong(null));
     }
 
+    @Test
     public void testGetAsByte() {
         Byte expected = 'a';
         mContentValues.put("Byte", expected);
@@ -107,6 +123,7 @@
         assertNull(mContentValues.getAsByte(null));
     }
 
+    @Test
     public void testGetAsInteger() {
         Integer expected = 20;
         mContentValues.put("Integer", expected);
@@ -120,6 +137,7 @@
         assertNull(mContentValues.getAsInteger(null));
     }
 
+    @Test
     public void testSize() {
         assertEquals(0, mContentValues.size());
 
@@ -135,6 +153,7 @@
         assertEquals(0, mContentValues.size());
     }
 
+    @Test
     public void testGetAsShort() {
         Short expected = 20;
         mContentValues.put("Short", expected);
@@ -148,6 +167,7 @@
         assertNull(mContentValues.getAsShort(null));
     }
 
+    @Test
     public void testHashCode() {
         assertEquals(0, mContentValues.hashCode());
 
@@ -166,6 +186,7 @@
         assertEquals(0, mContentValues.hashCode());
     }
 
+    @Test
     public void testGetAsFloat() {
         Float expected = 1.0F;
         mContentValues.put("Float", expected);
@@ -179,6 +200,7 @@
         assertNull(mContentValues.getAsFloat(null));
     }
 
+    @Test
     public void testGetAsBoolean() {
         mContentValues.put("Boolean", true);
         assertTrue(mContentValues.getAsBoolean("Boolean"));
@@ -190,6 +212,7 @@
         assertNull(mContentValues.getAsBoolean(null));
     }
 
+    @Test
     public void testToString() {
         assertNotNull(mContentValues.toString());
 
@@ -198,6 +221,7 @@
         assertTrue(mContentValues.toString().length() > 0);
     }
 
+    @Test
     public void testGet() {
         Object expected = "android";
         mContentValues.put("Object", "android");
@@ -211,6 +235,7 @@
         assertNull(mContentValues.get(null));
     }
 
+    @Test
     public void testEquals() {
         mContentValues.put("Boolean", false);
         mContentValues.put("String", "string");
@@ -222,6 +247,7 @@
         assertTrue(mContentValues.equals(cv));
     }
 
+    @Test
     public void testEqualsFailure() {
         // the target object is not an instance of ContentValues.
         assertFalse(mContentValues.equals(new String()));
@@ -237,6 +263,7 @@
         assertFalse(mContentValues.equals(cv));
     }
 
+    @Test
     public void testGetAsDouble() {
         Double expected = 10.2;
         mContentValues.put("Double", expected);
@@ -250,6 +277,7 @@
         assertNull(mContentValues.getAsDouble(null));
     }
 
+    @Test
     public void testPutString() {
         String expected = "cts";
         mContentValues.put("String", expected);
@@ -263,6 +291,7 @@
         mContentValues.put(null, (String)null);
     }
 
+    @Test
     public void testPutByte() {
         Byte expected = 'a';
         mContentValues.put("Byte", expected);
@@ -276,6 +305,7 @@
         mContentValues.put(null, (Byte)null);
     }
 
+    @Test
     public void testPutShort() {
         Short expected = 20;
         mContentValues.put("Short", expected);
@@ -289,6 +319,7 @@
         mContentValues.put(null, (Short)null);
     }
 
+    @Test
     public void testPutInteger() {
         Integer expected = 20;
         mContentValues.put("Integer", expected);
@@ -302,6 +333,7 @@
         mContentValues.put(null, (Integer)null);
     }
 
+    @Test
     public void testPutLong() {
         Long expected = 10L;
         mContentValues.put("Long", expected);
@@ -315,6 +347,7 @@
         mContentValues.put(null, (Long)null);
     }
 
+    @Test
     public void testPutFloat() {
         Float expected = 1.0F;
         mContentValues.put("Float", expected);
@@ -328,6 +361,7 @@
         mContentValues.put(null, (Float)null);
     }
 
+    @Test
     public void testPutDouble() {
         Double expected = 10.2;
         mContentValues.put("Double", expected);
@@ -341,6 +375,7 @@
         mContentValues.put(null, (Double)null);
     }
 
+    @Test
     public void testPutBoolean() {
         // set the expected value
         mContentValues.put("Boolean", true);
@@ -353,6 +388,7 @@
         mContentValues.put(null, (Boolean)null);
     }
 
+    @Test
     public void testPutByteArray() {
         byte[] expected = new byte[] {'1', '2', '3', '4'};
         mContentValues.put("byte[]", expected);
@@ -362,6 +398,7 @@
         mContentValues.put(null, (byte[])null);
     }
 
+    @Test
     public void testContainsKey() {
         mContentValues.put("Double", 10.2);
         mContentValues.put("Float", 1.0F);
@@ -376,6 +413,7 @@
         assertFalse(mContentValues.containsKey(null));
     }
 
+    @Test
     public void testClear() {
         assertEquals(0, mContentValues.size());
 
@@ -387,6 +425,7 @@
         assertEquals(0, mContentValues.size());
     }
 
+    @Test
     @SuppressWarnings("deprecation")
     public void testAccessStringArrayList() {
         // set the expected value
@@ -402,6 +441,7 @@
         assertNull(mContentValues.getStringArrayList(null));
     }
 
+    @Test
     public void testRemove() {
         assertEquals(0, mContentValues.size());
 
@@ -427,6 +467,7 @@
         mContentValues.remove(null);
     }
 
+    @Test
     public void testGetAsString() {
         String expected = "cts";
         mContentValues.put("String", expected);
@@ -440,6 +481,7 @@
         assertNull(mContentValues.getAsString(null));
     }
 
+    @Test
     public void testGetAsByteArray() {
         byte[] expected = new byte[] {'1', '2', '3', '4'};
         mContentValues.put("byte[]", expected);
@@ -449,26 +491,26 @@
         assertNull(mContentValues.getAsByteArray(null));
     }
 
+    @Test
     @SuppressWarnings({ "unchecked" })
     public void testWriteToParcel() {
+        final ContentValues before = new ContentValues();
+        before.put("Integer", -110);
+        before.put("String", "cts");
+        before.put("Boolean", false);
+
         Parcel p = Parcel.obtain();
-
-        mContentValues.put("Integer", -110);
-        mContentValues.put("String", "cts");
-        mContentValues.put("Boolean", false);
-
-        mContentValues.writeToParcel(p, 0);
-
+        before.writeToParcel(p, 0);
         p.setDataPosition(0);
-        HashMap<String, Object> values = p.readHashMap(ClassLoader.getSystemClassLoader());
-        assertNotNull(values);
-        assertEquals(3, values.size());
 
-        assertEquals(-110, values.get("Integer"));
-        assertEquals("cts", values.get("String"));
-        assertEquals(false, values.get("Boolean"));
+        final ContentValues after = ContentValues.CREATOR.createFromParcel(p);
+        assertEquals(3, after.size());
+        assertEquals(-110, after.get("Integer"));
+        assertEquals("cts", after.get("String"));
+        assertEquals(false, after.get("Boolean"));
     }
 
+    @Test
     public void testWriteToParcelFailure() {
         try {
             mContentValues.writeToParcel(null, -1);
@@ -478,10 +520,12 @@
         }
     }
 
+    @Test
     public void testDescribeContents() {
         assertEquals(0, mContentValues.describeContents());
     }
 
+    @Test
     public void testPutAll() {
         assertEquals(0, mContentValues.size());
 
@@ -497,6 +541,7 @@
         assertEquals(3, mContentValues.size());
     }
 
+    @Test
     public void testPutAllFailure() {
         try {
             mContentValues.putAll(null);
diff --git a/tests/tests/content/src/android/content/cts/ContextCtsActivity.java b/tests/tests/content/src/android/content/cts/ContextCtsActivity.java
new file mode 100644
index 0000000..a7ea89a
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContextCtsActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.content.cts;
+
+// Need the following import to get access to the app resources, since this
+// class is in a sub-package.
+import android.app.Activity;
+import android.os.Bundle;
+
+import android.content.cts.R;
+
+public class ContextCtsActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.local_sample);
+    }
+}
+
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index f84c771..f7dd6cb 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,39 +16,132 @@
 
 package android.content.cts;
 
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQuery;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
 import android.view.WindowManager;
 
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.IBinderPermissionTestService;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 public class ContextTest extends AndroidTestCase {
     private static final String TAG = "ContextTest";
+    private static final String ACTUAL_RESULT = "ResultSetByReceiver";
 
-    private Context mContext;
+    private static final String INTIAL_RESULT = "IntialResult";
+
+    private static final String VALUE_ADDED = "ValueAdded";
+    private static final String KEY_ADDED = "AddedByReceiver";
+
+    private static final String VALUE_REMOVED = "ValueWillBeRemove";
+    private static final String KEY_REMOVED = "ToBeRemoved";
+
+    private static final String VALUE_KEPT = "ValueKept";
+    private static final String KEY_KEPT = "ToBeKept";
+
+    private static final String MOCK_STICKY_ACTION = "android.content.cts.ContextTest."
+            + "STICKY_BROADCAST_RESULT";
+
+    private static final String ACTION_BROADCAST_TESTORDER =
+            "android.content.cts.ContextTest.BROADCAST_TESTORDER";
+    private final static String MOCK_ACTION1 = ACTION_BROADCAST_TESTORDER + "1";
+    private final static String MOCK_ACTION2 = ACTION_BROADCAST_TESTORDER + "2";
+
+    // Note: keep these constants in sync with the permissions used by BinderPermissionTestService.
+    //
+    // A permission that's granted to this test package.
+    public static final String GRANTED_PERMISSION = "android.permission.USE_CREDENTIALS";
+    // A permission that's not granted to this test package.
+    public static final String NOT_GRANTED_PERMISSION = "android.permission.HARDWARE_TEST";
+
+    private static final int BROADCAST_TIMEOUT = 10000;
+    private static final int ROOT_UID = 0;
+
+    private Object mLockObj;
+
+    private ArrayList<BroadcastReceiver> mRegisteredReceiverList;
+
+    private boolean mWallpaperChanged;
+    private BitmapDrawable mOriginalWallpaper;
+    private volatile IBinderPermissionTestService mBinderPermissionTestService;
+    private ServiceConnection mBinderPermissionTestConnection;
+
+    protected Context mContext;
+
+    /**
+     * Returns the Context object that's being tested.
+     */
+    protected Context getContextUnderTest() {
+        return getContext();
+    }
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mContext = getContext();
+        mContext = getContextUnderTest();
         mContext.setTheme(R.style.Test_Theme);
+
+        mLockObj = new Object();
+
+        mRegisteredReceiverList = new ArrayList<BroadcastReceiver>();
+
+        mOriginalWallpaper = (BitmapDrawable) mContext.getWallpaper();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mWallpaperChanged) {
+            mContext.setWallpaper(mOriginalWallpaper.getBitmap());
+        }
+
+        for (BroadcastReceiver receiver : mRegisteredReceiverList) {
+            mContext.unregisterReceiver(receiver);
+        }
+
+        super.tearDown();
     }
 
     public void testGetString() {
@@ -354,6 +447,10 @@
 
     private void assertValidFile(File file) throws Exception {
         Log.d(TAG, "Checking " + file);
+        if (file.exists()) {
+            assertTrue("File already exists and couldn't be deleted before test: " + file,
+                    file.delete());
+        }
         assertTrue("Failed to create " + file, file.createNewFile());
         assertTrue("Doesn't exist after create " + file, file.exists());
         assertTrue("Failed to delete after create " + file, file.delete());
@@ -382,7 +479,7 @@
     }
 
     private AttributeSet getAttributeSet(int resourceId) {
-        final XmlResourceParser parser = getContext().getResources().getXml(
+        final XmlResourceParser parser = mContext.getResources().getXml(
                 resourceId);
 
         try {
@@ -397,4 +494,886 @@
         assertNotNull(attr);
         return attr;
     }
+
+    private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        mContext.registerReceiver(receiver, filter);
+
+        mRegisteredReceiverList.add(receiver);
+    }
+
+    public void testSendOrderedBroadcast1() throws InterruptedException {
+        final HighPriorityBroadcastReceiver highPriorityReceiver =
+                new HighPriorityBroadcastReceiver();
+        final LowPriorityBroadcastReceiver lowPriorityReceiver =
+                new LowPriorityBroadcastReceiver();
+
+        final IntentFilter filterHighPriority = new IntentFilter(ResultReceiver.MOCK_ACTION);
+        filterHighPriority.setPriority(1);
+        final IntentFilter filterLowPriority = new IntentFilter(ResultReceiver.MOCK_ACTION);
+        registerBroadcastReceiver(highPriorityReceiver, filterHighPriority);
+        registerBroadcastReceiver(lowPriorityReceiver, filterLowPriority);
+
+        final Intent broadcastIntent = new Intent(ResultReceiver.MOCK_ACTION);
+        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendOrderedBroadcast(broadcastIntent, null);
+        new PollingCheck(BROADCAST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return highPriorityReceiver.hasReceivedBroadCast()
+                        && !lowPriorityReceiver.hasReceivedBroadCast();
+            }
+        }.run();
+
+        synchronized (highPriorityReceiver) {
+            highPriorityReceiver.notify();
+        }
+
+        new PollingCheck(BROADCAST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return highPriorityReceiver.hasReceivedBroadCast()
+                        && lowPriorityReceiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    public void testSendOrderedBroadcast2() throws InterruptedException {
+        final TestBroadcastReceiver broadcastReceiver = new TestBroadcastReceiver();
+        broadcastReceiver.mIsOrderedBroadcasts = true;
+
+        Bundle bundle = new Bundle();
+        bundle.putString(KEY_KEPT, VALUE_KEPT);
+        bundle.putString(KEY_REMOVED, VALUE_REMOVED);
+        Intent intent = new Intent(ResultReceiver.MOCK_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendOrderedBroadcast(intent, null, broadcastReceiver, null, 1,
+                INTIAL_RESULT, bundle);
+
+        synchronized (mLockObj) {
+            try {
+                mLockObj.wait(BROADCAST_TIMEOUT);
+            } catch (InterruptedException e) {
+                fail("unexpected InterruptedException.");
+            }
+        }
+
+        assertTrue("Receiver didn't make any response.", broadcastReceiver.hadReceivedBroadCast());
+        assertEquals("Incorrect code: " + broadcastReceiver.getResultCode(), 3,
+                broadcastReceiver.getResultCode());
+        assertEquals(ACTUAL_RESULT, broadcastReceiver.getResultData());
+        Bundle resultExtras = broadcastReceiver.getResultExtras(false);
+        assertEquals(VALUE_ADDED, resultExtras.getString(KEY_ADDED));
+        assertEquals(VALUE_KEPT, resultExtras.getString(KEY_KEPT));
+        assertNull(resultExtras.getString(KEY_REMOVED));
+    }
+
+    public void testRegisterReceiver1() throws InterruptedException {
+        final FilteredReceiver broadcastReceiver = new FilteredReceiver();
+        final IntentFilter filter = new IntentFilter(MOCK_ACTION1);
+
+        // Test registerReceiver
+        mContext.registerReceiver(broadcastReceiver, filter);
+
+        // 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());
+        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
+
+        mContext.unregisterReceiver(broadcastReceiver);
+
+        // Test unregisterReceiver
+        FilteredReceiver broadcastReceiver2 = new FilteredReceiver();
+        mContext.registerReceiver(broadcastReceiver2, filter);
+        mContext.unregisterReceiver(broadcastReceiver2);
+
+        // Test unwanted intent(action = MOCK_ACTION2)
+        broadcastReceiver2.reset();
+        waitForFilteredIntent(mContext, MOCK_ACTION2);
+        assertFalse(broadcastReceiver2.hadReceivedBroadCast1());
+        assertFalse(broadcastReceiver2.hadReceivedBroadCast2());
+
+        // Send wanted intent(action = MOCK_ACTION1), but the receiver is unregistered.
+        broadcastReceiver2.reset();
+        waitForFilteredIntent(mContext, MOCK_ACTION1);
+        assertFalse(broadcastReceiver2.hadReceivedBroadCast1());
+        assertFalse(broadcastReceiver2.hadReceivedBroadCast2());
+    }
+
+    public void testRegisterReceiver2() throws InterruptedException {
+        FilteredReceiver broadcastReceiver = new FilteredReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(MOCK_ACTION1);
+
+        // Test registerReceiver
+        mContext.registerReceiver(broadcastReceiver, filter, null, null);
+
+        // 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());
+        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);
+        // Test getWallpaper
+        Drawable testDrawable = mContext.getWallpaper();
+        // Test peekWallpaper
+        Drawable testDrawable2 = mContext.peekWallpaper();
+
+        mContext.setWallpaper(bitmap);
+        mWallpaperChanged = true;
+        synchronized(this) {
+            wait(500);
+        }
+
+        assertNotSame(testDrawable, mContext.peekWallpaper());
+        assertNotNull(mContext.getWallpaper());
+        assertNotSame(testDrawable2, mContext.peekWallpaper());
+        assertNotNull(mContext.peekWallpaper());
+
+        // set Wallpaper by context#setWallpaper(InputStream)
+        mContext.clearWallpaper();
+
+        testDrawable = mContext.getWallpaper();
+        InputStream stream = mContext.getResources().openRawResource(R.drawable.scenery);
+
+        mContext.setWallpaper(stream);
+        synchronized (this) {
+            wait(1000);
+        }
+
+        assertNotSame(testDrawable, mContext.peekWallpaper());
+    }
+
+    public void testAccessDatabase() {
+        String DATABASE_NAME = "databasetest";
+        String DATABASE_NAME1 = DATABASE_NAME + "1";
+        String DATABASE_NAME2 = DATABASE_NAME + "2";
+        SQLiteDatabase mDatabase;
+        File mDatabaseFile;
+
+        SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
+            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+                                    String editTable, SQLiteQuery query) {
+                return new android.database.sqlite.SQLiteCursor(db, masterQuery, editTable, query) {
+                    @Override
+                    public boolean requery() {
+                        setSelectionArguments(new String[] { "2" });
+                        return super.requery();
+                    }
+                };
+            }
+        };
+
+        // FIXME: Move cleanup into tearDown()
+        for (String db : mContext.databaseList()) {
+            File f = mContext.getDatabasePath(db);
+            if (f.exists()) {
+                mContext.deleteDatabase(db);
+            }
+        }
+
+        // Test openOrCreateDatabase with null and actual factory
+        mDatabase = mContext.openOrCreateDatabase(DATABASE_NAME1,
+                Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
+        assertNotNull(mDatabase);
+        mDatabase.close();
+        mDatabase = mContext.openOrCreateDatabase(DATABASE_NAME2,
+                Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
+        assertNotNull(mDatabase);
+        mDatabase.close();
+
+        // Test getDatabasePath
+        File actualDBPath = mContext.getDatabasePath(DATABASE_NAME1);
+
+        // Test databaseList()
+        List<String> list = Arrays.asList(mContext.databaseList());
+        assertTrue("1) database list: " + list, list.contains(DATABASE_NAME1));
+        assertTrue("2) database list: " + list, list.contains(DATABASE_NAME2));
+
+        // Test deleteDatabase()
+        for (int i = 1; i < 3; i++) {
+            mDatabaseFile = mContext.getDatabasePath(DATABASE_NAME + i);
+            assertTrue(mDatabaseFile.exists());
+            mContext.deleteDatabase(DATABASE_NAME + i);
+            mDatabaseFile = new File(actualDBPath, DATABASE_NAME + i);
+            assertFalse(mDatabaseFile.exists());
+        }
+    }
+
+    public void testEnforceUriPermission1() {
+        try {
+            Uri uri = Uri.parse("content://ctstest");
+            mContext.enforceUriPermission(uri, Binder.getCallingPid(),
+                    Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                    "enforceUriPermission is not working without possessing an IPC.");
+            fail("enforceUriPermission is not working without possessing an IPC.");
+        } catch (SecurityException e) {
+            // If the function is OK, it should throw a SecurityException here because currently no
+            // IPC is handled by this process.
+        }
+    }
+
+    public void testEnforceUriPermission2() {
+        Uri uri = Uri.parse("content://ctstest");
+        try {
+            mContext.enforceUriPermission(uri, NOT_GRANTED_PERMISSION,
+                    NOT_GRANTED_PERMISSION, Binder.getCallingPid(), Binder.getCallingUid(),
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                    "enforceUriPermission is not working without possessing an IPC.");
+            fail("enforceUriPermission is not working without possessing an IPC.");
+        } catch (SecurityException e) {
+            // If the function is ok, it should throw a SecurityException here because currently no
+            // IPC is handled by this process.
+        }
+    }
+
+    public void testGetPackageResourcePath() {
+        assertNotNull(mContext.getPackageResourcePath());
+    }
+
+    public void testStartActivity() {
+        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.");
+        } catch (ActivityNotFoundException e) {
+            // Because ContextWrapper is a wrapper class, so no need to test
+            // the details of the function's performance. Getting a result
+            // from the wrapped class is enough for testing.
+        }
+    }
+
+    public void testCreatePackageContext() throws PackageManager.NameNotFoundException {
+        Context actualContext = mContext.createPackageContext(getValidPackageName(),
+                Context.CONTEXT_IGNORE_SECURITY);
+
+        assertNotNull(actualContext);
+    }
+
+    public void testCreatePackageContextAsUser() throws Exception {
+        for (UserHandle user : new UserHandle[] {
+                android.os.Process.myUserHandle(),
+                UserHandle.ALL, UserHandle.CURRENT, UserHandle.SYSTEM
+        }) {
+            assertEquals(user, mContext
+                    .createPackageContextAsUser(getValidPackageName(), 0, user).getUser());
+        }
+    }
+
+    /**
+     * Helper method to retrieve a valid application package name to use for tests.
+     */
+    protected String getValidPackageName() {
+        List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackages(
+                PackageManager.GET_ACTIVITIES);
+        assertTrue(packages.size() >= 1);
+        return packages.get(0).packageName;
+    }
+
+    public void testGetMainLooper() {
+        assertNotNull(mContext.getMainLooper());
+    }
+
+    public void testGetApplicationContext() {
+        assertSame(mContext.getApplicationContext(), mContext.getApplicationContext());
+    }
+
+    public void testGetSharedPreferences() {
+        SharedPreferences sp;
+        SharedPreferences localSP;
+
+        sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        String packageName = mContext.getPackageName();
+        localSP = mContext.getSharedPreferences(packageName + "_preferences",
+                Context.MODE_PRIVATE);
+        assertSame(sp, localSP);
+    }
+
+    public void testRevokeUriPermission() {
+        Uri uri = Uri.parse("contents://ctstest");
+        mContext.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    public void testAccessService() throws InterruptedException {
+        MockContextService.reset();
+        bindExpectResult(mContext, new Intent(mContext, MockContextService.class));
+
+        // Check startService
+        assertTrue(MockContextService.hadCalledOnStart());
+        // Check bindService
+        assertTrue(MockContextService.hadCalledOnBind());
+
+        assertTrue(MockContextService.hadCalledOnDestory());
+        // Check unbinService
+        assertTrue(MockContextService.hadCalledOnUnbind());
+    }
+
+    public void testGetPackageCodePath() {
+        assertNotNull(mContext.getPackageCodePath());
+    }
+
+    public void testGetPackageName() {
+        assertEquals("android.content.cts", mContext.getPackageName());
+    }
+
+    public void testGetCacheDir() {
+        assertNotNull(mContext.getCacheDir());
+    }
+
+    public void testGetContentResolver() {
+        assertSame(mContext.getContentResolver(), mContext.getContentResolver());
+    }
+
+    public void testGetFileStreamPath() {
+        String TEST_FILENAME = "TestGetFileStreamPath";
+
+        // Test the path including the input filename
+        String fileStreamPath = mContext.getFileStreamPath(TEST_FILENAME).toString();
+        assertTrue(fileStreamPath.indexOf(TEST_FILENAME) >= 0);
+    }
+
+    public void testGetClassLoader() {
+        assertSame(mContext.getClassLoader(), mContext.getClassLoader());
+    }
+
+    public void testGetWallpaperDesiredMinimumHeightAndWidth() {
+        int height = mContext.getWallpaperDesiredMinimumHeight();
+        int width = mContext.getWallpaperDesiredMinimumWidth();
+
+        // returned value is <= 0, the caller should use the height of the
+        // default display instead.
+        // That is to say, the return values of desired minimumHeight and
+        // minimunWidth are at the same side of 0-dividing line.
+        assertTrue((height > 0 && width > 0) || (height <= 0 && width <= 0));
+    }
+
+    public void testAccessStickyBroadcast() throws InterruptedException {
+        ResultReceiver resultReceiver = new ResultReceiver();
+
+        Intent intent = new Intent(MOCK_STICKY_ACTION);
+        TestBroadcastReceiver stickyReceiver = new TestBroadcastReceiver();
+
+        mContext.sendStickyBroadcast(intent);
+
+        waitForReceiveBroadCast(resultReceiver);
+
+        assertEquals(intent.getAction(), mContext.registerReceiver(stickyReceiver,
+                new IntentFilter(MOCK_STICKY_ACTION)).getAction());
+
+        synchronized (mLockObj) {
+            mLockObj.wait(BROADCAST_TIMEOUT);
+        }
+
+        assertTrue("Receiver didn't make any response.", stickyReceiver.hadReceivedBroadCast());
+
+        mContext.unregisterReceiver(stickyReceiver);
+        mContext.removeStickyBroadcast(intent);
+
+        assertNull(mContext.registerReceiver(stickyReceiver,
+                new IntentFilter(MOCK_STICKY_ACTION)));
+        mContext.unregisterReceiver(stickyReceiver);
+    }
+
+    public void testCheckCallingOrSelfUriPermission() {
+        Uri uri = Uri.parse("content://ctstest");
+
+        int retValue = mContext.checkCallingOrSelfUriPermission(uri,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testGrantUriPermission() {
+        mContext.grantUriPermission("com.android.mms", Uri.parse("contents://ctstest"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    public void testCheckPermissionGranted() {
+        int returnValue = mContext.checkPermission(
+                GRANTED_PERMISSION, Process.myPid(), Process.myUid());
+        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
+    }
+
+    public void testCheckPermissionNotGranted() {
+        int returnValue = mContext.checkPermission(
+                NOT_GRANTED_PERMISSION, Process.myPid(), Process.myUid());
+        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
+    }
+
+    public void testCheckPermissionRootUser() {
+        // Test with root user, everything will be granted.
+        int returnValue = mContext.checkPermission(NOT_GRANTED_PERMISSION, 1, ROOT_UID);
+        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
+    }
+
+    public void testCheckPermissionInvalidRequest() {
+        // Test with null permission.
+        try {
+            int returnValue = mContext.checkPermission(null, 0, ROOT_UID);
+            fail("checkPermission should not accept null permission");
+        } catch (IllegalArgumentException e) {
+        }
+
+        // Test with invalid uid and included granted permission.
+        int returnValue = mContext.checkPermission(GRANTED_PERMISSION, 1, -11);
+        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
+    }
+
+    public void testCheckSelfPermissionGranted() {
+        int returnValue = mContext.checkSelfPermission(GRANTED_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
+    }
+
+    public void testCheckSelfPermissionNotGranted() {
+        int returnValue = mContext.checkSelfPermission(NOT_GRANTED_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
+    }
+
+    public void testEnforcePermissionGranted() {
+        mContext.enforcePermission(
+                GRANTED_PERMISSION, Process.myPid(), Process.myUid(),
+                "permission isn't granted");
+    }
+
+    public void testEnforcePermissionNotGranted() {
+        try {
+            mContext.enforcePermission(
+                    NOT_GRANTED_PERMISSION, Process.myPid(), Process.myUid(),
+                    "permission isn't granted");
+            fail("Permission shouldn't be granted.");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testCheckCallingOrSelfPermission_noIpc() {
+        // There's no ongoing Binder call, so this package's permissions are checked.
+        int retValue = mContext.checkCallingOrSelfPermission(GRANTED_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
+
+        retValue = mContext.checkCallingOrSelfPermission(NOT_GRANTED_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testCheckCallingOrSelfPermission_ipc() throws Exception {
+        bindBinderPermissionTestService();
+        try {
+            int retValue = mBinderPermissionTestService.doCheckCallingOrSelfPermission(
+                    GRANTED_PERMISSION);
+            assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
+
+            retValue = mBinderPermissionTestService.doCheckCallingOrSelfPermission(
+                    NOT_GRANTED_PERMISSION);
+            assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+        } finally {
+            mContext.unbindService(mBinderPermissionTestConnection);
+        }
+    }
+
+    public void testEnforceCallingOrSelfPermission_noIpc() {
+        // There's no ongoing Binder call, so this package's permissions are checked.
+        mContext.enforceCallingOrSelfPermission(
+                GRANTED_PERMISSION, "permission isn't granted");
+
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    NOT_GRANTED_PERMISSION, "permission isn't granted");
+            fail("Permission shouldn't be granted.");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testEnforceCallingOrSelfPermission_ipc() throws Exception {
+        bindBinderPermissionTestService();
+        try {
+            mBinderPermissionTestService.doEnforceCallingOrSelfPermission(GRANTED_PERMISSION);
+
+            try {
+                mBinderPermissionTestService.doEnforceCallingOrSelfPermission(
+                        NOT_GRANTED_PERMISSION);
+                fail("Permission shouldn't be granted.");
+            } catch (SecurityException expected) {
+            }
+        } finally {
+            mContext.unbindService(mBinderPermissionTestConnection);
+        }
+    }
+
+    public void testCheckCallingPermission_noIpc() {
+        // Denied because no IPC is active.
+        int retValue = mContext.checkCallingPermission(GRANTED_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testEnforceCallingPermission_noIpc() {
+        try {
+            mContext.enforceCallingPermission(
+                    GRANTED_PERMISSION,
+                    "enforceCallingPermission is not working without possessing an IPC.");
+            fail("enforceCallingPermission is not working without possessing an IPC.");
+        } catch (SecurityException e) {
+            // Currently no IPC is handled by this process, this exception is expected
+        }
+    }
+
+    public void testEnforceCallingPermission_ipc() throws Exception {
+        bindBinderPermissionTestService();
+        try {
+            mBinderPermissionTestService.doEnforceCallingPermission(GRANTED_PERMISSION);
+
+            try {
+                mBinderPermissionTestService.doEnforceCallingPermission(NOT_GRANTED_PERMISSION);
+                fail("Permission shouldn't be granted.");
+            } catch (SecurityException expected) {
+            }
+        } finally {
+            mContext.unbindService(mBinderPermissionTestConnection);
+        }
+    }
+
+    public void testCheckCallingPermission_ipc() throws Exception {
+        bindBinderPermissionTestService();
+        try {
+            int returnValue = mBinderPermissionTestService.doCheckCallingPermission(
+                    GRANTED_PERMISSION);
+            assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
+
+            returnValue = mBinderPermissionTestService.doCheckCallingPermission(
+                    NOT_GRANTED_PERMISSION);
+            assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
+        } finally {
+            mContext.unbindService(mBinderPermissionTestConnection);
+        }
+    }
+
+    private void bindBinderPermissionTestService() {
+        Intent intent = new Intent(mContext, IBinderPermissionTestService.class);
+        intent.setComponent(new ComponentName(
+                "com.android.cts", "com.android.cts.BinderPermissionTestService"));
+
+        mBinderPermissionTestConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                mBinderPermissionTestService =
+                        IBinderPermissionTestService.Stub.asInterface(iBinder);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName componentName) {
+            }
+        };
+
+        assertTrue("Service not bound", mContext.bindService(
+                intent, mBinderPermissionTestConnection, Context.BIND_AUTO_CREATE));
+
+        new PollingCheck(15 * 1000) {
+            protected boolean check() {
+                return mBinderPermissionTestService != null; // Service was bound.
+            }
+        }.run();
+    }
+
+    public void testCheckUriPermission1() {
+        Uri uri = Uri.parse("content://ctstest");
+
+        int retValue = mContext.checkUriPermission(uri, Binder.getCallingPid(), 0,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
+
+        retValue = mContext.checkUriPermission(uri, Binder.getCallingPid(),
+                Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testCheckUriPermission2() {
+        Uri uri = Uri.parse("content://ctstest");
+
+        int retValue = mContext.checkUriPermission(uri, NOT_GRANTED_PERMISSION,
+                NOT_GRANTED_PERMISSION, Binder.getCallingPid(), 0,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
+
+        retValue = mContext.checkUriPermission(uri, NOT_GRANTED_PERMISSION,
+                NOT_GRANTED_PERMISSION, Binder.getCallingPid(), Binder.getCallingUid(),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testCheckCallingUriPermission() {
+        Uri uri = Uri.parse("content://ctstest");
+
+        int retValue = mContext.checkCallingUriPermission(uri,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
+    }
+
+    public void testEnforceCallingUriPermission() {
+        try {
+            Uri uri = Uri.parse("content://ctstest");
+            mContext.enforceCallingUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                    "enforceCallingUriPermission is not working without possessing an IPC.");
+            fail("enforceCallingUriPermission is not working without possessing an IPC.");
+        } catch (SecurityException e) {
+            // If the function is OK, it should throw a SecurityException here because currently no
+            // IPC is handled by this process.
+        }
+    }
+
+    public void testGetDir() {
+        File dir = mContext.getDir("testpath", Context.MODE_PRIVATE);
+        assertNotNull(dir);
+        dir.delete();
+    }
+
+    public void testGetPackageManager() {
+        assertSame(mContext.getPackageManager(), mContext.getPackageManager());
+    }
+
+    public void testSendBroadcast1() throws InterruptedException {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+
+        mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION));
+
+        new PollingCheck(BROADCAST_TIMEOUT){
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    public void testSendBroadcast2() throws InterruptedException {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+
+        mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null);
+
+        new PollingCheck(BROADCAST_TIMEOUT){
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    public void testEnforceCallingOrSelfUriPermission() {
+        try {
+            Uri uri = Uri.parse("content://ctstest");
+            mContext.enforceCallingOrSelfUriPermission(uri,
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                    "enforceCallingOrSelfUriPermission is not working without possessing an IPC.");
+            fail("enforceCallingOrSelfUriPermission is not working without possessing an IPC.");
+        } catch (SecurityException e) {
+            // If the function is OK, it should throw a SecurityException here because currently no
+            // IPC is handled by this process.
+        }
+    }
+
+    public void testGetAssets() {
+        assertSame(mContext.getAssets(), mContext.getAssets());
+    }
+
+    public void testGetResources() {
+        assertSame(mContext.getResources(), mContext.getResources());
+    }
+
+    public void testStartInstrumentation() {
+        // Use wrong name
+        ComponentName cn = new ComponentName("com.android",
+                "com.android.content.FalseLocalSampleInstrumentation");
+        assertNotNull(cn);
+        assertNotNull(mContext);
+        // If the target instrumentation is wrong, the function should return false.
+        assertFalse(mContext.startInstrumentation(cn, null, null));
+    }
+
+    private void bindExpectResult(Context context, Intent service)
+            throws InterruptedException {
+        if (service == null) {
+            fail("No service created!");
+        }
+        TestConnection conn = new TestConnection(true, false);
+
+        context.bindService(service, conn, Context.BIND_AUTO_CREATE);
+        context.startService(service);
+
+        // Wait for a short time, so the service related operations could be
+        // working.
+        synchronized (this) {
+            wait(2500);
+        }
+        // Test stop Service
+        assertTrue(context.stopService(service));
+        context.unbindService(conn);
+
+        synchronized (this) {
+            wait(1000);
+        }
+    }
+
+    private interface Condition {
+        public boolean onCondition();
+    }
+
+    private synchronized void waitForCondition(Condition con) throws InterruptedException {
+        // check the condition every 1 second until the condition is fulfilled
+        // and wait for 3 seconds at most
+        for (int i = 0; !con.onCondition() && i <= 3; i++) {
+            wait(1000);
+        }
+    }
+
+    private void waitForReceiveBroadCast(final ResultReceiver receiver)
+            throws InterruptedException {
+        Condition con = new Condition() {
+            public boolean onCondition() {
+                return receiver.hasReceivedBroadCast();
+            }
+        };
+        waitForCondition(con);
+    }
+
+    private void waitForFilteredIntent(Context context, final String action)
+            throws InterruptedException {
+        context.sendBroadcast(new Intent(action), null);
+
+        synchronized (mLockObj) {
+            mLockObj.wait(BROADCAST_TIMEOUT);
+        }
+    }
+
+    private final class TestBroadcastReceiver extends BroadcastReceiver {
+        boolean mHadReceivedBroadCast;
+        boolean mIsOrderedBroadcasts;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (this) {
+                if (mIsOrderedBroadcasts) {
+                    setResultCode(3);
+                    setResultData(ACTUAL_RESULT);
+                }
+
+                Bundle map = getResultExtras(false);
+                if (map != null) {
+                    map.remove(KEY_REMOVED);
+                    map.putString(KEY_ADDED, VALUE_ADDED);
+                }
+                mHadReceivedBroadCast = true;
+                this.notifyAll();
+            }
+
+            synchronized (mLockObj) {
+                mLockObj.notify();
+            }
+        }
+
+        boolean hadReceivedBroadCast() {
+            return mHadReceivedBroadCast;
+        }
+
+        void reset(){
+            mHadReceivedBroadCast = false;
+        }
+    }
+
+    private class FilteredReceiver extends BroadcastReceiver {
+        private boolean mHadReceivedBroadCast1 = false;
+        private boolean mHadReceivedBroadCast2 = false;
+
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (MOCK_ACTION1.equals(action)) {
+                mHadReceivedBroadCast1 = true;
+            } else if (MOCK_ACTION2.equals(action)) {
+                mHadReceivedBroadCast2 = true;
+            }
+
+            synchronized (mLockObj) {
+                mLockObj.notify();
+            }
+        }
+
+        public boolean hadReceivedBroadCast1() {
+            return mHadReceivedBroadCast1;
+        }
+
+        public boolean hadReceivedBroadCast2() {
+            return mHadReceivedBroadCast2;
+        }
+
+        public void reset(){
+            mHadReceivedBroadCast1 = false;
+            mHadReceivedBroadCast2 = false;
+        }
+    }
+
+    private class TestConnection implements ServiceConnection {
+        public TestConnection(boolean expectDisconnect, boolean setReporter) {
+        }
+
+        void setMonitor(boolean v) {
+        }
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    }
+
+    public void testOpenFileOutput_mustNotCreateWorldReadableFile() throws Exception {
+        try {
+            mContext.openFileOutput("test.txt", Context.MODE_WORLD_READABLE);
+            fail("Exception expected");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testOpenFileOutput_mustNotCreateWorldWriteableFile() throws Exception {
+        try {
+            mContext.openFileOutput("test.txt", Context.MODE_WORLD_WRITEABLE);
+            fail("Exception expected");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testOpenFileOutput_mustNotWriteToParentDirectory() throws Exception {
+        try {
+            // Created files must be under the application's private directory.
+            mContext.openFileOutput("../test.txt", Context.MODE_PRIVATE);
+            fail("Exception expected");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testOpenFileOutput_mustNotUseAbsolutePath() throws Exception {
+        try {
+            // Created files must be under the application's private directory.
+            mContext.openFileOutput("/tmp/test.txt", Context.MODE_PRIVATE);
+            fail("Exception expected");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
 }
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperCtsActivity.java b/tests/tests/content/src/android/content/cts/ContextWrapperCtsActivity.java
deleted file mode 100644
index 7ea2ab7..0000000
--- a/tests/tests/content/src/android/content/cts/ContextWrapperCtsActivity.java
+++ /dev/null
@@ -1,34 +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.content.cts;
-
-// Need the following import to get access to the app resources, since this
-// class is in a sub-package.
-import android.app.Activity;
-import android.os.Bundle;
-
-import android.content.cts.R;
-
-public class ContextWrapperCtsActivity extends Activity {
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.local_sample);
-    }
-}
-
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index a01e9e0..abf3506 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -16,117 +16,29 @@
 
 package android.content.cts;
 
-import android.content.cts.R;
-
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteCursorDriver;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQuery;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.preference.PreferenceManager;
-import android.test.AndroidTestCase;
-import android.view.WindowManager;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 
 /**
  * Test {@link ContextWrapper}.
+ *
+ * <p>
+ * This class inherits most of its test methods from its parent ContextTest.
+ * Since ContextWrapper delegates its requests to the Context, the same test cases should pass
+ * for both Context and ContextWrapper.
+ *
+ * <p>
+ * There are some tests for ContextWrapper that don't make sense for Context - those are included
+ * in this class.
  */
-public class ContextWrapperTest extends AndroidTestCase {
-    private static final String ACTUAL_RESULT = "ResultSetByReceiver";
+public class ContextWrapperTest extends ContextTest {
 
-    private static final String INTIAL_RESULT = "IntialResult";
-
-    private static final String VALUE_ADDED = "ValueAdded";
-    private static final String KEY_ADDED = "AddedByReceiver";
-
-    private static final String VALUE_REMOVED = "ValueWillBeRemove";
-    private static final String KEY_REMOVED = "ToBeRemoved";
-
-    private static final String VALUE_KEPT = "ValueKept";
-    private static final String KEY_KEPT = "ToBeKept";
-
-    private static final String MOCK_STICKY_ACTION = "android.content.cts.ContextWrapperTest."
-        + "STICKY_BROADCAST_RESULT";
-
-    private static final String ACTION_BROADCAST_TESTORDER =
-        "android.content.cts.ContextWrapperTest.BROADCAST_TESTORDER";
-    private final static String MOCK_ACTION1 = ACTION_BROADCAST_TESTORDER + "1";
-    private final static String MOCK_ACTION2 = ACTION_BROADCAST_TESTORDER + "2";
-
-    // A permission that's granted to this test package.
-    public static final String GRANTED_PERMISSION = "android.permission.USE_CREDENTIALS";
-    // A permission that's not granted to this test package.
-    public static final String NOT_GRANTED_PERMISSION = "android.permission.HARDWARE_TEST";
-
-    private static final int BROADCAST_TIMEOUT = 10000;
-    private static final int ROOT_UID = 0;
-
-    private Context mContext;
-
-    private ContextWrapper mContextWrapper;
-    private Object mLockObj;
-
-    private ArrayList<BroadcastReceiver> mRegisteredReceiverList;
-
-    private boolean mWallpaperChanged;
-    private BitmapDrawable mOriginalWallpaper;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mLockObj = new Object();
-        mContext = getContext();
-        mContextWrapper = new ContextWrapper(mContext);
-
-        mRegisteredReceiverList = new ArrayList<BroadcastReceiver>();
-
-        mOriginalWallpaper = (BitmapDrawable) mContextWrapper.getWallpaper();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mWallpaperChanged) {
-            mContextWrapper.setWallpaper(mOriginalWallpaper.getBitmap());
-        }
-
-        for (BroadcastReceiver receiver : mRegisteredReceiverList) {
-            mContextWrapper.unregisterReceiver(receiver);
-        }
-
-        super.tearDown();
-    }
-
-    private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        mContextWrapper.registerReceiver(receiver, filter);
-
-        mRegisteredReceiverList.add(receiver);
+    /**
+     * Returns the ContextWrapper object that's being tested.
+     */
+    protected Context getContextUnderTest() {
+        return new ContextWrapper(getContext());
     }
 
     public void testConstructor() {
@@ -136,343 +48,12 @@
         new ContextWrapper(null);
     }
 
-    public void testSendOrderedBroadcast1() throws InterruptedException {
-        final HighPriorityBroadcastReceiver highPriorityReceiver =
-                new HighPriorityBroadcastReceiver();
-        final LowPriorityBroadcastReceiver lowPriorityReceiver =
-            new LowPriorityBroadcastReceiver();
-
-        final IntentFilter filterHighPriority = new IntentFilter(ResultReceiver.MOCK_ACTION);
-        filterHighPriority.setPriority(1);
-        final IntentFilter filterLowPriority = new IntentFilter(ResultReceiver.MOCK_ACTION);
-        registerBroadcastReceiver(highPriorityReceiver, filterHighPriority);
-        registerBroadcastReceiver(lowPriorityReceiver, filterLowPriority);
-
-        final Intent broadcastIntent = new Intent(ResultReceiver.MOCK_ACTION);
-        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContextWrapper.sendOrderedBroadcast(broadcastIntent, null);
-        new PollingCheck(BROADCAST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return highPriorityReceiver.hasReceivedBroadCast()
-                        && !lowPriorityReceiver.hasReceivedBroadCast();
-            }
-        }.run();
-
-        synchronized (highPriorityReceiver) {
-            highPriorityReceiver.notify();
-        }
-
-        new PollingCheck(BROADCAST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return highPriorityReceiver.hasReceivedBroadCast()
-                        && lowPriorityReceiver.hasReceivedBroadCast();
-            }
-        }.run();
-    }
-
-    public void testSendOrderedBroadcast2() throws InterruptedException {
-        final TestBroadcastReceiver broadcastReceiver = new TestBroadcastReceiver();
-        broadcastReceiver.mIsOrderedBroadcasts = true;
-
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_KEPT, VALUE_KEPT);
-        bundle.putString(KEY_REMOVED, VALUE_REMOVED);
-        Intent intent = new Intent(ResultReceiver.MOCK_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContextWrapper.sendOrderedBroadcast(intent, null, broadcastReceiver, null, 1,
-                INTIAL_RESULT, bundle);
-
-        synchronized (mLockObj) {
-            try {
-                mLockObj.wait(BROADCAST_TIMEOUT);
-            } catch (InterruptedException e) {
-                fail("unexpected InterruptedException.");
-            }
-        }
-
-        assertTrue("Receiver didn't make any response.", broadcastReceiver.hadReceivedBroadCast());
-        assertEquals("Incorrect code: " + broadcastReceiver.getResultCode(), 3,
-                broadcastReceiver.getResultCode());
-        assertEquals(ACTUAL_RESULT, broadcastReceiver.getResultData());
-        Bundle resultExtras = broadcastReceiver.getResultExtras(false);
-        assertEquals(VALUE_ADDED, resultExtras.getString(KEY_ADDED));
-        assertEquals(VALUE_KEPT, resultExtras.getString(KEY_KEPT));
-        assertNull(resultExtras.getString(KEY_REMOVED));
-    }
-
-    public void testRegisterReceiver1() throws InterruptedException {
-        final FilteredReceiver broadcastReceiver = new FilteredReceiver();
-        final IntentFilter filter = new IntentFilter(MOCK_ACTION1);
-
-        // Test registerReceiver
-        mContextWrapper.registerReceiver(broadcastReceiver, filter);
-
-        // Test unwanted intent(action = MOCK_ACTION2)
-        broadcastReceiver.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION2);
-        assertFalse(broadcastReceiver.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
-
-        // Send wanted intent(action = MOCK_ACTION1)
-        broadcastReceiver.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION1);
-        assertTrue(broadcastReceiver.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
-
-        mContextWrapper.unregisterReceiver(broadcastReceiver);
-
-        // Test unregisterReceiver
-        FilteredReceiver broadcastReceiver2 = new FilteredReceiver();
-        mContextWrapper.registerReceiver(broadcastReceiver2, filter);
-        mContextWrapper.unregisterReceiver(broadcastReceiver2);
-
-        // Test unwanted intent(action = MOCK_ACTION2)
-        broadcastReceiver2.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION2);
-        assertFalse(broadcastReceiver2.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver2.hadReceivedBroadCast2());
-
-        // Send wanted intent(action = MOCK_ACTION1), but the receiver is unregistered.
-        broadcastReceiver2.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION1);
-        assertFalse(broadcastReceiver2.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver2.hadReceivedBroadCast2());
-    }
-
-    public void testRegisterReceiver2() throws InterruptedException {
-        FilteredReceiver broadcastReceiver = new FilteredReceiver();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(MOCK_ACTION1);
-
-        // Test registerReceiver
-        mContextWrapper.registerReceiver(broadcastReceiver, filter, null, null);
-
-        // Test unwanted intent(action = MOCK_ACTION2)
-        broadcastReceiver.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION2);
-        assertFalse(broadcastReceiver.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
-
-        // Send wanted intent(action = MOCK_ACTION1)
-        broadcastReceiver.reset();
-        waitForFilteredIntent(mContextWrapper, MOCK_ACTION1);
-        assertTrue(broadcastReceiver.hadReceivedBroadCast1());
-        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
-
-        mContextWrapper.unregisterReceiver(broadcastReceiver);
-    }
-
-    public void testAccessWallpaper() throws IOException, InterruptedException {
-        // set Wallpaper by contextWrapper#setWallpaper(Bitmap)
-        Bitmap bitmap = Bitmap.createBitmap(20, 30, Bitmap.Config.RGB_565);
-        // Test getWallpaper
-        Drawable testDrawable = mContextWrapper.getWallpaper();
-        // Test peekWallpaper
-        Drawable testDrawable2 = mContextWrapper.peekWallpaper();
-
-        mContextWrapper.setWallpaper(bitmap);
-        mWallpaperChanged = true;
-        synchronized(this) {
-            wait(500);
-        }
-
-        assertNotSame(testDrawable, mContextWrapper.peekWallpaper());
-        assertNotNull(mContextWrapper.getWallpaper());
-        assertNotSame(testDrawable2, mContextWrapper.peekWallpaper());
-        assertNotNull(mContextWrapper.peekWallpaper());
-
-        // set Wallpaper by contextWrapper#setWallpaper(InputStream)
-        mContextWrapper.clearWallpaper();
-
-        testDrawable = mContextWrapper.getWallpaper();
-        InputStream stream = mContextWrapper.getResources().openRawResource(R.drawable.scenery);
-
-        mContextWrapper.setWallpaper(stream);
-        synchronized (this) {
-            wait(1000);
-        }
-
-        assertNotSame(testDrawable, mContextWrapper.peekWallpaper());
-    }
-
-    public void testAccessDatabase() {
-        String DATABASE_NAME = "databasetest";
-        String DATABASE_NAME1 = DATABASE_NAME + "1";
-        String DATABASE_NAME2 = DATABASE_NAME + "2";
-        SQLiteDatabase mDatabase;
-        File mDatabaseFile;
-
-        SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
-            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
-                    String editTable, SQLiteQuery query) {
-                return new android.database.sqlite.SQLiteCursor(db, masterQuery, editTable, query) {
-                    @Override
-                    public boolean requery() {
-                        setSelectionArguments(new String[] { "2" });
-                        return super.requery();
-                    }
-                };
-            }
-        };
-
-        // FIXME: Move cleanup into tearDown()
-        for (String db : mContextWrapper.databaseList()) {
-            File f = mContextWrapper.getDatabasePath(db);
-            if (f.exists()) {
-                mContextWrapper.deleteDatabase(db);
-            }
-        }
-
-        // Test openOrCreateDatabase with null and actual factory
-        mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME1,
-                ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
-        assertNotNull(mDatabase);
-        mDatabase.close();
-        mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME2,
-                ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
-        assertNotNull(mDatabase);
-        mDatabase.close();
-
-        // Test getDatabasePath
-        File actualDBPath = mContextWrapper.getDatabasePath(DATABASE_NAME1);
-
-        // Test databaseList()
-        List<String> list = Arrays.asList(mContextWrapper.databaseList());
-        assertTrue("1) database list: " + list, list.contains(DATABASE_NAME1));
-        assertTrue("2) database list: " + list, list.contains(DATABASE_NAME2));
-
-        // Test deleteDatabase()
-        for (int i = 1; i < 3; i++) {
-            mDatabaseFile = mContextWrapper.getDatabasePath(DATABASE_NAME + i);
-            assertTrue(mDatabaseFile.exists());
-            mContextWrapper.deleteDatabase(DATABASE_NAME + i);
-            mDatabaseFile = new File(actualDBPath, DATABASE_NAME + i);
-            assertFalse(mDatabaseFile.exists());
-        }
-    }
-
-    public void testEnforceUriPermission1() {
-        try {
-            Uri uri = Uri.parse("content://ctstest");
-            mContextWrapper.enforceUriPermission(uri, Binder.getCallingPid(),
-                    Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                    "enforceUriPermission is not working without possessing an IPC.");
-            fail("enforceUriPermission is not working without possessing an IPC.");
-        } catch (SecurityException e) {
-            // If the function is OK, it should throw a SecurityException here because currently no
-            // IPC is handled by this process.
-        }
-    }
-
-    public void testEnforceUriPermission2() {
-        Uri uri = Uri.parse("content://ctstest");
-        try {
-            mContextWrapper.enforceUriPermission(uri, NOT_GRANTED_PERMISSION,
-                    NOT_GRANTED_PERMISSION, Binder.getCallingPid(), Binder.getCallingUid(),
-                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                    "enforceUriPermission is not working without possessing an IPC.");
-            fail("enforceUriPermission is not working without possessing an IPC.");
-        } catch (SecurityException e) {
-            // If the function is ok, it should throw a SecurityException here because currently no
-            // IPC is handled by this process.
-        }
-    }
-
-    public void testGetPackageResourcePath() {
-        assertNotNull(mContextWrapper.getPackageResourcePath());
-    }
-
-    public void testStartActivity() {
-        Intent intent = new Intent(mContext, ContextWrapperCtsActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        try {
-            mContextWrapper.startActivity(intent);
-            fail("Test startActivity should thow 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
-            // from the wrapped class is enough for testing.
-        }
-    }
-
-    public void testCreatePackageContext() throws PackageManager.NameNotFoundException {
-        Context actualContext = mContextWrapper.createPackageContext(getValidPackageName(),
-                Context.CONTEXT_IGNORE_SECURITY);
-
-        assertNotNull(actualContext);
-    }
-
-    /**
-     * Helper method to retrieve a valid application package name to use for tests.
-     */
-    private String getValidPackageName() {
-        List<PackageInfo> packages = mContextWrapper.getPackageManager().getInstalledPackages(
-                PackageManager.GET_ACTIVITIES);
-        assertTrue(packages.size() >= 1);
-        return packages.get(0).packageName;
-    }
-
-    public void testGetMainLooper() {
-        assertNotNull(mContextWrapper.getMainLooper());
-    }
-
-    public void testGetApplicationContext() {
-        assertSame(mContext.getApplicationContext(), mContextWrapper.getApplicationContext());
-    }
-
-    public void testGetSharedPreferences() {
-        SharedPreferences sp;
-        SharedPreferences localSP;
-
-        sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        String packageName = mContextWrapper.getPackageName();
-        localSP = mContextWrapper.getSharedPreferences(packageName + "_preferences",
-                Context.MODE_PRIVATE);
-        assertSame(sp, localSP);
-    }
-
-    public void testRevokeUriPermission() {
-        Uri uri = Uri.parse("contents://ctstest");
-        mContextWrapper.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    public void testAccessService() throws InterruptedException {
-        MockContextWrapperService.reset();
-        bindExpectResult(mContextWrapper, new Intent(mContext, MockContextWrapperService.class));
-
-        // Check startService
-        assertTrue(MockContextWrapperService.hadCalledOnStart());
-        // Check bindService
-        assertTrue(MockContextWrapperService.hadCalledOnBind());
-
-        assertTrue(MockContextWrapperService.hadCalledOnDestory());
-        // Check unbinService
-        assertTrue(MockContextWrapperService.hadCalledOnUnbind());
-    }
-
-    public void testGetPackageCodePath() {
-        assertNotNull(mContextWrapper.getPackageCodePath());
-    }
-
-    public void testGetPackageName() {
-        assertEquals("android.content.cts", mContextWrapper.getPackageName());
-    }
-
-    public void testGetCacheDir() {
-        assertNotNull(mContextWrapper.getCacheDir());
-    }
-
-    public void testGetContentResolver() {
-        assertSame(mContext.getContentResolver(), mContextWrapper.getContentResolver());
-    }
-
     public void testAccessBaseContext() throws PackageManager.NameNotFoundException {
-        MockContextWrapper testContextWrapper = new MockContextWrapper(mContext);
+        Context context = getContext();
+        MockContextWrapper testContextWrapper = new MockContextWrapper(context);
 
         // Test getBaseContext()
-        assertSame(mContext, testContextWrapper.getBaseContext());
+        assertSame(context, testContextWrapper.getBaseContext());
 
         Context secondContext = testContextWrapper.createPackageContext(getValidPackageName(),
                 Context.CONTEXT_IGNORE_SECURITY);
@@ -486,352 +67,7 @@
         }
     }
 
-    public void testGetFileStreamPath() {
-        String TEST_FILENAME = "TestGetFileStreamPath";
-
-        // Test the path including the input filename
-        String fileStreamPath = mContextWrapper.getFileStreamPath(TEST_FILENAME).toString();
-        assertTrue(fileStreamPath.indexOf(TEST_FILENAME) >= 0);
-    }
-
-    public void testGetClassLoader() {
-        assertSame(mContext.getClassLoader(), mContextWrapper.getClassLoader());
-    }
-
-    public void testGetWallpaperDesiredMinimumHeightAndWidth() {
-        int height = mContextWrapper.getWallpaperDesiredMinimumHeight();
-        int width = mContextWrapper.getWallpaperDesiredMinimumWidth();
-
-        // returned value is <= 0, the caller should use the height of the
-        // default display instead.
-        // That is to say, the return values of desired minimumHeight and
-        // minimunWidth are at the same side of 0-dividing line.
-        assertTrue((height > 0 && width > 0) || (height <= 0 && width <= 0));
-    }
-
-    public void testAccessStickyBroadcast() throws InterruptedException {
-        ResultReceiver resultReceiver = new ResultReceiver();
-
-        Intent intent = new Intent(MOCK_STICKY_ACTION);
-        TestBroadcastReceiver stickyReceiver = new TestBroadcastReceiver();
-
-        mContextWrapper.sendStickyBroadcast(intent);
-
-        waitForReceiveBroadCast(resultReceiver);
-
-        assertEquals(intent.getAction(), mContextWrapper.registerReceiver(stickyReceiver,
-                new IntentFilter(MOCK_STICKY_ACTION)).getAction());
-
-        synchronized (mLockObj) {
-            mLockObj.wait(BROADCAST_TIMEOUT);
-        }
-
-        assertTrue("Receiver didn't make any response.", stickyReceiver.hadReceivedBroadCast());
-
-        mContextWrapper.unregisterReceiver(stickyReceiver);
-        mContextWrapper.removeStickyBroadcast(intent);
-
-        assertNull(mContextWrapper.registerReceiver(stickyReceiver,
-                new IntentFilter(MOCK_STICKY_ACTION)));
-        mContextWrapper.unregisterReceiver(stickyReceiver);
-    }
-
-    public void testCheckCallingOrSelfUriPermission() {
-        Uri uri = Uri.parse("content://ctstest");
-
-        int retValue = mContextWrapper.checkCallingOrSelfUriPermission(uri,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
-    public void testGrantUriPermission() {
-        mContextWrapper.grantUriPermission("com.android.mms", Uri.parse("contents://ctstest"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    public void testCheckPermissionGranted() {
-        int returnValue = mContextWrapper.checkPermission(
-                GRANTED_PERMISSION, Process.myPid(), Process.myUid());
-        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
-    }
-
-    public void testCheckPermissionNotGranted() {
-        int returnValue = mContextWrapper.checkPermission(
-                NOT_GRANTED_PERMISSION, Process.myPid(), Process.myUid());
-        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
-    }
-
-    public void testCheckPermissionRootUser() {
-        // Test with root user, everything will be granted.
-        int returnValue = mContextWrapper.checkPermission(NOT_GRANTED_PERMISSION, 1, ROOT_UID);
-        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
-    }
-
-    public void testCheckPermissionInvalidRequest() {
-        // Test with null permission.
-        try {
-            int returnValue = mContextWrapper.checkPermission(null, 0, ROOT_UID);
-            fail("checkPermission should not accept null permission");
-        } catch (IllegalArgumentException e) {
-        }
-
-        // Test with invalid uid and included granted permission.
-        int returnValue = mContextWrapper.checkPermission(GRANTED_PERMISSION, 1, -11);
-        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
-    }
-
-    public void testCheckSelfPermissionGranted() {
-        int returnValue = mContextWrapper.checkSelfPermission(GRANTED_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_GRANTED, returnValue);
-    }
-
-    public void testCheckSelfPermissionNotGranted() {
-        int returnValue = mContextWrapper.checkSelfPermission(NOT_GRANTED_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, returnValue);
-    }
-
-    public void testEnforcePermissionGranted() {
-        mContextWrapper.enforcePermission(
-                GRANTED_PERMISSION, Process.myPid(), Process.myUid(),
-                "permission isn't granted");
-    }
-
-    public void testEnforcePermissionNotGranted() {
-        try {
-            mContextWrapper.enforcePermission(
-                    NOT_GRANTED_PERMISSION, Process.myPid(), Process.myUid(),
-                    "permission isn't granted");
-            fail("Permission shouldn't be granted.");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    public void testCheckCallingOrSelfPermissionGranted() {
-        int retValue = mContextWrapper.checkCallingOrSelfPermission(GRANTED_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
-    }
-
-    public void testEnforceCallingOrSelfPermissionGranted() {
-        mContextWrapper.enforceCallingOrSelfPermission(
-                GRANTED_PERMISSION, "permission isn't granted");
-    }
-
-    public void testEnforceCallingOrSelfPermissionNotGranted() {
-        try {
-            mContextWrapper.enforceCallingOrSelfPermission(
-                    NOT_GRANTED_PERMISSION, "permission isn't granted");
-            fail("Permission shouldn't be granted.");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    public void testCheckCallingPermission() {
-        // Denied because no IPC is active.
-        int retValue = mContextWrapper.checkCallingPermission(GRANTED_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
-    public void testEnforceCallingPermission() {
-        try {
-            mContextWrapper.enforceCallingPermission(
-                    GRANTED_PERMISSION,
-                    "enforceCallingPermission is not working without possessing an IPC.");
-            fail("enforceCallingPermission is not working without possessing an IPC.");
-        } catch (SecurityException e) {
-            // Currently no IPC is handled by this process, this exception is expected
-        }
-    }
-
-    public void testCheckUriPermission1() {
-        Uri uri = Uri.parse("content://ctstest");
-
-        int retValue = mContextWrapper.checkUriPermission(uri, Binder.getCallingPid(), 0,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
-
-        retValue = mContextWrapper.checkUriPermission(uri, Binder.getCallingPid(),
-                Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
-    public void testCheckUriPermission2() {
-        Uri uri = Uri.parse("content://ctstest");
-
-        int retValue = mContextWrapper.checkUriPermission(uri, NOT_GRANTED_PERMISSION,
-                NOT_GRANTED_PERMISSION, Binder.getCallingPid(), 0,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_GRANTED, retValue);
-
-        retValue = mContextWrapper.checkUriPermission(uri, NOT_GRANTED_PERMISSION,
-                NOT_GRANTED_PERMISSION, Binder.getCallingPid(), Binder.getCallingUid(),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
-    public void testCheckCallingUriPermission() {
-        Uri uri = Uri.parse("content://ctstest");
-
-        int retValue = mContextWrapper.checkCallingUriPermission(uri,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertEquals(PackageManager.PERMISSION_DENIED, retValue);
-    }
-
-    public void testEnforceCallingUriPermission() {
-        try {
-            Uri uri = Uri.parse("content://ctstest");
-            mContextWrapper.enforceCallingUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                    "enforceCallingUriPermission is not working without possessing an IPC.");
-            fail("enforceCallingUriPermission is not working without possessing an IPC.");
-        } catch (SecurityException e) {
-            // If the function is OK, it should throw a SecurityException here because currently no
-            // IPC is handled by this process.
-        }
-    }
-
-    public void testGetDir() {
-        File dir = mContextWrapper.getDir("testpath", Context.MODE_PRIVATE);
-        assertNotNull(dir);
-        dir.delete();
-    }
-
-    public void testGetPackageManager() {
-        assertSame(mContext.getPackageManager(), mContextWrapper.getPackageManager());
-    }
-
-    public void testSendBroadcast1() throws InterruptedException {
-        final ResultReceiver receiver = new ResultReceiver();
-
-        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
-
-        mContextWrapper.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION));
-
-        new PollingCheck(BROADCAST_TIMEOUT){
-            @Override
-            protected boolean check() {
-                return receiver.hasReceivedBroadCast();
-            }
-        }.run();
-    }
-
-    public void testSendBroadcast2() throws InterruptedException {
-        final ResultReceiver receiver = new ResultReceiver();
-
-        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
-
-        mContextWrapper.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null);
-
-        new PollingCheck(BROADCAST_TIMEOUT){
-            @Override
-            protected boolean check() {
-                return receiver.hasReceivedBroadCast();
-            }
-        }.run();
-    }
-
-    public void testEnforceCallingOrSelfUriPermission() {
-        try {
-            Uri uri = Uri.parse("content://ctstest");
-            mContextWrapper.enforceCallingOrSelfUriPermission(uri,
-                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                    "enforceCallingOrSelfUriPermission is not working without possessing an IPC.");
-            fail("enforceCallingOrSelfUriPermission is not working without possessing an IPC.");
-        } catch (SecurityException e) {
-            // If the function is OK, it should throw a SecurityException here because currently no
-            // IPC is handled by this process.
-        }
-    }
-
-    public void testGetSystemService() {
-        // Test invalid service name
-        assertNull(mContextWrapper.getSystemService("invalid"));
-
-        // Test valid service name
-        assertNotNull(mContextWrapper.getSystemService(Context.WINDOW_SERVICE));
-    }
-
-    public void testGetSystemServiceByClass() {
-        // Test invalid service class
-        assertNull(mContextWrapper.getSystemService(Object.class));
-
-        // Test valid service name
-        assertNotNull(mContextWrapper.getSystemService(WindowManager.class));
-        assertEquals(mContextWrapper.getSystemService(Context.WINDOW_SERVICE),
-                mContextWrapper.getSystemService(WindowManager.class));
-    }
-
-    public void testGetAssets() {
-        assertSame(mContext.getAssets(), mContextWrapper.getAssets());
-    }
-
-    public void testGetResources() {
-        assertSame(mContext.getResources(), mContextWrapper.getResources());
-    }
-
-    public void testStartInstrumentation() {
-        // Use wrong name
-        ComponentName cn = new ComponentName("com.android",
-                "com.android.content.FalseLocalSampleInstrumentation");
-        assertNotNull(cn);
-        assertNotNull(mContextWrapper);
-        // If the target instrumentation is wrong, the function should return false.
-        assertFalse(mContextWrapper.startInstrumentation(cn, null, null));
-    }
-
-    private void bindExpectResult(Context contextWrapper, Intent service)
-            throws InterruptedException {
-        if (service == null) {
-            fail("No service created!");
-        }
-        TestConnection conn = new TestConnection(true, false);
-
-        contextWrapper.bindService(service, conn, Context.BIND_AUTO_CREATE);
-        contextWrapper.startService(service);
-
-        // Wait for a short time, so the service related operations could be
-        // working.
-        synchronized (this) {
-            wait(2500);
-        }
-        // Test stop Service
-        assertTrue(contextWrapper.stopService(service));
-        contextWrapper.unbindService(conn);
-
-        synchronized (this) {
-            wait(1000);
-        }
-    }
-
-    private interface Condition {
-        public boolean onCondition();
-    }
-
-    private synchronized void waitForCondition(Condition con) throws InterruptedException {
-        // check the condition every 1 second until the condition is fulfilled
-        // and wait for 3 seconds at most
-        for (int i = 0; !con.onCondition() && i <= 3; i++) {
-            wait(1000);
-        }
-    }
-
-    private void waitForReceiveBroadCast(final ResultReceiver receiver)
-            throws InterruptedException {
-        Condition con = new Condition() {
-            public boolean onCondition() {
-                return receiver.hasReceivedBroadCast();
-            }
-        };
-        waitForCondition(con);
-    }
-
-    private void waitForFilteredIntent(ContextWrapper contextWrapper, final String action)
-            throws InterruptedException {
-        contextWrapper.sendBroadcast(new Intent(action), null);
-
-        synchronized (mLockObj) {
-            mLockObj.wait(BROADCAST_TIMEOUT);
-        }
-    }
-
+    // TODO: this mock seems unnecessary. Remove it, and just use ContextWrapper?
     private static final class MockContextWrapper extends ContextWrapper {
         public MockContextWrapper(Context base) {
             super(base);
@@ -842,84 +78,4 @@
             super.attachBaseContext(base);
         }
     }
-
-    private final class TestBroadcastReceiver extends BroadcastReceiver {
-        boolean mHadReceivedBroadCast;
-        boolean mIsOrderedBroadcasts;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (this) {
-                if (mIsOrderedBroadcasts) {
-                    setResultCode(3);
-                    setResultData(ACTUAL_RESULT);
-                }
-
-                Bundle map = getResultExtras(false);
-                if (map != null) {
-                    map.remove(KEY_REMOVED);
-                    map.putString(KEY_ADDED, VALUE_ADDED);
-                }
-                mHadReceivedBroadCast = true;
-                this.notifyAll();
-            }
-
-            synchronized (mLockObj) {
-                mLockObj.notify();
-            }
-        }
-
-        boolean hadReceivedBroadCast() {
-            return mHadReceivedBroadCast;
-        }
-
-        void reset(){
-            mHadReceivedBroadCast = false;
-        }
-    }
-
-    private class FilteredReceiver extends BroadcastReceiver {
-        private boolean mHadReceivedBroadCast1 = false;
-        private boolean mHadReceivedBroadCast2 = false;
-
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (MOCK_ACTION1.equals(action)) {
-                mHadReceivedBroadCast1 = true;
-            } else if (MOCK_ACTION2.equals(action)) {
-                mHadReceivedBroadCast2 = true;
-            }
-
-            synchronized (mLockObj) {
-                mLockObj.notify();
-            }
-        }
-
-        public boolean hadReceivedBroadCast1() {
-            return mHadReceivedBroadCast1;
-        }
-
-        public boolean hadReceivedBroadCast2() {
-            return mHadReceivedBroadCast2;
-        }
-
-        public void reset(){
-            mHadReceivedBroadCast1 = false;
-            mHadReceivedBroadCast2 = false;
-        }
-    }
-
-    private class TestConnection implements ServiceConnection {
-        public TestConnection(boolean expectDisconnect, boolean setReporter) {
-        }
-
-        void setMonitor(boolean v) {
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-        }
-    }
 }
diff --git a/tests/tests/content/src/android/content/cts/MockContentProvider.java b/tests/tests/content/src/android/content/cts/MockContentProvider.java
index 0183177..d2b613c 100644
--- a/tests/tests/content/src/android/content/cts/MockContentProvider.java
+++ b/tests/tests/content/src/android/content/cts/MockContentProvider.java
@@ -16,21 +16,18 @@
 
 package android.content.cts;
 
-import static androidx.core.util.Preconditions.checkArgument;
 import static junit.framework.Assert.assertEquals;
 
 import android.annotation.NonNull;
 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;
 import android.content.UriMatcher;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
-import android.database.CursorWrapper;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -39,6 +36,7 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -51,8 +49,8 @@
 import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 
-public class MockContentProvider extends ContentProvider
-        implements PipeDataWriter<String> {
+public class MockContentProvider extends ContentProvider implements PipeDataWriter<String> {
+    private static final String TAG = "MockContentProvider";
 
     private static final String DEFAULT_AUTHORITY = "ctstest";
     private static final String DEFAULT_DBNAME = "ctstest.db";
@@ -63,8 +61,8 @@
     private static final int TESTTABLE1_CROSS = 3;
     private static final int TESTTABLE2 = 4;
     private static final int TESTTABLE2_ID = 5;
-    private static final int SELF_ID = 6;
     private static final int CRASH_ID = 6;
+    private static final int HANG_ID = 7;
 
     private static @Nullable Uri sRefreshedUri;
     private static boolean sRefreshReturnValue;
@@ -116,8 +114,8 @@
         URL_MATCHER.addURI(mAuthority, "testtable1/cross", TESTTABLE1_CROSS);
         URL_MATCHER.addURI(mAuthority, "testtable2", TESTTABLE2);
         URL_MATCHER.addURI(mAuthority, "testtable2/#", TESTTABLE2_ID);
-        URL_MATCHER.addURI(mAuthority, "self", SELF_ID);
         URL_MATCHER.addURI(mAuthority, "crash", CRASH_ID);
+        URL_MATCHER.addURI(mAuthority, "hang", HANG_ID);
 
         CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<>();
         CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
@@ -166,9 +164,9 @@
                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                     selectionArgs);
             break;
-        case SELF_ID:
+        case CRASH_ID:
             // Wha...?  Delete ME?!?  O.K.!
-            Log.i("MockContentProvider", "Delete self requested!");
+            Log.i(TAG, "Delete self requested!");
             count = 1;
             android.os.Process.killProcess(android.os.Process.myPid());
             break;
@@ -300,6 +298,12 @@
             qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
             break;
 
+        case HANG_ID:
+            while (true) {
+                Log.i(TAG, "Hanging provider by request...");
+                SystemClock.sleep(1000);
+            }
+
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
         }
@@ -393,7 +397,7 @@
             pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
             pw.print(args);
         } catch (UnsupportedEncodingException e) {
-            Log.w("MockContentProvider", "Ooops", e);
+            Log.w(TAG, "Ooops", e);
         } finally {
             if (pw != null) {
                 pw.flush();
@@ -416,7 +420,7 @@
         if (getCrashOnLaunch(getContext())) {
             // The test case wants us to crash our process on first launch.
             // Well, okay then!
-            Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+            Log.i(TAG, "TEST IS CRASHING SELF, CROSS FINGERS!");
             setCrashOnLaunch(getContext(), false);
             android.os.Process.killProcess(android.os.Process.myPid());
         }
diff --git a/tests/tests/content/src/android/content/cts/MockContextService.java b/tests/tests/content/src/android/content/cts/MockContextService.java
new file mode 100644
index 0000000..f634f68
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/MockContextService.java
@@ -0,0 +1,96 @@
+/*
+ * 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.content.cts;
+
+import android.app.Service;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+
+/**
+ * This class is used for {@link ContextTest}.
+ */
+public class MockContextService extends Service {
+    private static boolean mHadCalledOnBind = false;
+    private static boolean mHadCalledOnUnbind = false;
+    private static boolean mHadCalledOnStart = false;
+    private static boolean mHadCalledOnDestory = false;
+    private static final int TEST_MESSAGE_WHAT = 1;
+
+    private final IBinder mBinder = new Binder();
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(TEST_MESSAGE_WHAT), 1000);
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(TEST_MESSAGE_WHAT), 1000);
+    }
+
+    @Override
+    public void onDestroy() {
+        mHadCalledOnDestory = true;
+        mHandler.removeMessages(1);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mHadCalledOnUnbind = true;
+        return true;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mHadCalledOnBind = true;
+        return mBinder;
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        mHadCalledOnStart = true;
+    }
+
+    public static void reset() {
+        mHadCalledOnBind = false;
+        mHadCalledOnUnbind = false;
+        mHadCalledOnStart = false;
+        mHadCalledOnDestory = false;
+    }
+
+    public static boolean hadCalledOnBind() {
+        return mHadCalledOnBind;
+    }
+
+    public static boolean hadCalledOnUnbind() {
+        return mHadCalledOnUnbind;
+    }
+
+    public static boolean hadCalledOnStart() {
+        return mHadCalledOnStart;
+    }
+
+    public static boolean hadCalledOnDestory() {
+        return mHadCalledOnDestory;
+    }
+}
+
diff --git a/tests/tests/content/src/android/content/cts/MockContextWrapperService.java b/tests/tests/content/src/android/content/cts/MockContextWrapperService.java
deleted file mode 100644
index ef4b09a..0000000
--- a/tests/tests/content/src/android/content/cts/MockContextWrapperService.java
+++ /dev/null
@@ -1,98 +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.content.cts;
-
-import android.app.Service;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-
-/**
- * This class is used for {@link ContextWrapper}
- *
- * @see ContextWrapperTest
- */
-public class MockContextWrapperService extends Service {
-    private static boolean mHadCalledOnBind = false;
-    private static boolean mHadCalledOnUnbind = false;
-    private static boolean mHadCalledOnStart = false;
-    private static boolean mHadCalledOnDestory = false;
-    private static final int TEST_MESSAGE_WHAT = 1;
-
-    private final IBinder mBinder = new Binder();
-
-    private Handler mHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(TEST_MESSAGE_WHAT), 1000);
-        }
-    };
-
-    @Override
-    public void onCreate() {
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(TEST_MESSAGE_WHAT), 1000);
-    }
-
-    @Override
-    public void onDestroy() {
-        mHadCalledOnDestory = true;
-        mHandler.removeMessages(1);
-    }
-
-    @Override
-    public boolean onUnbind(Intent intent) {
-        mHadCalledOnUnbind = true;
-        return true;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        mHadCalledOnBind = true;
-        return mBinder;
-    }
-
-    @Override
-    public void onStart(Intent intent, int startId) {
-        mHadCalledOnStart = true;
-    }
-
-    public static void reset() {
-        mHadCalledOnBind = false;
-        mHadCalledOnUnbind = false;
-        mHadCalledOnStart = false;
-        mHadCalledOnDestory = false;
-    }
-
-    public static boolean hadCalledOnBind() {
-        return mHadCalledOnBind;
-    }
-
-    public static boolean hadCalledOnUnbind() {
-        return mHadCalledOnUnbind;
-    }
-
-    public static boolean hadCalledOnStart() {
-        return mHadCalledOnStart;
-    }
-
-    public static boolean hadCalledOnDestory() {
-        return mHadCalledOnDestory;
-    }
-}
-
diff --git a/tests/tests/content/src/android/content/cts/ResultReceiver.java b/tests/tests/content/src/android/content/cts/ResultReceiver.java
index 1b80774..8b9cf67 100644
--- a/tests/tests/content/src/android/content/cts/ResultReceiver.java
+++ b/tests/tests/content/src/android/content/cts/ResultReceiver.java
@@ -21,13 +21,11 @@
 import android.content.Intent;
 
 /**
- * This class is used for testing android.content.ContentWrapper.
- *
- * @see ContextWrapperTest
+ * This class is used for {@link ContextTest}.
  */
 public class ResultReceiver extends BroadcastReceiver {
     public static final String MOCK_ACTION =
-        "android.content.cts.ContextWrapperTest.BROADCAST_RESULT";
+        "android.content.cts.ContextTest.BROADCAST_RESULT";
 
     private boolean mReceivedBroadCast;
 
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index 7c9e538..24b56c9 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -47,6 +47,8 @@
 
     private File mPrefsFile;
 
+    private static volatile CountDownLatch sSharedPrefsListenerLatch;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -351,4 +353,38 @@
             StrictMode.setThreadPolicy(oldPolicy);
         }
     }
+
+    public void testSharedPrefsChangeListenerIsCalledOnCommit() throws InterruptedException {
+        // Setup on change listener
+        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));
+
+        // Verify listener is called for #remove
+        sSharedPrefsListenerLatch = new CountDownLatch(1);
+        prefs.edit().remove("test-key").commit();
+        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    public void testSharedPrefsChangeListenerIsCalledOnApply() throws InterruptedException {
+        // Setup on change listener
+        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));
+
+        // Verify listener is called for #remove
+        sSharedPrefsListenerLatch = new CountDownLatch(1);
+        prefs.edit().remove("test-key").apply();
+        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+    }
 }
diff --git a/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java b/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java
new file mode 100644
index 0000000..a8919b9
--- /dev/null
+++ b/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.om.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static android.content.om.OverlayInfo.CREATOR;
+
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+
+
+/**
+ * Verifies the member variables inside {@link OverlayInfo}
+ */
+@RunWith(AndroidJUnit4.class)
+public class OverlayInfoTest {
+
+    private static final String PKG_NAME = "source package name";
+    private static final String TARGET_PKG_NAME = "target package name";
+    private static final String CATEGORY = "sample category";
+    private static final String BASE_CODE_PATH = "base code path";
+
+    @Test
+    public void testEnsureValidState() {
+        OverlayInfo info = new OverlayInfo(PKG_NAME, TARGET_PKG_NAME, CATEGORY, BASE_CODE_PATH,
+            0, 0, 0, true);
+        assertNotNull(info);
+    }
+
+    @Test
+    public void testEnsureValidState_fail() {
+        try {
+            OverlayInfo info = new OverlayInfo(null, TARGET_PKG_NAME, CATEGORY, BASE_CODE_PATH,
+                0, 0, 0, true);
+            fail("Should throw exception.");
+        } catch (IllegalArgumentException e) {
+            // no op, working as intended.
+        }
+
+        try {
+            OverlayInfo info = new OverlayInfo(PKG_NAME, null, CATEGORY, BASE_CODE_PATH,
+                0, 0, 0, true);
+            fail("Should throw exception.");
+        } catch (IllegalArgumentException e) {
+            // no op, working as intended.
+        }
+
+        try {
+            OverlayInfo info = new OverlayInfo(PKG_NAME, TARGET_PKG_NAME, CATEGORY, null,
+                0, 0, 0, true);
+            fail("Should throw exception.");
+        } catch (IllegalArgumentException e) {
+            // no op, working as intended.
+        }
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        OverlayInfo info = new OverlayInfo(PKG_NAME, TARGET_PKG_NAME, CATEGORY, BASE_CODE_PATH,
+            0, 0, 0, true);
+        Parcel p = Parcel.obtain();
+        info.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        OverlayInfo copyInfo = CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertEquals(info.packageName, copyInfo.packageName);
+        assertEquals(info.targetPackageName, copyInfo.targetPackageName);
+        assertEquals(info.category, copyInfo.category);
+        assertEquals(info.baseCodePath, copyInfo.baseCodePath);
+        assertEquals(info.state, copyInfo.state);
+        assertEquals(info.userId, copyInfo.userId);
+        assertEquals(info.priority, copyInfo.priority);
+        assertEquals(info.isStatic, copyInfo.isStatic);
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/om/cts/OverlayManagerTest.java b/tests/tests/content/src/android/content/om/cts/OverlayManagerTest.java
new file mode 100644
index 0000000..bda44a2
--- /dev/null
+++ b/tests/tests/content/src/android/content/om/cts/OverlayManagerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.om.cts;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+
+import android.content.Context;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayInfo;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * This only tests the client API implementation of the OverlayManager
+ * and not the service implementation.
+ */
+@RunWith(AndroidJUnit4.class)
+public class OverlayManagerTest {
+
+    private OverlayManager mManager;
+    @Mock
+    private IOverlayManager mMockService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mManager = new OverlayManager(context, mMockService);
+    }
+
+    @Test
+    public void testSetEnabledExclusiveInCategory() throws Exception {
+        String packageName = "overlay source package name";
+        int userId = 10;
+        verify(mMockService, times(0)).setEnabledExclusiveInCategory(packageName, userId);
+        mManager.setEnabledExclusiveInCategory(packageName, userId);
+        verify(mMockService, times(1)).setEnabledExclusiveInCategory(packageName, userId);
+    }
+
+    @Test
+    public void testGetOverlayInfosForTarget() throws Exception {
+        String targetPackageName = "overlay target package name";
+        int userId = 10;
+        verify(mMockService, times(0)).getOverlayInfosForTarget(targetPackageName, userId);
+        mManager.getOverlayInfosForTarget(targetPackageName, userId);
+        verify(mMockService, times(1)).getOverlayInfosForTarget(targetPackageName, userId);
+    }
+}
\ No newline at end of file
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 60a7abd..00559be 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -18,22 +18,38 @@
 
 import android.content.cts.R;
 
+import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
+import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.GET_PROVIDERS;
+import static android.content.pm.PackageManager.GET_RECEIVERS;
+import static android.content.pm.PackageManager.GET_SERVICES;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -45,6 +61,8 @@
 public class PackageManagerTest extends AndroidTestCase {
     private PackageManager mPackageManager;
     private static final String PACKAGE_NAME = "android.content.cts";
+    private static final String CONTENT_PKG_NAME = "android.content.cts";
+    private static final String APPLICATION_NAME = "android.content.cts.MockApplication";
     private static final String ACTIVITY_ACTION_NAME = "android.intent.action.PMTEST";
     private static final String MAIN_ACTION_NAME = "android.intent.action.MAIN";
     private static final String SERVICE_ACTION_NAME =
@@ -55,6 +73,8 @@
     private static final String SERVICE_NAME = "android.content.pm.cts.TestPmService";
     private static final String RECEIVER_NAME = "android.content.pm.cts.PmTestReceiver";
     private static final String INSTRUMENT_NAME = "android.content.pm.cts.TestPmInstrumentation";
+    private static final String CALL_ABROAD_PERMISSION_NAME =
+            "android.content.cts.CALL_ABROAD_PERMISSION";
     private static final String PROVIDER_NAME = "android.content.cts.MockContentProvider";
     private static final String PERMISSIONGROUP_NAME = "android.permission-group.COST_MONEY";
 
@@ -110,7 +130,7 @@
         String testPermissionsGroup = "android.permission-group.COST_MONEY";
         List<PermissionInfo> permissions = mPackageManager.queryPermissionsByGroup(
                 testPermissionsGroup, PackageManager.GET_META_DATA);
-        checkPermissionInfoName("android.content.cts.CALL_ABROAD_PERMISSION", permissions);
+        checkPermissionInfoName(CALL_ABROAD_PERMISSION_NAME, permissions);
 
         ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
         List<ProviderInfo> providers = mPackageManager.queryContentProviders(PACKAGE_NAME,
@@ -405,7 +425,7 @@
         assertNotNull(mPackageManager.getDrawable(PACKAGE_NAME, iconRes, appInfo));
     }
 
-    public void testCheckSignaturesMatch() {
+    public void testCheckSignaturesMatch_byPackageName() {
         // Compare the signature of this package to another package installed by this test suite
         // (see AndroidTest.xml). Their signatures must match.
         assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(PACKAGE_NAME,
@@ -415,12 +435,31 @@
                 PACKAGE_NAME));
     }
 
-    public void testCheckSignaturesNoMatch() {
+    public void testCheckSignaturesMatch_byUid() throws NameNotFoundException {
+        // Compare the signature of this package to another package installed by this test suite
+        // (see AndroidTest.xml). Their signatures must match.
+        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
+        int uid2 = mPackageManager.getPackageInfo("com.android.cts.stub", 0).applicationInfo.uid;
+        assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(uid1, uid2));
+
+        // A UID's signature should match its own signature.
+        assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(uid1, uid1));
+    }
+
+    public void testCheckSignaturesNoMatch_byPackageName() {
         // This test package's signature shouldn't match the system's signature.
         assertEquals(PackageManager.SIGNATURE_NO_MATCH, mPackageManager.checkSignatures(
                 PACKAGE_NAME, "android"));
     }
 
+    public void testCheckSignaturesNoMatch_byUid() throws NameNotFoundException {
+        // This test package's signature shouldn't match the system's signature.
+        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
+        int uid2 = mPackageManager.getPackageInfo("android", 0).applicationInfo.uid;
+        assertEquals(PackageManager.SIGNATURE_NO_MATCH,
+                mPackageManager.checkSignatures(uid1, uid2));
+    }
+
     public void testCheckSignaturesUnknownPackage() {
         assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, mPackageManager.checkSignatures(
                 PACKAGE_NAME, "this.package.does.not.exist"));
@@ -509,4 +548,205 @@
         assertEquals(null, result[1]);
         assertEquals("com.android.cts.ctsshim", result[2]);
     }
+
+    public void testGetPackageUid() throws NameNotFoundException {
+        assertEquals(1000, mPackageManager.getPackageUid("android", 0));
+
+        int uid = mPackageManager.getApplicationInfo("com.android.cts.ctsshim", 0 /*flags*/).uid;
+        assertEquals(uid, mPackageManager.getPackageUid("com.android.cts.ctsshim", 0));
+    }
+
+    public void testGetPackageInfo() throws NameNotFoundException {
+        PackageInfo pkgInfo = mPackageManager.getPackageInfo(PACKAGE_NAME, GET_META_DATA
+                | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS | GET_SERVICES | GET_RECEIVERS);
+        assertTestPackageInfo(pkgInfo);
+    }
+
+    public void testGetPackageInfo_notFound() {
+        try {
+            mPackageManager.getPackageInfo("this.package.does.not.exist", 0);
+            fail("Exception expected");
+        } catch (NameNotFoundException expected) {
+        }
+    }
+
+    public void testGetInstalledPackages() throws Exception {
+        List<PackageInfo> pkgs = mPackageManager.getInstalledPackages(GET_META_DATA
+                | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS | GET_SERVICES | GET_RECEIVERS);
+
+        PackageInfo pkgInfo = findPackageOrFail(pkgs, PACKAGE_NAME);
+        assertTestPackageInfo(pkgInfo);
+    }
+
+    /**
+     * Asserts that the pkgInfo object correctly describes the {@link #PACKAGE_NAME} package.
+     */
+    private void assertTestPackageInfo(PackageInfo pkgInfo) {
+        // Check metadata
+        ApplicationInfo appInfo = pkgInfo.applicationInfo;
+        assertEquals(APPLICATION_NAME, appInfo.name);
+        assertEquals("Android TestCase", appInfo.loadLabel(mPackageManager));
+        assertEquals(PACKAGE_NAME, appInfo.packageName);
+        assertTrue(appInfo.enabled);
+        // The process name defaults to the package name when not set.
+        assertEquals(PACKAGE_NAME, appInfo.processName);
+        assertEquals(0, appInfo.flags & FLAG_SYSTEM);
+        assertEquals(FLAG_INSTALLED, appInfo.flags & FLAG_INSTALLED);
+        assertEquals(FLAG_HAS_CODE, appInfo.flags & FLAG_HAS_CODE);
+
+        // Check required permissions
+        List<String> requestedPermissions = Arrays.asList(pkgInfo.requestedPermissions);
+        assertThat(requestedPermissions).containsAllOf(
+                "android.permission.MANAGE_ACCOUNTS",
+                "android.permission.ACCESS_NETWORK_STATE",
+                "android.content.cts.permission.TEST_GRANTED");
+
+        // Check declared permissions
+        PermissionInfo declaredPermission = (PermissionInfo) findPackageItemOrFail(
+                pkgInfo.permissions, CALL_ABROAD_PERMISSION_NAME);
+        assertEquals("Call abroad", declaredPermission.loadLabel(mPackageManager));
+        assertEquals(PERMISSIONGROUP_NAME, declaredPermission.group);
+        assertEquals(PermissionInfo.PROTECTION_NORMAL, declaredPermission.protectionLevel);
+
+        // Check activities
+        ActivityInfo activity = findPackageItemOrFail(pkgInfo.activities, ACTIVITY_NAME);
+        assertTrue(activity.enabled);
+        assertTrue(activity.exported); // Has intent filters - export by default.
+        assertEquals(PACKAGE_NAME, activity.taskAffinity);
+        assertEquals(ActivityInfo.LAUNCH_SINGLE_TOP, activity.launchMode);
+
+        // Check services
+        ServiceInfo service = findPackageItemOrFail(pkgInfo.services, SERVICE_NAME);
+        assertTrue(service.enabled);
+        assertTrue(service.exported); // Has intent filters - export by default.
+        assertEquals(PACKAGE_NAME, service.packageName);
+        assertEquals(CALL_ABROAD_PERMISSION_NAME, service.permission);
+
+        // Check ContentProviders
+        ProviderInfo provider = findPackageItemOrFail(pkgInfo.providers, PROVIDER_NAME);
+        assertTrue(provider.enabled);
+        assertFalse(provider.exported); // Don't export by default.
+        assertEquals(PACKAGE_NAME, provider.packageName);
+        assertEquals("ctstest", provider.authority);
+
+        // Check Receivers
+        ActivityInfo receiver = findPackageItemOrFail(pkgInfo.receivers, RECEIVER_NAME);
+        assertTrue(receiver.enabled);
+        assertTrue(receiver.exported); // Has intent filters - export by default.
+        assertEquals(PACKAGE_NAME, receiver.packageName);
+    }
+
+    // Tests that other packages can be queried.
+    public void testGetInstalledPackages_OtherPackages() throws Exception {
+        List<PackageInfo> pkgInfos = mPackageManager.getInstalledPackages(0);
+
+        // Check a normal package.
+        PackageInfo pkgInfo = findPackageOrFail(pkgInfos, "com.android.cts.stub"); // A test package
+        assertEquals(0, pkgInfo.applicationInfo.flags & FLAG_SYSTEM);
+
+        // Check a system package.
+        pkgInfo = findPackageOrFail(pkgInfos, "android");
+        assertEquals(FLAG_SYSTEM, pkgInfo.applicationInfo.flags & FLAG_SYSTEM);
+    }
+
+    public void testGetInstalledApplications() throws Exception {
+        List<ApplicationInfo> apps = mPackageManager.getInstalledApplications(GET_META_DATA);
+
+        ApplicationInfo app = findPackageItemOrFail(
+                apps.toArray(new ApplicationInfo[] {}), APPLICATION_NAME);
+
+        assertEquals(APPLICATION_NAME, app.name);
+        assertEquals("Android TestCase", app.loadLabel(mPackageManager));
+        assertEquals(PACKAGE_NAME, app.packageName);
+        assertTrue(app.enabled);
+        // The process name defaults to the package name when not set.
+        assertEquals(PACKAGE_NAME, app.processName);
+    }
+
+    private PackageInfo findPackageOrFail(List<PackageInfo> pkgInfos, String pkgName) {
+        for (PackageInfo pkgInfo : pkgInfos) {
+            if (pkgName.equals(pkgInfo.packageName)) {
+                return pkgInfo;
+            }
+        }
+        fail("Package not found with name " + pkgName);
+        return null;
+    }
+
+    private <T extends PackageItemInfo> T findPackageItemOrFail(T[] items, String name) {
+        for (T item : items) {
+            if (name.equals(item.name)) {
+                return item;
+            }
+        }
+        fail("Package item not found with name " + name);
+        return null;
+    }
+
+    public void testGetPackagesHoldingPermissions() {
+        List<PackageInfo> pkgInfos = mPackageManager.getPackagesHoldingPermissions(
+                new String[] { GRANTED_PERMISSION_NAME }, 0);
+        findPackageOrFail(pkgInfos, PACKAGE_NAME);
+
+        pkgInfos = mPackageManager.getPackagesHoldingPermissions(
+                new String[] { NOT_GRANTED_PERMISSION_NAME }, 0);
+        for (PackageInfo pkgInfo : pkgInfos) {
+            if (PACKAGE_NAME.equals(pkgInfo.packageName)) {
+                fail("Must not return package " + PACKAGE_NAME);
+            }
+        }
+    }
+
+    public void testGetPermissionInfo() throws NameNotFoundException {
+        // Check a normal permission.
+        String permissionName = "android.permission.INTERNET";
+        PermissionInfo permissionInfo = mPackageManager.getPermissionInfo(permissionName, 0);
+        assertEquals(permissionName, permissionInfo.name);
+        assertEquals(PermissionInfo.PROTECTION_NORMAL, permissionInfo.getProtection());
+
+        // Check a dangerous (runtime) permission.
+        permissionName = "android.permission.RECORD_AUDIO";
+        permissionInfo = mPackageManager.getPermissionInfo(permissionName, 0);
+        assertEquals(permissionName, permissionInfo.name);
+        assertEquals(PermissionInfo.PROTECTION_DANGEROUS, permissionInfo.getProtection());
+        assertNotNull(permissionInfo.group);
+
+        // Check a signature permission.
+        permissionName = "android.permission.MODIFY_PHONE_STATE";
+        permissionInfo = mPackageManager.getPermissionInfo(permissionName, 0);
+        assertEquals(permissionName, permissionInfo.name);
+        assertEquals(PermissionInfo.PROTECTION_SIGNATURE, permissionInfo.getProtection());
+
+        // Check a special access (appop) permission.
+        permissionName = "android.permission.SYSTEM_ALERT_WINDOW";
+        permissionInfo = mPackageManager.getPermissionInfo(permissionName, 0);
+        assertEquals(permissionName, permissionInfo.name);
+        assertEquals(PermissionInfo.PROTECTION_SIGNATURE, permissionInfo.getProtection());
+        assertEquals(PermissionInfo.PROTECTION_FLAG_APPOP,
+                permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_APPOP);
+    }
+
+    public void testGetPermissionInfo_notFound() {
+        try {
+            mPackageManager.getPermissionInfo("android.permission.nonexistent.permission", 0);
+            fail("Exception expected");
+        } catch (NameNotFoundException expected) {
+        }
+    }
+
+    public void testGetPermissionGroupInfo() throws NameNotFoundException {
+        PermissionGroupInfo groupInfo = mPackageManager.getPermissionGroupInfo(
+                PERMISSIONGROUP_NAME, 0);
+        assertEquals(PERMISSIONGROUP_NAME, groupInfo.name);
+        assertEquals(PACKAGE_NAME, groupInfo.packageName);
+        assertFalse(TextUtils.isEmpty(groupInfo.loadDescription(mPackageManager)));
+    }
+
+    public void testGetPermissionGroupInfo_notFound() throws NameNotFoundException {
+        try {
+            mPackageManager.getPermissionGroupInfo("this.group.does.not.exist", 0);
+            fail("Exception expected");
+        } catch (NameNotFoundException expected) {
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigTest.java b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
index faca085..d834099 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
@@ -16,24 +16,44 @@
 
 package android.content.res.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 static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.NotFoundException;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import android.content.cts.R;
 
-public class ConfigTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ConfigTest {
+    private static final String TEST_PACKAGE = "android.content.cts";
+
+    private Context mContext;
+    private int mTargetSdkVersion;
+
     enum Properties {
         LANGUAGE,
         COUNTRY,
@@ -234,7 +254,19 @@
                 new String[]{willHave ? bagString : null});
     }
 
-    @SmallTest
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            ApplicationInfo appInfo = pm.getApplicationInfo(TEST_PACKAGE, 0);
+            mTargetSdkVersion = appInfo.targetSdkVersion;
+        } catch (NameNotFoundException e) {
+            fail("Should be able to find application info for this package");
+        }
+    }
+
+    @Test
     public void testAllEmptyConfigs() {
         /**
          * Test a resource that contains a value for each possible single
@@ -466,7 +498,7 @@
                 R.styleable.TestConfig, new String[]{"bag h670"});
     }
 
-    @SmallTest
+    @Test
     public void testAllClassicConfigs() {
         /**
          * Test a resource that contains a value for each possible single
@@ -671,7 +703,7 @@
                 R.styleable.TestConfig, new String[]{"bag h670"});
     }
     
-    @MediumTest
+    @Test
     public void testDensity() throws Exception {
         // have 32, 240 and the default 160 content.
         // rule is that closest wins, with down scaling (larger content)
@@ -744,7 +776,7 @@
                 R.styleable.TestConfig, new String[]{"bag 240dpi"});
     }
 
-    @MediumTest
+    @Test
     public void testScreenSize() throws Exception {
         // ensure that we fall back to the best available screen size
         // for a given configuration.
@@ -785,7 +817,7 @@
         checkValue(res, R.configVarying.xlarge, "xlarge");
     }
 
-    @MediumTest
+    @Test
     public void testNewScreenSize() throws Exception {
         // ensure that swNNNdp, wNNNdp, and hNNNdp are working correctly
         // for various common screen configurations.
@@ -922,7 +954,7 @@
 // TODO - add tests for special cases - ie, other key params seem ignored if 
 // nokeys is set
 
-    @MediumTest
+    @Test
     public void testPrecedence() {
         /**
          * Check for precedence of resources selected when there are multiple
@@ -1028,7 +1060,7 @@
                 R.styleable.TestConfig, new String[]{"bag mcc111 mnc222"});
     }
 
-    @MediumTest
+    @Test
     public void testCombinations() {
         /**
          * Verify that in cases of ties, the specific ordering is followed
@@ -1180,14 +1212,26 @@
                 R.styleable.TestConfig, new String[]{"bag dpad 63x57"});
     }
 
-    @MediumTest
+    @Test
     public void testVersions() {
+        final boolean isReleaseBuild = "REL".equals(android.os.Build.VERSION.CODENAME);
+
+        // Release builds must not have a dev SDK version
+        if (isReleaseBuild) {
+            assertTrue("Release builds must build with a valid SDK version",
+                    mTargetSdkVersion < 10000);
+        }
+
+        // ...and skip this test if this is a dev-SDK-version build
+        assumeTrue("This product was built with non-release SDK level 10000",
+                mTargetSdkVersion < 10000);
+
         // Check that we get the most recent resources that are <= our
         // current version.  Note the special version adjustment, so that
         // during development the resource version is incremented to the
         // next one.
         int vers = android.os.Build.VERSION.SDK_INT;
-        if (!"REL".equals(android.os.Build.VERSION.CODENAME)) {
+        if (!isReleaseBuild) {
             vers++;
         }
         String expected = "v" + vers + "cur";
@@ -1196,7 +1240,7 @@
         assertEquals("v3",  mContext.getResources().getString(R.string.version_v3));
     }
 
-    @MediumTest
+    @Test
     public void testNormalLocales() {
         Resources res;
         TotalConfig config = makeClassicConfig();
@@ -1232,7 +1276,7 @@
                 R.styleable.TestConfig, new String[]{"bag mk MK"});
     }
 
-    @MediumTest
+    @Test
     public void testExtendedLocales() {
         TotalConfig config = makeClassicConfig();
         // BCP 47 Locale kok
@@ -1297,7 +1341,7 @@
                 R.styleable.TestConfig, new String[]{"bag kok VARIANT"});
     }
 
-    @MediumTest
+    @Test
     public void testTlAndFilConversion() {
         TotalConfig config = makeClassicConfig();
 
@@ -1352,7 +1396,7 @@
                 R.styleable.TestConfig, new String[] { "bag tgl PH" });
     }
 
-    @MediumTest
+    @Test
     public void testGetLocalesConvertsTlToFil() {
         TotalConfig config = makeClassicConfig();
 
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 ab67321..a68445f 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -35,15 +35,19 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.ColorStateListDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.LocaleList;
+import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
 import android.view.WindowManager;
 
 import java.io.IOException;
@@ -66,6 +70,10 @@
         mResources = getContext().getResources();
     }
 
+    public void testIdNull() {
+        assertEquals(0, Resources.ID_NULL);
+    }
+
     public void testResources() {
         final AssetManager am = new AssetManager();
         final Configuration cfg = new Configuration();
@@ -948,4 +956,13 @@
         assertEquals(Typeface.BOLD_ITALIC,
                 mResources.getFont(R.font.sample_bolditalic_family).getStyle());
     }
+
+    public void testComplextColorDrawableAttrInflation() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        View view = layoutInflater.inflate(R.layout.complex_color_drawable_attr_layout, null);
+        assertTrue(view.getBackground() instanceof ColorStateListDrawable);
+    }
 }
diff --git a/tests/tests/content/src/android/content/res/cts/TypedArrayTest.java b/tests/tests/content/src/android/content/res/cts/TypedArrayTest.java
index f27f06c..38a6a5b 100644
--- a/tests/tests/content/src/android/content/res/cts/TypedArrayTest.java
+++ b/tests/tests/content/src/android/content/res/cts/TypedArrayTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.cts.R;
 import android.content.cts.util.XmlUtils;
 import android.content.pm.ActivityInfo;
@@ -32,6 +30,8 @@
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 
 public class TypedArrayTest extends AndroidTestCase {
@@ -72,6 +72,19 @@
         mTypedArray.recycle();
     }
 
+    public void testSourceStyleResourceId() {
+        final TypedArray t = getContext().getTheme().obtainStyledAttributes(
+                R.style.StyleA, R.styleable.style1);
+
+        assertEquals(R.style.StyleA, t.getSourceStyleResourceId(R.styleable.style1_type1, 0));
+        assertEquals(R.style.StyleB, t.getSourceStyleResourceId(R.styleable.style1_type2, 0));
+        assertEquals(R.style.StyleC, t.getSourceStyleResourceId(R.styleable.style1_type3, 0));
+        assertEquals(R.style.StyleB, t.getSourceStyleResourceId(R.styleable.style1_type4, 0));
+        assertEquals(0, t.getSourceStyleResourceId(R.styleable.style1_type5, 0));
+
+        t.recycle();
+    }
+
     public void testGetType() {
         final TypedArray t = getContext().getTheme().obtainStyledAttributes(
                 R.style.Whatever, R.styleable.style1);
diff --git a/tests/tests/database/Android.mk b/tests/tests/database/Android.mk
index 51d18a5..fa558f6 100644
--- a/tests/tests/database/Android.mk
+++ b/tests/tests/database/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    compatibility-device-util \
     android-common \
     ctstestrunner \
     ctstestrunner \
diff --git a/tests/tests/database/AndroidTest.xml b/tests/tests/database/AndroidTest.xml
index a1db9c0..9c53d2d 100644
--- a/tests/tests/database/AndroidTest.xml
+++ b/tests/tests/database/AndroidTest.xml
@@ -16,13 +16,14 @@
 <configuration description="Config for CTS Database 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDatabaseTestCases.apk" />
+        <option name="test-file-name" value="CtsProviderApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.database.cts" />
         <option name="runtime-hint" value="11m" />
-        <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/tests/database/apps/Android.bp b/tests/tests/database/apps/Android.bp
new file mode 100644
index 0000000..9953fc1
--- /dev/null
+++ b/tests/tests/database/apps/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 {
+  name: "CtsProviderApp",
+  sdk_version: "test_current",
+
+  srcs: [
+    "src/**/*.java"
+  ],
+
+  test_suites: [
+    "cts",
+    "vts",
+    "general-tests",
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/database/apps/AndroidManifest.xml b/tests/tests/database/apps/AndroidManifest.xml
new file mode 100644
index 0000000..a199a8f
--- /dev/null
+++ b/tests/tests/database/apps/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.providerapp">
+
+    <application>
+        <provider android:name=".DummyProvider"
+                android:authorities="com.android.cts.providerapp"
+                android:exported="true">
+        </provider>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/database/apps/src/com/android/cts/providerapp/DummyProvider.java b/tests/tests/database/apps/src/com/android/cts/providerapp/DummyProvider.java
new file mode 100644
index 0000000..8c06f05
--- /dev/null
+++ b/tests/tests/database/apps/src/com/android/cts/providerapp/DummyProvider.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 com.android.cts.providerapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class DummyProvider extends ContentProvider {
+    private static final String TAG = DummyProvider.class.getName();
+
+    private static final String AUTHORITY = "com.android.cts.providerapp";
+    private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+    private static final Uri TEST_URI1 = Uri.withAppendedPath(CONTENT_URI, "test1");
+    private static final Uri TEST_URI2 = Uri.withAppendedPath(CONTENT_URI, "test2");
+
+    private static final String CALL_NOTIFY = "notify";
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate");
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        Log.d(TAG, "query uri: " + uri);
+        if (!CONTENT_URI.equals(uri)) {
+            throw new UnsupportedOperationException();
+        }
+
+        final MatrixCursor cursor = new MatrixCursor(new String[] {
+                BaseColumns._ID, BaseColumns._COUNT
+        }, 2);
+        cursor.addRow(new Integer[] {100, 2});
+        cursor.addRow(new Integer[] {101, 3});
+        cursor.setNotificationUris(getContext().getContentResolver(),
+                Arrays.asList(TEST_URI1, TEST_URI2));
+        return cursor;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        Log.d(TAG, "call method: " + method + ", arg: " + arg);
+        if (!CALL_NOTIFY.equals(method)) {
+            throw new UnsupportedOperationException();
+        }
+
+        switch (arg) {
+            case "1":
+                getContext().getContentResolver().notifyChange(TEST_URI1, null);
+                break;
+            case "2":
+                getContext().getContentResolver().notifyChange(TEST_URI2, null);
+                break;
+            default:
+                throw new UnsupportedOperationException();
+        }
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
index ba2f1fd..9a4ba50 100644
--- a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
@@ -20,6 +20,7 @@
 import android.database.AbstractCursor;
 import android.database.CharArrayBuffer;
 import android.database.ContentObserver;
+import android.database.Cursor;
 import android.database.CursorIndexOutOfBoundsException;
 import android.database.CursorWindow;
 import android.database.DataSetObserver;
@@ -29,9 +30,15 @@
 import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 
+import org.junit.Assert;
+
 import java.io.File;
+import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test {@link AbstractCursor}.
@@ -52,6 +59,9 @@
     private SQLiteDatabase mDatabase;
     private File mDatabaseFile;
     private AbstractCursor mDatabaseCursor;
+    private Context mContext;
+
+    private static final long ON_CHANGE_TIMEOUT_MS = 5000;
 
     @Override
     protected void setUp() throws Exception {
@@ -60,6 +70,7 @@
         setupDatabase();
         ArrayList<ArrayList> list = createTestList(ROW_MAX, COLUMN_NAMES.length);
         mTestAbstractCursor = new TestAbstractCursor(COLUMN_NAMES, list);
+        mContext = getInstrumentation().getContext();
     }
 
     @Override
@@ -139,6 +150,47 @@
                 testUri);
     }
 
+    public void testSetNotificationUris_selfNotify() throws Exception {
+        final Uri testUri1 = Settings.System.getUriFor(Settings.System.TIME_12_24);
+        final Uri testUri2 = Settings.Global.getUriFor(
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
+        mDatabaseCursor.setNotificationUris(mContext.getContentResolver(),
+                Arrays.asList(testUri1, testUri2));
+        final MockContentObserver observer = new MockContentObserver();
+        mDatabaseCursor.registerContentObserver(observer);
+
+        mContext.getContentResolver().notifyChange(testUri1, null);
+        observer.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
+        observer.resetOnChangeWatch();
+        mContext.getContentResolver().notifyChange(testUri2, null);
+        observer.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
+    }
+
+    public void testSetNotificationsUris() throws Exception {
+        final Uri queryUri = Uri.parse("content://com.android.cts.providerapp");
+        try (Cursor cursor = mContext.getContentResolver().query(queryUri, null, null, null)) {
+            final MockContentObserver observer = new MockContentObserver();
+            cursor.registerContentObserver(observer);
+
+            mContext.getContentResolver().call(queryUri, "notify", "1", null);
+            observer.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
+            observer.resetOnChangeWatch();
+            mContext.getContentResolver().call(queryUri, "notify", "2", null);
+            observer.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
+        }
+    }
+
+    public void testGetNotificationUris() throws Exception {
+        final Uri[] notificationUris = new Uri[] {
+                Settings.Global.getUriFor(Settings.Global.MODE_RINGER),
+                Settings.Global.getUriFor(Settings.Global.BOOT_COUNT)
+        };
+        mDatabaseCursor.setNotificationUris(mContext.getContentResolver(),
+                Arrays.asList(notificationUris));
+        final List<Uri> actualNotificationUris = mDatabaseCursor.getNotificationUris();
+        Assert.assertArrayEquals(notificationUris, actualNotificationUris.toArray(new Uri[0]));
+    }
+
     public void testRespond() {
         Bundle b = new Bundle();
         Bundle bundle = mDatabaseCursor.respond(b);
@@ -159,14 +211,8 @@
     public void testOnChange() throws InterruptedException {
         MockContentObserver mock = new MockContentObserver();
         mTestAbstractCursor.registerContentObserver(mock);
-        assertFalse(mock.hadCalledOnChange());
         mTestAbstractCursor.onChange(true);
-        synchronized(mLockObj) {
-            if ( !mock.hadCalledOnChange() ) {
-                mLockObj.wait(5000);
-            }
-        }
-        assertTrue(mock.hadCalledOnChange());
+        mock.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
     }
 
     public void testOnMove() {
@@ -588,19 +634,18 @@
     }
 
     private class MockContentObserver extends ContentObserver {
-        public boolean mHadCalledOnChange;
+        private CountDownLatch mCountDownLatch;
 
         public MockContentObserver() {
             super(null);
+
+            mCountDownLatch = new CountDownLatch(1);
         }
 
         @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
-            mHadCalledOnChange = true;
-            synchronized(mLockObj) {
-                mLockObj.notify();
-            }
+            mCountDownLatch.countDown();
         }
 
         @Override
@@ -608,8 +653,14 @@
             return true;
         }
 
-        public boolean hadCalledOnChange() {
-            return mHadCalledOnChange;
+        public void resetOnChangeWatch() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        public void waitForOnChange(long timeoutMs) throws InterruptedException {
+            if (!mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                fail("Timed out waiting for onChange() to get called");
+            }
         }
     }
 
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 97b0b8f..a772881 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
@@ -16,7 +16,12 @@
 
 package android.database.sqlite.cts;
 
+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.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteCursor;
@@ -26,38 +31,49 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.os.CancellationSignal;
 import android.os.OperationCanceledException;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Semaphore;
 
-public class SQLiteQueryBuilderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteQueryBuilderTest {
     private SQLiteDatabase mDatabase;
     private final String TEST_TABLE_NAME = "test";
     private final String EMPLOYEE_TABLE_NAME = "employee";
     private static final String DATABASE_FILE = "database_test.db";
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
 
-        getContext().deleteDatabase(DATABASE_FILE);
-        mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null);
-        assertNotNull(mDatabase);
+        context.deleteDatabase(DATABASE_FILE);
+        mDatabase = Objects.requireNonNull(
+                context.openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null));
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
         mDatabase.close();
-        getContext().deleteDatabase(DATABASE_FILE);
-        super.tearDown();
+        context.deleteDatabase(DATABASE_FILE);
     }
 
+    @Test
     public void testConstructor() {
         new SQLiteQueryBuilder();
     }
 
+    @Test
     public void testSetDistinct() {
         String expected;
         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
@@ -65,7 +81,7 @@
         sqliteQueryBuilder.setDistinct(false);
         sqliteQueryBuilder.appendWhere("age=20");
         String sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         assertEquals(TEST_TABLE_NAME, sqliteQueryBuilder.getTables());
         expected = "SELECT age, address FROM " + TEST_TABLE_NAME + " WHERE (age=20)";
         assertEquals(expected, sql);
@@ -75,7 +91,7 @@
         sqliteQueryBuilder.setDistinct(true);
         sqliteQueryBuilder.appendWhere("age>32");
         sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
         expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME + " WHERE (age>32)";
         assertEquals(expected, sql);
@@ -85,13 +101,14 @@
         sqliteQueryBuilder.setDistinct(true);
         sqliteQueryBuilder.appendWhereEscapeString("age>32");
         sql = sqliteQueryBuilder.buildQuery(new String[] { "age", "address" },
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         assertEquals(EMPLOYEE_TABLE_NAME, sqliteQueryBuilder.getTables());
         expected = "SELECT DISTINCT age, address FROM " + EMPLOYEE_TABLE_NAME
                 + " WHERE ('age>32')";
         assertEquals(expected, sql);
     }
 
+    @Test
     public void testSetProjectionMap() {
         String expected;
         Map<String, String> projectMap = new HashMap<String, String>();
@@ -103,12 +120,12 @@
         sqliteQueryBuilder.setDistinct(false);
         sqliteQueryBuilder.setProjectionMap(projectMap);
         String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" },
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         expected = "SELECT name, age FROM " + TEST_TABLE_NAME;
         assertEquals(expected, sql);
 
         sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) "
                 + "FROM " + TEST_TABLE_NAME));
         assertTrue(sql.contains("age"));
@@ -117,13 +134,14 @@
 
         sqliteQueryBuilder.setProjectionMap(null);
         sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" },
-                null, null, null, null, null, null);
+                null, null, null, null, null);
         assertTrue(sql.matches("SELECT (name|address), (name|address) "
                 + "FROM " + TEST_TABLE_NAME));
         assertTrue(sql.contains("name"));
         assertTrue(sql.contains("address"));
     }
 
+    @Test
     public void testSetCursorFactory() {
         mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, " +
                 "name TEXT, age INTEGER, address TEXT);");
@@ -138,9 +156,10 @@
         assertTrue(cursor instanceof SQLiteCursor);
 
         SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
+            @Override
             public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
                     String editTable, SQLiteQuery query) {
-                return new MockCursor(db, masterQuery, editTable, query);
+                return new MockCursor(masterQuery, editTable, query);
             }
         };
 
@@ -152,12 +171,13 @@
     }
 
     private static class MockCursor extends SQLiteCursor {
-        public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
+        public MockCursor(SQLiteCursorDriver driver,
                 String editTable, SQLiteQuery query) {
-            super(db, driver, editTable, query);
+            super(driver, editTable, query);
         }
     }
 
+    @Test
     public void testBuildQueryString() {
         String expected;
         final String[] DEFAULT_TEST_PROJECTION = new String [] { "name", "age", "sum(salary)" };
@@ -176,6 +196,7 @@
         assertEquals(expected, sql);
     }
 
+    @Test
     public void testBuildQuery() {
         final String[] DEFAULT_TEST_PROJECTION = new String[] { "name", "sum(salary)" };
         final String DEFAULT_TEST_WHERE = "age > 25";
@@ -185,13 +206,14 @@
         sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
         sqliteQueryBuilder.setDistinct(false);
         String sql = sqliteQueryBuilder.buildQuery(DEFAULT_TEST_PROJECTION,
-                DEFAULT_TEST_WHERE, null, "name", DEFAULT_HAVING, "name", "2");
+                DEFAULT_TEST_WHERE, "name", DEFAULT_HAVING, "name", "2");
         String expected = "SELECT name, sum(salary) FROM " + TEST_TABLE_NAME
                 + " WHERE (" + DEFAULT_TEST_WHERE + ") " +
                 "GROUP BY name HAVING " + DEFAULT_HAVING + " ORDER BY name LIMIT 2";
         assertEquals(expected, sql);
     }
 
+    @Test
     public void testAppendColumns() {
         StringBuilder sb = new StringBuilder();
         String[] columns = new String[] { "name", "age" };
@@ -201,6 +223,7 @@
         assertEquals("name, age ", sb.toString());
     }
 
+    @Test
     public void testQuery() {
         createEmployeeTable();
 
@@ -240,6 +263,7 @@
         assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
     }
 
+    @Test
     public void testUnionQuery() {
         String expected;
         String[] innerProjection = new String[] {"name", "age", "location"};
@@ -253,12 +277,12 @@
                 "_id", innerProjection,
                 null, 2, "employee",
                 "age=25",
-                null, null, null);
+                null, null);
         String peopleSubQuery = peopleQueryBuilder.buildUnionSubQuery(
                 "_id", innerProjection,
                 null, 2, "people",
                 "location=LA",
-                null, null, null);
+                null, null);
         expected = "SELECT name, age, location FROM employee WHERE (age=25)";
         assertEquals(expected, employeeSubQuery);
         expected = "SELECT name, age, location FROM people WHERE (location=LA)";
@@ -275,6 +299,7 @@
         assertEquals(expected, unionQuery);
     }
 
+    @Test
     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
         createEmployeeTable();
 
@@ -288,6 +313,7 @@
         assertEquals(3, cursor.getCount());
     }
 
+    @Test
     public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
         createEmployeeTable();
 
@@ -306,6 +332,7 @@
         }
     }
 
+    @Test
     public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
         createEmployeeTable();
 
@@ -326,6 +353,7 @@
         }
     }
 
+    @Test
     public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
         createEmployeeTable();
 
@@ -401,6 +429,7 @@
         fail("Could not prove that the query actually blocked before cancel() was called.");
     }
 
+    @Test
     public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
         // Populate a table with a bunch of integers.
         mDatabase.execSQL("CREATE TABLE x (v INTEGER);");
@@ -460,7 +489,153 @@
         fail("Could not prove that the query actually canceled midway during execution.");
     }
 
+    @Test
+    public void testUpdate() throws Exception {
+        createEmployeeTable();
+
+        final ContentValues values = new ContentValues();
+        values.put("name", "Anonymous");
+        values.put("salary", 0);
+
+        {
+            final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("employee");
+            qb.appendWhere("month=3");
+            assertEquals(2, qb.update(mDatabase, values, null, null));
+        }
+        {
+            final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("employee");
+            assertEquals(1, qb.update(mDatabase, values, "month=?", new String[] { "2" }));
+        }
+    }
+
+    @Test
+    public void testDelete() throws Exception {
+        createEmployeeTable();
+
+        {
+            final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("employee");
+            qb.appendWhere("month=3");
+            assertEquals(2, qb.delete(mDatabase, null, null));
+            assertEquals(0, qb.delete(mDatabase, null, null));
+        }
+        {
+            final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("employee");
+            assertEquals(1, qb.delete(mDatabase, "month=?", new String[] { "2" }));
+            assertEquals(0, qb.delete(mDatabase, "month=?", new String[] { "2" }));
+        }
+    }
+
+    @Test
+    public void testStrictQuery() throws Exception {
+        createEmployeeTable();
+
+        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables("employee");
+        qb.setStrict(true);
+        qb.appendWhere("month=2");
+
+        // Should normally only be able to see one row
+        try (Cursor c = qb.query(mDatabase, null, null, null, null, null, null)) {
+            assertEquals(1, c.getCount());
+        }
+
+        // Trying sneaky queries should fail; even if they somehow succeed, we
+        // shouldn't get to see any other data.
+        try (Cursor c = qb.query(mDatabase, null, "1=1", null, null, null, null)) {
+            assertEquals(1, c.getCount());
+        } catch (Exception tolerated) {
+        }
+        try (Cursor c = qb.query(mDatabase, null, "1=1 --", null, null, null, null)) {
+            assertEquals(1, c.getCount());
+        } catch (Exception tolerated) {
+        }
+        try (Cursor c = qb.query(mDatabase, null, "1=1) OR (1=1", null, null, null, null)) {
+            assertEquals(1, c.getCount());
+        } catch (Exception tolerated) {
+        }
+        try (Cursor c = qb.query(mDatabase, null, "1=1)) OR ((1=1", null, null, null, null)) {
+            assertEquals(1, c.getCount());
+        } catch (Exception tolerated) {
+        }
+    }
+
+    @Test
+    public void testStrictUpdate() throws Exception {
+        createEmployeeTable();
+
+        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));
+
+        // Trying sneaky queries should fail; even if they somehow succeed, we
+        // shouldn't get to see any other data.
+        try {
+            assertEquals(1, qb.update(mDatabase, values, "1=1", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            assertEquals(1, qb.update(mDatabase, values, "1=1 --", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            assertEquals(1, qb.update(mDatabase, values, "1=1) OR (1=1", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            assertEquals(1, qb.update(mDatabase, values, "1=1)) OR ((1=1", null));
+        } catch (Exception tolerated) {
+        }
+    }
+
+    @Test
+    public void testStrictDelete() throws Exception {
+        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables("employee");
+        qb.setStrict(true);
+        qb.appendWhere("month=2");
+
+        // Should normally only be able to update one row
+        createEmployeeTable();
+        assertEquals(1, qb.delete(mDatabase, null, null));
+
+        // Trying sneaky queries should fail; even if they somehow succeed, we
+        // shouldn't get to see any other data.
+        try {
+            createEmployeeTable();
+            assertEquals(1, qb.delete(mDatabase, "1=1", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            createEmployeeTable();
+            assertEquals(1, qb.delete(mDatabase, "1=1 --", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            createEmployeeTable();
+            assertEquals(1, qb.delete(mDatabase, "1=1) OR (1=1", null));
+        } catch (Exception tolerated) {
+        }
+        try {
+            createEmployeeTable();
+            assertEquals(1, qb.delete(mDatabase, "1=1)) OR ((1=1", null));
+        } catch (Exception tolerated) {
+        }
+    }
+
     private void createEmployeeTable() {
+        mDatabase.execSQL("DROP TABLE IF EXISTS employee;");
         mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
                 "name TEXT, month INTEGER, salary INTEGER);");
         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteWalTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteWalTest.java
new file mode 100644
index 0000000..735e049
--- /dev/null
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteWalTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.database.sqlite.cts;
+
+import android.content.Context;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteCompatibilityWalFlags;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class SQLiteWalTest extends AndroidTestCase {
+    private static final String TAG = "SQLiteWalTest";
+
+    private static final String DB_FILE = "SQLiteWalTest.db";
+    private static final String SHM_SUFFIX = "-shm";
+    private static final String WAL_SUFFIX = "-wal";
+
+    private static final String BACKUP_SUFFIX = ".bak";
+
+    private static final String SQLITE_COMPATIBILITY_WAL_FLAGS = "sqlite_compatibility_wal_flags";
+    private static final String TRUNCATE_SIZE_KEY = "truncate_size";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        SystemUtil.runShellCommand("settings delete global " + SQLITE_COMPATIBILITY_WAL_FLAGS);
+
+        super.tearDown();
+    }
+
+    private void setCompatibilityWalFlags(String value) {
+        // settings put global sqlite_compatibility_wal_flags truncate_size=0
+
+        SystemUtil.runShellCommand("settings put global " + SQLITE_COMPATIBILITY_WAL_FLAGS + " "
+                + value);
+    }
+
+    private void copyFile(String from, String to) throws Exception {
+        (new File(to)).delete();
+
+        try (InputStream in = new FileInputStream(from)) {
+            try (OutputStream out = new FileOutputStream(to)) {
+                byte[] buf = new byte[1024 * 32];
+                int len;
+                while ((len = in.read(buf)) > 0) {
+                    out.write(buf, 0, len);
+                }
+            }
+        }
+    }
+
+    private void backupFile(String from) throws Exception {
+        copyFile(from, from + BACKUP_SUFFIX);
+    }
+
+    private void restoreFile(String from) throws Exception {
+        copyFile(from + BACKUP_SUFFIX, from);
+    }
+
+    private SQLiteDatabase openDatabase() {
+        SQLiteDatabase db = mContext.openOrCreateDatabase(
+                DB_FILE, Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null);
+        db.execSQL("PRAGMA synchronous=FULL");
+        return db;
+    }
+
+    private void assertTestTableExists(SQLiteDatabase db) {
+        assertEquals(1, DatabaseUtils.longForQuery(db, "SELECT count(*) FROM test", null));
+    }
+
+    private SQLiteDatabase prepareDatabase() {
+        SQLiteDatabase db = openDatabase();
+
+        db.execSQL("CREATE TABLE test (column TEXT);");
+        db.execSQL("INSERT INTO test (column) VALUES ("
+                + "'12345678901234567890123456789012345678901234567890')");
+
+        // Make sure all the 3 files exist and are bigger than 0 bytes.
+        assertTrue((new File(db.getPath())).exists());
+        assertTrue((new File(db.getPath() + SHM_SUFFIX)).exists());
+        assertTrue((new File(db.getPath() + WAL_SUFFIX)).exists());
+
+        assertTrue((new File(db.getPath())).length() > 0);
+        assertTrue((new File(db.getPath() + SHM_SUFFIX)).length() > 0);
+        assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
+
+        // Make sure the table has 1 row.
+        assertTestTableExists(db);
+
+        return db;
+    }
+
+    /**
+     * Open a WAL database when the WAL file size is bigger than the threshold, and make sure
+     * the file gets truncated.
+     */
+    public void testWalTruncate() throws Exception {
+        mContext.deleteDatabase(DB_FILE);
+
+        // Truncate the WAL file if it's bigger than 1 byte.
+        setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=1");
+        SQLiteCompatibilityWalFlags.reset();
+
+        SQLiteDatabase db = doOperation("testWalTruncate");
+
+        // Make sure the WAL file is truncated into 0 bytes.
+        assertEquals(0, (new File(db.getPath() + WAL_SUFFIX)).length());
+    }
+
+    /**
+     * Open a WAL database when the WAL file size is smaller than the threshold, and make sure
+     * the file does *not* get truncated.
+     */
+    public void testWalNoTruncate() throws Exception {
+        mContext.deleteDatabase(DB_FILE);
+
+        setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=1000000");
+        SQLiteCompatibilityWalFlags.reset();
+
+        SQLiteDatabase db = doOperation("testWalNoTruncate");
+
+        assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
+    }
+
+    /**
+     * When "truncate size" is set to 0, we don't truncate the wal file.
+     */
+    public void testWalTruncateDisabled() throws Exception {
+        mContext.deleteDatabase(DB_FILE);
+
+        setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=0");
+        SQLiteCompatibilityWalFlags.reset();
+
+        SQLiteDatabase db = doOperation("testWalTruncateDisabled");
+
+        assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
+    }
+
+    private SQLiteDatabase doOperation(String message) throws Exception {
+        listFiles(message + ": start");
+
+        SQLiteDatabase db = prepareDatabase();
+
+        listFiles(message + ": DB created and prepared");
+
+        // db.close() will remove the wal file, so back the files up.
+        backupFile(db.getPath());
+        backupFile(db.getPath() + SHM_SUFFIX);
+        backupFile(db.getPath() + WAL_SUFFIX);
+
+        listFiles(message + ": backup created");
+
+        // Close the DB, this will remove the WAL file.
+        db.close();
+
+        listFiles(message + ": DB closed");
+
+        // Restore the files.
+        restoreFile(db.getPath());
+        restoreFile(db.getPath() + SHM_SUFFIX);
+        restoreFile(db.getPath() + WAL_SUFFIX);
+
+        listFiles(message + ": DB restored");
+
+        // Open the DB again.
+        db = openDatabase();
+
+        listFiles(message + ": DB re-opened");
+
+        // Make sure the table still exists.
+        assertTestTableExists(db);
+
+        return db;
+    }
+
+    private void listFiles(String message) {
+        final File dir = mContext.getDatabasePath("a").getParentFile();
+        Log.i(TAG, "Listing files: " + message + " (" + dir.getAbsolutePath() + ")");
+
+        final File[] files = mContext.getDatabasePath("a").getParentFile().listFiles();
+        if (files == null || files.length == 0) {
+            Log.i(TAG, "  No files found");
+            return;
+        }
+        for (File f : files) {
+            Log.i(TAG, "  file: " + f.getName() + " " + f.length() + " bytes");
+        }
+    }
+}
diff --git a/tests/tests/display/AndroidTest.xml b/tests/tests/display/AndroidTest.xml
index 3e1b38d..6eeb53b 100644
--- a/tests/tests/display/AndroidTest.xml
+++ b/tests/tests/display/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Display 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDisplayTestCases.apk" />
diff --git a/tests/tests/display/src/android/display/cts/BrightnessTest.java b/tests/tests/display/src/android/display/cts/BrightnessTest.java
index 6b01df2..a4e3aa3 100644
--- a/tests/tests/display/src/android/display/cts/BrightnessTest.java
+++ b/tests/tests/display/src/android/display/cts/BrightnessTest.java
@@ -19,16 +19,19 @@
 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.Manifest;
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessCorrection;
 import android.hardware.display.DisplayManager;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
@@ -210,10 +213,20 @@
         BrightnessConfiguration config =
                 new BrightnessConfiguration.Builder(
                         new float[]{0.0f, 1000.0f},new float[]{20.0f, 500.0f})
+                        .addCorrectionByCategory(ApplicationInfo.CATEGORY_IMAGE,
+                                BrightnessCorrection.createScaleAndTranslateLog(0.80f, 0.2f))
+                        .addCorrectionByPackageName("some.package.name",
+                                BrightnessCorrection.createScaleAndTranslateLog(0.70f, 0.1f))
                         .setDescription("some test").build();
         mDisplayManager.setBrightnessConfiguration(config);
         BrightnessConfiguration returnedConfig = mDisplayManager.getBrightnessConfiguration();
         assertEquals(config, returnedConfig);
+        assertEquals(config.getCorrectionByCategory(ApplicationInfo.CATEGORY_IMAGE),
+                BrightnessCorrection.createScaleAndTranslateLog(0.80f, 0.2f));
+        assertEquals(config.getCorrectionByPackageName("some.package.name"),
+                BrightnessCorrection.createScaleAndTranslateLog(0.70f, 0.1f));
+        assertNull(config.getCorrectionByCategory(ApplicationInfo.CATEGORY_GAME));
+        assertNull(config.getCorrectionByPackageName("someother.package.name"));
 
         // After clearing the curve we should get back the default curve.
         mDisplayManager.setBrightnessConfiguration(null);
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index cb8e7cd..8283d61 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
@@ -192,7 +193,7 @@
         HdrCapabilities cap = display.getHdrCapabilities();
         int[] hdrTypes = cap.getSupportedHdrTypes();
         for (int type : hdrTypes) {
-            assertTrue(type >= 1 && type <= 3);
+            assertTrue(type >= 1 && type <= 4);
         }
         assertFalse(cap.getDesiredMaxLuminance() < -1.0f);
         assertFalse(cap.getDesiredMinLuminance() < -1.0f);
@@ -372,6 +373,22 @@
     }
 
     /**
+     * Verify that getColorSpace method returns the expected color space of the display.
+     */
+    @Test
+    public void testGetPreferredWideGamutColorSpace() {
+        final Display defaultDisplay = mWindowManager.getDefaultDisplay();
+        final ColorSpace colorSpace = defaultDisplay.getPreferredWideGamutColorSpace();
+
+        if (defaultDisplay.isWideColorGamut()) {
+            assertFalse(colorSpace.isSrgb());
+            assertTrue(colorSpace.isWideGamut());
+        } else {
+            assertNull(colorSpace);
+        }
+    }
+
+    /**
      * Used to force mode changes on a display.
      * <p>
      * Note that due to limitations of the Presentation class, the modes must have the same size
diff --git a/tests/tests/dpi/AndroidTest.xml b/tests/tests/dpi/AndroidTest.xml
index 5e07b7c..7a7fc6b 100644
--- a/tests/tests/dpi/AndroidTest.xml
+++ b/tests/tests/dpi/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS DPI 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDpiTestCases.apk" />
diff --git a/tests/tests/dpi2/Android.mk b/tests/tests/dpi2/Android.mk
index f366781..8b31db5 100644
--- a/tests/tests/dpi2/Android.mk
+++ b/tests/tests/dpi2/Android.mk
@@ -31,6 +31,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 3
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/dpi2/AndroidTest.xml b/tests/tests/dpi2/AndroidTest.xml
index 0c2f6dc..4928894 100644
--- a/tests/tests/dpi2/AndroidTest.xml
+++ b/tests/tests/dpi2/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS DPI test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- These target Cupcake not applicable to instant apps which target Oreo+ -->
+    <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" />
         <option name="test-file-name" value="CtsDpiTestCases2.apk" />
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index 86b1131..d04dfdd 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Dreams test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="sysui" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/drm/lib/TestPlugin.cpp b/tests/tests/drm/lib/TestPlugin.cpp
index 5708909..77871c7 100644
--- a/tests/tests/drm/lib/TestPlugin.cpp
+++ b/tests/tests/drm/lib/TestPlugin.cpp
@@ -110,12 +110,12 @@
     return rightsStatus;
 }
 
-status_t TestPlugIn::onConsumeRights(int uniqueId, DecryptHandle* decryptHandle,
+status_t TestPlugIn::onConsumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle,
             int action, bool reserve) {
     return DRM_NO_ERROR;
 }
 
-status_t TestPlugIn::onSetPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle,
+status_t TestPlugIn::onSetPlaybackStatus(int uniqueId, sp<DecryptHandle>& decryptHandle,
             int playbackStatus, int64_t position) {
     return DRM_NO_ERROR;
 }
@@ -147,35 +147,35 @@
 }
 
 status_t TestPlugIn::onOpenDecryptSession(
-            int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) {
+            int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length) {
     return DRM_ERROR_CANNOT_HANDLE;
 }
 
 status_t TestPlugIn::onOpenDecryptSession(
-            int uniqueId, DecryptHandle* decryptHandle, const char* uri) {
+            int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri) {
     return DRM_ERROR_CANNOT_HANDLE;
 }
 
-status_t TestPlugIn::onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle) {
+status_t TestPlugIn::onCloseDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) {
     return DRM_NO_ERROR;
 }
 
-status_t TestPlugIn::onInitializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle,
+status_t TestPlugIn::onInitializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle,
             int decryptUnitId, const DrmBuffer* headerInfo) {
     return DRM_NO_ERROR;
 }
 
-status_t TestPlugIn::onDecrypt(int uniqueId, DecryptHandle* decryptHandle,
+status_t TestPlugIn::onDecrypt(int uniqueId, sp<DecryptHandle>& decryptHandle,
             int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) {
     return DRM_NO_ERROR;
 }
 
 status_t TestPlugIn::onFinalizeDecryptUnit(
-            int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) {
+            int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) {
     return DRM_NO_ERROR;
 }
 
-ssize_t TestPlugIn::onPread(int uniqueId, DecryptHandle* decryptHandle,
+ssize_t TestPlugIn::onPread(int uniqueId, sp<DecryptHandle>& decryptHandle,
             void* buffer, ssize_t numBytes, off64_t offset) {
     return 0;
 }
diff --git a/tests/tests/drm/lib/TestPlugin.h b/tests/tests/drm/lib/TestPlugin.h
index 40d4ec7..da0c79a 100644
--- a/tests/tests/drm/lib/TestPlugin.h
+++ b/tests/tests/drm/lib/TestPlugin.h
@@ -53,10 +53,10 @@
 
     int onCheckRightsStatus(int uniqueId, const String8& path, int action);
 
-    status_t onConsumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve);
+    status_t onConsumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve);
 
     status_t onSetPlaybackStatus(
-            int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position);
+            int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position);
 
     bool onValidateAction(
             int uniqueId, const String8& path, int action, const ActionDescription& description);
@@ -74,22 +74,22 @@
     DrmSupportInfo* onGetSupportInfo(int uniqueId);
 
     status_t onOpenDecryptSession(
-            int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length);
+            int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length);
 
     status_t onOpenDecryptSession(
-            int uniqueId, DecryptHandle* decryptHandle, const char* uri);
+            int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri);
 
-    status_t onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle);
+    status_t onCloseDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle);
 
-    status_t onInitializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle,
+    status_t onInitializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle,
             int decryptUnitId, const DrmBuffer* headerInfo);
 
-    status_t onDecrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId,
+    status_t onDecrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId,
             const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV);
 
-    status_t onFinalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId);
+    status_t onFinalizeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId);
 
-    ssize_t onPread(int uniqueId, DecryptHandle* decryptHandle,
+    ssize_t onPread(int uniqueId, sp<DecryptHandle>& decryptHandle,
             void* buffer, ssize_t numBytes, off64_t offset);
 
 private:
diff --git a/tests/tests/drm/src/android/drm/cts/DRMTest.java b/tests/tests/drm/src/android/drm/cts/DRMTest.java
index e632842..62156e5 100644
--- a/tests/tests/drm/src/android/drm/cts/DRMTest.java
+++ b/tests/tests/drm/src/android/drm/cts/DRMTest.java
@@ -52,6 +52,7 @@
 import android.media.MediaExtractor;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
+import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 
@@ -131,6 +132,11 @@
         }
     }
 
+    public void testSupportsHttps() throws Exception {
+        mDrmManagerClient.getConstraints(Uri.parse("https://www.foo.com"),
+                                         DrmStore.Action.DEFAULT);
+    }
+
     public void testCanHandle() throws Exception {
         for (Config config : mConfigs) {
             assertTrue("Failed on plugin: " + config.getPluginName(),
diff --git a/tests/tests/dynamic_linker/Android.mk b/tests/tests/dynamic_linker/Android.mk
index ef122ca..be1e782 100644
--- a/tests/tests/dynamic_linker/Android.mk
+++ b/tests/tests/dynamic_linker/Android.mk
@@ -43,6 +43,19 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, .)
 LOCAL_MULTILIB := both
 LOCAL_JNI_SHARED_LIBRARIES := libdynamiclinker_native_lib_a libdynamiclinker_native_lib_b
+LOCAL_MANIFEST_FILE := AndroidManifest_27.xml
+LOCAL_PACKAGE_NAME := CtsDynamicLinkerTestCases27
+LOCAL_SDK_VERSION := current
+LOCAL_COMPATIBILITY_SUITE := cts vts
+include $(BUILD_CTS_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_SRC_FILES := $(call all-java-files-under, .)
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := libdynamiclinker_native_lib_a libdynamiclinker_native_lib_b
 LOCAL_MANIFEST_FILE := AndroidManifest.xml
 LOCAL_PACKAGE_NAME := CtsDynamicLinkerTestCases
 LOCAL_SDK_VERSION := current
diff --git a/tests/tests/dynamic_linker/AndroidManifest.xml b/tests/tests/dynamic_linker/AndroidManifest.xml
index db8099e..7a9166d 100644
--- a/tests/tests/dynamic_linker/AndroidManifest.xml
+++ b/tests/tests/dynamic_linker/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.dynamiclinker">
 
-    <application android:extractNativeLibs="false">
+    <application>
         <uses-library android:name="android.test.runner" />
     </application>
     <instrumentation
diff --git a/tests/tests/dynamic_linker/AndroidManifest_27.xml b/tests/tests/dynamic_linker/AndroidManifest_27.xml
new file mode 100644
index 0000000..eae214a
--- /dev/null
+++ b/tests/tests/dynamic_linker/AndroidManifest_27.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="com.android.dynamiclinker27">
+    <!-- Before 27, extractNativeLibs is default to true. -->
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
+
+    <application android:extractNativeLibs="false">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation
+        android:targetPackage="com.android.dynamiclinker27"
+        android:name="android.support.test.runner.AndroidJUnitRunner" />
+</manifest>
diff --git a/tests/tests/dynamic_linker/AndroidTest.xml b/tests/tests/dynamic_linker/AndroidTest.xml
index 14078ff..e345321 100644
--- a/tests/tests/dynamic_linker/AndroidTest.xml
+++ b/tests/tests/dynamic_linker/AndroidTest.xml
@@ -19,9 +19,13 @@
     <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" />
+        <option name="test-file-name" value="CtsDynamicLinkerTestCases27.apk" />
         <option name="test-file-name" value="CtsDynamicLinkerTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.dynamiclinker27" />
+    </test>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.dynamiclinker" />
     </test>
 </configuration>
diff --git a/tests/tests/dynamic_linker/com/android/dynamiclinker/DynamicLinkerTest.java b/tests/tests/dynamic_linker/com/android/dynamiclinker/DynamicLinkerTest.java
index f0d7c4b..a23bc50 100644
--- a/tests/tests/dynamic_linker/com/android/dynamiclinker/DynamicLinkerTest.java
+++ b/tests/tests/dynamic_linker/com/android/dynamiclinker/DynamicLinkerTest.java
@@ -19,6 +19,8 @@
 import junit.framework.TestCase;
 import android.support.test.InstrumentationRegistry;
 
+import java.io.File;
+
 public class DynamicLinkerTest extends TestCase {
 
   private native int functionA();
@@ -47,4 +49,9 @@
     assertEquals(1, functionB());
   }
 
+  public void testNativeLibraryNotExtracted() {
+    File dir = new File(InstrumentationRegistry.getContext().getApplicationInfo().nativeLibraryDir);
+    assertTrue(dir.isDirectory());
+    assertEquals(0, dir.list().length);
+  }
 }
diff --git a/tests/tests/externalservice/AndroidTest.xml b/tests/tests/externalservice/AndroidTest.xml
index 258b034..cc7b930 100644
--- a/tests/tests/externalservice/AndroidTest.xml
+++ b/tests/tests/externalservice/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS External Service test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- 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" />
     <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/gesture/Android.mk b/tests/tests/gesture/Android.mk
index 4be4bed..56e3b10 100755
--- a/tests/tests/gesture/Android.mk
+++ b/tests/tests/gesture/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.runner
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
diff --git a/tests/tests/gesture/AndroidTest.xml b/tests/tests/gesture/AndroidTest.xml
index 5276c29..10d1e6a 100644
--- a/tests/tests/gesture/AndroidTest.xml
+++ b/tests/tests/gesture/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Gesture 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="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsGestureTestCases.apk" />
diff --git a/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttf b/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttf
deleted file mode 100644
index 17cf0c7..0000000
--- a/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttf
+++ /dev/null
Binary files differ
diff --git a/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttx b/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttx
deleted file mode 100644
index e74087a..0000000
--- a/tests/tests/graphics/assets/ascii_a3em_weight100_upright.ttx
+++ /dev/null
@@ -1,208 +0,0 @@
-<?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.
--->
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
-
-  <GlyphOrder>
-    <GlyphID id="0" name=".notdef"/>
-    <GlyphID id="1" name="1em"/>
-    <GlyphID id="2" name="3em"/>
-  </GlyphOrder>
-
-  <head>
-    <tableVersion value="1.0"/>
-    <fontRevision value="1.0"/>
-    <checkSumAdjustment value="0x640cdb2f"/>
-    <magicNumber value="0x5f0f3cf5"/>
-    <flags value="00000000 00000011"/>
-    <unitsPerEm value="1000"/>
-    <created value="Thu Feb 22 10:04:28 2018"/>
-    <macStyle value="00000000 00000000"/>
-    <lowestRecPPEM value="7"/>
-    <fontDirectionHint value="2"/>
-    <glyphDataFormat value="0"/>
-  </head>
-
-  <hhea>
-    <tableVersion value="0x00010000"/>
-    <ascent value="1000"/>
-    <descent value="-200"/>
-    <lineGap value="0"/>
-    <caretSlopeRise value="1"/>
-    <caretSlopeRun value="0"/>
-    <caretOffset value="0"/>
-    <reserved0 value="0"/>
-    <reserved1 value="0"/>
-    <reserved2 value="0"/>
-    <reserved3 value="0"/>
-    <metricDataFormat value="0"/>
-  </hhea>
-
-  <maxp>
-    <tableVersion value="0x10000"/>
-    <maxZones value="0"/>
-    <maxTwilightPoints value="0"/>
-    <maxStorage value="0"/>
-    <maxFunctionDefs value="0"/>
-    <maxInstructionDefs value="0"/>
-    <maxStackElements value="0"/>
-    <maxSizeOfInstructions value="0"/>
-    <maxComponentElements value="0"/>
-  </maxp>
-
-  <OS_2>
-    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
-         will be recalculated by the compiler -->
-    <version value="3"/>
-    <xAvgCharWidth value="594"/>
-    <usWeightClass value="100"/>
-    <usWidthClass value="5"/>
-    <fsType value="00000000 00001000"/>
-    <ySubscriptXSize value="650"/>
-    <ySubscriptYSize value="600"/>
-    <ySubscriptXOffset value="0"/>
-    <ySubscriptYOffset value="75"/>
-    <ySuperscriptXSize value="650"/>
-    <ySuperscriptYSize value="600"/>
-    <ySuperscriptXOffset value="0"/>
-    <ySuperscriptYOffset value="350"/>
-    <yStrikeoutSize value="50"/>
-    <yStrikeoutPosition value="300"/>
-    <sFamilyClass value="0"/>
-    <panose>
-      <bFamilyType value="0"/>
-      <bSerifStyle value="0"/>
-      <bWeight value="5"/>
-      <bProportion value="0"/>
-      <bContrast value="0"/>
-      <bStrokeVariation value="0"/>
-      <bArmStyle value="0"/>
-      <bLetterForm value="0"/>
-      <bMidline value="0"/>
-      <bXHeight value="0"/>
-    </panose>
-    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
-    <achVendID value="UKWN"/>
-    <fsSelection value="00000000 01000000"/>
-    <usFirstCharIndex value="32"/>
-    <usLastCharIndex value="122"/>
-    <sTypoAscender value="800"/>
-    <sTypoDescender value="-200"/>
-    <sTypoLineGap value="200"/>
-    <usWinAscent value="1000"/>
-    <usWinDescent value="200"/>
-    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
-    <sxHeight value="500"/>
-    <sCapHeight value="700"/>
-    <usDefaultChar value="0"/>
-    <usBreakChar value="32"/>
-    <usMaxContext value="0"/>
-  </OS_2>
-
-  <hmtx>
-    <mtx name=".notdef" width="500" lsb="93"/>
-    <mtx name="1em" width="1000" lsb="93"/>
-    <mtx name="3em" width="3000" lsb="93"/>
-  </hmtx>
-
-  <cmap>
-    <tableVersion version="0"/>
-    <cmap_format_4 platformID="3" platEncID="10" language="0">
-        <map code="0x0061" name="3em" /> <!-- a -->
-        <map code="0x0062" name="1em" /> <!-- b -->
-        <map code="0x0063" name="1em" /> <!-- c -->
-        <map code="0x0064" name="1em" /> <!-- d -->
-        <map code="0x0065" name="1em" /> <!-- e -->
-        <map code="0x0066" name="1em" /> <!-- f -->
-        <map code="0x0067" name="1em" /> <!-- g -->
-        <map code="0x0068" name="1em" /> <!-- h -->
-        <map code="0x0069" name="1em" /> <!-- i -->
-        <map code="0x006A" name="1em" /> <!-- j -->
-        <map code="0x006B" name="1em" /> <!-- k -->
-        <map code="0x006C" name="1em" /> <!-- l -->
-        <map code="0x006D" name="1em" /> <!-- m -->
-        <map code="0x006E" name="1em" /> <!-- n -->
-        <map code="0x006F" name="1em" /> <!-- o -->
-        <map code="0x0070" name="1em" /> <!-- p -->
-        <map code="0x0071" name="1em" /> <!-- q -->
-        <map code="0x0072" name="1em" /> <!-- r -->
-        <map code="0x0073" name="1em" /> <!-- s -->
-        <map code="0x0074" name="1em" /> <!-- t -->
-        <map code="0x0075" name="1em" /> <!-- u -->
-        <map code="0x0076" name="1em" /> <!-- v -->
-        <map code="0x0077" name="1em" /> <!-- w -->
-        <map code="0x0078" name="1em" /> <!-- x -->
-        <map code="0x0079" name="1em" /> <!-- y -->
-        <map code="0x007A" name="1em" /> <!-- z -->
-    </cmap_format_4>
-  </cmap>
-
-  <loca>
-    <!-- The 'loca' table will be calculated by the compiler -->
-  </loca>
-
-  <glyf>
-    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
-  </glyf>
-
-  <name>
-    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
-      Copyright (C) 2018 The Android Open Source Project
-    </namerecord>
-    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
-      Regular
-    </namerecord>
-    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
-    </namerecord>
-    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
-      Licensed under the Apache License, Version 2.0 (the "License");
-      you may not use this file except in compliance with the License.
-      Unless required by applicable law or agreed to in writing, software
-      distributed under the License is distributed on an "AS IS" BASIS
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-      See the License for the specific language governing permissions and
-      limitations under the License.
-    </namerecord>
-    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
-      http://www.apache.org/licenses/LICENSE-2.0
-    </namerecord>
-  </name>
-
-  <post>
-    <formatType value="3.0"/>
-    <italicAngle value="0.0"/>
-    <underlinePosition value="-75"/>
-    <underlineThickness value="50"/>
-    <isFixedPitch value="0"/>
-    <minMemType42 value="0"/>
-    <maxMemType42 value="0"/>
-    <minMemType1 value="0"/>
-    <maxMemType1 value="0"/>
-  </post>
-
-</ttFont>
diff --git a/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttf b/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttf
deleted file mode 100644
index 36ed3ce..0000000
--- a/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttf
+++ /dev/null
Binary files differ
diff --git a/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttx b/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttx
deleted file mode 100644
index 4ce17a5..0000000
--- a/tests/tests/graphics/assets/ascii_b3em_weight100_italic.ttx
+++ /dev/null
@@ -1,208 +0,0 @@
-<?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.
--->
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
-
-  <GlyphOrder>
-    <GlyphID id="0" name=".notdef"/>
-    <GlyphID id="1" name="1em"/>
-    <GlyphID id="2" name="3em"/>
-  </GlyphOrder>
-
-  <head>
-    <tableVersion value="1.0"/>
-    <fontRevision value="1.0"/>
-    <checkSumAdjustment value="0x640cdb2f"/>
-    <magicNumber value="0x5f0f3cf5"/>
-    <flags value="00000000 00000011"/>
-    <unitsPerEm value="1000"/>
-    <created value="Thu Feb 22 10:04:28 2018"/>
-    <macStyle value="00000000 00000010"/>
-    <lowestRecPPEM value="7"/>
-    <fontDirectionHint value="2"/>
-    <glyphDataFormat value="0"/>
-  </head>
-
-  <hhea>
-    <tableVersion value="0x00010000"/>
-    <ascent value="1000"/>
-    <descent value="-200"/>
-    <lineGap value="0"/>
-    <caretSlopeRise value="1"/>
-    <caretSlopeRun value="0"/>
-    <caretOffset value="0"/>
-    <reserved0 value="0"/>
-    <reserved1 value="0"/>
-    <reserved2 value="0"/>
-    <reserved3 value="0"/>
-    <metricDataFormat value="0"/>
-  </hhea>
-
-  <maxp>
-    <tableVersion value="0x10000"/>
-    <maxZones value="0"/>
-    <maxTwilightPoints value="0"/>
-    <maxStorage value="0"/>
-    <maxFunctionDefs value="0"/>
-    <maxInstructionDefs value="0"/>
-    <maxStackElements value="0"/>
-    <maxSizeOfInstructions value="0"/>
-    <maxComponentElements value="0"/>
-  </maxp>
-
-  <OS_2>
-    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
-         will be recalculated by the compiler -->
-    <version value="3"/>
-    <xAvgCharWidth value="594"/>
-    <usWeightClass value="100"/>
-    <usWidthClass value="5"/>
-    <fsType value="00000000 00001000"/>
-    <ySubscriptXSize value="650"/>
-    <ySubscriptYSize value="600"/>
-    <ySubscriptXOffset value="0"/>
-    <ySubscriptYOffset value="75"/>
-    <ySuperscriptXSize value="650"/>
-    <ySuperscriptYSize value="600"/>
-    <ySuperscriptXOffset value="0"/>
-    <ySuperscriptYOffset value="350"/>
-    <yStrikeoutSize value="50"/>
-    <yStrikeoutPosition value="300"/>
-    <sFamilyClass value="0"/>
-    <panose>
-      <bFamilyType value="0"/>
-      <bSerifStyle value="0"/>
-      <bWeight value="5"/>
-      <bProportion value="0"/>
-      <bContrast value="0"/>
-      <bStrokeVariation value="0"/>
-      <bArmStyle value="0"/>
-      <bLetterForm value="0"/>
-      <bMidline value="0"/>
-      <bXHeight value="0"/>
-    </panose>
-    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
-    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
-    <achVendID value="UKWN"/>
-    <fsSelection value="00000000 00000001"/>
-    <usFirstCharIndex value="32"/>
-    <usLastCharIndex value="122"/>
-    <sTypoAscender value="800"/>
-    <sTypoDescender value="-200"/>
-    <sTypoLineGap value="200"/>
-    <usWinAscent value="1000"/>
-    <usWinDescent value="200"/>
-    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
-    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
-    <sxHeight value="500"/>
-    <sCapHeight value="700"/>
-    <usDefaultChar value="0"/>
-    <usBreakChar value="32"/>
-    <usMaxContext value="0"/>
-  </OS_2>
-
-  <hmtx>
-    <mtx name=".notdef" width="500" lsb="93"/>
-    <mtx name="1em" width="1000" lsb="93"/>
-    <mtx name="3em" width="3000" lsb="93"/>
-  </hmtx>
-
-  <cmap>
-    <tableVersion version="0"/>
-    <cmap_format_4 platformID="3" platEncID="10" language="0">
-        <map code="0x0061" name="1em" /> <!-- a -->
-        <map code="0x0062" name="3em" /> <!-- b -->
-        <map code="0x0063" name="1em" /> <!-- c -->
-        <map code="0x0064" name="1em" /> <!-- d -->
-        <map code="0x0065" name="1em" /> <!-- e -->
-        <map code="0x0066" name="1em" /> <!-- f -->
-        <map code="0x0067" name="1em" /> <!-- g -->
-        <map code="0x0068" name="1em" /> <!-- h -->
-        <map code="0x0069" name="1em" /> <!-- i -->
-        <map code="0x006A" name="1em" /> <!-- j -->
-        <map code="0x006B" name="1em" /> <!-- k -->
-        <map code="0x006C" name="1em" /> <!-- l -->
-        <map code="0x006D" name="1em" /> <!-- m -->
-        <map code="0x006E" name="1em" /> <!-- n -->
-        <map code="0x006F" name="1em" /> <!-- o -->
-        <map code="0x0070" name="1em" /> <!-- p -->
-        <map code="0x0071" name="1em" /> <!-- q -->
-        <map code="0x0072" name="1em" /> <!-- r -->
-        <map code="0x0073" name="1em" /> <!-- s -->
-        <map code="0x0074" name="1em" /> <!-- t -->
-        <map code="0x0075" name="1em" /> <!-- u -->
-        <map code="0x0076" name="1em" /> <!-- v -->
-        <map code="0x0077" name="1em" /> <!-- w -->
-        <map code="0x0078" name="1em" /> <!-- x -->
-        <map code="0x0079" name="1em" /> <!-- y -->
-        <map code="0x007A" name="1em" /> <!-- z -->
-    </cmap_format_4>
-  </cmap>
-
-  <loca>
-    <!-- The 'loca' table will be calculated by the compiler -->
-  </loca>
-
-  <glyf>
-    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
-  </glyf>
-
-  <name>
-    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
-      Copyright (C) 2018 The Android Open Source Project
-    </namerecord>
-    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
-      Regular
-    </namerecord>
-    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
-      Sample Font
-    </namerecord>
-    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
-    </namerecord>
-    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
-      Licensed under the Apache License, Version 2.0 (the "License");
-      you may not use this file except in compliance with the License.
-      Unless required by applicable law or agreed to in writing, software
-      distributed under the License is distributed on an "AS IS" BASIS
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-      See the License for the specific language governing permissions and
-      limitations under the License.
-    </namerecord>
-    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
-      http://www.apache.org/licenses/LICENSE-2.0
-    </namerecord>
-  </name>
-
-  <post>
-    <formatType value="3.0"/>
-    <italicAngle value="0.0"/>
-    <underlinePosition value="-75"/>
-    <underlineThickness value="50"/>
-    <isFixedPitch value="0"/>
-    <minMemType42 value="0"/>
-    <maxMemType42 value="0"/>
-    <minMemType1 value="0"/>
-    <maxMemType1 value="0"/>
-  </post>
-
-</ttFont>
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding0.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding0.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding0.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding0.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding0.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding0.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding0.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding0.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding1.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding1.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding1.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding1.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding1.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding1.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding1.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding1.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding2.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding2.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding2.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding2.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding2.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding2.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding2.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding2.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding3.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding3.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding3.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding3.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding3.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding3.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding3.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding3.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding4.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding4.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding4.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding4.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding4.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding4.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding4.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding4.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding6.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding6.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding6.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding6.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform0Encoding6.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding6.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform0Encoding6.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform0Encoding6.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform3Encoding1.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding1.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform3Encoding1.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding1.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform3Encoding1.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding1.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform3Encoding1.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding1.ttx
diff --git a/tests/tests/graphics/assets/CmapPlatform3Encoding10.ttf b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding10.ttf
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform3Encoding10.ttf
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding10.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/CmapPlatform3Encoding10.ttx b/tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding10.ttx
similarity index 100%
rename from tests/tests/graphics/assets/CmapPlatform3Encoding10.ttx
rename to tests/tests/graphics/assets/fonts/cmap_selection/CmapPlatform3Encoding10.ttx
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ascii.ttc b/tests/tests/graphics/assets/fonts/family_selection/ascii.ttc
new file mode 100644
index 0000000..e404f2b
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ascii.ttc
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttf b/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttf
new file mode 100644
index 0000000..792b7d7
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttx b/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttx
new file mode 100644
index 0000000..22b2941
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ascii_vf.ttx
@@ -0,0 +1,1942 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="a"/>
+    <GlyphID id="2" name="b"/>
+    <GlyphID id="3" name="c"/>
+    <GlyphID id="4" name="d"/>
+    <GlyphID id="5" name="e"/>
+    <GlyphID id="6" name="f"/>
+    <GlyphID id="7" name="g"/>
+    <GlyphID id="8" name="h"/>
+    <GlyphID id="9" name="i"/>
+    <GlyphID id="10" name="j"/>
+    <GlyphID id="11" name="k"/>
+    <GlyphID id="12" name="l"/>
+    <GlyphID id="13" name="m"/>
+    <GlyphID id="14" name="n"/>
+    <GlyphID id="15" name="o"/>
+    <GlyphID id="16" name="p"/>
+    <GlyphID id="17" name="q"/>
+    <GlyphID id="18" name="r"/>
+    <GlyphID id="19" name="s"/>
+    <GlyphID id="20" name="t"/>
+    <GlyphID id="21" name="u"/>
+    <GlyphID id="22" name="v"/>
+    <GlyphID id="23" name="w"/>
+    <GlyphID id="24" name="x"/>
+    <GlyphID id="25" name="y"/>
+    <GlyphID id="26" name="z"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="50"/>
+    <created value="Thu Feb 22 10:04:28 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="100"/>
+    <descent value="0"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="50" lsb="93"/>
+    <mtx name="a" width="50" lsb="0"/>
+    <mtx name="b" width="50" lsb="0"/>
+    <mtx name="c" width="50" lsb="0"/>
+    <mtx name="d" width="50" lsb="0"/>
+    <mtx name="e" width="50" lsb="0"/>
+    <mtx name="f" width="50" lsb="0"/>
+    <mtx name="g" width="50" lsb="0"/>
+    <mtx name="h" width="50" lsb="0"/>
+    <mtx name="i" width="50" lsb="0"/>
+    <mtx name="j" width="50" lsb="0"/>
+    <mtx name="k" width="50" lsb="0"/>
+    <mtx name="l" width="50" lsb="0"/>
+    <mtx name="m" width="50" lsb="0"/>
+    <mtx name="n" width="50" lsb="0"/>
+    <mtx name="o" width="50" lsb="0"/>
+    <mtx name="p" width="50" lsb="0"/>
+    <mtx name="q" width="50" lsb="0"/>
+    <mtx name="r" width="50" lsb="0"/>
+    <mtx name="s" width="50" lsb="0"/>
+    <mtx name="t" width="50" lsb="0"/>
+    <mtx name="u" width="50" lsb="0"/>
+    <mtx name="v" width="50" lsb="0"/>
+    <mtx name="w" width="50" lsb="0"/>
+    <mtx name="x" width="50" lsb="0"/>
+    <mtx name="y" width="50" lsb="0"/>
+    <mtx name="z" width="50" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="0" platEncID="3" language="0">
+        <map code="0x0061" name="a" /> <!-- a -->
+        <map code="0x0062" name="b" /> <!-- b -->
+        <map code="0x0063" name="c" /> <!-- c -->
+        <map code="0x0064" name="d" /> <!-- d -->
+        <map code="0x0065" name="e" /> <!-- e -->
+        <map code="0x0066" name="f" /> <!-- f -->
+        <map code="0x0067" name="g" /> <!-- g -->
+        <map code="0x0068" name="h" /> <!-- h -->
+        <map code="0x0069" name="i" /> <!-- i -->
+        <map code="0x006A" name="j" /> <!-- j -->
+        <map code="0x006B" name="k" /> <!-- k -->
+        <map code="0x006C" name="l" /> <!-- l -->
+        <map code="0x006D" name="m" /> <!-- m -->
+        <map code="0x006E" name="n" /> <!-- n -->
+        <map code="0x006F" name="o" /> <!-- o -->
+        <map code="0x0070" name="p" /> <!-- p -->
+        <map code="0x0071" name="q" /> <!-- q -->
+        <map code="0x0072" name="r" /> <!-- r -->
+        <map code="0x0073" name="s" /> <!-- s -->
+        <map code="0x0074" name="t" /> <!-- t -->
+        <map code="0x0075" name="u" /> <!-- u -->
+        <map code="0x0076" name="v" /> <!-- v -->
+        <map code="0x0077" name="w" /> <!-- w -->
+        <map code="0x0078" name="x" /> <!-- x -->
+        <map code="0x0079" name="y" /> <!-- y -->
+        <map code="0x007A" name="z" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="a" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="b" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="c" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="d" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="e" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="f" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="g" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="h" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="i" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="j" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="k" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="l" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="m" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="n" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="o" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="p" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="q" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="r" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="s" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="t" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="u" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="v" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="w" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="x" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="y" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="z" xMin="0" yMin="0" xMax="50" yMax="50">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="50" y="25" on="1"/>
+        <pt x="0" y="50" on="1"/>
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <fvar>
+    <Axis>
+      <!-- Weight axis but no effects to the glyph. -->
+      <AxisTag>wght</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>400.0</DefaultValue>
+      <MaxValue>1000.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <!-- Italic axis but no effects to the glyph. -->
+      <AxisTag>ital</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asca</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascb</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascc</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascd</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asce</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascf</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascg</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asch</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asci</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascj</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asck</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascl</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascm</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascn</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asco</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascp</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascq</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascr</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascs</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Asct</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascu</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascv</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascw</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascx</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascy</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+    <Axis>
+      <AxisTag>Ascz</AxisTag>
+      <Flags>0x0</Flags>
+      <MinValue>0.0</MinValue>
+      <DefaultValue>0.0</DefaultValue>
+      <MaxValue>1.0</MaxValue>
+      <AxisNameID>256</AxisNameID>
+    </Axis>
+  </fvar>
+
+  <HVAR>
+    <Version value="0x00010000"/>
+    <VarStore Format="1">
+      <Format value="1" />
+      <VarRegionList>
+        <Region index="0">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="1">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="2">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="3">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="4">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="5">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="6">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="7">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="8">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="9">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="10">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="11">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="12">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="13">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="14">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="15">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="16">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="17">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="18">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="19">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="20">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="21">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="22">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="23">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="24">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="25">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+        <Region index="26">
+          <VarRegionAxis index="0"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="1"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="2"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="3"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="4"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="5"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="6"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="7"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="8"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="9"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="10"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="11"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="12"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="13"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="14"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="15"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="16"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="17"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="18"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="19"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="20"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="21"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="22"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="23"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="24"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="25"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="26"><StartCoord value="0" /><PeakCoord value="0" /><EndCoord value="1" /></VarRegionAxis>
+          <VarRegionAxis index="27"><StartCoord value="1" /><PeakCoord value="1" /><EndCoord value="1" /></VarRegionAxis>
+        </Region>
+      </VarRegionList>
+      <VarData index="0">
+        <NumShorts value="0" />
+        <VarRegionIndex index="0" value="0" />
+        <VarRegionIndex index="1" value="1" />
+        <VarRegionIndex index="2" value="2" />
+        <VarRegionIndex index="3" value="3" />
+        <VarRegionIndex index="4" value="4" />
+        <VarRegionIndex index="5" value="5" />
+        <VarRegionIndex index="6" value="6" />
+        <VarRegionIndex index="7" value="7" />
+        <VarRegionIndex index="8" value="8" />
+        <VarRegionIndex index="9" value="9" />
+        <VarRegionIndex index="10" value="10" />
+        <VarRegionIndex index="11" value="11" />
+        <VarRegionIndex index="12" value="12" />
+        <VarRegionIndex index="13" value="13" />
+        <VarRegionIndex index="14" value="14" />
+        <VarRegionIndex index="15" value="15" />
+        <VarRegionIndex index="16" value="16" />
+        <VarRegionIndex index="17" value="17" />
+        <VarRegionIndex index="18" value="18" />
+        <VarRegionIndex index="19" value="19" />
+        <VarRegionIndex index="20" value="20" />
+        <VarRegionIndex index="21" value="21" />
+        <VarRegionIndex index="22" value="22" />
+        <VarRegionIndex index="23" value="23" />
+        <VarRegionIndex index="24" value="24" />
+        <VarRegionIndex index="25" value="25" />
+        <VarRegionIndex index="26" value="26" />
+        <Item index="0" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="1" value="[0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="2" value="[0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="3" value="[0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="4" value="[0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="5" value="[0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="6" value="[0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="7" value="[0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="8" value="[0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="9" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="10" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="11" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="12" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="13" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="14" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="15" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="16" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="17" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="18" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="19" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0]" />
+        <Item index="20" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0]" />
+        <Item index="21" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0]" />
+        <Item index="22" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0]" />
+        <Item index="23" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0]" />
+        <Item index="24" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0]" />
+        <Item index="25" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0]" />
+        <Item index="26" value="[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100]" />
+      </VarData>
+    </VarStore>
+    <AdvWidthMap>
+      <Map index="0" outer="0" inner="0" />
+      <Map index="1" outer="0" inner="1" />
+      <Map index="2" outer="0" inner="2" />
+      <Map index="3" outer="0" inner="3" />
+      <Map index="4" outer="0" inner="4" />
+      <Map index="5" outer="0" inner="5" />
+      <Map index="6" outer="0" inner="6" />
+      <Map index="7" outer="0" inner="7" />
+      <Map index="8" outer="0" inner="8" />
+      <Map index="9" outer="0" inner="9" />
+      <Map index="10" outer="0" inner="10" />
+      <Map index="11" outer="0" inner="11" />
+      <Map index="12" outer="0" inner="12" />
+      <Map index="13" outer="0" inner="13" />
+      <Map index="14" outer="0" inner="14" />
+      <Map index="15" outer="0" inner="15" />
+      <Map index="16" outer="0" inner="16" />
+      <Map index="17" outer="0" inner="17" />
+      <Map index="18" outer="0" inner="18" />
+      <Map index="19" outer="0" inner="19" />
+      <Map index="20" outer="0" inner="20" />
+      <Map index="21" outer="0" inner="21" />
+      <Map index="22" outer="0" inner="22" />
+      <Map index="23" outer="0" inner="23" />
+      <Map index="24" outer="0" inner="24" />
+      <Map index="25" outer="0" inner="25" />
+      <Map index="26" outer="0" inner="26" />
+    </AdvWidthMap>
+  </HVAR>
+
+  <gvar>
+    <version value="1" />
+    <reserved value="0" />
+    <glyphVariations glyph="a">
+      <tuple>
+        <coord axis="Asca" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="b">
+      <tuple>
+        <coord axis="Ascb" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="c">
+      <tuple>
+        <coord axis="Ascc" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="d">
+      <tuple>
+        <coord axis="Ascd" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="e">
+      <tuple>
+        <coord axis="Asce" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="f">
+      <tuple>
+        <coord axis="Ascf" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="g">
+      <tuple>
+        <coord axis="Ascg" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="h">
+      <tuple>
+        <coord axis="Asch" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="i">
+      <tuple>
+        <coord axis="Asci" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="j">
+      <tuple>
+        <coord axis="Ascj" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="k">
+      <tuple>
+        <coord axis="Asck" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="l">
+      <tuple>
+        <coord axis="Ascl" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="m">
+      <tuple>
+        <coord axis="Ascm" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="n">
+      <tuple>
+        <coord axis="Ascn" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="o">
+      <tuple>
+        <coord axis="Asco" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="p">
+      <tuple>
+        <coord axis="Ascp" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="q">
+      <tuple>
+        <coord axis="Ascq" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="r">
+      <tuple>
+        <coord axis="Ascr" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="s">
+      <tuple>
+        <coord axis="Ascs" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="t">
+      <tuple>
+        <coord axis="Asct" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="u">
+      <tuple>
+        <coord axis="Ascu" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="v">
+      <tuple>
+        <coord axis="Ascv" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="w">
+      <tuple>
+        <coord axis="Ascw" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="x">
+      <tuple>
+        <coord axis="Ascx" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="y">
+      <tuple>
+        <coord axis="Ascy" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+    <glyphVariations glyph="z">
+      <tuple>
+        <coord axis="Ascz" value="1.0" />
+        <delta pt="0" x="0" y="0" />
+        <delta pt="1" x="100" y="0" />
+        <delta pt="2" x="0" y="0" />
+        <!-- deltas for phantom points -->
+        <delta pt="3" x="0" y="0" />  <!-- (left, 0) -->
+        <delta pt="4" x="100" y="0" />  <!-- (right, 0) -->
+        <delta pt="5" x="0" y="0" />  <!-- (0, top) -->
+        <delta pt="6" x="0" y="0" />  <!-- (0, bottom) -->
+      </tuple>
+    </glyphVariations>
+  </gvar>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+    <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+      3 em signal
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf
new file mode 100644
index 0000000..f220eb3
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttx
new file mode 100644
index 0000000..ebedcb6
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:10 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="3em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf
new file mode 100644
index 0000000..b9ffb84
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttx
new file mode 100644
index 0000000..def6a29
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="3em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttf
new file mode 100644
index 0000000..075b068
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttx
new file mode 100644
index 0000000..d201183
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_c3em_weight200_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="200"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="3em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttf
new file mode 100644
index 0000000..5b47f0d
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttx
new file mode 100644
index 0000000..7c801a0
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_d3em_weight200_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="200"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="3em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttf
new file mode 100644
index 0000000..3e9133b
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttx
new file mode 100644
index 0000000..acb2006
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_e3em_weight300_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="300"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="3em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttf
new file mode 100644
index 0000000..3ba3feb
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttx
new file mode 100644
index 0000000..452ff66
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_f3em_weight300_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="300"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="3em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttf
new file mode 100644
index 0000000..b3175a1
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttx
new file mode 100644
index 0000000..34a638f
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_g3em_weight400_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:11 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="3em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttf
new file mode 100644
index 0000000..099c4f1
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttx
new file mode 100644
index 0000000..b18af66
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_h3em_weight400_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="3em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttf
new file mode 100644
index 0000000..b8edcb6
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttx
new file mode 100644
index 0000000..6daf8da
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_i3em_weight500_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="500"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="3em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttf
new file mode 100644
index 0000000..6d7dde9
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttx
new file mode 100644
index 0000000..c5843f0
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_j3em_weight500_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="500"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="3em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttf
new file mode 100644
index 0000000..eb6d7d1
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttx
new file mode 100644
index 0000000..d46059f
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_k3em_weight600_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="600"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="3em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttf
new file mode 100644
index 0000000..f509e94
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttx
new file mode 100644
index 0000000..03073d3
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="600"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="3em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttf
new file mode 100644
index 0000000..062f299
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttx
new file mode 100644
index 0000000..ca24fde
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_m3em_weight700_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:12 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="700"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="3em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttf
new file mode 100644
index 0000000..fdd0239
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttx
new file mode 100644
index 0000000..468d591
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_n3em_weight700_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:13 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="700"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="3em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_names.txt b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_names.txt
new file mode 100644
index 0000000..986d712
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_names.txt
@@ -0,0 +1,18 @@
+ascii_a3em_weight100_upright.ttf
+ascii_b3em_weight100_italic.ttf
+ascii_c3em_weight200_upright.ttf
+ascii_d3em_weight200_italic.ttf
+ascii_e3em_weight300_upright.ttf
+ascii_f3em_weight300_italic.ttf
+ascii_g3em_weight400_upright.ttf
+ascii_h3em_weight400_italic.ttf
+ascii_i3em_weight500_upright.ttf
+ascii_j3em_weight500_italic.ttf
+ascii_k3em_weight600_upright.ttf
+ascii_l3em_weight600_italic.ttf
+ascii_m3em_weight700_upright.ttf
+ascii_n3em_weight700_italic.ttf
+ascii_o3em_weight800_upright.ttf
+ascii_p3em_weight800_italic.ttf
+ascii_q3em_weight900_upright.ttf
+ascii_r3em_weight900_italic.ttf
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttf
new file mode 100644
index 0000000..993e7fa
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttx
new file mode 100644
index 0000000..b8c712f
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_o3em_weight800_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:13 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="800"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="3em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttf
new file mode 100644
index 0000000..f0d54f0
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttx
new file mode 100644
index 0000000..5d8ee18
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_p3em_weight800_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:13 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="800"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="3em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttf
new file mode 100644
index 0000000..c776c78
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttx
new file mode 100644
index 0000000..169fd73
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_q3em_weight900_upright.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:13 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="900"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="3em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttf b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttf
new file mode 100644
index 0000000..02f6246
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttx b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttx
new file mode 100644
index 0000000..131d7b1
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/family_selection/ttf/ascii_r3em_weight900_italic.ttx
@@ -0,0 +1,208 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:13 2018"/>
+    <macStyle value="00000000 00000010"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="900"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 00000001"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="3em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttf b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttf
new file mode 100644
index 0000000..42f2f83
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
new file mode 100644
index 0000000..20ca0e7
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
@@ -0,0 +1,222 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="2em"/>
+    <GlyphID id="3" name="3em"/>
+    <GlyphID id="4" name="4em"/>
+    <GlyphID id="5" name="5em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 22 10:04:28 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="2em" width="2000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+    <mtx name="4em" width="4000" lsb="93"/>
+    <mtx name="5em" width="5000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+
+        <map code="0x2010" name="2em" /> <!-- HYPHEN -->
+        <map code="0x058A" name="3em" /> <!-- ARMENIAN HYPHEN -->
+        <map code="0x05BE" name="4em" /> <!-- MAQAF -->
+        <map code="0x1400" name="5em" /> <!-- UCAS HYPHEN -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="2em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="4em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttf b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
new file mode 100644
index 0000000..eb18c0a
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttx b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
new file mode 100644
index 0000000..18a82a9
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
@@ -0,0 +1,243 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:10 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0009" name="1em" /> <!-- TAB -->
+        <map code="0x0020" name="1em" /> <!-- SPACE -->
+        <map code="0x002C" name="1em" /> <!-- , -->
+        <map code="0x002D" name="1em" /> <!-- - -->
+        <map code="0x002E" name="1em" /> <!-- . -->
+        <map code="0x0041" name="1em" /> <!-- A -->
+        <map code="0x0042" name="1em" /> <!-- B -->
+        <map code="0x0043" name="1em" /> <!-- C -->
+        <map code="0x0044" name="1em" /> <!-- D -->
+        <map code="0x0045" name="1em" /> <!-- E -->
+        <map code="0x0046" name="1em" /> <!-- F -->
+        <map code="0x0047" name="1em" /> <!-- G -->
+        <map code="0x0048" name="1em" /> <!-- H -->
+        <map code="0x0049" name="1em" /> <!-- I -->
+        <map code="0x004A" name="1em" /> <!-- J -->
+        <map code="0x004B" name="1em" /> <!-- K -->
+        <map code="0x004C" name="1em" /> <!-- L -->
+        <map code="0x004D" name="1em" /> <!-- M -->
+        <map code="0x004E" name="1em" /> <!-- N -->
+        <map code="0x004F" name="1em" /> <!-- O -->
+        <map code="0x0050" name="1em" /> <!-- P -->
+        <map code="0x0051" name="1em" /> <!-- Q -->
+        <map code="0x0052" name="1em" /> <!-- R -->
+        <map code="0x0053" name="1em" /> <!-- S -->
+        <map code="0x0054" name="1em" /> <!-- T -->
+        <map code="0x0055" name="1em" /> <!-- U -->
+        <map code="0x0056" name="1em" /> <!-- V -->
+        <map code="0x0057" name="1em" /> <!-- W -->
+        <map code="0x0058" name="1em" /> <!-- X -->
+        <map code="0x0059" name="1em" /> <!-- Y -->
+        <map code="0x005A" name="1em" /> <!-- Z -->
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+        <contour>
+            <pt x="0" y="0" on="1"/>
+            <pt x="1000" y="500" on="1"/>
+            <pt x="0" y="1000" on="1"/>
+        </contour>
+        <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttf b/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttf
new file mode 100644
index 0000000..2e746e0
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttx b/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttx
new file mode 100644
index 0000000..76bef67
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/layout/textrunadvances.ttx
@@ -0,0 +1,188 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+    <GlyphID id="2" name="3em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+    <mtx name="3em" width="3000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="1" nGroups="1" platformID="0" platEncID="4" language="0">
+      <map code="0x0061" name="3em" /> <!-- a -->
+      <map code="0x0062" name="1em" /> <!-- b -->
+      <map code="0x0063" name="1em" /> <!-- c -->
+      <map code="0x0064" name="1em" /> <!-- d -->
+      <map code="0x0065" name="1em" /> <!-- e -->
+      <map code="0x1f600" name="3em" /> <!-- an example of surrogat pair -->
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/a3em.ttx b/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
similarity index 100%
rename from tests/tests/graphics/assets/a3em.ttx
rename to tests/tests/graphics/assets/fonts/measurement/a3em.ttx
diff --git a/tests/tests/graphics/assets/b3em.ttx b/tests/tests/graphics/assets/fonts/measurement/b3em.ttx
similarity index 100%
rename from tests/tests/graphics/assets/b3em.ttx
rename to tests/tests/graphics/assets/fonts/measurement/b3em.ttx
diff --git a/tests/tests/graphics/assets/c3em.ttx b/tests/tests/graphics/assets/fonts/measurement/c3em.ttx
similarity index 100%
rename from tests/tests/graphics/assets/c3em.ttx
rename to tests/tests/graphics/assets/fonts/measurement/c3em.ttx
diff --git a/tests/tests/graphics/assets/samplefont.ttf b/tests/tests/graphics/assets/fonts/others/samplefont.ttf
similarity index 100%
rename from tests/tests/graphics/assets/samplefont.ttf
rename to tests/tests/graphics/assets/fonts/others/samplefont.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/samplefont.ttx b/tests/tests/graphics/assets/fonts/others/samplefont.ttx
similarity index 100%
rename from tests/tests/graphics/assets/samplefont.ttx
rename to tests/tests/graphics/assets/fonts/others/samplefont.ttx
diff --git a/tests/tests/graphics/assets/samplefont2.ttf b/tests/tests/graphics/assets/fonts/others/samplefont2.ttf
similarity index 100%
rename from tests/tests/graphics/assets/samplefont2.ttf
rename to tests/tests/graphics/assets/fonts/others/samplefont2.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/samplefont2.ttx b/tests/tests/graphics/assets/fonts/others/samplefont2.ttx
similarity index 100%
rename from tests/tests/graphics/assets/samplefont2.ttx
rename to tests/tests/graphics/assets/fonts/others/samplefont2.ttx
diff --git a/tests/tests/graphics/assets/samplefont3.ttf b/tests/tests/graphics/assets/fonts/others/samplefont3.ttf
similarity index 100%
rename from tests/tests/graphics/assets/samplefont3.ttf
rename to tests/tests/graphics/assets/fonts/others/samplefont3.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/samplefont3.ttx b/tests/tests/graphics/assets/fonts/others/samplefont3.ttx
similarity index 100%
rename from tests/tests/graphics/assets/samplefont3.ttx
rename to tests/tests/graphics/assets/fonts/others/samplefont3.ttx
diff --git a/tests/tests/graphics/assets/bombfont.ttf b/tests/tests/graphics/assets/fonts/security/bombfont.ttf
similarity index 100%
rename from tests/tests/graphics/assets/bombfont.ttf
rename to tests/tests/graphics/assets/fonts/security/bombfont.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/bombfont.ttx b/tests/tests/graphics/assets/fonts/security/bombfont.ttx
similarity index 100%
rename from tests/tests/graphics/assets/bombfont.ttx
rename to tests/tests/graphics/assets/fonts/security/bombfont.ttx
diff --git a/tests/tests/graphics/assets/bombfont2.ttf b/tests/tests/graphics/assets/fonts/security/bombfont2.ttf
similarity index 100%
rename from tests/tests/graphics/assets/bombfont2.ttf
rename to tests/tests/graphics/assets/fonts/security/bombfont2.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/bombfont2.ttx b/tests/tests/graphics/assets/fonts/security/bombfont2.ttx
similarity index 100%
rename from tests/tests/graphics/assets/bombfont2.ttx
rename to tests/tests/graphics/assets/fonts/security/bombfont2.ttx
diff --git a/tests/tests/graphics/assets/ft45987.ttf b/tests/tests/graphics/assets/fonts/security/ft45987.ttf
similarity index 100%
rename from tests/tests/graphics/assets/ft45987.ttf
rename to tests/tests/graphics/assets/fonts/security/ft45987.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/ft45987.ttf.README.txt b/tests/tests/graphics/assets/fonts/security/ft45987.ttf.README.txt
similarity index 100%
rename from tests/tests/graphics/assets/ft45987.ttf.README.txt
rename to tests/tests/graphics/assets/fonts/security/ft45987.ttf.README.txt
diff --git a/tests/tests/graphics/assets/out_of_unicode_end_cmap12.ttf b/tests/tests/graphics/assets/fonts/security/out_of_unicode_end_cmap12.ttf
similarity index 100%
rename from tests/tests/graphics/assets/out_of_unicode_end_cmap12.ttf
rename to tests/tests/graphics/assets/fonts/security/out_of_unicode_end_cmap12.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/out_of_unicode_end_cmap12.ttx b/tests/tests/graphics/assets/fonts/security/out_of_unicode_end_cmap12.ttx
similarity index 100%
rename from tests/tests/graphics/assets/out_of_unicode_end_cmap12.ttx
rename to tests/tests/graphics/assets/fonts/security/out_of_unicode_end_cmap12.ttx
diff --git a/tests/tests/graphics/assets/out_of_unicode_start_cmap12.ttf b/tests/tests/graphics/assets/fonts/security/out_of_unicode_start_cmap12.ttf
similarity index 100%
rename from tests/tests/graphics/assets/out_of_unicode_start_cmap12.ttf
rename to tests/tests/graphics/assets/fonts/security/out_of_unicode_start_cmap12.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/out_of_unicode_start_cmap12.ttx b/tests/tests/graphics/assets/fonts/security/out_of_unicode_start_cmap12.ttx
similarity index 100%
rename from tests/tests/graphics/assets/out_of_unicode_start_cmap12.ttx
rename to tests/tests/graphics/assets/fonts/security/out_of_unicode_start_cmap12.ttx
diff --git a/tests/tests/graphics/assets/too_large_end_cmap12.ttf b/tests/tests/graphics/assets/fonts/security/too_large_end_cmap12.ttf
similarity index 100%
rename from tests/tests/graphics/assets/too_large_end_cmap12.ttf
rename to tests/tests/graphics/assets/fonts/security/too_large_end_cmap12.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/too_large_end_cmap12.ttx b/tests/tests/graphics/assets/fonts/security/too_large_end_cmap12.ttx
similarity index 100%
rename from tests/tests/graphics/assets/too_large_end_cmap12.ttx
rename to tests/tests/graphics/assets/fonts/security/too_large_end_cmap12.ttx
diff --git a/tests/tests/graphics/assets/too_large_start_cmap12.ttf b/tests/tests/graphics/assets/fonts/security/too_large_start_cmap12.ttf
similarity index 100%
rename from tests/tests/graphics/assets/too_large_start_cmap12.ttf
rename to tests/tests/graphics/assets/fonts/security/too_large_start_cmap12.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/too_large_start_cmap12.ttx b/tests/tests/graphics/assets/fonts/security/too_large_start_cmap12.ttx
similarity index 100%
rename from tests/tests/graphics/assets/too_large_start_cmap12.ttx
rename to tests/tests/graphics/assets/fonts/security/too_large_start_cmap12.ttx
diff --git a/tests/tests/graphics/assets/unsorted_cmap12.ttf b/tests/tests/graphics/assets/fonts/security/unsorted_cmap12.ttf
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap12.ttf
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap12.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/unsorted_cmap12.ttx b/tests/tests/graphics/assets/fonts/security/unsorted_cmap12.ttx
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap12.ttx
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap12.ttx
diff --git a/tests/tests/graphics/assets/unsorted_cmap14_default_uvs.ttf b/tests/tests/graphics/assets/fonts/security/unsorted_cmap14_default_uvs.ttf
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap14_default_uvs.ttf
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap14_default_uvs.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/unsorted_cmap14_default_uvs.ttx b/tests/tests/graphics/assets/fonts/security/unsorted_cmap14_default_uvs.ttx
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap14_default_uvs.ttx
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap14_default_uvs.ttx
diff --git a/tests/tests/graphics/assets/unsorted_cmap14_non_default_uvs.ttf b/tests/tests/graphics/assets/fonts/security/unsorted_cmap14_non_default_uvs.ttf
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap14_non_default_uvs.ttf
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap14_non_default_uvs.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/unsorted_cmap14_non_default_uvs.ttx b/tests/tests/graphics/assets/fonts/security/unsorted_cmap14_non_default_uvs.ttx
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap14_non_default_uvs.ttx
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap14_non_default_uvs.ttx
diff --git a/tests/tests/graphics/assets/unsorted_cmap4.ttf b/tests/tests/graphics/assets/fonts/security/unsorted_cmap4.ttf
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap4.ttf
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap4.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/unsorted_cmap4.ttx b/tests/tests/graphics/assets/fonts/security/unsorted_cmap4.ttx
similarity index 100%
rename from tests/tests/graphics/assets/unsorted_cmap4.ttx
rename to tests/tests/graphics/assets/fonts/security/unsorted_cmap4.ttx
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttf b/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttf
new file mode 100644
index 0000000..f344c48
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttx b/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttx
new file mode 100644
index 0000000..18a82a9
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/ascii.ttx
@@ -0,0 +1,243 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:10 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0009" name="1em" /> <!-- TAB -->
+        <map code="0x0020" name="1em" /> <!-- SPACE -->
+        <map code="0x002C" name="1em" /> <!-- , -->
+        <map code="0x002D" name="1em" /> <!-- - -->
+        <map code="0x002E" name="1em" /> <!-- . -->
+        <map code="0x0041" name="1em" /> <!-- A -->
+        <map code="0x0042" name="1em" /> <!-- B -->
+        <map code="0x0043" name="1em" /> <!-- C -->
+        <map code="0x0044" name="1em" /> <!-- D -->
+        <map code="0x0045" name="1em" /> <!-- E -->
+        <map code="0x0046" name="1em" /> <!-- F -->
+        <map code="0x0047" name="1em" /> <!-- G -->
+        <map code="0x0048" name="1em" /> <!-- H -->
+        <map code="0x0049" name="1em" /> <!-- I -->
+        <map code="0x004A" name="1em" /> <!-- J -->
+        <map code="0x004B" name="1em" /> <!-- K -->
+        <map code="0x004C" name="1em" /> <!-- L -->
+        <map code="0x004D" name="1em" /> <!-- M -->
+        <map code="0x004E" name="1em" /> <!-- N -->
+        <map code="0x004F" name="1em" /> <!-- O -->
+        <map code="0x0050" name="1em" /> <!-- P -->
+        <map code="0x0051" name="1em" /> <!-- Q -->
+        <map code="0x0052" name="1em" /> <!-- R -->
+        <map code="0x0053" name="1em" /> <!-- S -->
+        <map code="0x0054" name="1em" /> <!-- T -->
+        <map code="0x0055" name="1em" /> <!-- U -->
+        <map code="0x0056" name="1em" /> <!-- V -->
+        <map code="0x0057" name="1em" /> <!-- W -->
+        <map code="0x0058" name="1em" /> <!-- X -->
+        <map code="0x0059" name="1em" /> <!-- Y -->
+        <map code="0x005A" name="1em" /> <!-- Z -->
+        <map code="0x0061" name="1em" /> <!-- a -->
+        <map code="0x0062" name="1em" /> <!-- b -->
+        <map code="0x0063" name="1em" /> <!-- c -->
+        <map code="0x0064" name="1em" /> <!-- d -->
+        <map code="0x0065" name="1em" /> <!-- e -->
+        <map code="0x0066" name="1em" /> <!-- f -->
+        <map code="0x0067" name="1em" /> <!-- g -->
+        <map code="0x0068" name="1em" /> <!-- h -->
+        <map code="0x0069" name="1em" /> <!-- i -->
+        <map code="0x006A" name="1em" /> <!-- j -->
+        <map code="0x006B" name="1em" /> <!-- k -->
+        <map code="0x006C" name="1em" /> <!-- l -->
+        <map code="0x006D" name="1em" /> <!-- m -->
+        <map code="0x006E" name="1em" /> <!-- n -->
+        <map code="0x006F" name="1em" /> <!-- o -->
+        <map code="0x0070" name="1em" /> <!-- p -->
+        <map code="0x0071" name="1em" /> <!-- q -->
+        <map code="0x0072" name="1em" /> <!-- r -->
+        <map code="0x0073" name="1em" /> <!-- s -->
+        <map code="0x0074" name="1em" /> <!-- t -->
+        <map code="0x0075" name="1em" /> <!-- u -->
+        <map code="0x0076" name="1em" /> <!-- v -->
+        <map code="0x0077" name="1em" /> <!-- w -->
+        <map code="0x0078" name="1em" /> <!-- x -->
+        <map code="0x0079" name="1em" /> <!-- y -->
+        <map code="0x007A" name="1em" /> <!-- z -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+        <contour>
+            <pt x="0" y="0" on="1"/>
+            <pt x="1000" y="500" on="1"/>
+            <pt x="0" y="1000" on="1"/>
+        </contour>
+        <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttf b/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttf
new file mode 100644
index 0000000..d58f5df
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttx b/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttx
new file mode 100644
index 0000000..44fcee2
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/hebrew.ttx
@@ -0,0 +1,267 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="2em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 22 10:04:28 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="2em" width="2000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0591" name="2em" /> <!-- HEBREW ACCENT ETNAHTA -->
+      <map code="0x0592" name="2em" /> <!-- HEBREW ACCENT SEGOL -->
+      <map code="0x0593" name="2em" /> <!-- HEBREW ACCENT SHALSHELET -->
+      <map code="0x0594" name="2em" /> <!-- HEBREW ACCENT ZAQEF QATAN -->
+      <map code="0x0595" name="2em" /> <!-- HEBREW ACCENT ZAQEF GADOL -->
+      <map code="0x0596" name="2em" /> <!-- HEBREW ACCENT TIPEHA -->
+      <map code="0x0597" name="2em" /> <!-- HEBREW ACCENT REVIA -->
+      <map code="0x0598" name="2em" /> <!-- HEBREW ACCENT ZARQA -->
+      <map code="0x0599" name="2em" /> <!-- HEBREW ACCENT PASHTA -->
+      <map code="0x059A" name="2em" /> <!-- HEBREW ACCENT YETIV -->
+      <map code="0x059B" name="2em" /> <!-- HEBREW ACCENT TEVIR -->
+      <map code="0x059C" name="2em" /> <!-- HEBREW ACCENT GERESH -->
+      <map code="0x059D" name="2em" /> <!-- HEBREW ACCENT GERESH MUQDAM -->
+      <map code="0x059E" name="2em" /> <!-- HEBREW ACCENT GERSHAYIM -->
+      <map code="0x059F" name="2em" /> <!-- HEBREW ACCENT QARNEY PARA -->
+      <map code="0x05A0" name="2em" /> <!-- HEBREW ACCENT TELISHA GEDOLA -->
+      <map code="0x05A1" name="2em" /> <!-- HEBREW ACCENT PAZER -->
+      <map code="0x05A2" name="2em" /> <!-- HEBREW ACCENT ATNAH HAFUKH -->
+      <map code="0x05A3" name="2em" /> <!-- HEBREW ACCENT MUNAH -->
+      <map code="0x05A4" name="2em" /> <!-- HEBREW ACCENT MAHAPAKH -->
+      <map code="0x05A5" name="2em" /> <!-- HEBREW ACCENT MERKHA -->
+      <map code="0x05A6" name="2em" /> <!-- HEBREW ACCENT MERKHA KEFULA -->
+      <map code="0x05A7" name="2em" /> <!-- HEBREW ACCENT DARGA -->
+      <map code="0x05A8" name="2em" /> <!-- HEBREW ACCENT QADMA -->
+      <map code="0x05A9" name="2em" /> <!-- HEBREW ACCENT TELISHA QETANA -->
+      <map code="0x05AA" name="2em" /> <!-- HEBREW ACCENT YERAH BEN YOMO -->
+      <map code="0x05AB" name="2em" /> <!-- HEBREW ACCENT OLE -->
+      <map code="0x05AC" name="2em" /> <!-- HEBREW ACCENT ILUY -->
+      <map code="0x05AD" name="2em" /> <!-- HEBREW ACCENT DEHI -->
+      <map code="0x05AE" name="2em" /> <!-- HEBREW ACCENT ZINOR -->
+      <map code="0x05AF" name="2em" /> <!-- HEBREW MARK MASORA CIRCLE -->
+      <map code="0x05B0" name="2em" /> <!-- HEBREW POINT SHEVA -->
+      <map code="0x05B1" name="2em" /> <!-- HEBREW POINT HATAF SEGOL -->
+      <map code="0x05B2" name="2em" /> <!-- HEBREW POINT HATAF PATAH -->
+      <map code="0x05B3" name="2em" /> <!-- HEBREW POINT HATAF QAMATS -->
+      <map code="0x05B4" name="2em" /> <!-- HEBREW POINT HIRIQ -->
+      <map code="0x05B5" name="2em" /> <!-- HEBREW POINT TSERE -->
+      <map code="0x05B6" name="2em" /> <!-- HEBREW POINT SEGOL -->
+      <map code="0x05B7" name="2em" /> <!-- HEBREW POINT PATAH -->
+      <map code="0x05B8" name="2em" /> <!-- HEBREW POINT QAMATS -->
+      <map code="0x05B9" name="2em" /> <!-- HEBREW POINT HOLAM -->
+      <map code="0x05BA" name="2em" /> <!-- HEBREW POINT HOLAM HASER FOR VAV -->
+      <map code="0x05BB" name="2em" /> <!-- HEBREW POINT QUBUTS -->
+      <map code="0x05BC" name="2em" /> <!-- HEBREW POINT DAGESH OR MAPIQ -->
+      <map code="0x05BD" name="2em" /> <!-- HEBREW POINT METEG -->
+      <map code="0x05BE" name="2em" /> <!-- HEBREW PUNCTUATION MAQAF -->
+      <map code="0x05BF" name="2em" /> <!-- HEBREW POINT RAFE -->
+      <map code="0x05C0" name="2em" /> <!-- HEBREW PUNCTUATION PASEQ -->
+      <map code="0x05C1" name="2em" /> <!-- HEBREW POINT SHIN DOT -->
+      <map code="0x05C2" name="2em" /> <!-- HEBREW POINT SIN DOT -->
+      <map code="0x05C3" name="2em" /> <!-- HEBREW PUNCTUATION SOF PASUQ -->
+      <map code="0x05C4" name="2em" /> <!-- HEBREW MARK UPPER DOT -->
+      <map code="0x05C5" name="2em" /> <!-- HEBREW MARK LOWER DOT -->
+      <map code="0x05C6" name="2em" /> <!-- HEBREW PUNCTUATION NUN HAFUKHA -->
+      <map code="0x05C7" name="2em" /> <!-- HEBREW POINT QAMATS QATAN -->
+      <map code="0x05D0" name="2em" /> <!-- HEBREW LETTER ALEF -->
+      <map code="0x05D1" name="2em" /> <!-- HEBREW LETTER BET -->
+      <map code="0x05D2" name="2em" /> <!-- HEBREW LETTER GIMEL -->
+      <map code="0x05D3" name="2em" /> <!-- HEBREW LETTER DALET -->
+      <map code="0x05D4" name="2em" /> <!-- HEBREW LETTER HE -->
+      <map code="0x05D5" name="2em" /> <!-- HEBREW LETTER VAV -->
+      <map code="0x05D6" name="2em" /> <!-- HEBREW LETTER ZAYIN -->
+      <map code="0x05D7" name="2em" /> <!-- HEBREW LETTER HET -->
+      <map code="0x05D8" name="2em" /> <!-- HEBREW LETTER TET -->
+      <map code="0x05D9" name="2em" /> <!-- HEBREW LETTER YOD -->
+      <map code="0x05DA" name="2em" /> <!-- HEBREW LETTER FINAL KAF -->
+      <map code="0x05DB" name="2em" /> <!-- HEBREW LETTER KAF -->
+      <map code="0x05DC" name="2em" /> <!-- HEBREW LETTER LAMED -->
+      <map code="0x05DD" name="2em" /> <!-- HEBREW LETTER FINAL MEM -->
+      <map code="0x05DE" name="2em" /> <!-- HEBREW LETTER MEM -->
+      <map code="0x05DF" name="2em" /> <!-- HEBREW LETTER FINAL NUN -->
+      <map code="0x05E0" name="2em" /> <!-- HEBREW LETTER NUN -->
+      <map code="0x05E1" name="2em" /> <!-- HEBREW LETTER SAMEKH -->
+      <map code="0x05E2" name="2em" /> <!-- HEBREW LETTER AYIN -->
+      <map code="0x05E3" name="2em" /> <!-- HEBREW LETTER FINAL PE -->
+      <map code="0x05E4" name="2em" /> <!-- HEBREW LETTER PE -->
+      <map code="0x05E5" name="2em" /> <!-- HEBREW LETTER FINAL TSADI -->
+      <map code="0x05E6" name="2em" /> <!-- HEBREW LETTER TSADI -->
+      <map code="0x05E7" name="2em" /> <!-- HEBREW LETTER QOF -->
+      <map code="0x05E8" name="2em" /> <!-- HEBREW LETTER RESH -->
+      <map code="0x05E9" name="2em" /> <!-- HEBREW LETTER SHIN -->
+      <map code="0x05EA" name="2em" /> <!-- HEBREW LETTER TAV -->
+      <map code="0x05EF" name="2em" /> <!-- HEBREW YOD TRIANGLE -->
+      <map code="0x05F0" name="2em" /> <!-- HEBREW LIGATURE YIDDISH DOUBLE VAV -->
+      <map code="0x05F1" name="2em" /> <!-- HEBREW LIGATURE YIDDISH VAV YOD -->
+      <map code="0x05F2" name="2em" /> <!-- HEBREW LIGATURE YIDDISH DOUBLE YOD -->
+      <map code="0x05F3" name="2em" /> <!-- HEBREW PUNCTUATION GERESH -->
+      <map code="0x05F4" name="2em" /> <!-- HEBREW PUNCTUATION GERSHAYIM -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="2em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttf b/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttf
new file mode 100644
index 0000000..8cd2961
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttx b/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttx
new file mode 100644
index 0000000..6c2f8da
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/user_fallback/ideograms.ttx
@@ -0,0 +1,234 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="2em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:10 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="2em" width="2000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x4E0D" name="2em" />
+      <map code="0x4E0E" name="2em" />
+      <map code="0x4E16" name="2em" />
+      <map code="0x4E43" name="2em" />
+      <map code="0x4E45" name="2em" />
+      <map code="0x4E4B" name="2em" />
+      <map code="0x4E5F" name="2em" />
+      <map code="0x4EC1" name="2em" />
+      <map code="0x4EE5" name="2em" />
+      <map code="0x4FDD" name="2em" />
+      <map code="0x5229" name="2em" />
+      <map code="0x52A0" name="2em" />
+      <map code="0x53CD" name="2em" />
+      <map code="0x53E4" name="2em" />
+      <map code="0x5442" name="2em" />
+      <map code="0x548C" name="2em" />
+      <map code="0x5929" name="2em" />
+      <map code="0x592A" name="2em" />
+      <map code="0x5948" name="2em" />
+      <map code="0x5973" name="2em" />
+      <map code="0x5974" name="2em" />
+      <map code="0x5B87" name="2em" />
+      <map code="0x5B89" name="2em" />
+      <map code="0x5BF8" name="2em" />
+      <map code="0x5DDD" name="2em" />
+      <map code="0x5DE6" name="2em" />
+      <map code="0x6075" name="2em" />
+      <map code="0x65BC" name="2em" />
+      <map code="0x65E1" name="2em" />
+      <map code="0x66FD" name="2em" />
+      <map code="0x672B" name="2em" />
+      <map code="0x6B62" name="2em" />
+      <map code="0x6B66" name="2em" />
+      <map code="0x6BD4" name="2em" />
+      <map code="0x6BDB" name="2em" />
+      <map code="0x6CE2" name="2em" />
+      <map code="0x70BA" name="2em" />
+      <map code="0x7531" name="2em" />
+      <map code="0x7559" name="2em" />
+      <map code="0x757F" name="2em" />
+      <map code="0x77E5" name="2em" />
+      <map code="0x793C" name="2em" />
+      <map code="0x79F0" name="2em" />
+      <map code="0x7F8E" name="2em" />
+      <map code="0x826F" name="2em" />
+      <map code="0x8863" name="2em" />
+      <map code="0x8A08" name="2em" />
+      <map code="0x9060" name="2em" />
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="2em" xMin="0" yMin="0" xMax="2000" yMax="2000">
+        <contour>
+            <pt x="0" y="0" on="1"/>
+            <pt x="1000" y="500" on="1"/>
+            <pt x="0" y="1000" on="1"/>
+        </contour>
+        <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2018 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttf b/tests/tests/graphics/assets/fonts/var_fonts/WeightEqualsEmVariableFont.ttf
similarity index 100%
rename from tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttf
rename to tests/tests/graphics/assets/fonts/var_fonts/WeightEqualsEmVariableFont.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttx b/tests/tests/graphics/assets/fonts/var_fonts/WeightEqualsEmVariableFont.ttx
similarity index 100%
rename from tests/tests/graphics/assets/WeightEqualsEmVariableFont.ttx
rename to tests/tests/graphics/assets/fonts/var_fonts/WeightEqualsEmVariableFont.ttx
diff --git a/tests/tests/graphics/assets/multiaxis.ttf b/tests/tests/graphics/assets/fonts/var_fonts/multiaxis.ttf
similarity index 100%
rename from tests/tests/graphics/assets/multiaxis.ttf
rename to tests/tests/graphics/assets/fonts/var_fonts/multiaxis.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/multiaxis.ttx b/tests/tests/graphics/assets/fonts/var_fonts/multiaxis.ttx
similarity index 100%
rename from tests/tests/graphics/assets/multiaxis.ttx
rename to tests/tests/graphics/assets/fonts/var_fonts/multiaxis.ttx
diff --git a/tests/tests/graphics/jni/Android.mk b/tests/tests/graphics/jni/Android.mk
index 2906bf5..af76936 100644
--- a/tests/tests/graphics/jni/Android.mk
+++ b/tests/tests/graphics/jni/Android.mk
@@ -32,6 +32,8 @@
 	android_graphics_cts_MediaVulkanGpuTest.cpp \
 	android_graphics_cts_VulkanFeaturesTest.cpp \
 	android_graphics_cts_VulkanPreTransformCtsActivity.cpp \
+	android_graphics_cts_VulkanSurfaceSupportTest.cpp \
+        android_graphics_fonts_cts_SystemFonts.cpp \
 	CameraTestHelpers.cpp \
 	ImageReaderTestHelpers.cpp \
 	MediaTestHelpers.cpp \
diff --git a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
index bb46671..db7919d 100644
--- a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
+++ b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
@@ -26,6 +26,8 @@
 extern int register_android_graphics_cts_MediaVulkanGpuTest(JNIEnv*);
 extern int register_android_graphics_cts_VulkanFeaturesTest(JNIEnv*);
 extern int register_android_graphics_cts_VulkanPreTransformCtsActivity(JNIEnv*);
+extern int register_android_graphics_cts_VulkanSurfaceSupportTest(JNIEnv*);
+extern int register_android_graphics_fonts_cts_SystemFontTest(JNIEnv*);
 
 jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
     JNIEnv* env = nullptr;
@@ -47,9 +49,13 @@
         return JNI_ERR;
     if (register_android_graphics_cts_VulkanPreTransformCtsActivity(env))
         return JNI_ERR;
+    if (register_android_graphics_cts_VulkanSurfaceSupportTest(env))
+        return JNI_ERR;
     if (register_android_graphics_cts_BasicVulkanGpuTest(env))
         return JNI_ERR;
     if (register_android_graphics_cts_CameraVulkanGpuTest(env))
         return JNI_ERR;
+    if (register_android_graphics_fonts_cts_SystemFontTest(env))
+        return JNI_ERR;
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
index beaa315..3eb7065 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
@@ -15,6 +15,12 @@
  *
  */
 
+#define LOG_TAG "VulkanPreTransformTestHelpers"
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR
+#endif
+
 #include <android/log.h>
 #include <cstring>
 
@@ -25,7 +31,7 @@
 #define ASSERT(a)                                              \
     if (!(a)) {                                                \
         ALOGE("Failure: " #a " at " __FILE__ ":%d", __LINE__); \
-        return -1;                                             \
+        return VK_TEST_ERROR;                                  \
     }
 #define VK_CALL(a) ASSERT(VK_SUCCESS == (a))
 
@@ -124,7 +130,7 @@
     }
 }
 
-int32_t DeviceInfo::init(JNIEnv* env, jobject jSurface) {
+VkTestResult DeviceInfo::init(JNIEnv* env, jobject jSurface) {
     ASSERT(jSurface);
 
     mWindow = ANativeWindow_fromSurface(env, jSurface);
@@ -164,7 +170,7 @@
     VK_CALL(vkEnumeratePhysicalDevices(mInstance, &gpuCount, nullptr));
     if (gpuCount == 0) {
         ALOGD("No physical device available");
-        return 1;
+        return VK_TEST_PHYSICAL_DEVICE_NOT_EXISTED;
     }
 
     std::vector<VkPhysicalDevice> gpus(gpuCount, VK_NULL_HANDLE);
@@ -205,6 +211,13 @@
     ASSERT(queueFamilyIndex < queueFamilyCount);
     mQueueFamilyIndex = queueFamilyIndex;
 
+    VkBool32 supported = VK_FALSE;
+    VK_CALL(vkGetPhysicalDeviceSurfaceSupportKHR(mGpu, mQueueFamilyIndex, mSurface, &supported));
+    if (supported == VK_FALSE) {
+        ALOGD("Surface format not supported");
+        return VK_TEST_SURFACE_FORMAT_NOT_SUPPORTED;
+    }
+
     const float priority = 1.0f;
     const VkDeviceQueueCreateInfo queueCreateInfo = {
             .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
@@ -229,7 +242,7 @@
 
     vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
 SwapchainInfo::SwapchainInfo(const DeviceInfo* const deviceInfo)
@@ -246,7 +259,7 @@
     }
 }
 
-int32_t SwapchainInfo::init(bool setPreTransform, int* outPreTransformHint) {
+VkTestResult SwapchainInfo::init(bool setPreTransform, int* outPreTransformHint) {
     VkSurfaceCapabilitiesKHR surfaceCapabilities;
     VK_CALL(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mDeviceInfo->gpu(), mDeviceInfo->surface(),
                                                       &surfaceCapabilities));
@@ -327,7 +340,7 @@
     VK_CALL(vkGetSwapchainImagesKHR(mDeviceInfo->device(), mSwapchain, &mSwapchainLength, nullptr));
     ALOGD("Swapchain length = %u", mSwapchainLength);
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
 Renderer::Renderer(const DeviceInfo* const deviceInfo, const SwapchainInfo* const swapchainInfo)
@@ -370,7 +383,7 @@
     }
 }
 
-int32_t Renderer::createRenderPass() {
+VkTestResult Renderer::createRenderPass() {
     const VkAttachmentDescription attachmentDescription = {
             .flags = 0,
             .format = mSwapchainInfo->format(),
@@ -412,10 +425,10 @@
     VK_CALL(vkCreateRenderPass(mDeviceInfo->device(), &renderPassCreateInfo, nullptr,
                                &mRenderPass));
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::createFrameBuffers() {
+VkTestResult Renderer::createFrameBuffers() {
     uint32_t swapchainLength = mSwapchainInfo->swapchainLength();
     std::vector<VkImage> images(swapchainLength, VK_NULL_HANDLE);
     VK_CALL(vkGetSwapchainImagesKHR(mDeviceInfo->device(), mSwapchainInfo->swapchain(),
@@ -467,10 +480,10 @@
                                     &mFramebuffers[i]));
     }
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::createVertexBuffers() {
+VkTestResult Renderer::createVertexBuffers() {
     const uint32_t queueFamilyIndex = mDeviceInfo->queueFamilyIndex();
     const VkBufferCreateInfo bufferCreateInfo = {
             .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
@@ -519,17 +532,15 @@
 
     VK_CALL(vkBindBufferMemory(mDeviceInfo->device(), mVertexBuffer, mDeviceMemory, 0));
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::loadShaderFromFile(const char* filePath, VkShaderModule* const outShader) {
+VkTestResult Renderer::loadShaderFromFile(const char* filePath, VkShaderModule* const outShader) {
     ASSERT(filePath);
 
     AAsset* file = AAssetManager_open(mAssetManager, filePath, AASSET_MODE_BUFFER);
-    if (!file) {
-        ALOGE("Failed to open shader file");
-        return -1;
-    }
+    ASSERT(file);
+
     size_t fileLength = AAsset_getLength(file);
     std::vector<char> fileContent(fileLength);
     AAsset_read(file, fileContent.data(), fileLength);
@@ -545,10 +556,10 @@
     VK_CALL(vkCreateShaderModule(mDeviceInfo->device(), &shaderModuleCreateInfo, nullptr,
                                  outShader));
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::createGraphicsPipeline() {
+VkTestResult Renderer::createGraphicsPipeline() {
     const VkPushConstantRange pushConstantRange = {
             .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
             .offset = 0,
@@ -717,10 +728,10 @@
     mVertexShader = VK_NULL_HANDLE;
     mFragmentShader = VK_NULL_HANDLE;
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::init(JNIEnv* env, jobject jAssetManager) {
+VkTestResult Renderer::init(JNIEnv* env, jobject jAssetManager) {
     mAssetManager = AAssetManager_fromJava(env, jAssetManager);
     ASSERT(mAssetManager);
 
@@ -827,10 +838,10 @@
     };
     VK_CALL(vkCreateSemaphore(mDeviceInfo->device(), &semaphoreCreateInfo, nullptr, &mSemaphore));
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
 
-int32_t Renderer::drawFrame() {
+VkTestResult Renderer::drawFrame() {
     uint32_t nextIndex;
     VK_CALL(vkAcquireNextImageKHR(mDeviceInfo->device(), mSwapchainInfo->swapchain(), UINT64_MAX,
                                   mSemaphore, VK_NULL_HANDLE, &nextIndex));
@@ -866,5 +877,5 @@
     };
     VK_CALL(vkQueuePresentKHR(mDeviceInfo->queue(), &presentInfo));
 
-    return 0;
+    return VK_TEST_SUCCESS;
 }
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
index 21174b4..600b375 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
@@ -17,23 +17,24 @@
 #ifndef VULKAN_PRE_TRANSFORM_TEST_HELPERS_H
 #define VULKAN_PRE_TRANSFORM_TEST_HELPERS_H
 
-#define LOG_TAG "vulkan"
-
-#ifndef VK_USE_PLATFORM_ANDROID_KHR
-#define VK_USE_PLATFORM_ANDROID_KHR
-#endif
-
 #include <android/asset_manager_jni.h>
 #include <android/native_window_jni.h>
 #include <jni.h>
 #include <vulkan/vulkan.h>
 #include <vector>
 
+typedef enum VkTestResult {
+    VK_TEST_ERROR = -1,
+    VK_TEST_SUCCESS = 0,
+    VK_TEST_PHYSICAL_DEVICE_NOT_EXISTED = 1,
+    VK_TEST_SURFACE_FORMAT_NOT_SUPPORTED = 2,
+} VkTestResult;
+
 class DeviceInfo {
 public:
     DeviceInfo();
     ~DeviceInfo();
-    int32_t init(JNIEnv* env, jobject jSurface);
+    VkTestResult init(JNIEnv* env, jobject jSurface);
     VkPhysicalDevice gpu() const { return mGpu; }
     VkSurfaceKHR surface() const { return mSurface; }
     uint32_t queueFamilyIndex() const { return mQueueFamilyIndex; }
@@ -54,7 +55,7 @@
 public:
     SwapchainInfo(const DeviceInfo* const deviceInfo);
     ~SwapchainInfo();
-    int32_t init(bool setPreTransform, int* outPreTransformHint);
+    VkTestResult init(bool setPreTransform, int* outPreTransformHint);
     VkFormat format() const { return mFormat; }
     VkExtent2D displaySize() const { return mDisplaySize; }
     VkSwapchainKHR swapchain() const { return mSwapchain; }
@@ -73,15 +74,15 @@
 public:
     Renderer(const DeviceInfo* const deviceInfo, const SwapchainInfo* const swapchainInfo);
     ~Renderer();
-    int32_t init(JNIEnv* env, jobject assetManager);
-    int32_t drawFrame();
+    VkTestResult init(JNIEnv* env, jobject assetManager);
+    VkTestResult drawFrame();
 
 private:
-    int32_t createRenderPass();
-    int32_t createFrameBuffers();
-    int32_t createVertexBuffers();
-    int32_t loadShaderFromFile(const char* filePath, VkShaderModule* const outShader);
-    int32_t createGraphicsPipeline();
+    VkTestResult createRenderPass();
+    VkTestResult createFrameBuffers();
+    VkTestResult createVertexBuffers();
+    VkTestResult loadShaderFromFile(const char* filePath, VkShaderModule* const outShader);
+    VkTestResult createGraphicsPipeline();
 
     const DeviceInfo* const mDeviceInfo;
     const SwapchainInfo* const mSwapchainInfo;
diff --git a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
index 2e3bf06..b08f384 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
@@ -19,6 +19,8 @@
 
 #include <jni.h>
 #include <android/bitmap.h>
+#include <android/hardware_buffer.h>
+#include <android/hardware_buffer_jni.h>
 
 #include "NativeTestHelpers.h"
 
@@ -41,11 +43,37 @@
     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
 }
 
+static void fillRgbaHardwareBuffer(JNIEnv* env, jclass, jobject hwBuffer) {
+    AHardwareBuffer* hardware_buffer = AHardwareBuffer_fromHardwareBuffer(env, hwBuffer);
+    AHardwareBuffer_Desc description;
+    AHardwareBuffer_describe(hardware_buffer, &description);
+    ASSERT_EQ(AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, description.format);
+
+    uint8_t* rgbaBytes;
+    AHardwareBuffer_lock(hardware_buffer,
+                         AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+                         -1,
+                         nullptr,
+                         reinterpret_cast<void**>(&rgbaBytes));
+    int c = 0;
+    for (int y = 0; y < description.width; ++y) {
+        for (int x = 0; x < description.height; ++x) {
+            rgbaBytes[c++] = static_cast<uint8_t>(x % 255);
+            rgbaBytes[c++] = static_cast<uint8_t>(y % 255);
+            rgbaBytes[c++] = 42;
+            rgbaBytes[c++] = 255;
+        }
+    }
+    AHardwareBuffer_unlock(hardware_buffer, nullptr);
+}
+
 static JNINativeMethod gMethods[] = {
     { "nValidateBitmapInfo", "(Landroid/graphics/Bitmap;IIZ)V",
         (void*) validateBitmapInfo },
     { "nValidateNdkAccessAfterRecycle", "(Landroid/graphics/Bitmap;)V",
         (void*) validateNdkAccessAfterRecycle },
+    { "nFillRgbaHwBuffer", "(Landroid/hardware/HardwareBuffer;)V",
+        (void*) fillRgbaHardwareBuffer },
 };
 
 int register_android_graphics_cts_BitmapTest(JNIEnv* env) {
diff --git a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
index 16fc851..508c777 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
@@ -15,11 +15,7 @@
  *
  */
 
-#define LOG_TAG "vulkan"
-
-#ifndef VK_USE_PLATFORM_ANDROID_KHR
-#define VK_USE_PLATFORM_ANDROID_KHR
-#endif
+#define LOG_TAG "VulkanPreTransformCtsActivity"
 
 #include <android/log.h>
 #include <jni.h>
@@ -51,21 +47,23 @@
 
     DeviceInfo deviceInfo;
     int preTransformHint;
-    int ret = deviceInfo.init(env, jSurface);
-    ASSERT(ret >= 0, "Failed to initialize Vulkan device");
-    if (ret > 0) {
+    VkTestResult ret = deviceInfo.init(env, jSurface);
+    if (ret == VK_TEST_PHYSICAL_DEVICE_NOT_EXISTED) {
         ALOGD("Hardware not supported for this test");
         return;
     }
+    ASSERT(ret == VK_TEST_SUCCESS, "Failed to initialize Vulkan device");
 
     SwapchainInfo swapchainInfo(&deviceInfo);
-    ASSERT(!swapchainInfo.init(setPreTransform, &preTransformHint), "Failed to initialize Vulkan swapchain");
+    ASSERT(swapchainInfo.init(setPreTransform, &preTransformHint) == VK_TEST_SUCCESS,
+           "Failed to initialize Vulkan swapchain");
 
     Renderer renderer(&deviceInfo, &swapchainInfo);
-    ASSERT(!renderer.init(env, jAssetManager), "Failed to initialize Vulkan renderer");
+    ASSERT(renderer.init(env, jAssetManager) == VK_TEST_SUCCESS,
+           "Failed to initialize Vulkan renderer");
 
     for (uint32_t i = 0; i < 120; ++i) {
-        ASSERT(!renderer.drawFrame(), "Failed to draw frame");
+        ASSERT(renderer.drawFrame() == VK_TEST_SUCCESS, "Failed to draw frame");
     }
 
     ASSERT(validatePixelValues(env, setPreTransform, preTransformHint), "Not properly rotated");
diff --git a/tests/tests/graphics/jni/android_graphics_cts_VulkanSurfaceSupportTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_VulkanSurfaceSupportTest.cpp
new file mode 100644
index 0000000..1621023
--- /dev/null
+++ b/tests/tests/graphics/jni/android_graphics_cts_VulkanSurfaceSupportTest.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "VulkanSurfaceSupportTest"
+
+#ifndef VK_USE_PLATFORM_ANDROID_KHR
+#define VK_USE_PLATFORM_ANDROID_KHR
+#endif
+
+#include <android/log.h>
+#include <jni.h>
+#include <array>
+
+#include "NativeTestHelpers.h"
+#include "VulkanPreTransformTestHelpers.h"
+
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+namespace {
+
+void createNativeTest(JNIEnv* env, jclass /*clazz*/, jobject jAssetManager, jobject jSurface,
+                      jboolean jSupported) {
+    ASSERT(jAssetManager, "jAssetManager is NULL");
+    ASSERT(jSurface, "jSurface is NULL");
+
+    DeviceInfo deviceInfo;
+    VkTestResult ret = deviceInfo.init(env, jSurface);
+    if (ret == VK_TEST_PHYSICAL_DEVICE_NOT_EXISTED) {
+        ALOGD("Hardware not supported for this test");
+        return;
+    }
+    if (ret == VK_TEST_SURFACE_FORMAT_NOT_SUPPORTED) {
+        ASSERT(jSupported == false, "Surface format should not be supported");
+        return;
+    }
+    ASSERT(ret == VK_TEST_SUCCESS, "Failed to initialize Vulkan device");
+    ASSERT(jSupported == true, "Surface format should be supported");
+
+    SwapchainInfo swapchainInfo(&deviceInfo);
+    ASSERT(swapchainInfo.init(false, nullptr) == VK_TEST_SUCCESS,
+           "Failed to initialize Vulkan swapchain");
+
+    Renderer renderer(&deviceInfo, &swapchainInfo);
+    ASSERT(renderer.init(env, jAssetManager) == VK_TEST_SUCCESS,
+           "Failed to initialize Vulkan renderer");
+
+    for (uint32_t i = 0; i < 3; ++i) {
+        ASSERT(renderer.drawFrame() == VK_TEST_SUCCESS, "Failed to draw frame");
+    }
+}
+
+const std::array<JNINativeMethod, 1> JNI_METHODS = {{
+        {"nCreateNativeTest", "(Landroid/content/res/AssetManager;Landroid/view/Surface;Z)V",
+         (void*)createNativeTest},
+}};
+
+} // anonymous namespace
+
+int register_android_graphics_cts_VulkanSurfaceSupportTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/graphics/cts/VulkanSurfaceSupportTest");
+    return env->RegisterNatives(clazz, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/graphics/jni/android_graphics_fonts_cts_SystemFonts.cpp b/tests/tests/graphics/jni/android_graphics_fonts_cts_SystemFonts.cpp
new file mode 100644
index 0000000..9006a3c
--- /dev/null
+++ b/tests/tests/graphics/jni/android_graphics_fonts_cts_SystemFonts.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "SystemFonts"
+
+#include <jni.h>
+#include <android/system_fonts.h>
+
+#include <array>
+#include <android/log.h>
+
+namespace {
+
+class ScopedUtfChars {
+public:
+    ScopedUtfChars(JNIEnv* env, jstring s) : mEnv(env), mString(s) {
+        if (s == nullptr) {
+            mUtfChars = nullptr;
+            mSize = 0;
+        } else {
+            mUtfChars = mEnv->GetStringUTFChars(mString, nullptr);
+            mSize = mEnv->GetStringUTFLength(mString);
+        }
+    }
+
+    ~ScopedUtfChars() {
+        if (mUtfChars) {
+            mEnv->ReleaseStringUTFChars(mString, mUtfChars);
+        }
+    }
+
+    const char* c_str() const {
+        return mUtfChars;
+    }
+
+    size_t size() const {
+      return mSize;
+    }
+
+private:
+    JNIEnv* mEnv;
+    jstring mString;
+    const char* mUtfChars;
+    size_t mSize;
+};
+
+class ScopedStringChars {
+public:
+    ScopedStringChars(JNIEnv* env, jstring s) : mEnv(env), mString(s) {
+        if (s == nullptr) {
+            mChars = nullptr;
+            mSize = 0;
+        } else {
+            mChars = mEnv->GetStringChars(mString, NULL);
+            mSize = mEnv->GetStringLength(mString);
+        }
+    }
+
+    ~ScopedStringChars() {
+        if (mChars != nullptr) {
+            mEnv->ReleaseStringChars(mString, mChars);
+        }
+    }
+
+    const jchar* get() const {
+        return mChars;
+    }
+
+    size_t size() const {
+        return mSize;
+    }
+
+
+private:
+    JNIEnv* const mEnv;
+    const jstring mString;
+    const jchar* mChars;
+    size_t mSize;
+};
+
+
+jlong nOpenIterator(JNIEnv*, jclass) {
+    return reinterpret_cast<jlong>(ASystemFontIterator_open());
+}
+
+void nCloseIterator(JNIEnv*, jclass, jlong ptr) {
+    ASystemFontIterator_close(reinterpret_cast<ASystemFontIterator*>(ptr));
+}
+
+jlong nGetNext(JNIEnv*, jclass, jlong ptr) {
+    return reinterpret_cast<jlong>(ASystemFontIterator_next(
+            reinterpret_cast<ASystemFontIterator*>(ptr)));
+}
+
+void nCloseFont(JNIEnv*, jclass, jlong ptr) {
+    return ASystemFont_close(reinterpret_cast<ASystemFont*>(ptr));
+}
+
+jstring nGetFilePath(JNIEnv* env, jclass, jlong ptr) {
+    return env->NewStringUTF(ASystemFont_getFontFilePath(reinterpret_cast<ASystemFont*>(ptr)));
+}
+
+jint nGetWeight(JNIEnv*, jclass, jlong ptr) {
+    return ASystemFont_getWeight(reinterpret_cast<ASystemFont*>(ptr));
+}
+
+jboolean nIsItalic(JNIEnv*, jclass, jlong ptr) {
+    return ASystemFont_isItalic(reinterpret_cast<ASystemFont*>(ptr));
+}
+
+jstring nGetLocale(JNIEnv* env, jclass, jlong ptr) {
+    return env->NewStringUTF(ASystemFont_getLocale(reinterpret_cast<ASystemFont*>(ptr)));
+}
+
+jint nGetCollectionIndex(JNIEnv*, jclass, jlong ptr) {
+    return ASystemFont_getCollectionIndex(reinterpret_cast<ASystemFont*>(ptr));
+}
+
+jint nGetAxisCount(JNIEnv*, jclass, jlong ptr) {
+    return ASystemFont_getAxisCount(reinterpret_cast<ASystemFont*>(ptr));
+}
+
+jint nGetAxisTag(JNIEnv*, jclass, jlong ptr, jint axisIndex) {
+    return ASystemFont_getAxisTag(reinterpret_cast<ASystemFont*>(ptr), axisIndex);
+}
+
+jfloat nGetAxisValue(JNIEnv*, jclass, jlong ptr, jint axisIndex) {
+    return ASystemFont_getAxisValue(reinterpret_cast<ASystemFont*>(ptr), axisIndex);
+}
+
+jlong nMatchFamilyStyleCharacter(JNIEnv* env, jclass, jstring familyName, jint weight,
+                                 jboolean italic, jstring langTags, jstring text) {
+    ScopedUtfChars familyNameChars(env, familyName);
+    ScopedUtfChars langTagsChars(env, langTags);
+    ScopedStringChars textChars(env, text);
+    return reinterpret_cast<jlong>(ASystemFont_matchFamilyStyleCharacter(
+        familyNameChars.c_str(), weight, italic, langTagsChars.c_str(), textChars.get(),
+        textChars.size(), nullptr));
+}
+
+jint nMatchFamilyStyleCharacter_runLength(JNIEnv* env, jclass, jstring familyName, jint weight,
+                                          jboolean italic, jstring langTags, jstring text) {
+    ScopedUtfChars familyNameChars(env, familyName);
+    ScopedUtfChars langTagsChars(env, langTags);
+    ScopedStringChars textChars(env, text);
+    uint32_t runLength = 0;
+    ASystemFont* ptr = ASystemFont_matchFamilyStyleCharacter(
+            familyNameChars.c_str(), weight, italic, langTagsChars.c_str(), textChars.get(),
+            textChars.size(), &runLength);
+    ASystemFont_close(ptr);
+    return runLength;
+}
+
+const std::array<JNINativeMethod, 14> JNI_METHODS = {{
+    { "nOpenIterator", "()J", (void*) nOpenIterator },
+    { "nCloseIterator", "(J)V", (void*) nCloseIterator },
+    { "nNext", "(J)J", (void*) nGetNext },
+    { "nCloseFont", "(J)V", (void*) nCloseFont },
+    { "nGetFilePath", "(J)Ljava/lang/String;", (void*) nGetFilePath },
+    { "nGetWeight", "(J)I", (void*) nGetWeight },
+    { "nIsItalic", "(J)Z", (void*) nIsItalic },
+    { "nGetLocale", "(J)Ljava/lang/String;", (void*) nGetLocale },
+    { "nGetCollectionIndex", "(J)I", (void*) nGetCollectionIndex },
+    { "nGetAxisCount", "(J)I", (void*) nGetAxisCount },
+    { "nGetAxisTag", "(JI)I", (void*) nGetAxisTag },
+    { "nGetAxisValue", "(JI)F", (void*) nGetAxisValue },
+    { "nMatchFamilyStyleCharacter",
+          "(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;)J",
+          (void*) nMatchFamilyStyleCharacter },
+    { "nMatchFamilyStyleCharacter_runLength",
+          "(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;)I",
+          (void*) nMatchFamilyStyleCharacter_runLength },
+
+}};
+
+}
+
+int register_android_graphics_fonts_cts_SystemFontTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/graphics/fonts/NativeSystemFontHelper");
+    return env->RegisterNatives(clazz, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/graphics/res/color/csl_with_theme_attr.xml b/tests/tests/graphics/res/color/csl_with_theme_attr.xml
new file mode 100644
index 0000000..19aed64
--- /dev/null
+++ b/tests/tests/graphics/res/color/csl_with_theme_attr.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="?attr/themeVectorDrawableFillColor"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable.xml b/tests/tests/graphics/res/drawable/gradientdrawable.xml
index ed8ff96..3d147dd 100644
--- a/tests/tests/graphics/res/drawable/gradientdrawable.xml
+++ b/tests/tests/graphics/res/drawable/gradientdrawable.xml
@@ -15,7 +15,11 @@
  * limitations under the License.
  -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
+<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" />
     <corners android:radius="8px" />
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_mix_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_mix_theme.xml
new file mode 100644
index 0000000..63bd5dd
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_mix_theme.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="270"
+        android:centerColor="@color/colorPrimary"
+        android:endColor="?attr/colorPrimaryDark"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/heart.xml b/tests/tests/graphics/res/drawable/heart.xml
new file mode 100644
index 0000000..31c69f1
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/heart.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="256dp"
+        android:width="256dp"
+        android:viewportWidth="32"
+        android:viewportHeight="32">
+
+    <!-- draw a path -->
+    <path
+        android:name="path"
+        android:fillColor="@color/csl_with_theme_attr"
+        android:pathData="M20.5,9.5
+                        c-1.965,0,-3.83,1.268,-4.5,3
+                        c-0.17,-1.732,-2.547,-3,-4.5,-3
+                        C8.957,9.5,7,11.432,7,14
+                        c0,3.53,3.793,6.257,9,11.5
+                        c5.207,-5.242,9,-7.97,9,-11.5
+                        C25,11.432,23.043,9.5,20.5,9.5z" />
+</vector>
diff --git a/tests/tests/graphics/res/drawable/vector_drawable_grouping_1.xml b/tests/tests/graphics/res/drawable/vector_drawable_grouping_1.xml
index 7839ad1..1010ce3 100644
--- a/tests/tests/graphics/res/drawable/vector_drawable_grouping_1.xml
+++ b/tests/tests/graphics/res/drawable/vector_drawable_grouping_1.xml
@@ -16,6 +16,10 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:height="64dp"
         android:width="64dp"
+        android:opticalInsetLeft="10px"
+        android:opticalInsetTop="20px"
+        android:opticalInsetRight="30px"
+        android:opticalInsetBottom="40px"
         android:viewportHeight="256"
         android:viewportWidth="256" >
 
diff --git a/tests/tests/graphics/res/drawable/vector_icon_create.xml b/tests/tests/graphics/res/drawable/vector_icon_create.xml
index 7db4ad5..55113f3 100644
--- a/tests/tests/graphics/res/drawable/vector_icon_create.xml
+++ b/tests/tests/graphics/res/drawable/vector_icon_create.xml
@@ -19,6 +19,10 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:height="64dp"
         android:width="64dp"
+        android:opticalInsetLeft="1px"
+        android:opticalInsetTop="2px"
+        android:opticalInsetRight="3px"
+        android:opticalInsetBottom="4px"
         android:viewportHeight="24"
         android:viewportWidth="24" >
 
diff --git a/tests/tests/graphics/res/font/ascii.ttc b/tests/tests/graphics/res/font/ascii.ttc
new file mode 100644
index 0000000..e404f2b
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii.ttc
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_a3em_weight100_upright.ttf b/tests/tests/graphics/res/font/ascii_a3em_weight100_upright.ttf
new file mode 100644
index 0000000..f220eb3
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_a3em_weight100_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_b3em_weight100_italic.ttf b/tests/tests/graphics/res/font/ascii_b3em_weight100_italic.ttf
new file mode 100644
index 0000000..b9ffb84
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_b3em_weight100_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_c3em_weight200_upright.ttf b/tests/tests/graphics/res/font/ascii_c3em_weight200_upright.ttf
new file mode 100644
index 0000000..075b068
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_c3em_weight200_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_d3em_weight200_italic.ttf b/tests/tests/graphics/res/font/ascii_d3em_weight200_italic.ttf
new file mode 100644
index 0000000..5b47f0d
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_d3em_weight200_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_e3em_weight300_upright.ttf b/tests/tests/graphics/res/font/ascii_e3em_weight300_upright.ttf
new file mode 100644
index 0000000..3e9133b
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_e3em_weight300_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_f3em_weight300_italic.ttf b/tests/tests/graphics/res/font/ascii_f3em_weight300_italic.ttf
new file mode 100644
index 0000000..3ba3feb
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_f3em_weight300_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_g3em_weight400_upright.ttf b/tests/tests/graphics/res/font/ascii_g3em_weight400_upright.ttf
new file mode 100644
index 0000000..b3175a1
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_g3em_weight400_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_h3em_weight400_italic.ttf b/tests/tests/graphics/res/font/ascii_h3em_weight400_italic.ttf
new file mode 100644
index 0000000..099c4f1
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_h3em_weight400_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_i3em_weight500_upright.ttf b/tests/tests/graphics/res/font/ascii_i3em_weight500_upright.ttf
new file mode 100644
index 0000000..b8edcb6
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_i3em_weight500_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_j3em_weight500_italic.ttf b/tests/tests/graphics/res/font/ascii_j3em_weight500_italic.ttf
new file mode 100644
index 0000000..6d7dde9
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_j3em_weight500_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_k3em_weight600_upright.ttf b/tests/tests/graphics/res/font/ascii_k3em_weight600_upright.ttf
new file mode 100644
index 0000000..eb6d7d1
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_k3em_weight600_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_l3em_weight600_italic.ttf b/tests/tests/graphics/res/font/ascii_l3em_weight600_italic.ttf
new file mode 100644
index 0000000..f509e94
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_l3em_weight600_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_m3em_weight700_upright.ttf b/tests/tests/graphics/res/font/ascii_m3em_weight700_upright.ttf
new file mode 100644
index 0000000..062f299
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_m3em_weight700_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_n3em_weight700_italic.ttf b/tests/tests/graphics/res/font/ascii_n3em_weight700_italic.ttf
new file mode 100644
index 0000000..fdd0239
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_n3em_weight700_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_o3em_weight800_upright.ttf b/tests/tests/graphics/res/font/ascii_o3em_weight800_upright.ttf
new file mode 100644
index 0000000..993e7fa
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_o3em_weight800_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_p3em_weight800_italic.ttf b/tests/tests/graphics/res/font/ascii_p3em_weight800_italic.ttf
new file mode 100644
index 0000000..f0d54f0
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_p3em_weight800_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_q3em_weight900_upright.ttf b/tests/tests/graphics/res/font/ascii_q3em_weight900_upright.ttf
new file mode 100644
index 0000000..c776c78
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_q3em_weight900_upright.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_r3em_weight900_italic.ttf b/tests/tests/graphics/res/font/ascii_r3em_weight900_italic.ttf
new file mode 100644
index 0000000..02f6246
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_r3em_weight900_italic.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/ascii_vf.ttf b/tests/tests/graphics/res/font/ascii_vf.ttf
new file mode 100644
index 0000000..792b7d7
--- /dev/null
+++ b/tests/tests/graphics/res/font/ascii_vf.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/raw-nodpi/b78329453.jpg b/tests/tests/graphics/res/raw-nodpi/b78329453.jpg
new file mode 100644
index 0000000..b962b9e
--- /dev/null
+++ b/tests/tests/graphics/res/raw-nodpi/b78329453.jpg
Binary files differ
diff --git a/tests/tests/graphics/res/raw/f16.png b/tests/tests/graphics/res/raw/f16.png
new file mode 100644
index 0000000..2c3aed2
--- /dev/null
+++ b/tests/tests/graphics/res/raw/f16.png
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_arw.arw b/tests/tests/graphics/res/raw/sample_arw.arw
new file mode 100644
index 0000000..2b05931
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_arw.arw
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_cr2.cr2 b/tests/tests/graphics/res/raw/sample_cr2.cr2
new file mode 100644
index 0000000..adbbc97
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_cr2.cr2
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_nef.nef b/tests/tests/graphics/res/raw/sample_nef.nef
new file mode 100644
index 0000000..282614b
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_nef.nef
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_nrw.nrw b/tests/tests/graphics/res/raw/sample_nrw.nrw
new file mode 100644
index 0000000..f91eff4
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_nrw.nrw
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_orf.orf b/tests/tests/graphics/res/raw/sample_orf.orf
new file mode 100644
index 0000000..60eea3a
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_orf.orf
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_pef.pef b/tests/tests/graphics/res/raw/sample_pef.pef
new file mode 100644
index 0000000..d4f6d48
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_pef.pef
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_raf.raf b/tests/tests/graphics/res/raw/sample_raf.raf
new file mode 100644
index 0000000..edb23b4
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_raf.raf
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_rw2.rw2 b/tests/tests/graphics/res/raw/sample_rw2.rw2
new file mode 100644
index 0000000..9db5b45
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_rw2.rw2
Binary files differ
diff --git a/tests/tests/graphics/res/raw/sample_srw.srw b/tests/tests/graphics/res/raw/sample_srw.srw
new file mode 100644
index 0000000..cb9b033
--- /dev/null
+++ b/tests/tests/graphics/res/raw/sample_srw.srw
Binary files differ
diff --git a/tests/tests/graphics/res/values/attrs.xml b/tests/tests/graphics/res/values/attrs.xml
index 4c3d9db..dbbf3e2 100644
--- a/tests/tests/graphics/res/values/attrs.xml
+++ b/tests/tests/graphics/res/values/attrs.xml
@@ -142,4 +142,5 @@
     <attr name="themeGravity" />
     <attr name="themeTileMode" />
     <attr name="themeAngle" />
+    <attr name="themeVectorDrawableFillColor" />
 </resources>
diff --git a/tests/tests/graphics/res/values/colors.xml b/tests/tests/graphics/res/values/colors.xml
index f3cc325..64f1589 100644
--- a/tests/tests/graphics/res/values/colors.xml
+++ b/tests/tests/graphics/res/values/colors.xml
@@ -23,4 +23,7 @@
     <color name="testcolor1">#ff00ff00</color>
     <color name="testcolor2">#ffff0000</color>
     <color name="failColor">#ff0000ff</color>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
 </resources>
diff --git a/tests/tests/graphics/res/values/styles.xml b/tests/tests/graphics/res/values/styles.xml
index 80780fb..eca9589 100644
--- a/tests/tests/graphics/res/values/styles.xml
+++ b/tests/tests/graphics/res/values/styles.xml
@@ -164,6 +164,18 @@
         <item name="themeGravity">48</item>
         <item name="themeTileMode">2</item>
         <item name="themeType">0</item>
+        <item name="themeVectorDrawableFillColor">#F00F</item>
+    </style>
+
+    <attr name="colorPrimary" format="reference|color" />
+    <attr name="colorPrimaryDark" format="reference|color" />
+    <attr name="colorAccent" format="reference|color" />
+
+    <style name="Theme_MixedGradientTheme">
+        <!-- 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="WhiteBackgroundNoWindowAnimation"
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
index 7645e25..f5f1560 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -85,13 +85,13 @@
         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGBA_F16, true, sRGB);
         cs = b.getColorSpace();
         assertNotNull(cs);
-        assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
+        assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
 
         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGBA_F16, true,
                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
         cs = b.getColorSpace();
         assertNotNull(cs);
-        assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
+        assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
 
         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565, true, sRGB);
         cs = b.getColorSpace();
@@ -342,7 +342,7 @@
     @Test
     public void setPixel() {
         verifySetPixel("green-p3.png", 0xffff0000, 0xea3323ff);
-        verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x7519117f);
+        verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x7519127f);
     }
 
     private void verifySetPixel(@NonNull String fileName,
@@ -355,13 +355,15 @@
             ColorSpace cs = b.getColorSpace();
             assertNotNull(cs);
             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
-
+            assertTrue(b.isMutable());
             verifySetPixel(b, newColor, expectedColor);
 
             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
+            assertTrue(b.isMutable());
             verifySetPixel(b, newColor, expectedColor);
 
             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
+            assertTrue(b.isMutable());
             verifySetPixel(b, newColor, expectedColor);
         } catch (IOException e) {
             fail();
@@ -370,6 +372,7 @@
 
     private static void verifySetPixel(@NonNull Bitmap b,
             @ColorInt int newColor, @ColorInt int expectedColor) {
+        assertTrue(b.isMutable());
         b.setPixel(0, 0, newColor);
 
         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
@@ -382,7 +385,7 @@
     @Test
     public void setPixels() {
         verifySetPixels("green-p3.png", 0xffff0000, 0xea3323ff);
-        verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x7519117f);
+        verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x7519127f);
     }
 
     private void verifySetPixels(@NonNull String fileName,
@@ -399,9 +402,11 @@
             verifySetPixels(b, newColor, expectedColor);
 
             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
+            assertTrue(b.isMutable());
             verifySetPixels(b, newColor, expectedColor);
 
             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
+            assertTrue(b.isMutable());
             verifySetPixels(b, newColor, expectedColor);
         } catch (IOException e) {
             fail();
@@ -410,6 +415,7 @@
 
     private static void verifySetPixels(@NonNull Bitmap b,
             @ColorInt int newColor, @ColorInt int expectedColor) {
+        assertTrue(b.isMutable());
         int[] pixels = new int[b.getWidth() * b.getHeight()];
         Arrays.fill(pixels, newColor);
         b.setPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
@@ -603,7 +609,7 @@
             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
             assertEquals(opts.inPreferredColorSpace, opts.outColorSpace);
 
-            verifyGetPixel(b, 0x3ff00ff, 0xff00ff00);
+            verifyGetPixel(b, 0x2ff00ff, 0xff00ff00);
         } catch (IOException e) {
             fail();
         }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
index 4067585..88c213d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
@@ -39,10 +39,11 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 
+import com.android.compatibility.common.util.CddTest;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -721,6 +722,51 @@
     }
 
     @Test
+    public void testJpegInfiniteLoop() {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inSampleSize = 19;
+        Bitmap bm = BitmapFactory.decodeResource(mRes, R.raw.b78329453, options);
+        assertNotNull(bm);
+    }
+
+    private static final class DNG {
+        public final int resId;
+        public final int width;
+        public final int height;
+
+        DNG(int resId, int width, int height) {
+            this.resId    = resId;
+            this.width    = width;
+            this.height   = height;
+        }
+    }
+
+    @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),
+        };
+
+        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());
+        }
+    }
+
+    @Test
     public void testDecodePngFromPipe() {
         // This test verifies that we can send a PNG over a pipe and
         // successfully decode it. This behavior worked in N, so this
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRGBAF16Test.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRGBAF16Test.java
index 02c9425..6b9eb5a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRGBAF16Test.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRGBAF16Test.java
@@ -25,6 +25,7 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -107,9 +108,9 @@
     @Test
     public void testGetPixel() {
         // Opaque pixels from opaque bitmap
-        assertEquals(0xff0f131f, mOpaqueBitmap.getPixel(0, 0));
-        assertEquals(0xff0f1421, mOpaqueBitmap.getPixel(1, 0));
-        assertEquals(0xff101523, mOpaqueBitmap.getPixel(2, 0));
+        validatePixel(0xff0f131f, mOpaqueBitmap.getPixel(0, 0));
+        validatePixel(0xff0f1421, mOpaqueBitmap.getPixel(1, 0));
+        validatePixel(0xff101523, mOpaqueBitmap.getPixel(2, 0));
 
         // Opaque pixels from transparent bitmap
         assertEquals(0xffff0000, mTransparentBitmap.getPixel(0, 0));
@@ -148,4 +149,11 @@
         Bitmap b = mask.copy(Config.RGBA_F16, false);
         assertNotNull(b);
     }
+
+    private void validatePixel(int expected, int actual) {
+        assertEquals(Color.alpha(expected), Color.alpha(actual));
+        assertEquals(Color.red(expected), Color.red(actual), 1);
+        assertEquals(Color.green(expected), Color.green(actual), 1);
+        assertEquals(Color.blue(expected), Color.blue(actual), 1);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 1aec304..f4cdf7a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -30,8 +30,11 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorSpace;
+import android.graphics.ColorSpace.Named;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.Picture;
+import android.hardware.HardwareBuffer;
 import android.os.Debug;
 import android.os.Parcel;
 import android.os.StrictMode;
@@ -54,6 +57,8 @@
 import java.nio.CharBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -75,6 +80,22 @@
     private Bitmap mBitmap;
     private BitmapFactory.Options mOptions;
 
+    public static List<ColorSpace> getRgbColorSpaces() {
+        List<ColorSpace> rgbColorSpaces;
+        rgbColorSpaces = new ArrayList<ColorSpace>();
+        for (ColorSpace.Named e : ColorSpace.Named.values()) {
+            ColorSpace cs = ColorSpace.get(e);
+            if (cs.getModel() != ColorSpace.Model.RGB) {
+                continue;
+            }
+            if (((ColorSpace.Rgb) cs).getTransferParameters() == null) {
+                continue;
+            }
+            rgbColorSpaces.add(cs);
+        }
+        return rgbColorSpaces;
+    }
+
     @Before
     public void setup() {
         mRes = InstrumentationRegistry.getTargetContext().getResources();
@@ -207,8 +228,10 @@
     public void testCreateBitmap1() {
         int[] colors = createColors(100);
         Bitmap bitmap = Bitmap.createBitmap(colors, 10, 10, Config.RGB_565);
+        assertFalse(bitmap.isMutable());
         Bitmap ret = Bitmap.createBitmap(bitmap);
         assertNotNull(ret);
+        assertFalse(ret.isMutable());
         assertEquals(10, ret.getWidth());
         assertEquals(10, ret.getHeight());
         assertEquals(Config.RGB_565, ret.getConfig());
@@ -223,8 +246,10 @@
     public void testCreateBitmap2() {
         // special case: output bitmap is equal to the input bitmap
         mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888);
+        assertFalse(mBitmap.isMutable()); // createBitmap w/ colors should be immutable
         Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100);
         assertNotNull(ret);
+        assertFalse(ret.isMutable()); // createBitmap from subset should be immutable
         assertTrue(mBitmap.equals(ret));
 
         //normal case
@@ -277,19 +302,33 @@
         mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888);
         Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100, null, false);
         assertNotNull(ret);
+        assertFalse(ret.isMutable()); // subset should be immutable
         assertTrue(mBitmap.equals(ret));
 
         // normal case
         mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
         ret = Bitmap.createBitmap(mBitmap, 10, 10, 50, 50, new Matrix(), true);
+        assertTrue(ret.isMutable());
         assertNotNull(ret);
         assertFalse(mBitmap.equals(ret));
     }
 
     @Test
+    public void testCreateBitmapFromHardwareBitmap() {
+        Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
+                HARDWARE_OPTIONS);
+        assertEquals(Config.HARDWARE, hardwareBitmap.getConfig());
+
+        Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 100, 100, null, false);
+        assertEquals(Config.HARDWARE, ret.getConfig());
+        assertFalse(ret.isMutable());
+    }
+
+    @Test
     public void testCreateBitmap4() {
         Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
         assertNotNull(ret);
+        assertTrue(ret.isMutable());
         assertEquals(100, ret.getWidth());
         assertEquals(200, ret.getHeight());
         assertEquals(Config.RGB_565, ret.getConfig());
@@ -306,6 +345,7 @@
     public void testCreateBitmap_matrix() {
         int[] colorArray = new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.BLACK };
         Bitmap src = Bitmap.createBitmap(2, 2, Config.ARGB_8888);
+        assertTrue(src.isMutable());
         src.setPixels(colorArray,0, 2, 0, 0, 2, 2);
 
         // baseline
@@ -313,21 +353,25 @@
 
         // null
         Bitmap dst = Bitmap.createBitmap(src, 0, 0, 2, 2, null, false);
+        assertTrue(dst.isMutable());
         verify2x2BitmapContents(colorArray, dst);
 
         // identity matrix
         Matrix matrix = new Matrix();
         dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
+        assertTrue(dst.isMutable());
         verify2x2BitmapContents(colorArray, dst);
 
         // big scale - only red visible
         matrix.setScale(10, 10);
         dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
+        assertTrue(dst.isMutable());
         verify2x2BitmapContents(new int[] { Color.RED, Color.RED, Color.RED, Color.RED }, dst);
 
         // rotation
         matrix.setRotate(90);
         dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
+        assertTrue(dst.isMutable());
         verify2x2BitmapContents(
                 new int[] { Color.BLUE, Color.RED, Color.BLACK, Color.GREEN }, dst);
     }
@@ -379,6 +423,7 @@
         // normal case
         Bitmap ret = Bitmap.createBitmap(colors, 5, 10, 10, 5, Config.RGB_565);
         assertNotNull(ret);
+        assertFalse(ret.isMutable());
         assertEquals(10, ret.getWidth());
         assertEquals(5, ret.getHeight());
         assertEquals(Config.RGB_565, ret.getConfig());
@@ -409,8 +454,26 @@
         assertEquals(metrics.densityDpi, bitmap.getDensity());
 
         int[] colors = createColors(100);
-        assertNotNull(Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888));
-        assertNotNull(Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888));
+        bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888);
+        assertNotNull(bitmap);
+        assertFalse(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888);
+        assertNotNull(bitmap);
+        assertFalse(bitmap.isMutable());
+    }
+
+    @Test
+    public void testCreateBitmap_noDisplayMetrics_mutable() {
+        Bitmap bitmap;
+        bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        assertTrue(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true);
+        assertTrue(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true, ColorSpace.get(Named.SRGB));
+        assertTrue(bitmap.isMutable());
     }
 
     @Test
@@ -430,12 +493,76 @@
     }
 
     @Test
+    public void testCreateBitmap_noDisplayMetrics_immutable() {
+        int[] colors = createColors(100);
+        Bitmap bitmap;
+        bitmap = Bitmap.createBitmap(colors, 0, 10, 10, 10, Config.ARGB_8888);
+        assertFalse(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(colors, 10, 10, Config.ARGB_8888);
+        assertFalse(bitmap.isMutable());
+    }
+
+    @Test
+    public void testCreateBitmap_Picture_immutable() {
+        Picture picture = new Picture();
+        Canvas canvas = picture.beginRecording(200, 100);
+
+        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+        p.setColor(0x88FF0000);
+        canvas.drawCircle(50, 50, 40, p);
+
+        p.setColor(Color.GREEN);
+        p.setTextSize(30);
+        canvas.drawText("Pictures", 60, 60, p);
+        picture.endRecording();
+
+        Bitmap bitmap;
+        bitmap = Bitmap.createBitmap(picture);
+        assertFalse(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(picture, 100, 100, Config.HARDWARE);
+        assertFalse(bitmap.isMutable());
+
+        bitmap = Bitmap.createBitmap(picture, 100, 100, Config.ARGB_8888);
+        assertFalse(bitmap.isMutable());
+    }
+
+    @Test
     public void testCreateScaledBitmap() {
         mBitmap = Bitmap.createBitmap(100, 200, Config.RGB_565);
+        assertTrue(mBitmap.isMutable());
         Bitmap ret = Bitmap.createScaledBitmap(mBitmap, 50, 100, false);
         assertNotNull(ret);
         assertEquals(50, ret.getWidth());
         assertEquals(100, ret.getHeight());
+        assertTrue(ret.isMutable());
+    }
+
+    @Test
+    public void testWrapHardwareBufferSucceeds() {
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+            assertNotNull(bitmap);
+            bitmap.recycle();
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrapHardwareBufferWithInvalidUsageFails() {
+        try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1,
+            HardwareBuffer.USAGE_CPU_WRITE_RARELY)) {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails() {
+        try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1,
+            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)) {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.CIE_LAB));
+        }
     }
 
     @Test
@@ -471,6 +598,13 @@
         mBitmap.eraseColor(0);
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testEraseColorLongOnRecycled() {
+        mBitmap.recycle();
+
+        mBitmap.eraseColor(Color.pack(0));
+    }
+
     @Test(expected=IllegalStateException.class)
     public void testEraseColorOnImmutable() {
         mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
@@ -479,6 +613,14 @@
         mBitmap.eraseColor(0);
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testEraseColorLongOnImmutable() {
+        mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
+
+        //abnormal case: bitmap is immutable
+        mBitmap.eraseColor(Color.pack(0));
+    }
+
     @Test
     public void testEraseColor() {
         // normal case
@@ -488,6 +630,198 @@
         assertEquals(0xffff0000, mBitmap.getPixel(50, 50));
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetColorOOB() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.getColor(-1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetColorOOB2() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.getColor(5, -10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetColorOOB3() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.getColor(100, 10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetColorOOB4() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.getColor(99, 1000);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetColorRecycled() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.recycle();
+        mBitmap.getColor(0, 0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetColorHardware() {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.HARDWARE;
+        mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, options);
+        mBitmap.getColor(50, 50);
+
+    }
+
+    private static float clamp(float f) {
+        return clamp(f, 0.0f, 1.0f);
+    }
+
+    private static float clamp(float f, float min, float max) {
+        return Math.min(Math.max(f, min), max);
+    }
+
+    @Test
+    public void testGetColor() {
+        final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
+        List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
+        for (Config config : new Config[] { Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565 }) {
+            for (ColorSpace bitmapColorSpace : rgbColorSpaces) {
+                mBitmap = Bitmap.createBitmap(1, 1, config, /*hasAlpha*/ false,
+                        bitmapColorSpace);
+                bitmapColorSpace = mBitmap.getColorSpace();
+                for (ColorSpace eraseColorSpace : rgbColorSpaces) {
+                    for (long wideGamutLong : new long[] {
+                            Color.pack(1.0f, 0.0f, 0.0f, 1.0f, eraseColorSpace),
+                            Color.pack(0.0f, 1.0f, 0.0f, 1.0f, eraseColorSpace),
+                            Color.pack(0.0f, 0.0f, 1.0f, 1.0f, eraseColorSpace)}) {
+                        mBitmap.eraseColor(wideGamutLong);
+
+                        Color result = mBitmap.getColor(0, 0);
+                        if (mBitmap.getColorSpace().equals(sRGB)) {
+                            assertEquals(mBitmap.getPixel(0, 0), result.toArgb());
+                        }
+                        if (eraseColorSpace.equals(bitmapColorSpace)) {
+                            final Color wideGamutColor = Color.valueOf(wideGamutLong);
+                            ColorUtils.verifyColor("Erasing to Bitmap's ColorSpace "
+                                    + bitmapColorSpace, wideGamutColor, result, .0f);
+
+                        } else {
+                            Color convertedColor = Color.valueOf(
+                                    Color.convert(wideGamutLong, bitmapColorSpace));
+                            if (mBitmap.getConfig() != Config.RGBA_F16) {
+                                // It's possible that we have to clip to fit into the Config.
+                                convertedColor = Color.valueOf(
+                                        clamp(convertedColor.red()),
+                                        clamp(convertedColor.green()),
+                                        clamp(convertedColor.blue()),
+                                        convertedColor.alpha(),
+                                        bitmapColorSpace);
+                            }
+                            ColorUtils.verifyColor("Bitmap(Config: " + mBitmap.getConfig()
+                                    + ", ColorSpace: " + bitmapColorSpace
+                                    + ") erasing to " + Color.valueOf(wideGamutLong),
+                                    convertedColor, result, .03f);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static class ARGB {
+        public float alpha;
+        public float red;
+        public float green;
+        public float blue;
+        ARGB(float alpha, float red, float green, float blue) {
+            this.alpha = alpha;
+            this.red = red;
+            this.green = green;
+            this.blue = blue;
+        }
+    };
+
+    @Test
+    public void testEraseColorLong() {
+        List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
+        for (Config config : new Config[]{Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16}) {
+            mBitmap = Bitmap.createBitmap(100, 100, config);
+            // pack SRGB colors into ColorLongs.
+            for (int color : new int[]{ Color.RED, Color.BLUE, Color.GREEN, Color.BLACK,
+                    Color.WHITE, Color.TRANSPARENT }) {
+                if (config.equals(Config.RGB_565) && Float.compare(Color.alpha(color), 1.0f) != 0) {
+                    // 565 doesn't support alpha.
+                    continue;
+                }
+                mBitmap.eraseColor(Color.pack(color));
+                // The Bitmap is either SRGB or SRGBLinear (F16). getPixel(), which retrieves the
+                // color in SRGB, should match exactly.
+                ColorUtils.verifyColor("Config " + config + " mismatch at 10, 10 ",
+                        color, mBitmap.getPixel(10, 10), 0);
+                ColorUtils.verifyColor("Config " + config + " mismatch at 50, 50 ",
+                        color, mBitmap.getPixel(50, 50), 0);
+            }
+
+            // Use arbitrary colors in various ColorSpaces. getPixel() should approximately match
+            // the SRGB version of the color.
+            for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f),
+                                          new ARGB(1.0f, .3f, .6f, .9f),
+                                          new ARGB(0.5f, .2f, .8f, .7f) }) {
+                if (config.equals(Config.RGB_565) && Float.compare(color.alpha, 1.0f) != 0) {
+                    continue;
+                }
+                int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
+                for (ColorSpace cs : rgbColorSpaces) {
+                    long longColor = Color.convert(srgbColor, cs);
+                    mBitmap.eraseColor(longColor);
+                    // These tolerances were chosen by trial and error. It is expected that
+                    // some conversions do not round-trip perfectly.
+                    int tolerance = 1;
+                    if (config.equals(Config.RGB_565)) {
+                        tolerance = 4;
+                    } else if (cs.equals(ColorSpace.get(ColorSpace.Named.SMPTE_C))) {
+                        tolerance = 3;
+                    }
+
+                    ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs
+                            + ", mismatch at 10, 10 ", srgbColor, mBitmap.getPixel(10, 10),
+                            tolerance);
+                    ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs
+                            + ", mismatch at 50, 50 ", srgbColor, mBitmap.getPixel(50, 50),
+                            tolerance);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testEraseColorOnP3() {
+        // Use a ColorLong with a different ColorSpace than the Bitmap. getPixel() should
+        // approximately match the SRGB version of the color.
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888, true,
+                ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        int srgbColor = Color.argb(.5f, .3f, .6f, .7f);
+        long acesColor = Color.convert(srgbColor, ColorSpace.get(ColorSpace.Named.ACES));
+        mBitmap.eraseColor(acesColor);
+        ColorUtils.verifyColor("Mismatch at 15, 15", srgbColor, mBitmap.getPixel(15, 15), 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEraseColorXYZ() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEraseColorLAB() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEraseColorUnknown() {
+        mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mBitmap.eraseColor(-1L);
+    }
+
     @Test(expected=IllegalStateException.class)
     public void testExtractAlphaFromRecycled() {
         mBitmap.recycle();
@@ -782,6 +1116,63 @@
     }
 
     @Test
+    public void testSetColorSpace() {
+        // Use arbitrary colors and assign to various ColorSpaces.
+        for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f),
+                new ARGB(1.0f, .3f, .6f, .9f),
+                new ARGB(0.5f, .2f, .8f, .7f) }) {
+
+            int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
+            for (ColorSpace.Named e : ColorSpace.Named.values()) {
+                ColorSpace cs = ColorSpace.get(e);
+                if (cs.getModel() != ColorSpace.Model.RGB) {
+                    continue;
+                }
+                if (((ColorSpace.Rgb) cs).getTransferParameters() == null) {
+                    continue;
+                }
+
+                mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+                mBitmap.eraseColor(srgbColor);
+                mBitmap.setColorSpace(cs);
+
+                // This tolerance was chosen by trial and error. It is expected that
+                // some conversions do not round-trip perfectly.
+                int tolerance = 2;
+                long newColor = Color.pack(color.red, color.green, color.blue, color.alpha, cs);
+                ColorUtils.verifyColor("Mismatch after setting the colorSpace to " + cs.getName(),
+                        Color.toArgb(newColor), mBitmap.getPixel(5, 5), tolerance);
+            }
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSetColorSpaceRecycled() {
+        mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        mBitmap.recycle();
+        mBitmap.setColorSpace(ColorSpace.get(Named.DISPLAY_P3));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorSpaceNull() {
+        mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        mBitmap.setColorSpace(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorSpaceXYZ() {
+        mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        mBitmap.setColorSpace(ColorSpace.get(Named.CIE_XYZ));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorSpaceReducedRange() {
+        mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true,
+                ColorSpace.get(Named.EXTENDED_SRGB));
+        mBitmap.setColorSpace(ColorSpace.get(Named.DISPLAY_P3));
+    }
+
+    @Test
     public void testSetConfig() {
         mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
         int alloc = mBitmap.getAllocationByteCount();
@@ -1297,6 +1688,30 @@
         assertFalse(bitmap1.sameAs(bitmap4));
     }
 
+    @Test
+    public void testSameAs_wrappedHardwareBuffer() {
+        try (HardwareBuffer hwBufferA = createTestBuffer(512, 512, true);
+             HardwareBuffer hwBufferB = createTestBuffer(512, 512, true);
+             HardwareBuffer hwBufferC = createTestBuffer(512, 512, true);) {
+            // Fill buffer C with generated data
+            nFillRgbaHwBuffer(hwBufferC);
+
+            // Create the test bitmaps
+            Bitmap bitmap1 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB));
+            Bitmap bitmap2 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB));
+            Bitmap bitmap3 = BitmapFactory.decodeResource(mRes, R.drawable.robot);
+            Bitmap bitmap4 = Bitmap.wrapHardwareBuffer(hwBufferB, ColorSpace.get(Named.SRGB));
+            Bitmap bitmap5 = Bitmap.wrapHardwareBuffer(hwBufferC, ColorSpace.get(Named.SRGB));
+
+            // Run the compare-a-thon
+            assertTrue(bitmap1.sameAs(bitmap2));  // SAME UNDERLYING BUFFER
+            assertTrue(bitmap2.sameAs(bitmap1));  // SAME UNDERLYING BUFFER
+            assertFalse(bitmap1.sameAs(bitmap3)); // HW vs. NON-HW
+            assertTrue(bitmap1.sameAs(bitmap4));  // DIFFERENT BUFFERS, SAME CONTENT
+            assertFalse(bitmap1.sameAs(bitmap5)); // DIFFERENT BUFFERS, DIFFERENT CONTENT
+        }
+    }
+
     @Test(expected=IllegalStateException.class)
     public void testHardwareGetPixel() {
         Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
@@ -1362,6 +1777,12 @@
     }
 
     @Test(expected = IllegalStateException.class)
+    public void testHardwareEraseColorLong() {
+        Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
+        bitmap.eraseColor(Color.pack(0));
+    }
+
+    @Test(expected = IllegalStateException.class)
     public void testHardwareCopyPixelsToBuffer() {
         Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS);
         ByteBuffer byteBuf = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight());
@@ -1457,6 +1878,14 @@
         nValidateNdkAccessAfterRecycle(bitmap);
     }
 
+    @Test
+    public void bitmapIsMutable() {
+        Bitmap b = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        assertTrue("CreateBitmap w/ params should be mutable", b.isMutable());
+        assertTrue("CreateBitmap from bitmap should be mutable",
+                Bitmap.createBitmap(b).isMutable());
+    }
+
     private void runGcAndFinalizersSync() {
         final CountDownLatch fence = new CountDownLatch(1);
         new Object() {
@@ -1529,6 +1958,41 @@
 
     @Test
     @LargeTest
+    public void testWrappedHardwareBufferBitmapNotLeaking() {
+        Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
+        Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        opts.inPreferredConfig = Config.HARDWARE;
+        opts.inScaled = false;
+
+        final ColorSpace colorSpace = ColorSpace.get(Named.SRGB);
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
+            for (int i = 0; i < 2000; i++) {
+                if (i == 2) {
+                    // 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);
+                }
+                if (i % 100 == 5) {
+                    assertNotLeaking(i, meminfoStart, meminfoEnd);
+                }
+                Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, colorSpace);
+                assertNotNull(bitmap);
+                // Make sure nothing messed with the bitmap
+                assertEquals(128, bitmap.getWidth());
+                assertEquals(128, bitmap.getHeight());
+                assertEquals(Config.HARDWARE, bitmap.getConfig());
+                bitmap.recycle();
+            }
+        }
+
+        assertNotLeaking(2000, meminfoStart, meminfoEnd);
+    }
+
+    @Test
+    @LargeTest
     public void testDrawingHardwareBitmapNotLeaking() {
         Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
         Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
@@ -1565,6 +2029,64 @@
         assertNotLeaking(2000, meminfoStart, meminfoEnd);
     }
 
+    @Test
+    public void testWrapHardwareBufferHoldsReference() {
+        RenderTarget renderTarget = RenderTarget.create();
+        renderTarget.setDefaultSize(128, 128);
+        final Surface surface = renderTarget.getSurface();
+        Bitmap bitmap = null;
+
+        // Create hardware-buffer and wrap it in a Bitmap
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
+            // Fill buffer with colors (x, y, 42, 255)
+            nFillRgbaHwBuffer(hwBuffer);
+            bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+        }
+
+        // Buffer is closed at this point. Ensure bitmap still works by drawing it
+        assertEquals(128, bitmap.getWidth());
+        assertEquals(128, bitmap.getHeight());
+        assertEquals(Config.HARDWARE, bitmap.getConfig());
+
+        // Copy bitmap to target bitmap we can read from
+        Bitmap dstBitmap = bitmap.copy(Config.ARGB_8888, false);
+        bitmap.recycle();
+
+        // Ensure that the bitmap has valid contents
+        int pixel = dstBitmap.getPixel(0, 0);
+        assertEquals(255 << 24 | 42, pixel);
+        dstBitmap.recycle();
+    }
+
+    @Test
+    public void testWrapHardwareBufferPreservesColors() {
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, true)) {
+            // Fill buffer with colors (x, y, 42, 255)
+            nFillRgbaHwBuffer(hwBuffer);
+
+            // Create HW bitmap from this buffer
+            Bitmap srcBitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+            assertNotNull(srcBitmap);
+
+            // Copy it to target non-HW bitmap
+            Bitmap dstBitmap = srcBitmap.copy(Config.ARGB_8888, false);
+            srcBitmap.recycle();
+
+            // Ensure all colors are as expected (matches the nFillRgbaHwBuffer call used above).
+            for (int y = 0; y < 128; ++y) {
+                for (int x = 0; x < 128; ++x) {
+                    int pixel = dstBitmap.getPixel(x, y);
+                    short a = 255;
+                    short r = (short) (x % 255);
+                    short g = (short) (y % 255);
+                    short b = 42;
+                    assertEquals(a << 24 | r << 16 | g << 8 | b, pixel);
+                }
+            }
+            dstBitmap.recycle();
+        }
+    }
+
     private void strictModeTest(Runnable runnable) {
         StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -1583,6 +2105,19 @@
             boolean is565);
     private static native void nValidateNdkAccessAfterRecycle(Bitmap bitmap);
 
+    private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
+
+    private static HardwareBuffer createTestBuffer(int width, int height, boolean cpuAccess) {
+        long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
+        if (cpuAccess) {
+            usage |= HardwareBuffer.USAGE_CPU_WRITE_RARELY;
+        }
+        // We can assume that RGBA_8888 format is supported for every platform.
+        HardwareBuffer hwBuffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+                1, usage);
+        return hwBuffer;
+    }
+
     private static int scaleFromDensity(int size, int sdensity, int tdensity) {
         if (sdensity == Bitmap.DENSITY_NONE || sdensity == tdensity) {
             return size;
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
index af11307..2c45d6e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 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,1768 +13,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.graphics.cts;
 
-import static org.junit.Assert.assertArrayEquals;
-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.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
 import android.graphics.Canvas;
-import android.graphics.Canvas.EdgeType;
-import android.graphics.Canvas.VertexMode;
 import android.graphics.Color;
-import android.graphics.ComposeShader;
-import android.graphics.DrawFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.Picture;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.RadialGradient;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region.Op;
-import android.graphics.Shader;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
+import android.graphics.ColorSpace;
 import android.support.test.runner.AndroidJUnit4;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.SpannedString;
-import android.util.DisplayMetrics;
 
-import com.android.compatibility.common.util.ColorUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class CanvasTest {
-    private final static int PAINT_COLOR = 0xff00ff00;
-    private final static int BITMAP_WIDTH = 10;
-    private final static int BITMAP_HEIGHT = 28;
-    private final static int FLOAT_ARRAY_LEN = 9;
-
-    // used for save related methods tests
-    private final float[] values1 = {
-            1, 2, 3, 4, 5, 6, 7, 8, 9
-    };
-
-    private final float[] values2 = {
-            9, 8, 7, 6, 5, 4, 3, 2, 1
-    };
-
-    private Paint mPaint;
-    private Canvas mCanvas;
-    private Bitmap mImmutableBitmap;
-    private Bitmap mMutableBitmap;
+    Canvas mCanvas;
 
     @Before
     public void setup() {
-        mPaint = new Paint();
-        mPaint.setColor(PAINT_COLOR);
-
-        final Resources res = InstrumentationRegistry.getTargetContext().getResources();
-        BitmapFactory.Options opt = new BitmapFactory.Options();
-        opt.inScaled = false; // bitmap will only be immutable if not scaled during load
-        mImmutableBitmap = BitmapFactory.decodeResource(res, R.drawable.start, opt);
-        assertFalse(mImmutableBitmap.isMutable());
-        mMutableBitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Config.ARGB_8888);
-        mCanvas = new Canvas(mMutableBitmap);
-    }
-
-    @Test
-    public void testCanvas() {
-        new Canvas();
-
-        mMutableBitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Config.ARGB_8888);
-        new Canvas(mMutableBitmap);
-    }
-
-    @Test(expected=IllegalStateException.class)
-    public void testCanvasFromImmutableBitmap() {
-        // Should throw out IllegalStateException when creating Canvas with an ImmutableBitmap
-        new Canvas(mImmutableBitmap);
-    }
-
-    @Test(expected=RuntimeException.class)
-    public void testCanvasFromRecycledBitmap() {
-        // Should throw out RuntimeException when creating Canvas with a MutableBitmap which
-        // is recycled
-        mMutableBitmap.recycle();
-        new Canvas(mMutableBitmap);
-    }
-
-    @Test(expected=IllegalStateException.class)
-    public void testSetBitmapToImmutableBitmap() {
-        // Should throw out IllegalStateException when setting an ImmutableBitmap to a Canvas
-        mCanvas.setBitmap(mImmutableBitmap);
-    }
-
-    @Test(expected=RuntimeException.class)
-    public void testSetBitmapToRecycledBitmap() {
-        // Should throw out RuntimeException when setting Bitmap which is recycled to a Canvas
-        mMutableBitmap.recycle();
-        mCanvas.setBitmap(mMutableBitmap);
-    }
-
-    @Test
-    public void testSetBitmap() {
-        mMutableBitmap = Bitmap.createBitmap(BITMAP_WIDTH, 31, Config.ARGB_8888);
-        mCanvas.setBitmap(mMutableBitmap);
-        assertEquals(BITMAP_WIDTH, mCanvas.getWidth());
-        assertEquals(31, mCanvas.getHeight());
-    }
-
-    @Test
-    public void testSetBitmapFromEmpty() {
-        Canvas canvas = new Canvas();
-        assertEquals(0, canvas.getWidth());
-        assertEquals(0, canvas.getHeight());
-
-        // now ensure that we can "grow" the canvas
-
-        Bitmap normal = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
-        canvas.setBitmap(normal);
-        assertEquals(10, canvas.getWidth());
-        assertEquals(10, canvas.getHeight());
-
-        // now draw, and check that the clip was "open"
-        canvas.drawColor(0xFFFF0000);
-        assertEquals(0xFFFF0000, normal.getPixel(5, 5));
-    }
-
-    @Test
-    public void testSetBitmapCleanClip() {
-        mCanvas.setBitmap(Bitmap.createBitmap(10, 10, Config.ARGB_8888));
-        Rect r = new Rect(2, 2, 8, 8);
-        mCanvas.save();
-        mCanvas.clipRect(r);
-        assertEquals(r, mCanvas.getClipBounds());
-
-        // "reset" the canvas, and then check that the clip is wide open
-        // and not the previous value
-
-        mCanvas.setBitmap(Bitmap.createBitmap(20, 20, Config.ARGB_8888));
-        r = new Rect(0, 0, 20, 20);
-        assertEquals(r, mCanvas.getClipBounds());
-    }
-
-    @Test
-    public void testSetBitmapSaveCount() {
-        Canvas c = new Canvas(Bitmap.createBitmap(10, 10, Config.ARGB_8888));
-        int initialSaveCount = c.getSaveCount();
-
-        c.save();
-        assertEquals(c.getSaveCount(), initialSaveCount + 1);
-
-        // setBitmap should restore the saveCount to its original/base value
-        c.setBitmap(Bitmap.createBitmap(10, 10, Config.ARGB_8888));
-        assertEquals(c.getSaveCount(), initialSaveCount);
-    }
-
-    @Test
-    public void testIsOpaque() {
-        assertFalse(mCanvas.isOpaque());
-    }
-
-    @Test(expected=IllegalStateException.class)
-    public void testRestoreWithoutSave() {
-        // Should throw out IllegalStateException because cannot restore Canvas before save
-        mCanvas.restore();
-    }
-
-    @Test
-    public void testRestore() {
-        mCanvas.save();
-        mCanvas.restore();
-    }
-
-    @Test
-    public void testSave() {
-        final Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        mCanvas.save();
-
-        final Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        final float[] values3 = new float[FLOAT_ARRAY_LEN];
-        final Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restore();
-        final float[] values4 = new float[FLOAT_ARRAY_LEN];
-        final Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testSaveLayer1() {
-        final Paint p = new Paint();
-        final RectF rF = new RectF(0, 10, 31, 0);
-
-        // test save everything
-        Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        mCanvas.saveLayer(rF, p);
-
-        Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        float[] values3 = new float[FLOAT_ARRAY_LEN];
-        Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restore();
-        float[] values4 = new float[FLOAT_ARRAY_LEN];
-        Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testSaveLayer2() {
-        final Paint p = new Paint();
-
-        // test save everything
-        Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        mCanvas.saveLayer(10, 0, 0, 31, p);
-
-        Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        float[] values3 = new float[FLOAT_ARRAY_LEN];
-        Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restore();
-        float[] values4 = new float[FLOAT_ARRAY_LEN];
-        Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testSaveLayerAlpha1() {
-        final RectF rF = new RectF(0, 10, 31, 0);
-
-        // test save everything
-        Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        mCanvas.saveLayerAlpha(rF, 0xff);
-
-        Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        float[] values3 = new float[FLOAT_ARRAY_LEN];
-        Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restore();
-        float[] values4 = new float[FLOAT_ARRAY_LEN];
-        Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testSaveLayerAlpha2() {
-        // test save everything
-        Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        mCanvas.saveLayerAlpha(0, 10, 31, 0, 0xff);
-
-        Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        float[] values3 = new float[FLOAT_ARRAY_LEN];
-        Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restore();
-        float[] values4 = new float[FLOAT_ARRAY_LEN];
-        Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testGetSaveCount() {
-        // why is 1 not 0
-        assertEquals(1, mCanvas.getSaveCount());
-        mCanvas.save();
-        assertEquals(2, mCanvas.getSaveCount());
-        mCanvas.save();
-        assertEquals(3, mCanvas.getSaveCount());
-        mCanvas.saveLayer(new RectF(), new Paint());
-        assertEquals(4, mCanvas.getSaveCount());
-        mCanvas.saveLayerAlpha(new RectF(), 0);
-        assertEquals(5, mCanvas.getSaveCount());
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testRestoreToCountIllegalSaveCount() {
-        // Should throw out IllegalArgumentException because saveCount is less than 1
-        mCanvas.restoreToCount(0);
-    }
-
-    @Test
-    public void testRestoreToCountExceptionBehavior() {
-        int restoreTo = mCanvas.save();
-        mCanvas.save();
-        int beforeCount = mCanvas.getSaveCount();
-
-        boolean exceptionObserved = false;
-        try {
-            mCanvas.restoreToCount(restoreTo - 1);
-        } catch (IllegalArgumentException e) {
-            exceptionObserved = true;
-        }
-
-        // restore to count threw, AND did no restoring
-        assertTrue(exceptionObserved);
-        assertEquals(beforeCount, mCanvas.getSaveCount());
-    }
-
-    @Test
-    public void testRestoreToCount() {
-        final Matrix m1 = new Matrix();
-        m1.setValues(values1);
-        mCanvas.setMatrix(m1);
-        final int count = mCanvas.save();
-        assertTrue(count > 0);
-
-        final Matrix m2 = new Matrix();
-        m2.setValues(values2);
-        mCanvas.setMatrix(m2);
-
-        final float[] values3 = new float[FLOAT_ARRAY_LEN];
-        final Matrix m3 = mCanvas.getMatrix();
-        m3.getValues(values3);
-
-        assertArrayEquals(values2, values3, 0.0f);
-
-        mCanvas.restoreToCount(count);
-        final float[] values4 = new float[FLOAT_ARRAY_LEN];
-        final Matrix m4 = mCanvas.getMatrix();
-        m4.getValues(values4);
-
-        assertArrayEquals(values1, values4, 0.0f);
-    }
-
-    @Test
-    public void testGetMatrix1() {
-        final float[] f1 = {
-                1, 2, 3, 4, 5, 6, 7, 8, 9
-        };
-        final Matrix m1 = new Matrix();
-        m1.setValues(f1);
-        mCanvas.setMatrix(m1);
-
-        final Matrix m2 = new Matrix(m1);
-        mCanvas.getMatrix(m2);
-
-        assertTrue(m1.equals(m2));
-
-        final float[] f2 = new float[FLOAT_ARRAY_LEN];
-        m2.getValues(f2);
-
-        assertArrayEquals(f1, f2, 0.0f);
-    }
-
-    @Test
-    public void testGetMatrix2() {
-        final float[] f1 = {
-                1, 2, 3, 4, 5, 6, 7, 8, 9
-        };
-        final Matrix m1 = new Matrix();
-        m1.setValues(f1);
-
-        mCanvas.setMatrix(m1);
-        final Matrix m2 = mCanvas.getMatrix();
-
-        assertTrue(m1.equals(m2));
-
-        final float[] f2 = new float[FLOAT_ARRAY_LEN];
-        m2.getValues(f2);
-
-        assertArrayEquals(f1, f2, 0.0f);
-    }
-
-    @Test
-    public void testTranslate() {
-        preCompare();
-
-        mCanvas.translate(0.10f, 0.28f);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-            1.0f, 0.0f, 0.1f, 0.0f, 1.0f, 0.28f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testScale1() {
-        preCompare();
-
-        mCanvas.scale(0.5f, 0.5f);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testScale2() {
-        preCompare();
-
-        mCanvas.scale(3.0f, 3.0f, 1.0f, 1.0f);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                3.0f, 0.0f, -2.0f, 0.0f, 3.0f, -2.0f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testRotate1() {
-        preCompare();
-
-        mCanvas.rotate(90);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testRotate2() {
-        preCompare();
-
-        mCanvas.rotate(30, 1.0f, 0.0f);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                0.8660254f, -0.5f, 0.13397461f, 0.5f, 0.8660254f, -0.5f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testSkew() {
-        preCompare();
-
-        mCanvas.skew(1.0f, 3.0f);
-
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                1.0f, 1.0f, 0.0f, 3.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testConcat() {
-        preCompare();
-
-        final Matrix m = new Matrix();
-        final float[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8};
-
-        m.setValues(values);
-        mCanvas.concat(m);
-
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f
-        }, values, 0.0f);
-    }
-
-    @Test
-    public void testClipRectF() {
-        mCanvas.save();
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(new RectF(0, 0, 10, 31), Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(new RectF(10, 31, 11, 32), Op.INTERSECT));
-        // replace with the original clip
-        mCanvas.restore();
-        // intersect with clip that covers top portion of canvas
-        assertTrue(mCanvas.clipRect(new RectF(0, 0, 20, 10), Op.INTERSECT));
-        // intersect with clip that covers bottom portion of canvas
-        assertFalse(mCanvas.clipRect(new RectF(0, 10, 20, 32), Op.INTERSECT));
-        // ensure that difference doesn't widen already closed clip
-        assertFalse(mCanvas.clipRect(new RectF(0, 0, 10, 31), Op.DIFFERENCE));
-    }
-
-    @Test
-    public void testClipRect() {
-        mCanvas.save();
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(new Rect(0, 0, 10, 31), Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(new Rect(10, 31, 11, 32), Op.INTERSECT));
-        // replace with the original clip
-        mCanvas.restore();
-        // intersect with clip that covers top portion of canvas
-        assertTrue(mCanvas.clipRect(new Rect(0, 0, 20, 10), Op.INTERSECT));
-        // intersect with clip that covers bottom portion of canvas
-        assertFalse(mCanvas.clipRect(new Rect(0, 10, 20, 32), Op.INTERSECT));
-        // ensure that difference doesn't widen already closed clip
-        assertFalse(mCanvas.clipRect(new Rect(0, 0, 10, 31), Op.DIFFERENCE));
-    }
-
-    @Test
-    public void testClipRect4I() {
-        mCanvas.save();
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(0, 0, 10, 31, Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(10, 31, 11, 32, Op.INTERSECT));
-        // replace with the original clip
-        mCanvas.restore();
-        // intersect with clip that covers top portion of canvas
-        assertTrue(mCanvas.clipRect(0, 0, 20, 10, Op.INTERSECT));
-        // intersect with clip that covers bottom portion of canvas
-        assertFalse(mCanvas.clipRect(0, 10, 20, 32, Op.INTERSECT));
-        // ensure that difference doesn't widen already closed clip
-        assertFalse(mCanvas.clipRect(0, 0, 10, 31, Op.DIFFERENCE));
-    }
-
-    @Test
-    public void testClipRect4F() {
-        mCanvas.save();
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(0f, 0f, 10f, 31f, Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(10f, 31f, 11f, 32f, Op.INTERSECT));
-        // replace with the original clip
-        mCanvas.restore();
-        // intersect with clip that covers top portion of canvas
-        assertTrue(mCanvas.clipRect(0f, 0f, 20f, 10f, Op.INTERSECT));
-        // intersect with clip that covers bottom portion of canvas
-        assertFalse(mCanvas.clipRect(0f, 10f, 20f, 32f, Op.INTERSECT));
-        // ensure that difference doesn't widen already closed clip
-        assertFalse(mCanvas.clipRect(0f, 0f, 10f, 31f, Op.DIFFERENCE));
-    }
-
-    @Test
-    public void testClipOutRectF() {
-        // remove center, clip not empty
-        assertTrue(mCanvas.clipOutRect(new RectF(1, 1, 9, 27)));
-        // replace clip, verify difference doesn't widen
-        assertFalse(mCanvas.clipRect(new RectF(0, 0, 0, 0)));
-        assertFalse(mCanvas.clipOutRect(new RectF(0, 0, 100, 100)));
-    }
-
-    @Test
-    public void testClipOutRect() {
-        // remove center, clip not empty
-        assertTrue(mCanvas.clipOutRect(new Rect(1, 1, 9, 27)));
-        // replace clip, verify difference doesn't widen
-        assertFalse(mCanvas.clipRect(new Rect(0, 0, 0, 0)));
-        assertFalse(mCanvas.clipOutRect(new Rect(0, 0, 100, 100)));
-    }
-
-    @Test
-    public void testClipOutRect4I() {
-        // remove center, clip not empty
-        assertTrue(mCanvas.clipOutRect(1, 1, 9, 27));
-        // replace clip, verify difference doesn't widen
-        assertFalse(mCanvas.clipRect(0, 0, 0, 0));
-        assertFalse(mCanvas.clipOutRect(0, 0, 100, 100));
-    }
-
-    @Test
-    public void testClipOutRect4F() {
-        // remove center, clip not empty
-        assertTrue(mCanvas.clipOutRect(1f, 1f, 9f, 27f));
-        // replace clip, verify difference doesn't widen
-        assertFalse(mCanvas.clipRect(0f, 0f, 0f, 0f));
-        assertFalse(mCanvas.clipOutRect(0f, 0f, 100f, 100f));
-    }
-
-    @Test
-    public void testIntersectClipRectF() {
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(new RectF(0, 0, 10, 31)));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(new RectF(10, 31, 11, 32)));
-    }
-
-    @Test
-    public void testIntersectClipRect() {
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(new Rect(0, 0, 10, 31)));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(new Rect(10, 31, 11, 32)));
-    }
-
-    @Test
-    public void testIntersectClipRect4F() {
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipRect(0, 0, 10, 31));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipRect(10, 31, 11, 32));
-    }
-
-    @Test
-    public void testClipPath1() {
-        final Path p = new Path();
-        p.addRect(new RectF(0, 0, 10, 31), Direction.CCW);
-        assertTrue(mCanvas.clipPath(p));
-    }
-
-    @Test
-    public void testClipPath2() {
-        final Path p = new Path();
-        p.addRect(new RectF(0, 0, 10, 31), Direction.CW);
-
-        final Path pIn = new Path();
-        pIn.addOval(new RectF(0, 0, 20, 10), Direction.CW);
-
-        final Path pOut = new Path();
-        pOut.addRoundRect(new RectF(10, 31, 11, 32), 0.5f, 0.5f, Direction.CW);
-
-        mCanvas.save();
-        // intersect with clip larger than canvas
-        assertTrue(mCanvas.clipPath(p, Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipPath(pOut, Op.INTERSECT));
-        // replace with the original clip
-        mCanvas.restore();
-        // intersect with clip that covers top portion of canvas
-        assertTrue(mCanvas.clipPath(pIn, Op.INTERSECT));
-        // intersect with clip outside of canvas bounds
-        assertFalse(mCanvas.clipPath(pOut, Op.INTERSECT));
-        // ensure that difference doesn't widen already closed clip
-        assertFalse(mCanvas.clipPath(p, Op.DIFFERENCE));
-    }
-
-    @Test
-    public void testClipOutPath() {
-        final Path p = new Path();
-        p.addRect(new RectF(5, 5, 10, 10), Direction.CW);
-        assertTrue(mCanvas.clipOutPath(p));
-    }
-
-    @Test
-    public void testClipInversePath() {
-        final Path p = new Path();
-        p.addRoundRect(new RectF(0, 0, 10, 10), 0.5f, 0.5f, Direction.CW);
-        p.setFillType(Path.FillType.INVERSE_WINDING);
-        assertTrue(mCanvas.clipPath(p, Op.INTERSECT));
-
-        mCanvas.drawColor(PAINT_COLOR);
-
-        assertEquals(Color.TRANSPARENT, mMutableBitmap.getPixel(0, 0));
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 20));
-    }
-
-    @Test
-    public void testGetDrawFilter() {
-        assertNull(mCanvas.getDrawFilter());
-        final DrawFilter dF = new DrawFilter();
-        mCanvas.setDrawFilter(dF);
-
-        assertTrue(dF.equals(mCanvas.getDrawFilter()));
-    }
-
-    @Test
-    public void testQuickReject1() {
-        assertFalse(mCanvas.quickReject(new RectF(0, 0, 10, 31), EdgeType.AA));
-        assertFalse(mCanvas.quickReject(new RectF(0, 0, 10, 31), EdgeType.BW));
-    }
-
-    @Test
-    public void testQuickReject2() {
-        final Path p = new Path();
-        p.addRect(new RectF(0, 0, 10, 31), Direction.CCW);
-
-        assertFalse(mCanvas.quickReject(p, EdgeType.AA));
-        assertFalse(mCanvas.quickReject(p, EdgeType.BW));
-    }
-
-    @Test
-    public void testQuickReject3() {
-        assertFalse(mCanvas.quickReject(0, 0, 10, 31, EdgeType.AA));
-        assertFalse(mCanvas.quickReject(0, 0, 10, 31, EdgeType.BW));
-    }
-
-    @Test
-    public void testGetClipBounds1() {
-        final Rect r = new Rect();
-
-        assertTrue(mCanvas.getClipBounds(r));
-        assertEquals(BITMAP_WIDTH, r.width());
-        assertEquals(BITMAP_HEIGHT, r.height());
-    }
-
-    @Test
-    public void testGetClipBounds2() {
-        final Rect r = mCanvas.getClipBounds();
-
-        assertEquals(BITMAP_WIDTH, r.width());
-        assertEquals(BITMAP_HEIGHT, r.height());
-    }
-
-    private void verifyDrewColor(int color) {
-        assertEquals(color, mMutableBitmap.getPixel(0, 0));
-        assertEquals(color, mMutableBitmap.getPixel(BITMAP_WIDTH / 2, BITMAP_HEIGHT / 2));
-        assertEquals(color, mMutableBitmap.getPixel(BITMAP_WIDTH - 1, BITMAP_HEIGHT - 1));
-    }
-
-    @Test
-    public void testDrawRGB() {
-        final int alpha = 0xff;
-        final int red = 0xff;
-        final int green = 0xff;
-        final int blue = 0xff;
-
-        mCanvas.drawRGB(red, green, blue);
-
-        final int color = alpha << 24 | red << 16 | green << 8 | blue;
-        verifyDrewColor(color);
-    }
-
-    @Test
-    public void testDrawARGB() {
-        final int alpha = 0xff;
-        final int red = 0x22;
-        final int green = 0x33;
-        final int blue = 0x44;
-
-        mCanvas.drawARGB(alpha, red, green, blue);
-        final int color = alpha << 24 | red << 16 | green << 8 | blue;
-        verifyDrewColor(color);
-    }
-
-    @Test
-    public void testDrawColor1() {
-        final int color = Color.RED;
-
-        mCanvas.drawColor(color);
-        verifyDrewColor(color);
-    }
-
-    @Test
-    public void testDrawColor2() {
-        mCanvas.drawColor(Color.RED, Mode.CLEAR);
-        mCanvas.drawColor(Color.RED, Mode.DARKEN);
-        mCanvas.drawColor(Color.RED, Mode.DST);
-        mCanvas.drawColor(Color.RED, Mode.DST_ATOP);
-        mCanvas.drawColor(Color.RED, Mode.DST_IN);
-        mCanvas.drawColor(Color.RED, Mode.DST_OUT);
-        mCanvas.drawColor(Color.RED, Mode.DST_OVER);
-        mCanvas.drawColor(Color.RED, Mode.LIGHTEN);
-        mCanvas.drawColor(Color.RED, Mode.MULTIPLY);
-        mCanvas.drawColor(Color.RED, Mode.SCREEN);
-        mCanvas.drawColor(Color.RED, Mode.SRC);
-        mCanvas.drawColor(Color.RED, Mode.SRC_ATOP);
-        mCanvas.drawColor(Color.RED, Mode.SRC_IN);
-        mCanvas.drawColor(Color.RED, Mode.SRC_OUT);
-        mCanvas.drawColor(Color.RED, Mode.SRC_OVER);
-        mCanvas.drawColor(Color.RED, Mode.XOR);
-    }
-
-    @Test
-    public void testDrawPaint() {
-        mCanvas.drawPaint(mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawPointsInvalidOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
-        mCanvas.drawPoints(new float[]{
-                10.0f, 29.0f
-        }, -1, 2, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawPointsInvalidCount() {
-        // Should throw out ArrayIndexOutOfBoundsException because of invalid count
-        mCanvas.drawPoints(new float[]{
-                10.0f, 29.0f
-        }, 0, 31, mPaint);
-    }
-
-    @Test
-    public void testDrawPoints1() {
-        // normal case
-        mCanvas.drawPoints(new float[] {
-                0, 0
-        }, 0, 2, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test
-    public void testDrawPoints2() {
-        mCanvas.drawPoints(new float[]{0, 0}, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test
-    public void testDrawPoint() {
-        mCanvas.drawPoint(0, 0, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test
-    public void testDrawLine() {
-        mCanvas.drawLine(0, 0, 10, 12, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawLinesInvalidOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
-        mCanvas.drawLines(new float[]{
-                0, 0, 10, 31
-        }, 2, 4, new Paint());
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawLinesInvalidCount() {
-        // Should throw out ArrayIndexOutOfBoundsException because of invalid count
-        mCanvas.drawLines(new float[]{
-                0, 0, 10, 31
-        }, 0, 8, new Paint());
-    }
-
-    @Test
-    public void testDrawLines1() {
-        // normal case
-        mCanvas.drawLines(new float[] {
-                0, 0, 10, 12
-        }, 0, 4, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    @Test
-    public void testDrawLines2() {
-        mCanvas.drawLines(new float[] {
-                0, 0, 10, 12
-        }, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-    }
-
-    private void verifyDrewPaint() {
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(0, 0));
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(5, 6));
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(9, 11));
-    }
-
-    @Test
-    public void testDrawRect1() {
-        mCanvas.drawRect(new RectF(0, 0, 10, 12), mPaint);
-
-        verifyDrewPaint();
-    }
-
-    @Test
-    public void testDrawRect2() {
-        mCanvas.drawRect(new Rect(0, 0, 10, 12), mPaint);
-
-        verifyDrewPaint();
-    }
-
-    @Test
-    public void testDrawRect3() {
-        mCanvas.drawRect(0, 0, 10, 12, mPaint);
-
-        verifyDrewPaint();
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawOvalNull() {
-        // Should throw out NullPointerException because oval is null
-        mCanvas.drawOval(null, mPaint);
-    }
-
-    @Test
-    public void testDrawOval() {
-        // normal case
-        mCanvas.drawOval(new RectF(0, 0, 10, 12), mPaint);
-    }
-
-    @Test
-    public void testDrawCircle() {
-        // special case: circle's radius <= 0
-        mCanvas.drawCircle(10.0f, 10.0f, -1.0f, mPaint);
-
-        // normal case
-        mCanvas.drawCircle(10, 12, 3, mPaint);
-
-        assertEquals(PAINT_COLOR, mMutableBitmap.getPixel(9, 11));
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawArcNullOval() {
-        // Should throw NullPointerException because oval is null
-        mCanvas.drawArc(null, 10.0f, 29.0f, true, mPaint);
-    }
-
-    @Test
-    public void testDrawArc() {
-        // normal case
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 11, false, mPaint);
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 11, true, mPaint);
-
-        // special case: sweepAngle >= abs(360)
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 400, true, mPaint);
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, -400, true, mPaint);
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, Float.POSITIVE_INFINITY, true, mPaint);
-        mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, Float.NEGATIVE_INFINITY, true, mPaint);
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawRoundRectNull() {
-        // Should throw out NullPointerException because RoundRect is null
-        mCanvas.drawRoundRect(null, 10.0f, 29.0f, mPaint);
-    }
-
-    @Test
-    public void testDrawRoundRect() {
-        mCanvas.drawRoundRect(new RectF(0, 0, 10, 12), 8, 8, mPaint);
-    }
-
-    @Test
-    public void testDrawPath() {
-        mCanvas.drawPath(new Path(), mPaint);
-    }
-
-    @Test(expected=RuntimeException.class)
-    public void testDrawBitmapAtPointRecycled() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        b.recycle();
-
-        // Should throw out RuntimeException because bitmap has been recycled
-        mCanvas.drawBitmap(b, 10.0f, 29.0f, mPaint);
-    }
-
-    @Test
-    public void testDrawBitmapAtPoint() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 12, Config.ARGB_8888);
-        mCanvas.drawBitmap(b, 10, 12, null);
-        mCanvas.drawBitmap(b, 5, 12, mPaint);
-    }
-
-    @Test(expected=RuntimeException.class)
-    public void testDrawBitmapSrcDstFloatRecycled() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        b.recycle();
-
-        // Should throw out RuntimeException because bitmap has been recycled
-        mCanvas.drawBitmap(b, null, new RectF(), mPaint);
-    }
-
-    @Test
-    public void testDrawBitmapSrcDstFloat() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        mCanvas.drawBitmap(b, new Rect(), new RectF(), null);
-        mCanvas.drawBitmap(b, new Rect(), new RectF(), mPaint);
-    }
-
-    @Test(expected=RuntimeException.class)
-    public void testDrawBitmapSrcDstIntRecycled() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        b.recycle();
-
-        // Should throw out RuntimeException because bitmap has been recycled
-        mCanvas.drawBitmap(b, null, new Rect(), mPaint);
-    }
-
-    @Test
-    public void testDrawBitmapSrcDstInt() {
-        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        mCanvas.drawBitmap(b, new Rect(), new Rect(), null);
-        mCanvas.drawBitmap(b, new Rect(), new Rect(), mPaint);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapIntsNegativeWidth() {
-        // Should throw out IllegalArgumentException because width is less than 0
-        mCanvas.drawBitmap(new int[2008], 10, 10, 10, 10, -1, 10, true, null);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapIntsNegativeHeight() {
-        // Should throw out IllegalArgumentException because height is less than 0
-        mCanvas.drawBitmap(new int[2008], 10, 10, 10, 10, 10, -1, true, null);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapIntsBadStride() {
-        // Should throw out IllegalArgumentException because stride less than width and
-        // bigger than -width
-        mCanvas.drawBitmap(new int[2008], 10, 5, 10, 10, 10, 10, true, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapIntsNegativeOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
-        mCanvas.drawBitmap(new int[2008], -1, 10, 10, 10, 10, 10, true, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapIntsBadOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
-        // is bigger than colors' length
-        mCanvas.drawBitmap(new int[29], 10, 29, 10, 10, 20, 10, true, null);
-    }
-
-    @Test
-    public void testDrawBitmapInts() {
-        final int[] colors = new int[2008];
-
-        // special case: width equals to 0
-        mCanvas.drawBitmap(colors, 10, 10, 10, 10, 0, 10, true, null);
-
-        // special case: height equals to 0
-        mCanvas.drawBitmap(colors, 10, 10, 10, 10, 10, 0, true, null);
-
-        // normal case
-        mCanvas.drawBitmap(colors, 10, 10, 10, 10, 10, 29, true, null);
-        mCanvas.drawBitmap(colors, 10, 10, 10, 10, 10, 29, true, mPaint);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapFloatsNegativeWidth() {
-        // Should throw out IllegalArgumentException because width is less than 0
-        mCanvas.drawBitmap(new int[2008], 10, 10, 10.0f, 10.0f, -1, 10, true, null);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapFloatsNegativeHeight() {
-        // Should throw out IllegalArgumentException because height is less than 0
-        mCanvas.drawBitmap(new int[2008], 10, 10, 10.0f, 10.0f, 10, -1, true, null);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDrawBitmapFloatsBadStride() {
-        // Should throw out IllegalArgumentException because stride less than width and
-        // bigger than -width
-        mCanvas.drawBitmap(new int[2008], 10, 5, 10.0f, 10.0f, 10, 10, true, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapFloatsNegativeOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
-        mCanvas.drawBitmap(new int[2008], -1, 10, 10.0f, 10.0f, 10, 10, true, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapFloatsBadOffset() {
-        // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
-        // is bigger than colors' length
-        mCanvas.drawBitmap(new int[29], 10, 29, 10.0f, 10.0f, 20, 10, true, null);
-    }
-
-    @Test
-    public void testDrawBitmapFloats() {
-        final int[] colors = new int[2008];
-
-        // special case: width equals to 0
-        mCanvas.drawBitmap(colors, 10, 10, 10.0f, 10.0f, 0, 10, true, null);
-
-        // special case: height equals to 0
-        mCanvas.drawBitmap(colors, 10, 10, 10.0f, 10.0f, 10, 0, true, null);
-
-        // normal case
-        mCanvas.drawBitmap(colors, 10, 10, 10.0f, 10.0f, 10, 29, true, null);
-        mCanvas.drawBitmap(colors, 10, 10, 10.0f, 10.0f, 10, 29, true, mPaint);
-    }
-
-    @Test
-    public void testDrawBitmapMatrix() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-        mCanvas.drawBitmap(b, new Matrix(), null);
-        mCanvas.drawBitmap(b, new Matrix(), mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshNegativeWidth() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because meshWidth less than 0
-        mCanvas.drawBitmapMesh(b, -1, 10, null, 0, null, 0, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshNegativeHeight() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because meshHeight is less than 0
-        mCanvas.drawBitmapMesh(b, 10, -1, null, 0, null, 0, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshNegativeVertOffset() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because vertOffset is less than 0
-        mCanvas.drawBitmapMesh(b, 10, 10, null, -1, null, 0, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshNegativeColorOffset() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because colorOffset is less than 0
-        mCanvas.drawBitmapMesh(b, 10, 10, null, 10, null, -1, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshTooFewVerts() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because verts' length is too short
-        mCanvas.drawBitmapMesh(b, 10, 10, new float[] {
-                10.0f, 29.0f
-        }, 10, null, 10, null);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawBitmapMeshTooFewColors() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // Should throw out ArrayIndexOutOfBoundsException because colors' length is too short
-        // abnormal case: colors' length is too short
-        final float[] verts = new float[2008];
-        mCanvas.drawBitmapMesh(b, 10, 10, verts, 10, new int[] {
-                10, 29
-        }, 10, null);
-    }
-
-    @Test
-    public void testDrawBitmapMesh() {
-        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Config.ARGB_8888);
-
-        // special case: meshWidth equals to 0
-        mCanvas.drawBitmapMesh(b, 0, 10, null, 10, null, 10, null);
-
-        // special case: meshHeight equals to 0
-        mCanvas.drawBitmapMesh(b, 10, 0, null, 10, null, 10, null);
-
-        // normal case
-        final float[] verts = new float[2008];
-        final int[] colors = new int[2008];
-        mCanvas.drawBitmapMesh(b, 10, 10, verts, 10, colors, 10, null);
-        mCanvas.drawBitmapMesh(b, 10, 10, verts, 10, colors, 10, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawVerticesTooFewVerts() {
-        final float[] verts = new float[10];
-        final float[] texs = new float[10];
-        final int[] colors = new int[10];
-        final short[] indices = {
-                0, 1, 2, 3, 4, 1
-        };
-
-        // Should throw out ArrayIndexOutOfBoundsException because sum of vertOffset and
-        // vertexCount is bigger than verts' length
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 10, verts, 8, texs, 0, colors, 0, indices,
-                0, 4, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawVerticesTooFewTexs() {
-        final float[] verts = new float[10];
-        final float[] texs = new float[10];
-        final int[] colors = new int[10];
-        final short[] indices = {
-                0, 1, 2, 3, 4, 1
-        };
-
-        // Should throw out ArrayIndexOutOfBoundsException because sum of texOffset and
-        // vertexCount is bigger thatn texs' length
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 10, verts, 0, texs, 30, colors, 0, indices,
-                0, 4, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawVerticesTooFewColors() {
-        final float[] verts = new float[10];
-        final float[] texs = new float[10];
-        final int[] colors = new int[10];
-        final short[] indices = {
-                0, 1, 2, 3, 4, 1
-        };
-
-        // Should throw out ArrayIndexOutOfBoundsException because sum of colorOffset and
-        // vertexCount is bigger than colors' length
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 10, verts, 0, texs, 0, colors, 30, indices,
-                0, 4, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawVerticesTooFewIndices() {
-        final float[] verts = new float[10];
-        final float[] texs = new float[10];
-        final int[] colors = new int[10];
-        final short[] indices = {
-                0, 1, 2, 3, 4, 1
-        };
-
-        // Should throw out ArrayIndexOutOfBoundsException because sum of indexOffset and
-        // indexCount is bigger than indices' length
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 10, verts, 0, texs, 0, colors, 0, indices,
-                10, 30, mPaint);
-    }
-
-    @Test
-    public void testDrawVertices() {
-        final float[] verts = new float[10];
-        final float[] texs = new float[10];
-        final int[] colors = new int[10];
-        final short[] indices = {
-                0, 1, 2, 3, 4, 1
-        };
-
-        // special case: in texs, colors, indices, one of them, two of them and
-        // all are null
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 0, verts, 0, null, 0, colors, 0, indices, 0, 0,
-                mPaint);
-
-        mCanvas.drawVertices(VertexMode.TRIANGLE_STRIP, 0, verts, 0, null, 0, null, 0, indices, 0,
-                0, mPaint);
-
-        mCanvas.drawVertices(VertexMode.TRIANGLE_FAN, 0, verts, 0, null, 0, null, 0, null, 0, 0,
-                mPaint);
-
-        // normal case: texs, colors, indices are not null
-        mCanvas.drawVertices(VertexMode.TRIANGLES, 10, verts, 0, texs, 0, colors, 0, indices, 0, 6,
-                mPaint);
-
-        mCanvas.drawVertices(VertexMode.TRIANGLE_STRIP, 10, verts, 0, texs, 0, colors, 0, indices,
-                0, 6, mPaint);
-
-        mCanvas.drawVertices(VertexMode.TRIANGLE_FAN, 10, verts, 0, texs, 0, colors, 0, indices, 0,
-                6, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawArrayTextNegativeIndex() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // Should throw out IndexOutOfBoundsException because index is less than 0
-        mCanvas.drawText(text, -1, 7, 10, 10, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawArrayTextNegativeCount() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // Should throw out IndexOutOfBoundsException because count is less than 0
-        mCanvas.drawText(text, 0, -1, 10, 10, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawArrayTextTextLengthTooSmall() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // Should throw out IndexOutOfBoundsException because sum of index and count
-        // is bigger than text's length
-        mCanvas.drawText(text, 0, 10, 10, 10, mPaint);
-    }
-
-    @Test
-    public void testDrawArrayText() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // normal case
-        mCanvas.drawText(text, 0, 7, 10, 10, mPaint);
-    }
-
-    @Test
-    public void testDrawStringTextAtPosition() {
-        mCanvas.drawText("android", 10, 30, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextTextAtPositionWithOffsetsNegativeStart() {
-        // Should throw out IndexOutOfBoundsException because start is less than 0
-        mCanvas.drawText("android", -1, 7, 10, 30, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextTextAtPositionWithOffsetsNegativeEnd() {
-        // Should throw out IndexOutOfBoundsException because end is less than 0
-        mCanvas.drawText("android", 0, -1, 10, 30, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextTextAtPositionWithOffsetsStartEndMismatch() {
-        // Should throw out IndexOutOfBoundsException because start is bigger than end
-        mCanvas.drawText("android", 3, 1, 10, 30, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextTextAtPositionWithOffsetsTextTooLong() {
-        // Should throw out IndexOutOfBoundsException because end subtracts start should
-        // bigger than text's length
-        mCanvas.drawText("android", 0, 10, 10, 30, mPaint);
-    }
-
-    @Test
-    public void testDrawTextTextAtPositionWithOffsets() {
-        final String t1 = "android";
-        mCanvas.drawText(t1, 0, 7, 10, 30, mPaint);
-
-        final SpannedString t2 = new SpannedString(t1);
-        mCanvas.drawText(t2, 0, 7, 10, 30, mPaint);
-
-        final SpannableString t3 = new SpannableString(t2);
-        mCanvas.drawText(t3, 0, 7, 10, 30, mPaint);
-
-        final SpannableStringBuilder t4 = new SpannableStringBuilder(t1);
-        mCanvas.drawText(t4, 0, 7, 10, 30, mPaint);
-
-        final StringBuffer t5 = new StringBuffer(t1);
-        mCanvas.drawText(t5, 0, 7, 10, 30, mPaint);
-    }
-
-    @Test
-    public void testDrawTextRun() {
-        final String text = "android";
-        final Paint paint = new Paint();
-
-        mCanvas.drawTextRun(text, 0, 0, 0, 0, 0.0f, 0.0f, false, paint);
-        mCanvas.drawTextRun(text, 0, text.length(), 0, text.length(), 0.0f, 0.0f, false, paint);
-        mCanvas.drawTextRun(text, text.length(), text.length(), text.length(), text.length(),
-                0.0f, 0.0f, false, paint);
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawTextRunNullCharArray() {
-        // Should throw out NullPointerException because text is null
-        mCanvas.drawTextRun((char[]) null, 0, 0, 0, 0, 0.0f, 0.0f, false, new Paint());
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawTextRunNullCharSequence() {
-        // Should throw out NullPointerException because text is null
-        mCanvas.drawTextRun((CharSequence) null, 0, 0, 0, 0, 0.0f, 0.0f, false, new Paint());
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawTextRunCharArrayNullPaint() {
-        // Should throw out NullPointerException because paint is null
-        mCanvas.drawTextRun("android".toCharArray(), 0, 0, 0, 0, 0.0f, 0.0f, false, null);
-    }
-
-    @Test(expected=NullPointerException.class)
-    public void testDrawTextRunCharSequenceNullPaint() {
-        // Should throw out NullPointerException because paint is null
-        mCanvas.drawTextRun("android", 0, 0, 0, 0, 0.0f, 0.0f, false, null);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunNegativeIndex() {
-        final String text = "android";
-        final Paint paint = new Paint();
-
-        // Should throw out IndexOutOfBoundsException because index is less than 0
-        mCanvas.drawTextRun(text.toCharArray(), -1, text.length(), 0, text.length(), 0.0f, 0.0f,
-                false, new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunNegativeCount() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because count is less than 0
-        mCanvas.drawTextRun(text.toCharArray(), 0, -1, 0, text.length(), 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunContestIndexTooLarge() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because contextIndex is bigger than index
-        mCanvas.drawTextRun(text.toCharArray(), 0, text.length(), 1, text.length(), 0.0f, 0.0f,
-                false, new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunContestIndexTooSmall() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because contextIndex + contextCount
-        // is less than index + count
-        mCanvas.drawTextRun(text, 0, text.length(), 0, text.length() - 1, 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunIndexTooLarge() {
-        final String text = "android";
-        final Paint paint = new Paint();
-
-        // Should throw out IndexOutOfBoundsException because index + count is bigger than
-        // text length
-        mCanvas.drawTextRun(text.toCharArray(), 0, text.length() + 1, 0, text.length() + 1,
-                0.0f, 0.0f, false, new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunNegativeContextStart() {
-        final String text = "android";
-        final Paint paint = new Paint();
-
-        // Should throw out IndexOutOfBoundsException because contextStart is less than 0
-        mCanvas.drawTextRun(text, 0, text.length(), -1, text.length(), 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunStartLessThanContextStart() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because start is less than contextStart
-        mCanvas.drawTextRun(text, 0, text.length(), 1, text.length(), 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunEndLessThanStart() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because end is less than start
-        mCanvas.drawTextRun(text, 1, 0, 0, text.length(), 0.0f, 0.0f, false, new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunContextEndLessThanEnd() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because contextEnd is less than end
-        mCanvas.drawTextRun(text, 0, text.length(), 0, text.length() - 1, 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawTextRunContextEndLargerThanTextLength() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because contextEnd is bigger than
-        // text length
-        mCanvas.drawTextRun(text, 0, text.length(), 0, text.length() + 1, 0.0f, 0.0f, false,
-                new Paint());
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawPosTextWithIndexAndCountNegativeIndex() {
-        final char[] text = {
-                'a', 'n', 'd', 'r', 'o', 'i', 'd'
-        };
-        final float[] pos = new float[]{
-                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
-                7.0f, 7.0f
-        };
-
-        // Should throw out IndexOutOfBoundsException because index is less than 0
-        mCanvas.drawPosText(text, -1, 7, pos, mPaint);
-    }
-
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawPosTextWithIndexAndCountTextTooShort() {
-        final char[] text = {
-                'a', 'n', 'd', 'r', 'o', 'i', 'd'
-        };
-        final float[] pos = new float[]{
-                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
-                7.0f, 7.0f
-        };
-
-        // Should throw out IndexOutOfBoundsException because sum of index and count is
-        // bigger than text's length
-        mCanvas.drawPosText(text, 1, 10, pos, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawPosTextWithIndexAndCountCountTooLarge() {
-        final char[] text = {
-                'a', 'n', 'd', 'r', 'o', 'i', 'd'
-        };
-
-        // Should throw out IndexOutOfBoundsException because 2 times of count is
-        // bigger than pos' length
-        mCanvas.drawPosText(text, 1, 10, new float[] {
-                10.0f, 30.f
-        }, mPaint);
-    }
-
-    @Test
-    public void testDrawPosTextWithIndexAndCount() {
-        final char[] text = {
-                'a', 'n', 'd', 'r', 'o', 'i', 'd'
-        };
-        final float[] pos = new float[]{
-                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
-                7.0f, 7.0f
-        };
-
-        // normal case
-        mCanvas.drawPosText(text, 0, 7, pos, mPaint);
-    }
-
-    @Test(expected=IndexOutOfBoundsException.class)
-    public void testDrawPosTextCountTooLarge() {
-        final String text = "android";
-
-        // Should throw out IndexOutOfBoundsException because 2 times of count is
-        // bigger than pos' length
-        mCanvas.drawPosText(text, new float[]{
-                10.0f, 30.f
-        }, mPaint);
-    }
-
-    @Test
-    public void testDrawPosText() {
-        final String text = "android";
-        final float[] pos = new float[]{
-                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
-                7.0f, 7.0f
-        };
-        // normal case
-        mCanvas.drawPosText(text, pos, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawTextOnPathWithIndexAndCountNegativeIndex() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // Should throw out ArrayIndexOutOfBoundsException because index is smaller than 0
-        mCanvas.drawTextOnPath(text, -1, 7, new Path(), 10.0f, 10.0f, mPaint);
-    }
-
-    @Test(expected=ArrayIndexOutOfBoundsException.class)
-    public void testDrawTextOnPathWithIndexAndCountTextTooShort() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // Should throw out ArrayIndexOutOfBoundsException because sum of index and
-        // count is bigger than text's length
-        mCanvas.drawTextOnPath(text, 0, 10, new Path(), 10.0f, 10.0f, mPaint);
-    }
-
-    @Test
-    public void testDrawTextOnPathWithIndexAndCount() {
-        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
-
-        // normal case
-        mCanvas.drawTextOnPath(text, 0, 7, new Path(), 10.0f, 10.0f, mPaint);
-    }
-
-    @Test
-    public void testDrawTextOnPathtestDrawTextRunNegativeCount() {
-        final Path path = new Path();
-
-        // no character in text
-        mCanvas.drawTextOnPath("", path, 10.0f, 10.0f, mPaint);
-
-        // There are characters in text
-        mCanvas.drawTextOnPath("android", path, 10.0f, 10.0f, mPaint);
-    }
-
-    @Test
-    public void testDrawPicture1() {
-        mCanvas.drawPicture(new Picture());
-    }
-
-    @Test
-    public void testDrawPicture2() {
-        final RectF dst = new RectF(0, 0, 10, 31);
-        final Picture p = new Picture();
-
-        // picture width or length not bigger than 0
-        mCanvas.drawPicture(p, dst);
-
-        p.beginRecording(10, 30);
-        mCanvas.drawPicture(p, dst);
-    }
-
-    @Test
-    public void testDrawPicture3() {
-        final Rect dst = new Rect(0, 10, 30, 0);
-        final Picture p = new Picture();
-
-        // picture width or length not bigger than 0
-        mCanvas.drawPicture(p, dst);
-
-        p.beginRecording(10, 30);
-        mCanvas.drawPicture(p, dst);
-    }
-
-    @Test
-    public void testDensity() {
-        // set Density
-        mCanvas.setDensity(DisplayMetrics.DENSITY_DEFAULT);
-        assertEquals(DisplayMetrics.DENSITY_DEFAULT, mCanvas.getDensity());
-
-        // set Density
-        mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH);
-        assertEquals(DisplayMetrics.DENSITY_HIGH, mCanvas.getDensity());
+        Bitmap bm = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        mCanvas = new Canvas(bm);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void testDrawHwBitmap_inSwCanvas() {
-        Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        mCanvas.drawBitmap(hwBitmap, 0, 0, null); // we verify this specific call should IAE
+    public void testDrawColorXYZ() {
+        mCanvas.drawColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ)));
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void testDrawHwBitmap_inPictureCanvas_inSwCanvas() {
-        Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        Picture picture = new Picture();
-        Canvas pictureCanvas = picture.beginRecording(100, 100);
-        pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
-        mCanvas.drawPicture(picture); // we verify this specific call should IAE
+    public void testDrawColorLAB() {
+        mCanvas.drawColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB)));
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void testDrawHwBitmap_inPictureCanvas_inPictureCanvas_inSwCanvas() {
-        Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        Picture innerPicture = new Picture();
-        Canvas pictureCanvas = innerPicture.beginRecording(100, 100);
-        pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
-
-        Picture outerPicture = new Picture();
-        Canvas outerPictureCanvas = outerPicture.beginRecording(100, 100);
-        outerPictureCanvas.drawPicture(innerPicture);
-        mCanvas.drawPicture(outerPicture); // we verify this specific call should IAE
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testHwBitmapShaderInSwCanvas1() {
-        Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
-                Shader.TileMode.REPEAT);
-        RadialGradient gradientShader = new RadialGradient(10, 10, 30, Color.BLACK, Color.CYAN,
-                Shader.TileMode.REPEAT);
-        Shader shader = new ComposeShader(gradientShader, bitmapShader, Mode.OVERLAY);
-        Paint p = new Paint();
-        p.setShader(shader);
-        mCanvas.drawRect(0, 0, 10, 10, p);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testHwBitmapShaderInSwCanvas2() {
-        Bitmap hwBitmap = mImmutableBitmap.copy(Config.HARDWARE, false);
-        BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
-                Shader.TileMode.REPEAT);
-        RadialGradient gradientShader = new RadialGradient(10, 10, 30, Color.BLACK, Color.CYAN,
-                Shader.TileMode.REPEAT);
-        Shader shader = new ComposeShader(bitmapShader, gradientShader, Mode.OVERLAY);
-        Paint p = new Paint();
-        p.setShader(shader);
-        mCanvas.drawRect(0, 0, 10, 10, p);
-    }
-
-    private void preCompare() {
-        final float[] values = new float[FLOAT_ARRAY_LEN];
-        mCanvas.getMatrix().getValues(values);
-        assertArrayEquals(new float[] {
-                1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, values, 0.0f);
-    }
-
-    private RectF getDeviceClip() {
-        final RectF clip = new RectF(mCanvas.getClipBounds());
-        mCanvas.getMatrix().mapRect(clip);
-        return clip;
-    }
-
-    @Test
-    public void testDrawBitmapColorBehavior() {
-        try {
-            // Create a wide gamut bitmap where the pixel value is slightly less than max red.
-            Resources resources = InstrumentationRegistry.getTargetContext().getResources();
-            InputStream in = resources.getAssets().open("almost-red-adobe.png");
-            Bitmap bitmap = BitmapFactory.decodeStream(in);
-
-            // Draw the bitmap to an sRGB canvas.
-            Bitmap canvasBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(canvasBitmap);
-            canvas.drawBitmap(bitmap, 0, 0, null);
-
-            // Verify that the pixel is now max red.
-            Assert.assertEquals(0xFFFF0000, canvasBitmap.getPixel(0, 0));
-        } catch (IOException e) {
-            fail();
-        }
-    }
-
-    @Test
-    public void testShadowLayer_paintColorPreserved() {
-        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        Paint paint = new Paint();
-
-        paint.setShadowLayer(5.0f, 10.0f, 10.0f, 0xFFFF0000);
-        paint.setColor(0xFF0000FF);
-        canvas.drawPaint(paint);
-
-        // Since the shadow is in the background, the canvas should be blue.
-        ColorUtils.verifyColor(0xFF0000FF, bitmap.getPixel(50, 50));
+    public void testDrawColorUnknown() {
+        mCanvas.drawColor(-1L);
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
index b535edb..ad72765 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
@@ -313,17 +313,19 @@
 
     @Test
     public void testIsSRGB() {
-        assertTrue(ColorSpace.get(ColorSpace.Named.SRGB).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.DISPLAY_P3).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.CIE_LAB).isSrgb());
-        assertFalse(ColorSpace.get(ColorSpace.Named.CIE_XYZ).isSrgb());
+        for (ColorSpace.Named e : ColorSpace.Named.values()) {
+            ColorSpace colorSpace = ColorSpace.get(e);
+            if (e == ColorSpace.Named.SRGB) {
+                assertTrue(colorSpace.isSrgb());
+            } else {
+                assertFalse("Incorrectly treating " + colorSpace + " as SRGB!",
+                            colorSpace.isSrgb());
+            }
+        }
 
-        ColorSpace.Rgb cs = new ColorSpace.Rgb("My sRGB", SRGB_TO_XYZ,
+        ColorSpace.Rgb cs = new ColorSpace.Rgb("Almost sRGB", SRGB_TO_XYZ,
                 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f));
-        assertTrue(cs.isSrgb());
+        assertFalse(cs.isSrgb());
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 7ae151e..01aed2a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -24,11 +24,14 @@
 import android.graphics.Color;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
 import android.util.TypedValue;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.List;
 
@@ -36,6 +39,8 @@
 @RunWith(AndroidJUnit4.class)
 public class ColorTest {
 
+    private static final String LOG_TAG = ColorTest.class.getSimpleName();
+
     @Test
     public void resourceColor() {
         int colors [][] = {
@@ -116,9 +121,22 @@
                 assertEquals("Color should be expected value", expectedColor, value.data);
             }
         }
+
+        // System-API colors are used to allow updateable platform components to use the same colors
+        // as the system. The actualy value of the color does not matter. Hence only enforce that
+        // 'colors' contains all the public colors and ignore System-api colors.
+        int numPublicApiColors = 0;
+        for (Field declaredColor : android.R.color.class.getDeclaredFields()) {
+            if (Arrays.stream(declaredColor.getDeclaredAnnotations()).anyMatch(
+                    (Annotation a) -> a.toString().contains("SystemApi"))) {
+                Log.i(LOG_TAG, declaredColor.getName() + " is SystemApi");
+            } else {
+                numPublicApiColors++;
+            }
+        }
+
         assertEquals("Test no longer in sync with colors in android.R.color",
-                colors.length,
-                android.R.color.class.getDeclaredFields().length);
+                colors.length, numPublicApiColors);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/Color_ColorLongTest.java b/tests/tests/graphics/src/android/graphics/cts/Color_ColorLongTest.java
index f7893c7..a5ccec2 100644
--- a/tests/tests/graphics/src/android/graphics/cts/Color_ColorLongTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/Color_ColorLongTest.java
@@ -15,14 +15,6 @@
  */
 package android.graphics.cts;
 
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-import android.graphics.ColorSpace.Named;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import static android.graphics.Color.alpha;
 import static android.graphics.Color.blue;
 import static android.graphics.Color.colorSpace;
@@ -33,6 +25,7 @@
 import static android.graphics.Color.red;
 import static android.graphics.Color.toArgb;
 import static android.graphics.Color.valueOf;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -40,6 +33,15 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.ColorSpace.Named;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class Color_ColorLongTest {
@@ -89,6 +91,11 @@
         colorSpace(0xffffffffffffffffL);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorSpace2() {
+        colorSpace((long) ColorSpace.Named.values().length);
+    }
+
     @Test
     public void testIsSrgb() {
         ColorSpace p3 = ColorSpace.get(Named.DISPLAY_P3);
@@ -361,6 +368,14 @@
     }
 
     @Test
+    public void testConvertToBT709() {
+        int sRgb = Color.argb(1.0f, 0.5f, 0.5f, 0.5f);
+        long bt709 = Color.convert(sRgb, ColorSpace.get(Named.BT709));
+
+        assertEquals(ColorSpace.get(Named.BT709), Color.colorSpace(bt709));
+    }
+
+    @Test
     public void testConvertColorInt() {
         long color = convert(0xffff8000, ColorSpace.get(Named.ADOBE_RGB));
 
@@ -406,4 +421,11 @@
         assertEquals(0.4597f, green(color), 0.01f);
         assertEquals(0.0000f, blue(color), 0.01f);
     }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConvertFailureUnnamedColorSpace() {
+        float[] primaries = new float[]{ .7f, .6f, .5f, .4f, .3f, .2f };
+        ColorSpace cs = new ColorSpace.Rgb("mock", primaries, ColorSpace.ILLUMINANT_A, .7);
+        Color.convert(Color.BLUE, cs);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/FontVariationAxisTest.java b/tests/tests/graphics/src/android/graphics/cts/FontVariationAxisTest.java
index 5063aa8..49dd42d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FontVariationAxisTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FontVariationAxisTest.java
@@ -17,7 +17,9 @@
 package android.graphics.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 static org.junit.Assert.fail;
 
 import android.graphics.fonts.FontVariationAxis;
@@ -164,4 +166,17 @@
             assertEquals(axes[i].getStyleValue(), newAxes[i].getStyleValue(), FLOT_EQUALITY_PREC);
         }
     }
+
+    @Test
+    public void testEquals() {
+        assertTrue(new FontVariationAxis("wght", 1.0f).equals(new FontVariationAxis("wght", 1.0f)));
+        assertFalse(new FontVariationAxis("wght", 1.0f).equals(
+                new FontVariationAxis("slnt", 1.0f)));
+    }
+
+    @Test
+    public void testHashCode() {
+        assertEquals(new FontVariationAxis("wght", 1.0f).hashCode(),
+                new FontVariationAxis("wght", 1.0f).hashCode());
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index b7f9c6d..786f933 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -26,6 +26,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -65,6 +66,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.concurrent.Callable;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
 import java.util.function.ToIntFunction;
@@ -199,6 +201,14 @@
                 .build();
     }
 
+    private Callable<AssetFileDescriptor> getAsCallable(int resId) {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Uri uri = getAsContentUri(resId);
+        return () -> {
+            return context.getContentResolver().openAssetFileDescriptor(uri, "r");
+        };
+    }
+
     private interface SourceCreator extends IntFunction<ImageDecoder.Source> {};
 
     private SourceCreator[] mCreators = new SourceCreator[] {
@@ -206,6 +216,7 @@
             resId -> ImageDecoder.createSource(getAsDirectByteBuffer(resId)),
             resId -> ImageDecoder.createSource(getAsReadOnlyByteBuffer(resId)),
             resId -> ImageDecoder.createSource(getAsFile(resId)),
+            resId -> ImageDecoder.createSource(getAsCallable(resId)),
     };
 
     private interface UriCreator extends IntFunction<Uri> {};
@@ -1679,16 +1690,19 @@
         };
         Listener l = new Listener();
         SourceCreator f = mCreators[0];
-        for (int resId : new int[] { R.drawable.png_test, R.raw.basi6a16 }) {
+        for (int resId : new int[] { R.drawable.png_test, R.raw.f16 }) {
             Bitmap normal = null;
             try {
-                normal = ImageDecoder.decodeBitmap(f.apply(resId));
+                normal = ImageDecoder.decodeBitmap(f.apply(resId), ((decoder, info, source) -> {
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }));
             } catch (IOException e) {
                 fail("Failed with exception " + e);
             }
             assertNotNull(normal);
             int normalByteCount = normal.getAllocationByteCount();
-            for (int allocator : ALLOCATORS) {
+            int[] allocators = { ImageDecoder.ALLOCATOR_HARDWARE, ImageDecoder.ALLOCATOR_DEFAULT };
+            for (int allocator : allocators) {
                 l.allocator = allocator;
                 Bitmap test = null;
                 try {
@@ -1699,39 +1713,14 @@
                 assertNotNull(test);
                 int byteCount = test.getAllocationByteCount();
 
-                if (allocator == ImageDecoder.ALLOCATOR_HARDWARE
-                        || allocator == ImageDecoder.ALLOCATOR_DEFAULT) {
-                    if (resId == R.drawable.png_test) {
-                        // We do not support 565 in HARDWARE, so no RAM savings
-                        // are possible.
-                        assertEquals(normalByteCount, byteCount);
-                    } else { // R.raw.basi6a16
-                        // This image defaults to F16. MEMORY_POLICY_LOW_RAM
-                        // forces "test" to decode to 8888. But if the device
-                        // does not support F16 in HARDWARE, "normal" is also
-                        // 8888. Its Config is HARDWARE either way, but we can
-                        // detect its underlying pixel format by checking the
-                        // ColorSpace, which is always LINEAR_EXTENDED_SRGB for
-                        // F16.
-                        if (normal.getColorSpace().equals(
-                                    ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB))) {
-                            // F16. "test" should be smaller.
-                            assertTrue(byteCount < normalByteCount);
-                        } else {
-                            // 8888. No RAM savings possible.
-                            assertEquals(normalByteCount, byteCount);
-                        }
-                    }
-                } else {
-                    // Not decoding to HARDWARE, but |normal| was. Again, if basi6a16
-                    // was decoded to 8888, which we can detect by looking at the color
-                    // space, no savings are possible.
-                    if (resId == R.raw.basi6a16 && !normal.getColorSpace().equals(
-                                ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB))) {
-                        assertEquals(normalByteCount, byteCount);
-                    } else {
-                        assertTrue(byteCount < normalByteCount);
-                    }
+                if (resId == R.drawable.png_test) {
+                    // We do not support 565 in HARDWARE, so no RAM savings
+                    // are possible.
+                    assertEquals(normalByteCount, byteCount);
+                } else { // R.raw.f16
+                    // This image defaults to F16. MEMORY_POLICY_LOW_RAM
+                    // forces "test" to decode to 8888.
+                    assertTrue(byteCount < normalByteCount);
                 }
             }
         }
@@ -1764,8 +1753,8 @@
                                    // If this were stored in drawable/, it would
                                    // be converted from 16-bit to 8. FIXME: Is
                                    // behavior still desirable now that we have
-                                   // F16?
-                                   R.raw.basi6a16 };
+                                   // F16? b/119760146
+                                   R.raw.f16 };
         // An opaque image can be converted to 565, but postProcess will promote
         // to 8888 in case alpha is added. The third image defaults to F16, so
         // even with postProcess it will only be promoted to 8888.
@@ -2103,7 +2092,7 @@
             ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
             for (ColorSpace cs : new ColorSpace[] {
                     sSRGB,
-                    ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB),
+                    ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
                     ColorSpace.get(ColorSpace.Named.DISPLAY_P3),
                     ColorSpace.get(ColorSpace.Named.ADOBE_RGB),
                     ColorSpace.get(ColorSpace.Named.BT709),
@@ -2112,10 +2101,11 @@
                     ColorSpace.get(ColorSpace.Named.NTSC_1953),
                     ColorSpace.get(ColorSpace.Named.SMPTE_C),
                     ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB),
-                    // FIXME: These will not match due to b/77276533.
-                    // ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
-                    // ColorSpace.get(ColorSpace.Named.ACES),
-                    // ColorSpace.get(ColorSpace.Named.ACESCG),
+                    ColorSpace.get(ColorSpace.Named.ACES),
+                    ColorSpace.get(ColorSpace.Named.ACESCG),
+                    // FIXME: This returns LINEAR_SRGB.
+                    // See b/117601185 and b/77276533
+                    //ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB),
             }) {
                 try {
                     Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
@@ -2192,6 +2182,18 @@
     }
 
     @Test
+    public void testJpegInfiniteLoop() {
+        ImageDecoder.Source src = mCreators[0].apply(R.raw.b78329453);
+        try {
+            ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                decoder.setTargetSampleSize(19);
+            });
+        } catch (IOException e) {
+            fail();
+        }
+    }
+
+    @Test
     @LargeTest
     public void testReuse() {
         for (Record record : RECORDS) {
diff --git a/tests/tests/graphics/src/android/graphics/cts/InsetsTest.java b/tests/tests/graphics/src/android/graphics/cts/InsetsTest.java
new file mode 100644
index 0000000..cba4a7f
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/InsetsTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.graphics.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InsetsTest {
+
+    @Test
+    public void testEmptyInsets() {
+        assertEquals(Insets.NONE, Insets.of(0, 0, 0, 0));
+    }
+
+    @Test
+    public void testInsetsAppliedInOrder() {
+        Insets insets = Insets.of(1, 2, 3, 4);
+        assertEquals(1, insets.left);
+        assertEquals(2, insets.top);
+        assertEquals(3, insets.right);
+        assertEquals(4, insets.bottom);
+    }
+
+    @Test
+    public void testInsetsFromNullRect() {
+        assertEquals(Insets.NONE, Insets.of(null));
+    }
+
+    @Test
+    public void testInsetsFromRect() {
+        Rect rect = new Rect(1, 2, 3, 4);
+        Insets insets = Insets.of(rect);
+        assertEquals(1, insets.left);
+        assertEquals(2, insets.top);
+        assertEquals(3, insets.right);
+        assertEquals(4, insets.bottom);
+    }
+
+    @Test
+    public void testInsetsEquality() {
+        Rect rect = new Rect(10, 20, 30, 40);
+        Insets insets1 = Insets.of(rect);
+        Insets insets2 = Insets.of(10, 20, 30, 40);
+        assertEquals(insets1, insets2);
+    }
+
+    @Test
+    public void testAdd() {
+        Insets insets1 = Insets.of(10, 20, 30, 40);
+        Insets insets2 = Insets.of(50, 60, 70, 80);
+        assertEquals(Insets.add(insets1, insets2), Insets.of(60, 80, 100, 120));
+    }
+
+    @Test
+    public void testSubtract() {
+        Insets insets1 = Insets.of(10, 20, 30, 40);
+        Insets insets2 = Insets.of(50, 70, 90, 110);
+        assertEquals(Insets.subtract(insets2, insets1), Insets.of(40, 50, 60, 70));
+    }
+
+    @Test
+    public void testMin() {
+        Insets insets1 = Insets.of(1, 10, 100, 1000);
+        Insets insets2 = Insets.of(1000, 100, 10, 1);
+        assertEquals(Insets.min(insets2, insets1), Insets.of(1, 10, 10, 1));
+    }
+
+    @Test
+    public void testMax() {
+        Insets insets1 = Insets.of(1, 10, 100, 1000);
+        Insets insets2 = Insets.of(1000, 100, 10, 1);
+        assertEquals(Insets.max(insets2, insets1), Insets.of(1000, 100, 100, 1000));
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/LinearGradientTest.java b/tests/tests/graphics/src/android/graphics/cts/LinearGradientTest.java
index 185e6a1..d5f7f2e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/LinearGradientTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/LinearGradientTest.java
@@ -23,6 +23,7 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.LinearGradient;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -35,6 +36,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Function;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class LinearGradientTest {
@@ -46,7 +49,7 @@
         float[] position = { 0.0f, 1.0f / 3.0f, 2.0f / 3.0f };
 
         lg = new LinearGradient(0, 0, 0, 40, color, position, TileMode.CLAMP);
-        b = drawLinearGradient(lg);
+        b = drawLinearGradient(lg, Config.ARGB_8888);
 
         // The pixels in same gradient line should be equivalent
         assertEquals(b.getPixel(10, 10), b.getPixel(20, 10));
@@ -62,7 +65,7 @@
         assertTrue(Color.red(b.getPixel(10, 20)) < Color.red(b.getPixel(10, 25)));
 
         lg = new LinearGradient(0, 0, 0, 40, Color.RED, Color.BLUE, TileMode.CLAMP);
-        b = drawLinearGradient(lg);
+        b = drawLinearGradient(lg, Config.ARGB_8888);
 
         // The pixels in same gradient line should be equivalent
         assertEquals(b.getPixel(10, 10), b.getPixel(20, 10));
@@ -73,10 +76,67 @@
         assertTrue(Color.blue(b.getPixel(10, 15)) < Color.blue(b.getPixel(10, 30)));
     }
 
-    private Bitmap drawLinearGradient(LinearGradient lg) {
+    @Test
+    public void testLinearGradientLong() {
+        ColorSpace p3 = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+        long red = Color.pack(1, 0, 0, 1, p3);
+        long green = Color.pack(0, 1, 0, 1, p3);
+        long blue = Color.pack(0, 0, 1, 1, p3);
+        long[] colors = new long[] { blue, green, red };
+        float[] positions = null;
+
+        LinearGradient lg = new LinearGradient(0, 0, 0, 40, colors, positions, TileMode.CLAMP);
+        Bitmap b = drawLinearGradient(lg, Config.RGBA_F16);
+        final ColorSpace bitmapColorSpace = b.getColorSpace();
+        Function<Long, Color> convert = (l) -> {
+            return Color.valueOf(Color.convert(l, bitmapColorSpace));
+        };
+
+        ColorUtils.verifyColor("Top-most color should be mostly blue!",
+                convert.apply(blue), b.getColor(0, 0), 0.09f);
+
+        ColorUtils.verifyColor("Middle color should be mostly green!",
+                convert.apply(green), b.getColor(0, 20), 0.09f);
+
+        ColorUtils.verifyColor("Bottom-most color should be mostly red!",
+                convert.apply(red), b.getColor(0, 39), 0.08f);
+
+        ColorUtils.verifyColor("The pixels in same gradient line should be equivalent!",
+                b.getColor(10, 10), b.getColor(20, 10), 0f);
+        // BLUE -> GREEN, B sub-value decreasing while G sub-value increasing
+        assertTrue(b.getColor(10, 0).blue() > b.getColor(10, 5).blue());
+        assertTrue(b.getColor(10, 5).blue() > b.getColor(10, 10).blue());
+        assertTrue(b.getColor(10, 0).green() < b.getColor(10, 5).green());
+        assertTrue(b.getColor(10, 5).green() < b.getColor(10, 10).green());
+        // GREEN -> RED, G sub-value decreasing while R sub-value increasing
+        assertTrue(b.getColor(10, 20).green() > b.getColor(10, 30).green());
+        assertTrue(b.getColor(10, 30).green() > b.getColor(10, 35).green());
+        assertTrue(b.getColor(10, 20).red() < b.getColor(10, 30).red());
+        assertTrue(b.getColor(10, 30).red() < b.getColor(10, 35).red());
+
+        lg = new LinearGradient(0, 0, 0, 40, red, blue, TileMode.CLAMP);
+        b = drawLinearGradient(lg, Config.RGBA_F16);
+
+        ColorUtils.verifyColor("Top-most color should be mostly red!",
+                convert.apply(red), b.getColor(0, 0), .03f);
+
+        ColorUtils.verifyColor("Bottom-most color should be mostly blue!",
+                convert.apply(blue), b.getColor(0, 39), 0.016f);
+
+
+        ColorUtils.verifyColor("The pixels in same gradient line should be equivalent!",
+                b.getColor(10, 10), b.getColor(20, 10), 0f);
+        // RED -> BLUE, R sub-value decreasing while B sub-value increasing
+        assertTrue(b.getColor(10, 0).red() > b.getColor(10, 15).red());
+        assertTrue(b.getColor(10, 15).red() > b.getColor(10, 30).red());
+        assertTrue(b.getColor(10, 0).blue() < b.getColor(10, 15).blue());
+        assertTrue(b.getColor(10, 15).blue() < b.getColor(10, 30).blue());
+    }
+
+    private Bitmap drawLinearGradient(LinearGradient lg, Config c) {
         Paint paint = new Paint();
         paint.setShader(lg);
-        Bitmap b = Bitmap.createBitmap(40, 40, Config.ARGB_8888);
+        Bitmap b = Bitmap.createBitmap(40, 40, c);
         b.eraseColor(Color.BLACK);
         Canvas canvas = new Canvas(b);
         canvas.drawPaint(paint);
@@ -90,9 +150,84 @@
         Matrix m = new Matrix();
         m.setScale(0, 0);
         gradient.setLocalMatrix(m);
-        Bitmap bitmap = drawLinearGradient(gradient);
+        Bitmap bitmap = drawLinearGradient(gradient, Config.ARGB_8888);
 
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(0, 0), 1);
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(20, 20), 1);
     }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorInts() {
+        int[] colors = null;
+        new LinearGradient(0.5f, 0, 1.5f, 0, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorLongs() {
+        long[] colors = null;
+        new LinearGradient(0.5f, 0, 1.5f, 0, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorInts() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new int[0], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorLongs() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new long[0], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorInts() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new int[1], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorLongs() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new long[1], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = Color.pack(Color.BLUE);
+        colors[1] = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new LinearGradient(0.5f, 0, 1.5f, 0, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs2() {
+        long color0 = Color.pack(Color.BLUE);
+        long color1 = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new LinearGradient(0.5f, 0, 1.5f, 0, color0, color1, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsInts() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new int[2], new float[3], TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsLongs() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, new long[2], new float[3], TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = -1L;
+        colors[0] = -2L;
+        new LinearGradient(0.5f, 0, 1.5f, 0, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, -1L, Color.pack(Color.RED), TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong2() {
+        new LinearGradient(0.5f, 0, 1.5f, 0, Color.pack(Color.RED), -1L, TileMode.CLAMP);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 3a539b8..0d5b5dd 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -16,6 +16,12 @@
 
 package android.graphics.cts;
 
+import static android.graphics.Paint.CURSOR_AFTER;
+import static android.graphics.Paint.CURSOR_AT;
+import static android.graphics.Paint.CURSOR_AT_OR_AFTER;
+import static android.graphics.Paint.CURSOR_AT_OR_BEFORE;
+import static android.graphics.Paint.CURSOR_BEFORE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -24,7 +30,10 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
+import android.graphics.BlendMode;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.ColorSpace;
 import android.graphics.MaskFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -34,6 +43,8 @@
 import android.graphics.Paint.Style;
 import android.graphics.Path;
 import android.graphics.PathEffect;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.Typeface;
@@ -42,14 +53,19 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.Hyphenator;
 import android.text.SpannedString;
 
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.ColorUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Locale;
-
-import com.android.compatibility.common.util.CddTest;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -73,6 +89,42 @@
     }
 
     @Test
+    public void testDefaultColor() {
+        Supplier<Paint> set = () -> {
+            Paint result = new Paint();
+            result.setColor(Color.BLUE);
+            assertEquals(Color.BLUE, result.getColor());
+            result.setShadowLayer(10.0f, 1.0f, 1.0f, Color.RED);
+            assertEquals(Color.RED, result.getShadowLayerColor());
+
+            Paint def = new Paint();
+            result.set(def);
+            return result;
+        };
+        Supplier<Paint> reset = () -> {
+            Paint result = new Paint();
+            result.setColor(Color.GREEN);
+            assertEquals(Color.GREEN, result.getColor());
+            result.setShadowLayer(10.0f, 1.0f, 1.0f, Color.WHITE);
+            assertEquals(Color.WHITE, result.getShadowLayerColor());
+
+            result.reset();
+            return result;
+        };
+        for (Paint p : new Paint[]{ new Paint(),
+                                    new Paint(1),
+                                    new Paint(new Paint()),
+                                    set.get(),
+                                    reset.get()}) {
+            assertEquals(Color.BLACK, p.getColor());
+            assertEquals(Color.TRANSPARENT, p.getShadowLayerColor());
+
+            assertEquals(Color.BLACK, Color.toArgb(p.getColorLong()));
+            assertEquals(Color.TRANSPARENT, Color.toArgb(p.getShadowLayerColorLong()));
+        }
+    }
+
+    @Test
     public void testBreakText() {
         String text = "HIJKLMN";
         char[] textChars = text.toCharArray();
@@ -711,13 +763,53 @@
         p.setAlpha(255);
         assertEquals(255, p.getAlpha());
 
-        // set value should between 0 and 255, so 266 is rounded to 10
+        // set value should between 0 and 255, ensure return value is always in range
         p.setAlpha(266);
-        assertEquals(10, p.getAlpha());
+        assertTrue(0 <= p.getAlpha() && p.getAlpha() <= 255);
 
-        // set value should between 0 and 255, so -20 is rounded to 236
+        // set value should between 0 and 255, ensure return value is always in range
         p.setAlpha(-20);
-        assertEquals(236, p.getAlpha());
+        assertTrue(0 <= p.getAlpha() && p.getAlpha() <= 255);
+    }
+
+    @Test
+    public void testSetAlpha() {
+        for (ColorSpace.Named e : new ColorSpace.Named[] {
+                ColorSpace.Named.SRGB,
+                ColorSpace.Named.LINEAR_EXTENDED_SRGB,
+                ColorSpace.Named.DISPLAY_P3}) {
+            ColorSpace cs = ColorSpace.get(e);
+
+            // Arbitrary colors
+            final float red = .2f;
+            final float green = .7f;
+            final float blue = .9f;
+            final long desiredColor = Color.pack(red, green, blue, 1.0f, cs);
+
+            Paint p = new Paint();
+            p.setColor(desiredColor);
+            final long origColor = p.getColorLong();
+            assertEquals(desiredColor, origColor);
+
+            final float origRed = Color.red(origColor);
+            final float origGreen = Color.green(origColor);
+            final float origBlue = Color.blue(origColor);
+
+            // There is a slight difference in the packed color.
+            assertEquals(red, Color.red(origColor), 0.002f);
+            assertEquals(green, Color.green(origColor), 0.002f);
+            assertEquals(blue, Color.blue(origColor), 0.002f);
+
+            for (int alpha = 0; alpha <= 255; ++alpha) {
+                p.setAlpha(alpha);
+                assertEquals(alpha, p.getAlpha());
+
+                final long color = p.getColorLong();
+                assertEquals(origRed, Color.red(color), 0.0f);
+                assertEquals(origGreen, Color.green(color), 0.0f);
+                assertEquals(origBlue, Color.blue(color), 0.0f);
+            }
+        }
     }
 
     @Test
@@ -752,8 +844,13 @@
     }
 
     @Test
-    public void testSetShadowLayer() {
-        new Paint().setShadowLayer(10, 1, 1, 0);
+    public void testSetGetShadowLayer() {
+        Paint paint = new Paint();
+        paint.setShadowLayer(10, 1, 1, 0);
+        assertEquals(10, paint.getShadowLayerRadius(), 0.0f);
+        assertEquals(1, paint.getShadowLayerDx(), 0.0f);
+        assertEquals(1, paint.getShadowLayerDy(), 0.0f);
+        assertEquals(0, paint.getShadowLayerColor());
     }
 
     @Test
@@ -914,7 +1011,8 @@
 
         Paint p = new Paint();
         Context context = InstrumentationRegistry.getTargetContext();
-        Typeface typeface = Typeface.createFromAsset(context.getAssets(), "multiaxis.ttf");
+        Typeface typeface = Typeface.createFromAsset(context.getAssets(),
+                "fonts/var_fonts/multiaxis.ttf");
         p.setTypeface(typeface);
 
         // multiaxis.ttf supports "wght", "PRIV", "PR12" axes.
@@ -974,11 +1072,15 @@
         String text1 = "hello";
         Rect bounds1 = new Rect();
         Rect bounds2 = new Rect();
+        Rect bounds3 = new Rect();
         p.getTextBounds(text1, 0, text1.length(), bounds1);
         char[] textChars1 = text1.toCharArray();
         p.getTextBounds(textChars1, 0, textChars1.length, bounds2);
+        CharSequence charSequence1 = new StringBuilder(text1);
+        p.getTextBounds(charSequence1, 0, textChars1.length, bounds3);
         // verify that string and char array methods produce consistent results
         assertEquals(bounds1, bounds2);
+        assertEquals(bounds2, bounds3);
         String text2 = "hello world";
 
         // verify substring produces consistent results
@@ -1721,4 +1823,424 @@
         p1.setTypeface(p2.getTypeface());
         assertTrue(p1.equalsForTextMeasurement(p2));
     }
+
+    @Test
+    public void testWordSpacing() {
+        Paint p = new Paint();
+        assertEquals(0.0f, p.getWordSpacing(), 0.0f);  // The default value is 0.
+        p.setWordSpacing(10.0f);
+        assertEquals(10.0f, p.getWordSpacing(), 0.0f);
+        p.setWordSpacing(20.0f);
+        assertEquals(20.0f, p.getWordSpacing(), 0.0f);
+    }
+
+    @Test
+    public void testStrikeThruPosition_notCrashes() {
+        // We can't expect any values of strike-through position in CTS.
+        // Just make sure calling that method doesn't crash the app.
+        new Paint().getStrikeThruPosition();
+    }
+
+    @Test
+    public void testStrikeThruThickness_notCrashes() {
+        // We can't expect any values of strike-through thickness in CTS.
+        // Just make sure calling that method doesn't crash the app.
+        new Paint().getStrikeThruThickness();
+    }
+
+    @Test
+    public void testUnderlinePosition_notCrashes() {
+        // We can't expect any values of underline position in CTS.
+        // Just make sure calling that method doesn't crash the app.
+        new Paint().getUnderlinePosition();
+    }
+
+    @Test
+    public void testUnderlineThickness_notCrashes() {
+        // We can't expect any values of underline thickness in CTS.
+        // Just make sure calling that method doesn't crash the app.
+        new Paint().getUnderlineThickness();
+    }
+
+    @Test
+    public void testSetGetHyphenEdit() {
+        Paint paint = new Paint();
+
+        // By default, no hyphen edit is specified.
+        assertEquals(Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.unpackStartHyphenEdit(paint.getHyphenEdit()));
+        assertEquals(Hyphenator.END_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.unpackEndHyphenEdit(paint.getHyphenEdit()));
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.END_HYPHEN_EDIT_NO_EDIT));
+        assertEquals(Hyphenator.START_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.unpackStartHyphenEdit(paint.getHyphenEdit()));
+        assertEquals(Hyphenator.END_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.unpackEndHyphenEdit(paint.getHyphenEdit()));
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN));
+        assertEquals(Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.unpackStartHyphenEdit(paint.getHyphenEdit()));
+        assertEquals(Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.unpackEndHyphenEdit(paint.getHyphenEdit()));
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN));
+        assertEquals(Hyphenator.START_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.unpackStartHyphenEdit(paint.getHyphenEdit()));
+        assertEquals(Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.unpackEndHyphenEdit(paint.getHyphenEdit()));
+    }
+
+    @Test
+    public void testHyphenEdit() {
+        final Paint paint = new Paint();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        // The hyphenation.ttf font supports following characters
+        // - U+0061..U+007A (a..z): The glyph has 1em width.
+        // - U+2010 (HYPHEN): The glyph has 2em width.
+        // - U+058A (ARMENIAN HYPHEN): The glyph has 3em width.
+        // - U+05BE (MAQAF): The glyph has 4em width.
+        // - U+1400 (UCAS HYPHEN): The glyph has 5em width.
+        paint.setTypeface(Typeface.createFromAsset(context.getAssets(),
+                  "fonts/layout/hyphenation.ttf"));
+        paint.setTextSize(10.0f);  // Make 1em = 10px
+
+        assertEquals(30.0f, paint.measureText("abc", 0, 3), 0.0f);
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN));
+        assertEquals(50.0f, paint.measureText("abc", 0, 3), 0.0f);  // "abc-" in visual.
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN));
+        assertEquals(70.0f, paint.measureText("abc", 0, 3), 0.0f);  // "-abc-" in visual.
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN));
+        assertEquals(60.0f, paint.measureText("abc", 0, 3), 0.0f);  // "abcU+058A" in visual.
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_MAQAF));
+        assertEquals(70.0f, paint.measureText("abc", 0, 3), 0.0f);  // "abcU+05BE" in visual.
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN));
+        assertEquals(80.0f, paint.measureText("abc", 0, 3), 0.0f);  // "abcU+1400" in visual.
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN));
+        // "abcU+200D-" in visual. Note that ZWJ is zero width.
+        assertEquals(50.0f, paint.measureText("abc", 0, 3), 0.0f);
+
+        paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+                Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN));
+        assertEquals(40.0f, paint.measureText("abc", 0, 3), 0.0f);  // "ab-" in visual.
+    }
+
+    @Test
+    public void testGetTextRunAdvances() {
+        final Paint paint = new Paint();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        paint.setTypeface(Typeface.createFromAsset(context.getAssets(),
+                  "fonts/layout/textrunadvances.ttf"));
+        // The textrunadvances.ttf font supports following characters
+        // - U+0061 (a): The glyph has 3em width.
+        // - U+0062..U+0065 (b..e): The glyph has 1em width.
+        // - U+1F600 (GRINNING FACE): The glyph has 3em width.
+        paint.setTextSize(10.0f);  // Make 1em = 10px
+
+        final char[] chars = { 'a', 'b', 'a', 'b' };
+        final float[] buffer = new float[32];
+
+        assertEquals(80.0f,
+                paint.getTextRunAdvances(chars, 0, 4, 0, 4, false /* isRtl */, buffer, 0), 0.0f);
+        assertEquals(30.0f, buffer[0], 0.0f);
+        assertEquals(10.0f, buffer[1], 0.0f);
+        assertEquals(30.0f, buffer[2], 0.0f);
+        assertEquals(10.0f, buffer[3], 0.0f);
+
+        // Output offset test
+        assertEquals(40.0f,
+                paint.getTextRunAdvances(chars, 1, 2, 1, 2, false /* isRtl */, buffer, 5), 0.0f);
+        assertEquals(10.0f, buffer[5], 0.0f);
+        assertEquals(30.0f, buffer[6], 0.0f);
+
+        // Surrogate pairs
+        final char[] chars2 = Character.toChars(0x1F600);
+        assertEquals(30.0f,
+                paint.getTextRunAdvances(chars2, 0, 2, 0, 2, false /* isRtl */, buffer, 0), 0.0f);
+        assertEquals(30.0f, buffer[0], 0.0f);
+        assertEquals(0.0f, buffer[1], 0.0f);
+    }
+
+    private int getTextRunCursor(String text, int offset, int cursorOpt) {
+        final int contextStart = 0;
+        final int contextEnd = text.length();
+        final int contextCount = text.length();
+        Paint p = new Paint();
+        int result = p.getTextRunCursor(new StringBuilder(text), // as a CharSequence
+                contextStart, contextEnd, false /* isRtl */, offset, cursorOpt);
+        assertEquals(result, p.getTextRunCursor(text.toCharArray(),
+                contextStart, contextCount, false /* isRtl */, offset, cursorOpt));
+        assertEquals(result, p.getTextRunCursor(new StringBuilder(text),  // as a CharSequence
+                contextStart, contextCount, true /* isRtl */, offset, cursorOpt));
+        assertEquals(result, p.getTextRunCursor(text.toCharArray(),
+                contextStart, contextCount, true, offset, cursorOpt));
+        return result;
+    }
+
+    @Test
+    public void testGetRunCursor_CURSOR_AFTER() {
+        assertEquals(1, getTextRunCursor("abc", 0, CURSOR_AFTER));
+        assertEquals(2, getTextRunCursor("abc", 1, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("abc", 2, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("abc", 3, CURSOR_AFTER));
+
+        // Surrogate pairs
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 0, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 1, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 2, CURSOR_AFTER));
+        assertEquals(4, getTextRunCursor("a\uD83D\uDE00c", 3, CURSOR_AFTER));
+        assertEquals(4, getTextRunCursor("a\uD83D\uDE00c", 4, CURSOR_AFTER));
+
+        // Combining marks
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 0, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 1, CURSOR_AFTER));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 2, CURSOR_AFTER));
+        assertEquals(4, getTextRunCursor("a\u0061\u0302c", 3, CURSOR_AFTER));
+        assertEquals(4, getTextRunCursor("a\u0061\u0302c", 4, CURSOR_AFTER));
+    }
+
+    @Test
+    public void testGetRunCursor_CURSOR_AT() {
+        assertEquals(0, getTextRunCursor("abc", 0, CURSOR_AT));
+        assertEquals(1, getTextRunCursor("abc", 1, CURSOR_AT));
+        assertEquals(2, getTextRunCursor("abc", 2, CURSOR_AT));
+        assertEquals(3, getTextRunCursor("abc", 3, CURSOR_AT));
+
+        // Surrogate pairs
+        assertEquals(0, getTextRunCursor("a\uD83D\uDE00c", 0, CURSOR_AT));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 1, CURSOR_AT));
+        assertEquals(-1, getTextRunCursor("a\uD83D\uDE00c", 2, CURSOR_AT));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 3, CURSOR_AT));
+        assertEquals(4, getTextRunCursor("a\uD83D\uDE00c", 4, CURSOR_AT));
+
+        // Combining marks
+        assertEquals(0, getTextRunCursor("a\u0061\u0302c", 0, CURSOR_AT));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 1, CURSOR_AT));
+        assertEquals(-1, getTextRunCursor("a\u0061\u0302c", 2, CURSOR_AT));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 3, CURSOR_AT));
+        assertEquals(4, getTextRunCursor("a\u0061\u0302c", 4, CURSOR_AT));
+    }
+
+    @Test
+    public void testGetRunCursor_CURSOR_AT_OR_AFTER() {
+        assertEquals(0, getTextRunCursor("abc", 0, CURSOR_AT_OR_AFTER));
+        assertEquals(1, getTextRunCursor("abc", 1, CURSOR_AT_OR_AFTER));
+        assertEquals(2, getTextRunCursor("abc", 2, CURSOR_AT_OR_AFTER));
+        assertEquals(3, getTextRunCursor("abc", 3, CURSOR_AT_OR_AFTER));
+
+        // Surrogate pairs
+        assertEquals(0, getTextRunCursor("a\uD83D\uDE00c", 0, CURSOR_AT_OR_AFTER));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 1, CURSOR_AT_OR_AFTER));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 2, CURSOR_AT_OR_AFTER));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 3, CURSOR_AT_OR_AFTER));
+        assertEquals(4, getTextRunCursor("a\uD83D\uDE00c", 4, CURSOR_AT_OR_AFTER));
+
+        // Combining marks
+        assertEquals(0, getTextRunCursor("a\u0061\u0302c", 0, CURSOR_AT_OR_AFTER));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 1, CURSOR_AT_OR_AFTER));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 2, CURSOR_AT_OR_AFTER));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 3, CURSOR_AT_OR_AFTER));
+        assertEquals(4, getTextRunCursor("a\u0061\u0302c", 4, CURSOR_AT_OR_AFTER));
+    }
+
+    @Test
+    public void testGetRunCursor_CURSOR_AT_OR_BEFORE() {
+        assertEquals(0, getTextRunCursor("abc", 0, CURSOR_AT_OR_BEFORE));
+        assertEquals(1, getTextRunCursor("abc", 1, CURSOR_AT_OR_BEFORE));
+        assertEquals(2, getTextRunCursor("abc", 2, CURSOR_AT_OR_BEFORE));
+        assertEquals(3, getTextRunCursor("abc", 3, CURSOR_AT_OR_BEFORE));
+
+        // Surrogate pairs
+        assertEquals(0, getTextRunCursor("a\uD83D\uDE00c", 0, CURSOR_AT_OR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 1, CURSOR_AT_OR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 2, CURSOR_AT_OR_BEFORE));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 3, CURSOR_AT_OR_BEFORE));
+        assertEquals(4, getTextRunCursor("a\uD83D\uDE00c", 4, CURSOR_AT_OR_BEFORE));
+
+        // Combining marks
+        assertEquals(0, getTextRunCursor("a\u0061\u0302c", 0, CURSOR_AT_OR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 1, CURSOR_AT_OR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 2, CURSOR_AT_OR_BEFORE));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 3, CURSOR_AT_OR_BEFORE));
+        assertEquals(4, getTextRunCursor("a\u0061\u0302c", 4, CURSOR_AT_OR_BEFORE));
+    }
+
+    @Test
+    public void testGetRunCursor_CURSOR_BEFORE() {
+        assertEquals(0, getTextRunCursor("abc", 0, CURSOR_BEFORE));
+        assertEquals(0, getTextRunCursor("abc", 1, CURSOR_BEFORE));
+        assertEquals(1, getTextRunCursor("abc", 2, CURSOR_BEFORE));
+        assertEquals(2, getTextRunCursor("abc", 3, CURSOR_BEFORE));
+
+        // Surrogate pairs
+        assertEquals(0, getTextRunCursor("a\uD83D\uDE00c", 0, CURSOR_BEFORE));
+        assertEquals(0, getTextRunCursor("a\uD83D\uDE00c", 1, CURSOR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 2, CURSOR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\uD83D\uDE00c", 3, CURSOR_BEFORE));
+        assertEquals(3, getTextRunCursor("a\uD83D\uDE00c", 4, CURSOR_BEFORE));
+
+        // Combining marks
+        assertEquals(0, getTextRunCursor("a\u0061\u0302c", 0, CURSOR_BEFORE));
+        assertEquals(0, getTextRunCursor("a\u0061\u0302c", 1, CURSOR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 2, CURSOR_BEFORE));
+        assertEquals(1, getTextRunCursor("a\u0061\u0302c", 3, CURSOR_BEFORE));
+        assertEquals(3, getTextRunCursor("a\u0061\u0302c", 4, CURSOR_BEFORE));
+    }
+
+    @Test
+    public void testGetBlendModeFromPorterDuffMode() {
+        Paint p = new Paint();
+        PorterDuff.Mode[] porterDuffModes = PorterDuff.Mode.values();
+        for (PorterDuff.Mode mode : porterDuffModes) {
+            p.setXfermode(new PorterDuffXfermode(mode));
+            assertEquals(getBlendModeFromPorterDuffMode(mode), p.getBlendMode());
+        }
+
+    }
+
+    private BlendMode getBlendModeFromPorterDuffMode(PorterDuff.Mode mode) {
+        switch (mode) {
+            case CLEAR: return BlendMode.CLEAR;
+            case SRC: return BlendMode.SRC;
+            case DST: return BlendMode.DST;
+            case SRC_OVER: return BlendMode.SRC_OVER;
+            case DST_OVER: return BlendMode.DST_OVER;
+            case SRC_IN: return BlendMode.SRC_IN;
+            case DST_IN: return BlendMode.DST_IN;
+            case SRC_OUT: return BlendMode.SRC_OUT;
+            case DST_OUT: return BlendMode.DST_OUT;
+            case SRC_ATOP: return BlendMode.SRC_ATOP;
+            case DST_ATOP: return BlendMode.DST_ATOP;
+            case XOR: return BlendMode.XOR;
+            case DARKEN: return BlendMode.DARKEN;
+            case LIGHTEN: return BlendMode.LIGHTEN;
+             // The odd one out, see b/73224934. PorterDuff.Mode.MULTIPLY was improperly mapped
+            // to Skia's modulate
+            case MULTIPLY: return BlendMode.MODULATE;
+            case SCREEN: return BlendMode.SCREEN;
+            case ADD: return BlendMode.PLUS;
+            case OVERLAY: return BlendMode.OVERLAY;
+            default: throw new IllegalArgumentException("Unknown PorterDuffmode: " + mode);
+        }
+    }
+
+    private void testColorLongs(String methodName, BiConsumer<Paint, Long> setColor,
+                                Function<Paint, Integer> getColor,
+                                Function<Paint, Long> getColorLong) {
+        // Pack SRGB colors into ColorLongs
+        for (int color : new int[]{ Color.RED, Color.BLUE, Color.GREEN, Color.BLACK,
+                Color.WHITE, Color.TRANSPARENT }) {
+            final Paint p = new Paint();
+            final long longColor = Color.pack(color);
+            setColor.accept(p, longColor);
+
+            assertEquals(color, getColor.apply(p).intValue());
+            assertEquals(longColor, getColorLong.apply(p).longValue());
+        }
+
+        // Arbitrary colors in various ColorSpaces
+        for (int srgbColor : new int[]{ Color.argb(1.0f, .5f, .5f, .5f),
+                                        Color.argb(1.0f, .3f, .6f, .9f),
+                                        Color.argb(0.5f, .2f, .8f, .7f) }) {
+            for (ColorSpace.Named e : ColorSpace.Named.values()) {
+                ColorSpace cs = ColorSpace.get(e);
+                if (cs.getModel() != ColorSpace.Model.RGB) {
+                    continue;
+                }
+                if (((ColorSpace.Rgb) cs).getTransferParameters() == null) {
+                    continue;
+                }
+
+                final long longColor = Color.convert(srgbColor, cs);
+                Paint p = new Paint();
+                setColor.accept(p, longColor);
+                assertEquals(longColor, getColorLong.apply(p).longValue());
+
+                // These tolerances were chosen by trial and error. It is expected that
+                // some conversions do not round-trip perfectly.
+                int tolerance = 0;
+                if (cs.equals(ColorSpace.get(ColorSpace.Named.SMPTE_C))) {
+                    tolerance = 2;
+                }
+                int color = getColor.apply(p);
+                ColorUtils.verifyColor("Paint#" + methodName + " mismatch for " + cs, srgbColor,
+                        color, tolerance);
+            }
+        }
+    }
+
+    @Test
+    public void testSetColorLong() {
+        testColorLongs("setColor", (p, c) -> p.setColor(c), (p) -> p.getColor(),
+                (p) -> p.getColorLong());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorXYZ() {
+        Paint p = new Paint();
+        p.setColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorLAB() {
+        Paint p = new Paint();
+        p.setColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetColorUnknown() {
+        Paint p = new Paint();
+        p.setColor(-1L);
+    }
+
+    @Test
+    public void testSetShadowLayerLong() {
+        testColorLongs("setShadowLayer", (p, c) -> p.setShadowLayer(10.0f, 1.0f, 1.0f, c),
+                (p) -> p.getShadowLayerColor(), (p) -> p.getShadowLayerColorLong());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetShadowLayerXYZ() {
+        Paint p = new Paint();
+        p.setShadowLayer(10.0f, 1.0f, 1.0f,
+                Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetShadowLayerLAB() {
+        Paint p = new Paint();
+        p.setShadowLayer(10.0f, 1.0f, 1.0f,
+                Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetShadowLayerUnknown() {
+        Paint p = new Paint();
+        p.setShadowLayer(10.0f, 1.0f, 1.0f, -1L);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
index d83eecf..d566b41 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PictureTest.java
@@ -34,9 +34,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PictureTest {
@@ -49,7 +46,6 @@
     // In particular, this test verifies that, in the following situations,
     // the created picture (effectively) has balanced saves and restores:
     //   - copy constructed picture from actively recording picture
-    //   - writeToStream/createFromStream created picture from actively recording picture
     //   - actively recording picture after draw call
     @Test
     public void testSaveRestoreBalance() {
@@ -65,17 +61,6 @@
 
         assertEquals(expectedSaveCount, canvas.getSaveCount());
 
-        ByteArrayOutputStream bout = new ByteArrayOutputStream();
-        original.writeToStream(bout);
-
-        assertEquals(expectedSaveCount, canvas.getSaveCount());
-
-        Picture serialized = Picture.createFromStream(new ByteArrayInputStream(bout.toByteArray()));
-        // The serialization/deserialization process will balance the saves and restores
-        verifyBalance(serialized);
-
-        assertEquals(expectedSaveCount, canvas.getSaveCount());
-
         Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
         Canvas drawDest = new Canvas(bitmap);
         original.draw(drawDest);
@@ -118,7 +103,6 @@
     @Test
     public void testPicture() {
         Picture picture = new Picture();
-        ByteArrayOutputStream bout = new ByteArrayOutputStream();
 
         Canvas canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT);
         assertNotNull(canvas);
@@ -131,16 +115,6 @@
         verifySize(picture);
         verifyBitmap(bitmap);
 
-        picture.writeToStream(bout);
-        picture = Picture.createFromStream(new ByteArrayInputStream(bout.toByteArray()));
-
-        // create a new Canvas with a new bitmap
-        bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
-        canvas = new Canvas(bitmap);
-        picture.draw(canvas);
-        verifySize(picture);
-        verifyBitmap(bitmap);
-
         Picture pic = new Picture(picture);
         bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
         canvas = new Canvas(bitmap);
diff --git a/tests/tests/graphics/src/android/graphics/cts/RadialGradientTest.java b/tests/tests/graphics/src/android/graphics/cts/RadialGradientTest.java
index d133bb9..c53e938 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RadialGradientTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RadialGradientTest.java
@@ -16,12 +16,16 @@
 
 package android.graphics.cts;
 
+import static org.junit.Assert.assertTrue;
+
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.RadialGradient;
 import android.graphics.Shader.TileMode;
 import android.support.test.filters.SmallTest;
@@ -32,6 +36,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Function;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RadialGradientTest {
@@ -57,4 +63,171 @@
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(1, 0), 1);
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(2, 0), 1);
     }
+
+    @Test
+    public void testColorLong() {
+        ColorSpace p3 = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+        long red = Color.pack(1, 0, 0, 1, p3);
+        long blue = Color.pack(0, 0, 1, 1, p3);
+        RadialGradient gradient = new RadialGradient(50, 50, 25, red, blue, TileMode.CLAMP);
+
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.RGBA_F16);
+        bitmap.eraseColor(Color.TRANSPARENT);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint paint = new Paint();
+        paint.setShader(gradient);
+        canvas.drawPaint(paint);
+
+        final ColorSpace bitmapColorSpace = bitmap.getColorSpace();
+        Function<Long, Color> convert = (l) -> {
+            return Color.valueOf(Color.convert(l, bitmapColorSpace));
+        };
+
+        final Color centerColor = bitmap.getColor(50, 50);
+        ColorUtils.verifyColor("Center color should be red!", convert.apply(red),
+                centerColor, 0.034f);
+        Color blueColor = convert.apply(blue);
+        for (Point p : new Point[] { new Point(0, 0), new Point(50, 0), new Point(99, 0),
+                new Point(0, 50), new Point(0, 99), new Point(99, 0), new Point(99, 50),
+                new Point(99, 99) }) {
+            ColorUtils.verifyColor("Edge point " + p + " should be blue", blueColor,
+                    bitmap.getColor(p.x, p.y), .001f);
+        }
+
+        final double[] negativeOneAndOne = new double[] { -1, 1 };
+        Color lastColor = centerColor;
+        Point lastPoint = new Point(0, 0);
+        // On several different radii, verify that colors trend from red to blue.
+        for (double radius = 4; radius < 25; radius += 4) {
+            // These correspond to the first point we check at a given radius.
+            Color currentColor = null;
+            Point currentPoint = null;
+            for (double angle = 0; angle <= Math.PI / 2.0; angle += Math.PI / 8.0) {
+                double dx = Math.cos(angle) * radius;
+                double dy = Math.sin(angle) * radius;
+                for (double nx : negativeOneAndOne) {
+                    for (double ny : negativeOneAndOne) {
+                        int x = 50 + (int) (nx * dx);
+                        int y = 50 + (int) (ny * dy);
+                        Color c = bitmap.getColor(x, y);
+                        if (currentColor == null) {
+                            currentColor = c;
+                            currentPoint = new Point(x, y);
+                            assertTrue("Outer " + currentPoint + " (" + currentColor
+                                    + ") should be less red than inner " + lastPoint
+                                    + " (" + lastColor + ")", currentColor.red() < lastColor.red());
+                            assertTrue("Outer " + currentPoint + " (" + currentColor
+                                    + ") should be more blue than inner " + lastPoint
+                                    + " (" + lastColor + ")",
+                                    currentColor.blue() > lastColor.blue());
+                        } else {
+                            ColorUtils.verifyColor("Point(" + x + ", " + y + ") should match "
+                                    + currentPoint, currentColor, c, .08f);
+                        }
+                    }
+                }
+            }
+
+            lastColor = currentColor;
+            lastPoint = currentPoint;
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorInts() {
+        int[] colors = null;
+        new RadialGradient(0.5f, 0.5f, 1, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorLongs() {
+        long[] colors = null;
+        new RadialGradient(0.5f, 0.5f, 1, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorInts() {
+        new RadialGradient(0.5f, 0.5f, 1, new int[0], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorLongs() {
+        new RadialGradient(0.5f, 0.5f, 1, new long[0], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorInts() {
+        new RadialGradient(0.5f, 0.5f, 1, new int[1], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorLongs() {
+        new RadialGradient(0.5f, 0.5f, 1, new long[1], null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = Color.pack(Color.BLUE);
+        colors[1] = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new RadialGradient(0.5f, 0.5f, 1, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs2() {
+        long color0 = Color.pack(Color.BLUE);
+        long color1 = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new RadialGradient(0.5f, 0.5f, 1, color0, color1, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsInts() {
+        new RadialGradient(0.5f, 0.5f, 1, new int[2], new float[3], TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsLongs() {
+        new RadialGradient(0.5f, 0.5f, 1, new long[2], new float[3], TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = -1L;
+        colors[0] = -2L;
+        new RadialGradient(0.5f, 0.5f, 1, colors, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong() {
+        new RadialGradient(0.5f, 0.5f, 1, -1L, Color.pack(Color.RED), TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong2() {
+        new RadialGradient(0.5f, 0.5f, 1, Color.pack(Color.RED), -1L, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZeroRadius() {
+        new RadialGradient(0.5f, 0.5f, 0, Color.RED, Color.BLUE, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZeroRadiusArray() {
+        new RadialGradient(0.5f, 0.5f, 0, new int[] {Color.RED, Color.BLUE}, null, TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZeroRadiusLong() {
+        new RadialGradient(0.5f, 0.5f, 0, Color.pack(Color.RED), Color.pack(Color.BLUE),
+                TileMode.CLAMP);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZeroRadiusLongArray() {
+        new RadialGradient(0.5f, 0.5f, 0, new long[]{Color.pack(Color.RED), Color.pack(Color.BLUE)},
+                null, TileMode.CLAMP);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/SweepGradientTest.java b/tests/tests/graphics/src/android/graphics/cts/SweepGradientTest.java
index cb7ec0a..2a67b97 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SweepGradientTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SweepGradientTest.java
@@ -17,11 +17,13 @@
 package android.graphics.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
@@ -37,6 +39,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Function;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SweepGradientTest {
@@ -166,4 +170,149 @@
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(0, 0), 1);
         ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(1, 0), 1);
     }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorInts() {
+        int[] colors = null;
+        new SweepGradient(1, 0.5f, colors, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullColorLongs() {
+        long[] colors = null;
+        new SweepGradient(1, 0.5f, colors, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorInts() {
+        new SweepGradient(1, 0.5f, new int[0], null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNoColorLongs() {
+        new SweepGradient(1, 0.5f, new long[0], null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorInts() {
+        new SweepGradient(1, 0.5f, new int[1], null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testOneColorLongs() {
+        new SweepGradient(1, 0.5f, new long[1], null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = Color.pack(Color.BLUE);
+        colors[1] = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new SweepGradient(1, 0.5f, colors, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchColorLongs2() {
+        long color0 = Color.pack(Color.BLUE);
+        long color1 = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+        new SweepGradient(1, 0.5f, color0, color1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsInts() {
+        new SweepGradient(1, 0.5f, new int[2], new float[3]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMismatchPositionsLongs() {
+        new SweepGradient(1, 0.5f, new long[2], new float[3]);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLongs() {
+        long[] colors = new long[2];
+        colors[0] = -1L;
+        colors[0] = -2L;
+        new SweepGradient(1, 0.5f, colors, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong() {
+        new SweepGradient(1, 0.5f, -1L, Color.pack(Color.RED));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidColorLong2() {
+        new SweepGradient(1, 0.5f, Color.pack(Color.RED), -1L);
+    }
+
+    private String toString(double angle) {
+        double factor = angle * Math.PI;
+        return String.format("%.2f", angle) + "(pi)";
+    }
+
+    @Test
+    public void testColorLong() {
+        ColorSpace p3 = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+        long red = Color.pack(1, 0, 0, 1, p3);
+        long blue = Color.pack(0, 0, 1, 1, p3);
+        SweepGradient gradient = new SweepGradient(50, 50, red, blue);
+
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.RGBA_F16);
+        bitmap.eraseColor(Color.TRANSPARENT);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint paint = new Paint();
+        paint.setShader(gradient);
+        canvas.drawPaint(paint);
+
+        final ColorSpace bitmapColorSpace = bitmap.getColorSpace();
+        Function<Long, Color> convert = (l) -> {
+            return Color.valueOf(Color.convert(l, bitmapColorSpace));
+        };
+
+        Color lastColor = null;
+        double lastAngle = 0;
+        for (double angle = Math.PI / 8.0; angle < Math.PI * 2.0; angle += Math.PI / 8.0) {
+            // currentColor is the Color at this angle.
+            Color currentColor = null;
+            double lastRadius = 0;
+            for (double radius = 4; radius < 25; radius += 4) {
+                double dx = Math.cos(angle) * radius;
+                double dy = Math.sin(angle) * radius;
+                int x = 50 + (int) (dx);
+                int y = 50 + (int) (dy);
+                Color c = bitmap.getColor(x, y);
+                if (currentColor == null) {
+                    // Checking the first radius at this angle.
+                    currentColor = c;
+                    if (lastColor == null) {
+                        // This should be pretty close to the initial color.
+                        ColorUtils.verifyColor("First color (at angle " + toString(angle)
+                                + " and radius " + radius  + " should be mostly red",
+                                convert.apply(red), c, .08f);
+                        lastColor = currentColor;
+                        lastAngle = angle;
+                    } else {
+                        assertTrue("Angle " + toString(angle)
+                                + " should be less red than prior angle "
+                                + toString(lastAngle), c.red() < lastColor.red());
+                        assertTrue("Angle " + toString(angle)
+                                + " should be more blue than prior angle "
+                                + toString(lastAngle), c.blue() > lastColor.blue());
+                    }
+                } else {
+                    // Already have a Color at this angle. This one should match.
+                    ColorUtils.verifyColor("Radius " + radius + " at angle " + toString(angle)
+                            + " should match same angle with radius " + lastRadius, currentColor,
+                            c, .05f);
+                }
+                lastRadius = radius;
+            }
+
+            lastColor = currentColor;
+            lastAngle = angle;
+        }
+    }
+
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceCustomFallbackBuilderTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceCustomFallbackBuilderTest.java
new file mode 100644
index 0000000..a01a47f
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceCustomFallbackBuilderTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.graphics.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontStyle;
+import android.graphics.fonts.FontTestUtil;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextPaint;
+import android.util.Pair;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TypefaceCustomFallbackBuilderTest {
+
+    /**
+     * Returns Typeface with a font family which has 100-900 weight and upright/italic style fonts.
+     */
+    private Typeface createFullFamilyTypeface() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        FontFamily.Builder b = null;
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            final int weight = style.first.intValue();
+            final boolean italic = style.second.booleanValue();
+
+            if (b == null) {
+                b = new FontFamily.Builder(new Font.Builder(am,
+                          FontTestUtil.getFontPathFromStyle(weight, italic)).build());
+            } else {
+                b.addFont(new Font.Builder(am,
+                          FontTestUtil.getFontPathFromStyle(weight, italic)).build());
+            }
+        }
+        return new Typeface.CustomFallbackBuilder(b.build()).build();
+    }
+
+    @Test
+    public void testSingleFont_path() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            final int weight = style.first.intValue();
+            final boolean italic = style.second.booleanValue();
+
+            final String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            assertEquals(path, FontTestUtil.getSelectedFontPathInAsset(
+                    new Typeface.CustomFallbackBuilder(
+                            new FontFamily.Builder(
+                                    new Font.Builder(am, path).build()).build()).build()));
+        }
+    }
+
+    @Test
+    public void testSingleFont_ttc() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            final int weight = style.first.intValue();
+            final boolean italic = style.second.booleanValue();
+
+            final int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            assertEquals(ttcIndex, FontTestUtil.getSelectedTtcIndex(
+                    new Typeface.CustomFallbackBuilder(
+                            new FontFamily.Builder(
+                                    new Font.Builder(am, FontTestUtil.getTtcFontFileInAsset())
+                                    .setTtcIndex(ttcIndex).build()).build()).build()));
+        }
+    }
+
+    @Test
+    public void testSingleFont_vf() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            final int weight = style.first.intValue();
+            final boolean italic = style.second.booleanValue();
+
+            final String varSettings = FontTestUtil.getVarSettingsFromStyle(weight, italic);
+            assertEquals(varSettings, FontTestUtil.getSelectedVariationSettings(
+                    new Typeface.CustomFallbackBuilder(new FontFamily.Builder(
+                            new Font.Builder(am, FontTestUtil.getVFFontInAsset())
+                                .setFontVariationSettings(varSettings).build()).build()).build()));
+        }
+    }
+
+    @Test
+    public void testFamily_defaultStyle() throws IOException {
+        final Typeface typeface = createFullFamilyTypeface();
+        // If none of setWeight/setItalic is called, the default style(400, upright) is selected.
+        assertEquals(new Pair<Integer, Boolean>(400, false),
+                FontTestUtil.getSelectedStyle(typeface));
+    }
+
+    @Test
+    public void testFamily_selectStyle() throws IOException {
+        final Typeface typeface = createFullFamilyTypeface();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            final int weight = style.first.intValue();
+            final boolean italic = style.second.booleanValue();
+            assertEquals(style,
+                    FontTestUtil.getSelectedStyle(Typeface.create(typeface, weight, italic)));
+        }
+    }
+
+    @Test
+    public void testFamily_selectStyleByBuilder() throws IOException {
+        for (Pair<Integer, Boolean> testStyle : FontTestUtil.getAllStyles()) {
+            final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+            FontFamily.Builder b = null;
+            for (Pair<Integer, Boolean> familyStyle : FontTestUtil.getAllStyles()) {
+                final int weight = familyStyle.first.intValue();
+                final boolean italic = familyStyle.second.booleanValue();
+
+                if (b == null) {
+                    b = new FontFamily.Builder(new Font.Builder(am,
+                              FontTestUtil.getFontPathFromStyle(weight, italic)).build());
+                } else {
+                    b.addFont(new Font.Builder(am,
+                              FontTestUtil.getFontPathFromStyle(weight, italic)).build());
+                }
+            }
+            final Typeface typeface = new Typeface.CustomFallbackBuilder(b.build())
+                    .setStyle(new FontStyle(testStyle.first.intValue(),
+                              testStyle.second.booleanValue()
+                            ?  FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)).build();
+
+            assertEquals(testStyle, FontTestUtil.getSelectedStyle(typeface));
+        }
+    }
+
+    @Test
+    public void testFamily_closestDefault() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final FontFamily.Builder b = new FontFamily.Builder(
+                new Font.Builder(am, FontTestUtil.getFontPathFromStyle(300, false)).build())
+                    .addFont(new Font.Builder(am,
+                              FontTestUtil.getFontPathFromStyle(700, false)).build());
+
+        final Typeface typeface = new Typeface.CustomFallbackBuilder(b.build()).build();
+        // If font family doesn't have 400/upright style, the default style should be closest font
+        // instead.
+        assertEquals(new Pair<Integer, Boolean>(300, false),
+                  FontTestUtil.getSelectedStyle(typeface));
+
+        // If 600 is specified, 700 is selected since it is closer than 300.
+        assertEquals(new Pair<Integer, Boolean>(700, false),
+                  FontTestUtil.getSelectedStyle(Typeface.create(typeface, 600, false)));
+    }
+
+    @Test
+    public void testUserFallback() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final FontFamily baseFamily = new FontFamily.Builder(
+                new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build()).build();
+        final FontFamily fallbackFamily = new FontFamily.Builder(
+                new Font.Builder(am, "fonts/user_fallback/hebrew.ttf").build()).build();
+
+        // baseFamily supports all ASCII alphabet characters but not supports Hebrew characters.
+        // FallbackFamily suppors all Hebrew alphabet characters and its width is 2em.
+        final Typeface typeface = new Typeface.CustomFallbackBuilder(baseFamily)
+                .addCustomFallback(fallbackFamily)
+                .build();
+
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(10.0f);  // Make 1em = 10px.
+        paint.setTypeface(typeface);
+
+        assertEquals(10.0f, paint.measureText("a", 0, 1), 0.0f);
+        assertEquals(20.0f, paint.measureText("\u05D0", 0, 1), 0.0f);  // Hebrew letter
+    }
+
+    @Test
+    public void testMaxCustomFallback() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(font).build());
+        // Start from 1 since the first font family is already passed to the constructor.
+        for (int i = 1; i < Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount(); ++i) {
+            b.addCustomFallback(new FontFamily.Builder(font).build());
+        }
+        assertNotNull(b.build());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMaxCustomFallback_exceed_limits() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(font).build());
+        // Start from 1 since the first font family is already passed to the constructor.
+        for (int i = 1; i < Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() + 1; ++i) {
+            b.addCustomFallback(new FontFamily.Builder(font).build());
+        }
+    }
+
+    @Test
+    public void testMaxCustomFallbackAtLeast64() throws IOException {
+        assertTrue(Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() >= 64);
+    }
+
+    @Test
+    public void testMaxCustomFallback_must_be_positive() {
+        assertTrue(Typeface.CustomFallbackBuilder.getMaxCustomFallbackCount() > 0);
+    }
+
+    @Test
+    public void testUserFallbackOverLocaleFallback() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final FontFamily baseFamily = new FontFamily.Builder(
+                new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build()).build();
+        final FontFamily fallbackFamily = new FontFamily.Builder(
+                new Font.Builder(am, "fonts/user_fallback/ideograms.ttf").build()).build();
+
+        // baseFamily supports all ASCII alphabet characters but not supports Hebrew characters.
+        // FallbackFamily suppors all Hebrew alphabet characters and its width is 2em.
+        final Typeface typeface = new Typeface.CustomFallbackBuilder(baseFamily)
+                .addCustomFallback(fallbackFamily)
+                .build();
+
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(10.0f);  // Make 1em = 10px.
+        paint.setTypeface(typeface);
+        paint.setTextLocale(Locale.JAPANESE);
+
+        assertEquals(10.0f, paint.measureText("a", 0, 1), 0.0f);
+        assertEquals(20.0f, paint.measureText("\u4E0D", 0, 1), 0.0f);  // Hebrew letter
+    }
+
+    @Test
+    public void testSystemFallback_SansSerif() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        {
+            final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(font).build());
+            assertNotNull(b.setSystemFallback("sans-serif").build());
+        }
+    }
+
+    @Test
+    public void testSystemFallback_Serif() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        {
+            final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(font).build());
+            assertNotNull(b.setSystemFallback("serif").build());
+        }
+    }
+
+    @Test
+    public void testSystemFallback_anyString() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        {
+            final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(font).build());
+            assertNotNull(b.setSystemFallback("any-string-is-fine").build());
+        }
+    }
+
+    @Test
+    public void testSystemFallback_emptyString() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        {
+            final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(font).build());
+            assertNotNull(b.setSystemFallback("").build());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSystemFallback_null() throws IOException {
+        final AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        final Font font = new Font.Builder(am, "fonts/user_fallback/ascii.ttf").build();
+        final Typeface.CustomFallbackBuilder b = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(font).build());
+        b.setSystemFallback(null);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 425b8a5..938144a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -186,7 +186,8 @@
 
     @Test
     public void testCreateFromAsset() {
-        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
+        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/others/samplefont.ttf");
         assertNotNull(typeface);
     }
 
@@ -236,7 +237,7 @@
                 fail("Failed to create new File!");
             }
         }
-        InputStream is = mContext.getAssets().open("samplefont.ttf");
+        InputStream is = mContext.getAssets().open("fonts/others/samplefont.ttf");
         FileOutputStream fOutput = new FileOutputStream(file);
         byte[] dataBuffer = new byte[1024];
         int readLength = 0;
@@ -250,7 +251,8 @@
 
     @Test
     public void testInvalidCmapFont() {
-        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont.ttf");
+        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/security/bombfont.ttf");
         assertNotNull(typeface);
         final String testString = "abcde";
         float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
@@ -260,7 +262,8 @@
 
     @Test
     public void testInvalidCmapFont2() {
-        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont2.ttf");
+        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/security/bombfont2.ttf");
         assertNotNull(typeface);
         final String testString = "abcde";
         float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
@@ -273,10 +276,10 @@
         // Following three font doen't have any coverage between U+0000..U+10FFFF. Just make sure
         // they don't crash us.
         final String[] INVALID_CMAP_FONTS = {
-            "out_of_unicode_start_cmap12.ttf",
-            "out_of_unicode_end_cmap12.ttf",
-            "too_large_start_cmap12.ttf",
-            "too_large_end_cmap12.ttf",
+            "fonts/security/out_of_unicode_start_cmap12.ttf",
+            "fonts/security/out_of_unicode_end_cmap12.ttf",
+            "fonts/security/too_large_start_cmap12.ttf",
+            "fonts/security/too_large_end_cmap12.ttf",
         };
         for (final String file : INVALID_CMAP_FONTS) {
             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
@@ -288,7 +291,10 @@
     public void testInvalidCmapFont_unsortedEntries() {
         // Following two font files have glyph for U+0400 and U+0100 but the fonts must not be used
         // due to invalid cmap data. For more details, see each ttx source file.
-        final String[] INVALID_CMAP_FONTS = { "unsorted_cmap4.ttf", "unsorted_cmap12.ttf" };
+        final String[] INVALID_CMAP_FONTS = {
+            "fonts/security/unsorted_cmap4.ttf",
+            "fonts/security/unsorted_cmap12.ttf"
+        };
         for (final String file : INVALID_CMAP_FONTS) {
             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
             assertNotNull(typeface);
@@ -301,8 +307,8 @@
         // Following two font files have glyph for U+0400 U+FE00 and U+0100 U+FE00 but the fonts
         // must not be used due to invalid cmap data. For more details, see each ttx source file.
         final String[] INVALID_CMAP_VS_FONTS = {
-            "unsorted_cmap14_default_uvs.ttf",
-            "unsorted_cmap14_non_default_uvs.ttf"
+            "fonts/security/unsorted_cmap14_default_uvs.ttf",
+            "fonts/security/unsorted_cmap14_non_default_uvs.ttf"
         };
         for (final String file : INVALID_CMAP_VS_FONTS) {
             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
@@ -316,19 +322,23 @@
 
     @Test
     public void testCreateFromAsset_cachesTypeface() {
-        Typeface typeface1 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
+        Typeface typeface1 = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/others/samplefont.ttf");
         assertNotNull(typeface1);
 
-        Typeface typeface2 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
+        Typeface typeface2 = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/others/samplefont.ttf");
         assertNotNull(typeface2);
         assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
 
-        Typeface typeface3 = Typeface.createFromAsset(mContext.getAssets(), "samplefont2.ttf");
+        Typeface typeface3 = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/others/samplefont2.ttf");
         assertNotNull(typeface3);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface3);
 
-        Typeface typeface4 = Typeface.createFromAsset(mContext.getAssets(), "samplefont3.ttf");
+        Typeface typeface4 = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/others/samplefont3.ttf");
         assertNotNull(typeface4);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface4);
@@ -338,38 +348,45 @@
 
     @Test
     public void testBadFont() {
-        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "ft45987.ttf");
+        Typeface typeface = Typeface.createFromAsset(mContext.getAssets(),
+                "fonts/security/ft45987.ttf");
         assertNotNull(typeface);
     }
 
     @Test
     public void testTypefaceBuilder_AssetSource() {
-        Typeface typeface1 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf").build();
+        Typeface typeface1 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont.ttf").build();
         assertNotNull(typeface1);
 
-        Typeface typeface2 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf").build();
+        Typeface typeface2 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont.ttf").build();
         assertNotNull(typeface2);
         assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
 
-        Typeface typeface3 = new Typeface.Builder(mContext.getAssets(), "samplefont2.ttf").build();
+        Typeface typeface3 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont2.ttf").build();
         assertNotNull(typeface3);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface3);
 
-        Typeface typeface4 = new Typeface.Builder(mContext.getAssets(), "samplefont3.ttf").build();
+        Typeface typeface4 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont3.ttf").build();
         assertNotNull(typeface4);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface2, typeface4);
         assertNotSame("Different font asset should return different Typeface object",
                 typeface3, typeface4);
 
-        Typeface typeface5 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf")
+        Typeface typeface5 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont.ttf")
                 .setFontVariationSettings("'wdth' 1.0").build();
         assertNotNull(typeface5);
         assertNotSame("Different font font variation should return different Typeface object",
                 typeface2, typeface5);
 
-        Typeface typeface6 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf")
+        Typeface typeface6 = new Typeface.Builder(mContext.getAssets(),
+                "fonts/others/samplefont.ttf")
                 .setFontVariationSettings("'wdth' 2.0").build();
         assertNotNull(typeface6);
         assertNotSame("Different font font variation should return different Typeface object",
@@ -416,7 +433,7 @@
 
         assertNull(new Typeface.Builder(assets, "invalid path").build());
 
-        assertNull(new Typeface.Builder(assets, "samplefont.ttf")
+        assertNull(new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
                 .setTtcIndex(100 /* non-existing ttc index */).build());
 
         // If fallback is set, the builder never returns null.
@@ -466,22 +483,28 @@
 
         // Cache should work for the same fallback.
         assertSame(sansSerifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("sans-serif")
                         .setTtcIndex(100 /* non-existing ttc index */).build());
         assertSame(serifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("serif")
                         .setTtcIndex(100 /* non-existing ttc index */).build());
         assertSame(boldSansSerifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("sans-serif")
                         .setTtcIndex(100 /* non-existing ttc index */).setWeight(700).build());
         assertSame(boldSerifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("serif")
                         .setTtcIndex(100 /* non-existing ttc index */).setWeight(700).build());
         assertSame(italicSansSerifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("sans-serif")
                         .setTtcIndex(100 /* non-existing ttc index */).setItalic(true).build());
         assertSame(italicSerifTypeface,
-                new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
+                new Typeface.Builder(assets, "fonts/others/samplefont.ttf")
+                        .setFallback("serif")
                         .setTtcIndex(100 /* non-existing ttc index */).setItalic(true).build());
     }
 
@@ -498,14 +521,22 @@
     public void testTypeface_SupportedCmapEncodingTest() {
         // We support the following combinations of cmap platfrom/endcoding pairs.
         String[] fontPaths = {
-            "CmapPlatform0Encoding0.ttf",  // Platform ID == 0, Encoding ID == 0
-            "CmapPlatform0Encoding1.ttf",  // Platform ID == 0, Encoding ID == 1
-            "CmapPlatform0Encoding2.ttf",  // Platform ID == 0, Encoding ID == 2
-            "CmapPlatform0Encoding3.ttf",  // Platform ID == 0, Encoding ID == 3
-            "CmapPlatform0Encoding4.ttf",  // Platform ID == 0, Encoding ID == 4
-            "CmapPlatform0Encoding6.ttf",  // Platform ID == 0, Encoding ID == 6
-            "CmapPlatform3Encoding1.ttf",  // Platform ID == 3, Encoding ID == 1
-            "CmapPlatform3Encoding10.ttf",  // Platform ID == 3, Encoding ID == 10
+            // Platform ID == 0, Encoding ID == 0
+            "fonts/cmap_selection/CmapPlatform0Encoding0.ttf",
+            // Platform ID == 0, Encoding ID == 1
+            "fonts/cmap_selection/CmapPlatform0Encoding1.ttf",
+            // Platform ID == 0, Encoding ID == 2
+            "fonts/cmap_selection/CmapPlatform0Encoding2.ttf",
+            // Platform ID == 0, Encoding ID == 3
+            "fonts/cmap_selection/CmapPlatform0Encoding3.ttf",
+            // Platform ID == 0, Encoding ID == 4
+            "fonts/cmap_selection/CmapPlatform0Encoding4.ttf",
+            // Platform ID == 0, Encoding ID == 6
+            "fonts/cmap_selection/CmapPlatform0Encoding6.ttf",
+            // Platform ID == 3, Encoding ID == 1
+            "fonts/cmap_selection/CmapPlatform3Encoding1.ttf",
+            // Platform ID == 3, Encoding ID == 10
+            "fonts/cmap_selection/CmapPlatform3Encoding10.ttf",
         };
 
         for (String fontPath : fontPaths) {
@@ -522,7 +553,7 @@
 
     @Test
     public void testTypefaceBuilder_customFallback() {
-        final String fontPath = "samplefont2.ttf";
+        final String fontPath = "fonts/others/samplefont2.ttf";
         final Typeface regularTypeface = new Typeface.Builder(mContext.getAssets(), fontPath)
                 .setWeight(400).build();
         final Typeface blackTypeface = new Typeface.Builder(mContext.getAssets(), fontPath)
@@ -618,22 +649,23 @@
         paint.setTextSize(100);  // Make 1em = 100px
 
         // By default, WeightEqualsEmVariableFont has 0 'wght' value.
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf").build());
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
+                .build());
         assertEquals(0.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 100").build());
         assertEquals(10.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 300").build());
         assertEquals(30.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 800").build());
         assertEquals(80.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 550").build());
         assertEquals(55.0f, paint.measureText("a"), 0.0f);
     }
@@ -651,11 +683,11 @@
         paint.setTextSize(100);  // Make 1em = 100px
 
         // Unsupported axes do not affect the result.
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 300, 'wdth' 10").build());
         assertEquals(30.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wdth' 10, 'wght' 300").build());
         assertEquals(30.0f, paint.measureText("a"), 0.0f);
     }
@@ -673,11 +705,11 @@
         paint.setTextSize(100);  // Make 1em = 100px
 
         // Out of range value needs to be clipped at the minimum or maximum values.
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' -100").build());
         assertEquals(0.0f, paint.measureText("a"), 0.0f);
 
-        paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
+        paint.setTypeface(new Typeface.Builder(am, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
                 .setFontVariationSettings("'wght' 1300").build());
         assertEquals(100.0f, paint.measureText("a"), 0.0f);
     }
@@ -727,11 +759,13 @@
     public void testTypefaceCreate_customFont_getWeight() {
         final AssetManager am = mContext.getAssets();
 
-        Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf").build();
+        Typeface typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf").build();
         assertEquals(100, typeface.getWeight());
         assertFalse(typeface.isItalic());
 
-        typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").build();
+        typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf").build();
         assertEquals(100, typeface.getWeight());
         assertTrue(typeface.isItalic());
 
@@ -740,12 +774,15 @@
     @Test
     public void testTypefaceCreate_customFont_customWeight() {
         final AssetManager am = mContext.getAssets();
-        Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf")
+        Typeface typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf")
                 .setWeight(400).build();
         assertEquals(400, typeface.getWeight());
         assertFalse(typeface.isItalic());
 
-        typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").setWeight(400).build();
+        typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf")
+                .setWeight(400).build();
         assertEquals(400, typeface.getWeight());
         assertTrue(typeface.isItalic());
     }
@@ -754,12 +791,15 @@
     public void testTypefaceCreate_customFont_customItalic() {
         final AssetManager am = mContext.getAssets();
 
-        Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf")
+        Typeface typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_a3em_weight100_upright.ttf")
                 .setItalic(true).build();
         assertEquals(100, typeface.getWeight());
         assertTrue(typeface.isItalic());
 
-        typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").setItalic(false).build();
+        typeface = new Builder(am,
+                "fonts/family_selection/ttf/ascii_b3em_weight100_italic.ttf")
+                .setItalic(false).build();
         assertEquals(100, typeface.getWeight());
         assertFalse(typeface.isItalic());
     }
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index 2fe4352..c4483a6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -28,6 +28,9 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PropertyUtil;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -36,7 +39,6 @@
 import org.junit.runner.RunWith;
 
 import java.io.UnsupportedEncodingException;
-import com.android.compatibility.common.util.CddTest;
 
 /**
  * Test that the Vulkan loader is present, supports the required extensions, and that system
@@ -64,6 +66,8 @@
     private static final int VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT = 0x8;
     private static final int VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT = 0x10;
 
+    private static final int API_LEVEL_BEFORE_ANDROID_HARDWARE_BUFFER_REQ = 28;
+
     private PackageManager mPm;
     private FeatureInfo mVulkanHardwareLevel = null;
     private FeatureInfo mVulkanHardwareVersion = null;
@@ -171,8 +175,18 @@
     @CddTest(requirement="7.9.2/C-1-5")
     @Test
     public void testVulkan1_1Requirements() throws JSONException {
-        if (mVulkanHardwareVersion == null || mVulkanHardwareVersion.version < VULKAN_1_1)
+        if (mVulkanHardwareVersion == null || mVulkanHardwareVersion.version < VULKAN_1_1
+                || !PropertyUtil.isVendorApiLevelNewerThan(
+                        API_LEVEL_BEFORE_ANDROID_HARDWARE_BUFFER_REQ)) {
             return;
+        }
+        assertTrue("Devices with Vulkan 1.1 must support " +
+                VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME +
+                " (version >= " + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION +
+                ")",
+                hasExtension(mBestDevice,
+                    VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
+                    VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION));
         assertTrue("Devices with Vulkan 1.1 must support SYNC_FD external semaphores",
                 hasHandleType(mBestDevice.getJSONArray("externalSemaphoreProperties"),
                     VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformCtsActivity.java
index 32cb757..b00a072 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformCtsActivity.java
@@ -35,7 +35,7 @@
         System.loadLibrary("ctsgraphics_jni");
     }
 
-    private static final String TAG = "vulkan";
+    private static final String TAG = VulkanPreTransformCtsActivity.class.getSimpleName();
 
     private static boolean sOrientationRequested = false;
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
index d368f0e..835934a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
@@ -92,7 +92,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class VulkanPreTransformTest {
-    private static final String TAG = "vulkan";
+    private static final String TAG = VulkanPreTransformTest.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static VulkanPreTransformCtsActivity sActivity = null;
     private Context mContext;
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanSurfaceSupportTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanSurfaceSupportTest.java
new file mode 100644
index 0000000..bd144c7
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanSurfaceSupportTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.graphics.cts;
+
+import android.content.res.AssetManager;
+import android.graphics.ImageFormat;
+import android.media.ImageReader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Surface;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class VulkanSurfaceSupportTest {
+
+    static {
+        System.loadLibrary("ctsgraphics_jni");
+    }
+
+    private static final String TAG = VulkanSurfaceSupportTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    @Test
+    public void testVulkanUnsupportedFormat() {
+        ImageReader reader = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 3);
+        nCreateNativeTest(
+                InstrumentationRegistry.getContext().getAssets(), reader.getSurface(), false);
+    }
+
+    @Test
+    public void testVulkanSupportedFormat() {
+        ImageReader reader = ImageReader.newInstance(640, 480, ImageFormat.RGB_565, 3);
+        nCreateNativeTest(
+                InstrumentationRegistry.getContext().getAssets(), reader.getSurface(), true);
+    }
+
+    private static native void nCreateNativeTest(
+            AssetManager manager, Surface surface, boolean supported);
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
index 8def08f..dc63c02 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
@@ -131,6 +131,14 @@
         assertEquals(-21, iconDrawable.getChangingConfigurations());
     }
 
+    @Test
+    public void testGetAlpha() {
+        ColorDrawable drawable = new ColorDrawable(Color.RED);
+        AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(null, drawable);
+        iconDrawable.setAlpha(128);
+        assertEquals(128, iconDrawable.getAlpha());
+    }
+
     /**
      * When setBound isn't called before draw method is called.
      * Nothing is drawn.
@@ -252,11 +260,13 @@
     public void testGetOpacity() {
         AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(
             new ColorDrawable(Color.RED), new ColorDrawable(Color.BLUE));
+        // Drawable#getOpacity is deprecated, AdaptiveIconDrawable
+        // should return PixelFormat.TRANSLUCENT always
         iconDrawable.setOpacity(PixelFormat.OPAQUE);
-        assertEquals(PixelFormat.OPAQUE, iconDrawable.getOpacity());
+        assertEquals(PixelFormat.TRANSLUCENT, iconDrawable.getOpacity());
 
         iconDrawable.setOpacity(PixelFormat.TRANSPARENT);
-        assertEquals(PixelFormat.TRANSPARENT, iconDrawable.getOpacity());
+        assertEquals(PixelFormat.TRANSLUCENT, iconDrawable.getOpacity());
     }
 
     @Test
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 3780124..1c1cebf 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -271,7 +271,7 @@
         cb.assertStarted(true);
 
         // Extra time, to wait for the message to post.
-        cb.waitForEnd(DURATION * 3);
+        cb.waitForEnd(DURATION * 20);
         cb.assertEnded(true);
         assertFalse(drawable.isRunning());
     }
@@ -335,7 +335,7 @@
 
         // Add extra duration to wait for the message posted by the end of the
         // animation. This should help fix flakiness.
-        cb.waitForEnd(DURATION * 3);
+        cb.waitForEnd(DURATION * 10);
         cb.assertEnded(true);
     }
 
@@ -386,7 +386,7 @@
             cb.waitForEnd(DURATION * repeatCount);
             cb.assertEnded(false);
 
-            cb.waitForEnd(DURATION * 2);
+            cb.waitForEnd(DURATION * 20);
             cb.assertEnded(true);
 
             drawable.setRepeatCount(AnimatedImageDrawable.REPEAT_INFINITE);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
index 422f06d..4b98f22 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
@@ -29,7 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.cts.R;
 import android.graphics.drawable.AnimatedVectorDrawable;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
@@ -226,7 +225,7 @@
         });
     }
 
-    @MediumTest
+    @LargeTest
     @Test
     public void testEmptyAnimatorSet() throws Throwable {
         int resId = R.drawable.avd_empty_animator;
@@ -246,7 +245,7 @@
         AnimatedVectorDrawableTest.waitForAVDStop(callback, MAX_TIMEOUT_MS);
         // Check that the AVD with empty AnimatorSet has finished
         callback.assertEnded(true);
-        callback.assertAVDRuntime(0, TimeUnit.MILLISECONDS.toNanos(64)); // 4 frames
+        callback.assertAVDRuntime(0, TimeUnit.MILLISECONDS.toNanos(300));
     }
 
     // Does a fuzzy comparison between two images.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
index f399cf6..2cd8bc0 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -28,13 +28,12 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.cts.R;
-import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
@@ -108,6 +107,17 @@
         }
     }
 
+    @SmallTest
+    @Test
+    public void testGetOpticalInsets() throws Exception {
+        XmlPullParser parser = mResources.getXml(mResId);
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        AnimatedVectorDrawable drawable = new AnimatedVectorDrawable();
+        drawable.inflate(mResources, parser, attrs);
+
+        assertEquals(Insets.of(10, 20, 30, 40), drawable.getOpticalInsets());
+    }
+
     @Test
     public void testGetChangingConfigurations() {
         AnimatedVectorDrawable avd = new AnimatedVectorDrawable();
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
index 536c16f..5f62847 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
@@ -25,43 +25,36 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.content.res.Resources.Theme;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
-import android.graphics.Rect;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
 import android.graphics.cts.R;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable.ConstantState;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.AttributeSet;
+import android.view.Gravity;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParserException;
 
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Shader;
-import android.graphics.Bitmap.Config;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Drawable.ConstantState;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.AttributeSet;
-import android.util.LayoutDirection;
-import android.util.Xml;
-import android.view.Gravity;
-
 import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -143,6 +136,23 @@
     }
 
     @Test
+    public void testBitmapDrawableOpticalInset() {
+        InputStream source = mContext.getResources().openRawResource(R.raw.testimage);
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(source);
+
+        int intrinsicWidth = bitmapDrawable.getIntrinsicWidth();
+        int intrinsicHeight = bitmapDrawable.getIntrinsicHeight();
+
+        bitmapDrawable.setGravity(Gravity.CENTER);
+        bitmapDrawable.setBounds(0, 0, intrinsicWidth * 3, intrinsicHeight * 3);
+
+        Insets opticalInsets = bitmapDrawable.getOpticalInsets();
+        Insets expected = Insets.of(intrinsicWidth, intrinsicHeight, intrinsicWidth,
+                    intrinsicHeight);
+        assertEquals(expected, opticalInsets);
+    }
+
+    @Test
     public void testAccessMipMap() {
         Bitmap source = BitmapFactory.decodeResource(mContext.getResources(), R.raw.testimage);
         BitmapDrawable bitmapDrawable = new BitmapDrawable(source);
@@ -284,10 +294,12 @@
 
         // exceptional test
         bitmapDrawable.setAlpha(-1);
-        assertEquals(255, bitmapDrawable.getPaint().getAlpha());
+        assertTrue(0 <= bitmapDrawable.getPaint().getAlpha()
+                   && bitmapDrawable.getPaint().getAlpha() <= 255);
 
         bitmapDrawable.setAlpha(256);
-        assertEquals(0, bitmapDrawable.getPaint().getAlpha());
+        assertTrue(0 <= bitmapDrawable.getPaint().getAlpha()
+                   && bitmapDrawable.getPaint().getAlpha() <= 255);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ColorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ColorDrawableTest.java
index ee34254..675f15c 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ColorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ColorDrawableTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
 import android.content.res.Resources;
@@ -148,6 +149,15 @@
     }
 
     @Test
+    public void testGetColorFilter() {
+        final ColorDrawable d = new ColorDrawable(Color.WHITE);
+        PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(Color.BLACK, Mode.SRC_OVER);
+        d.setColorFilter(colorFilter);
+
+        assertSame(colorFilter, d.getColorFilter());
+    }
+
+    @Test
     public void testSetTint() {
         final ColorDrawable d = new ColorDrawable(Color.WHITE);
         assertEquals(Color.WHITE, DrawableTestUtils.getPixel(d, 0, 0));
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ColorStateListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ColorStateListDrawableTest.java
new file mode 100644
index 0000000..7487e55
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ColorStateListDrawableTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.graphics.drawable.cts;
+
+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 android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.ColorStateListDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ColorStateListDrawableTest {
+    private ColorStateList mColorStateList;
+    private ColorStateListDrawable mDrawable;
+    private static final int[] STATE_RED = new int[]{1};
+    private static final int[] STATE_BLUE = new int[]{2};
+
+    @Before
+    public void setup() {
+        final int[][] state = new int[][]{STATE_RED, STATE_BLUE};
+        final int[] colors = new int[]{Color.RED, Color.BLUE};
+        mColorStateList = new ColorStateList(state, colors);
+        mDrawable = new ColorStateListDrawable(mColorStateList);
+    }
+
+    @Test
+    public void testDefaultConstructor() {
+        ColorStateListDrawable drawable = new ColorStateListDrawable();
+        assertFalse(drawable.isStateful());
+        assertEquals(
+                drawable.getColorStateList().getDefaultColor(),
+                new ColorDrawable().getColor());
+    }
+
+    @Test
+    public void testDraw() {
+        Canvas c = new Canvas();
+        mDrawable.draw(c);
+    }
+
+    @Test
+    public void testGetCurrent() {
+        assertTrue(mDrawable.getCurrent() instanceof ColorDrawable);
+    }
+
+    @Test
+    public void testIsStateful() {
+        assertTrue(mDrawable.isStateful());
+        mDrawable.setColorStateList(ColorStateList.valueOf(Color.GREEN));
+        assertFalse(mDrawable.isStateful());
+    }
+
+    @Test
+    public void testAlpha() {
+        int transBlue = (Color.BLUE & 0xFFFFFF) | 127 << 24;
+        mDrawable.setColorStateList(ColorStateList.valueOf(transBlue));
+        assertEquals(mDrawable.getOpacity(), PixelFormat.TRANSLUCENT);
+        assertEquals(mDrawable.getAlpha(), 127);
+
+        mDrawable.setAlpha(0);
+        assertEquals(mDrawable.getOpacity(), PixelFormat.TRANSPARENT);
+        assertEquals(mDrawable.getAlpha(), 0);
+        assertEquals(mDrawable.getColorStateList().getDefaultColor(), transBlue);
+
+        mDrawable.setAlpha(255);
+        assertEquals(mDrawable.getOpacity(), PixelFormat.OPAQUE);
+        assertEquals(mDrawable.getAlpha(), 255);
+        assertEquals(mDrawable.getColorStateList().getDefaultColor(), transBlue);
+
+        mDrawable.clearAlpha();
+        assertEquals(mDrawable.getAlpha(), 127);
+    }
+
+    @Test
+    public void testSetState() {
+        ColorDrawable colorDrawable = (ColorDrawable) mDrawable.getCurrent();
+        assertEquals(colorDrawable.getColor(), mColorStateList.getDefaultColor());
+        mDrawable.setState(STATE_BLUE);
+        assertEquals(colorDrawable.getColor(), Color.BLUE);
+        mDrawable.setState(STATE_RED);
+        assertEquals(colorDrawable.getColor(), Color.RED);
+    }
+
+    @Test
+    public void testMutate() {
+        Drawable.ConstantState oldState = mDrawable.getConstantState();
+        assertEquals(mDrawable.mutate(), mDrawable);
+        assertNotEquals(mDrawable.getConstantState(), oldState);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
index 9b93a48..b8f5cbb 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableContainerTest.java
@@ -41,6 +41,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
@@ -53,6 +54,8 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.annotation.Nullable;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -825,6 +828,22 @@
         assertEquals(true, mDrawableContainer.isStateful());
     }
 
+    @Test
+    public void testGetOpticalBoundsWithNoInternalDrawable() {
+        DrawableContainer container = new DrawableContainer();
+        assertEquals(Insets.NONE, container.getOpticalInsets());
+    }
+
+    @Test
+    public void testGetOpticalBoundsFromInternalDrawable() {
+        mMockDrawableContainer.setConstantState(mDrawableContainerState);
+        MockDrawable mockDrawable = new MockDrawable();
+        mockDrawable.setInsets(Insets.of(20, 40, 60, 100));
+
+        addAndSelectDrawable(mockDrawable);
+        assertEquals(Insets.of(20, 40, 60, 100), mDrawableContainer.getOpticalInsets());
+    }
+
     private void addAndSelectDrawable(Drawable drawable) {
         int pos = mDrawableContainerState.addChild(drawable);
         mDrawableContainer.selectDrawable(pos);
@@ -861,6 +880,8 @@
         private boolean mHasCalledOnStateChanged;
         private boolean mHasCalledOnLevelChanged;
 
+        private Insets mInsets = null;
+
         @Override
         public int getOpacity() {
             return PixelFormat.OPAQUE;
@@ -878,6 +899,15 @@
         public void setColorFilter(ColorFilter colorFilter) {
         }
 
+        public void setInsets(@Nullable Insets insets) {
+            mInsets = insets;
+        }
+
+        @Override
+        public Insets getOpticalInsets() {
+            return mInsets != null ? mInsets : Insets.NONE;
+        }
+
         public boolean hasOnBoundsChangedCalled() {
             return mHasCalledOnBoundsChanged;
         }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java
index 3c26f39..4fd64b8 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTest.java
@@ -16,44 +16,6 @@
 
 package android.graphics.drawable.cts;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.cts.R;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Drawable.Callback;
-import android.net.Uri;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.AndroidTestCase;
-import android.util.AttributeSet;
-import android.util.StateSet;
-import android.util.TypedValue;
-import android.util.Xml;
-import android.view.View;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -66,6 +28,43 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+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;
+import android.graphics.drawable.Drawable.Callback;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DrawableTest {
@@ -722,6 +721,12 @@
         assertSame(mockDrawable, mockDrawable.mutate());
     }
 
+    @Test
+    public void testDefaultOpticalInsetsIsNone() {
+        Drawable mockDrawable = new MockDrawable();
+        assertEquals(Insets.NONE, mockDrawable.getOpticalInsets());
+    }
+
     // 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.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTestUtils.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTestUtils.java
index d518668..570243c 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTestUtils.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableTestUtils.java
@@ -24,13 +24,14 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
-import androidx.annotation.IntegerRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
 
+import androidx.annotation.IntegerRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import junit.framework.Assert;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -173,11 +174,21 @@
                 int givenColor = actual.getPixel(x, y);
                 if (idealColor == givenColor)
                     continue;
+                if (Color.alpha(idealColor) + Color.alpha(givenColor) == 0) {
+                    continue;
+                }
 
+                float idealAlpha = Color.alpha(idealColor) / 255.0f;
+                float givenAlpha = Color.alpha(givenColor) / 255.0f;
+
+                // compare premultiplied color values
                 float totalError = 0;
-                totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor));
-                totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor));
-                totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor));
+                totalError += Math.abs((idealAlpha * Color.red(idealColor))
+                                     - (givenAlpha * Color.red(givenColor)));
+                totalError += Math.abs((idealAlpha * Color.green(idealColor))
+                                     - (givenAlpha * Color.green(givenColor)));
+                totalError += Math.abs((idealAlpha * Color.blue(idealColor))
+                                     - (givenAlpha * Color.blue(givenColor)));
                 totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor));
 
                 if ((totalError / 1024.0f) >= pixelThreshold) {
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 c7acccc..7ebae70 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
@@ -35,6 +35,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -49,6 +50,8 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.util.StateSet;
 
+import androidx.annotation.Nullable;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -153,6 +156,14 @@
     }
 
     @Test
+    public void testCallbackIsSet() {
+        Drawable dr = new MockDrawable();
+
+        DrawableWrapper wrapper = new MyWrapper(dr);
+        assertEquals(wrapper, dr.getCallback());
+    }
+
+    @Test
     public void testDraw() {
         Drawable mockDrawable = spy(new ColorDrawable(Color.BLUE));
         doNothing().when(mockDrawable).draw(any());
@@ -219,6 +230,7 @@
         DrawableWrapper wrapper = new MyWrapper(mockDrawable);
         assertTrue(wrapper.isVisible());
 
+        reset(mockDrawable);
         assertTrue(wrapper.setVisible(false, false));
         assertFalse(wrapper.isVisible());
         verify(mockDrawable, times(1)).setVisible(anyBoolean(), anyBoolean());
@@ -377,6 +389,21 @@
         verify(mockDrawable, times(1)).getIntrinsicHeight();
     }
 
+    @Test
+    public void testGetOpticalInsetsNoInternalDrawable() {
+        DrawableWrapper wrapper = new MockDrawableWrapper(null);
+        assertEquals(Insets.NONE, wrapper.getOpticalInsets());
+    }
+
+    @Test
+    public void testGetOpticalInsetsFromInternalDrawable() {
+        MockDrawable drawable = new MockDrawable();
+        drawable.setInsets(Insets.of(30, 60, 90, 120));
+        DrawableWrapper wrapper = new MockDrawableWrapper(drawable);
+
+        assertEquals(Insets.of(30, 60, 90, 120), wrapper.getOpticalInsets());
+    }
+
     @SuppressWarnings("deprecation")
     @Test
     public void testGetConstantState() {
@@ -390,6 +417,7 @@
     private static class MockDrawable extends Drawable {
         private boolean mCalledOnLevelChange = false;
         private ColorFilter mColorFilter;
+        private Insets mInsets = null;
 
         @Override
         public void draw(Canvas canvas) {
@@ -414,6 +442,15 @@
             return mColorFilter;
         }
 
+        public void setInsets(@Nullable Insets insets) {
+            mInsets = insets;
+        }
+
+        @Override
+        public Insets getOpticalInsets() {
+            return mInsets != null ? mInsets : Insets.NONE;
+        }
+
         @Override
         protected boolean onLevelChange(int level) {
             mCalledOnLevelChange = true;
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 4a05376..ed3a54a 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -23,13 +23,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.cts.R;
@@ -367,9 +370,9 @@
         gradientDrawable.setColor(color);
         assertEquals("Color was set to RED", color, gradientDrawable.getColor());
 
-        color = null;
-        gradientDrawable.setColor(color);
-        assertEquals("Color was set to null (TRANSPARENT)", color, gradientDrawable.getColor());
+        gradientDrawable.setColor(null);
+        assertEquals("Color was set to null (TRANSPARENT)",
+                ColorStateList.valueOf(Color.TRANSPARENT), gradientDrawable.getColor());
     }
 
     @Test
@@ -557,6 +560,35 @@
         }
     }
 
+    @Test
+    public void testOpticalInsets() {
+        GradientDrawable drawable =
+                (GradientDrawable) mResources.getDrawable(R.drawable.gradientdrawable);
+        assertEquals(Insets.of(1, 2, 3, 4), drawable.getOpticalInsets());
+    }
+
+    @Test
+    public void testInflationWithThemeAndNonThemeResources() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Theme theme = context.getResources().newTheme();
+        theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+        final Theme ctxTheme = context.getTheme();
+        ctxTheme.setTo(theme);
+
+        GradientDrawable drawable = (GradientDrawable)
+                ctxTheme.getDrawable(R.drawable.gradientdrawable_mix_theme);
+
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, 10, 10);
+        drawable.draw(canvas);
+        int[] colors = drawable.getColors();
+        assertEquals(3, colors.length);
+        assertEquals(0, colors[0]);
+        assertEquals(context.getColor(R.color.colorPrimary), colors[1]);
+        assertEquals(context.getColor(R.color.colorPrimaryDark), colors[2]);
+    }
+
     private void verifyPreloadDensityInner(Resources res, int densityDpi)
             throws XmlPullParserException, IOException {
         final Rect tempPadding = new Rect();
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
index b1c1f03..01685a8 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
@@ -26,15 +26,13 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.cts.R;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.InsetDrawable;
 import android.support.test.InstrumentationRegistry;
@@ -459,6 +457,12 @@
         }
     }
 
+    @Test
+    public void testOpticalInsets() {
+        InsetDrawable drawable = new InsetDrawable(mPassDrawable, 1, 2, 3, 4);
+        assertEquals(Insets.of(1, 2, 3, 4), drawable.getOpticalInsets());
+    }
+
     private void verifyPreloadDensityInner(Resources res, int densityDpi)
             throws XmlPullParserException, IOException {
         // Capture initial state at default density.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
index 2760940..fb54a68 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/NinePatchDrawableTest.java
@@ -195,10 +195,12 @@
         assertEquals(0, mNinePatchDrawable.getPaint().getAlpha());
 
         mNinePatchDrawable.setAlpha(-1);
-        assertEquals(0xff, mNinePatchDrawable.getPaint().getAlpha());
+        assertTrue(0 <= mNinePatchDrawable.getPaint().getAlpha()
+                   && mNinePatchDrawable.getPaint().getAlpha() <= 255);
 
         mNinePatchDrawable.setAlpha(0xfffe);
-        assertEquals(0xfe, mNinePatchDrawable.getPaint().getAlpha());
+        assertTrue(0 <= mNinePatchDrawable.getPaint().getAlpha()
+                   && mNinePatchDrawable.getPaint().getAlpha() <= 255);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ShapeDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ShapeDrawableTest.java
index d563102..11f8fc5 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ShapeDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ShapeDrawableTest.java
@@ -373,7 +373,7 @@
         a.setShape(new OvalShape());
 
         ShapeDrawable b = (ShapeDrawable) a.getConstantState().newDrawable();
-        assertSame(a.getShape(), b.getShape());
+        assertEquals(a.getShape(), b.getShape());
         a.mutate();
 
         assertNotNull(a.getShape());
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
index c96a1ca..1c50ae3 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
@@ -30,6 +30,7 @@
 import android.graphics.cts.R;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.DrawableContainer.DrawableContainerState;
 import android.graphics.drawable.StateListDrawable;
@@ -332,6 +333,66 @@
     }
 
     @Test
+    public void testGetStateCount() {
+        StateListDrawable stateList = new StateListDrawable();
+        stateList.addState(new int[]{0}, new ColorDrawable(Color.RED));
+
+        assertEquals(1, stateList.getStateCount());
+
+        stateList.addState(new int[]{1}, new ColorDrawable(Color.GREEN));
+
+        assertEquals(2, stateList.getStateCount());
+
+        stateList.addState(new int[]{2}, new ColorDrawable(Color.BLUE));
+
+        assertEquals(3, stateList.getStateCount());
+    }
+
+    @Test
+    public void testGetStateDrawable() {
+        StateListDrawable stateList = new StateListDrawable();
+
+        ColorDrawable colorDrawable = new ColorDrawable(Color.RED);
+        int[] stateSet = new int[]{1};
+        stateList.addState(stateSet, colorDrawable);
+
+        Drawable drawable = stateList.getStateDrawable(0);
+        assertSame(colorDrawable, drawable);
+    }
+
+    @Test
+    public void testGetStateSet() {
+        StateListDrawable stateList = new StateListDrawable();
+
+        ColorDrawable colorDrawable = new ColorDrawable(Color.GREEN);
+        int[] stateSet = new int[]{0};
+
+        stateList.addState(stateSet, colorDrawable);
+        int[] resolvedStateSet = stateList.getStateSet(0);
+        assertEquals(stateSet, resolvedStateSet);
+    }
+
+    @Test
+    public void testGetStateDrawableIndex() {
+        StateListDrawable stateList = new StateListDrawable();
+
+        ColorDrawable drawable1 = new ColorDrawable(Color.CYAN);
+        ColorDrawable drawable2 = new ColorDrawable(Color.YELLOW);
+        ColorDrawable drawable3 = new ColorDrawable(Color.GREEN);
+        int[] stateSet1 = new int[]{42};
+        int[] stateSet2 = new int[]{27};
+        int[] stateSet3 = new int[]{57};
+
+        stateList.addState(stateSet1, drawable1);
+        stateList.addState(stateSet2, drawable2);
+        stateList.addState(stateSet3, drawable3);
+
+        assertEquals(0, stateList.findStateDrawableIndex(stateSet1));
+        assertEquals(1, stateList.findStateDrawableIndex(stateSet2));
+        assertEquals(2, stateList.findStateDrawableIndex(stateSet3));
+    }
+
+    @Test
     public void testMutate() {
         StateListDrawable d1 =
             (StateListDrawable) mResources.getDrawable(R.drawable.statelistdrawable);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
index 1a3f774..72a7cdb 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
@@ -23,6 +23,8 @@
 import android.content.Context;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Shader.TileMode;
@@ -33,6 +35,7 @@
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.NinePatchDrawable;
 import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.VectorDrawable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -170,4 +173,14 @@
         NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) d.getDrawable(1);
         verifyNinePatchDrawable(ninePatchDrawable);
     }
+
+    @Test
+    public void testVectorDrawableWithStateListColorThemeAttrs() {
+        VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.heart);
+        d.setBounds(0, 0, 64, 64);
+        Bitmap bitmap = Bitmap.createBitmap(64, 64, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        d.draw(canvas);
+        assertEquals(0xff0000ff, bitmap.getPixel(32, 32));
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
index 76b6e2c..673a1b1 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
@@ -27,28 +27,27 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.cts.R;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.VectorDrawable;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Xml;
 
+import androidx.annotation.Nullable;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 
 @SmallTest
@@ -425,6 +424,13 @@
         }
     }
 
+    @Test
+    public void testOpticalInsets() {
+        VectorDrawable drawable =
+                (VectorDrawable) mResources.getDrawable(R.drawable.vector_icon_create);
+        assertEquals(Insets.of(1, 2, 3, 4), drawable.getOpticalInsets());
+    }
+
     private void verifyPreloadDensityInner(Resources res, int densityDpi)
             throws XmlPullParserException, IOException {
         // Capture initial state at default density.
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java
new file mode 100644
index 0000000..b761c52
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.graphics.fonts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.AssetManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontFamilyTest {
+    private static final String TAG = "FontFamilyTest";
+    private static final String FONT_DIR = "fonts/family_selection/ttf/";
+
+    @Test
+    public void testBuilder_SingleFont() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font font = new Font.Builder(am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        FontFamily family = new FontFamily.Builder(font).build();
+        assertNotNull(family);
+        assertEquals(1, family.getSize());
+        assertSame(font, family.getFont(0));
+    }
+
+    @Test
+    public void testBuilder_MultipleFont() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font boldFont = new Font.Builder(
+                am, FONT_DIR + "ascii_m3em_weight700_upright.ttf").build();
+        FontFamily family = new FontFamily.Builder(regularFont).addFont(boldFont).build();
+        assertNotNull(family);
+        assertEquals(2, family.getSize());
+        assertNotSame(family.getFont(0), family.getFont(1));
+        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == boldFont);
+        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == boldFont);
+    }
+
+    @Test
+    public void testBuilder_MultipleFont_overrideWeight() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font boldFont = new Font.Builder(am, FONT_DIR + "ascii_g3em_weight400_upright.ttf")
+                .setWeight(700).build();
+        FontFamily family = new FontFamily.Builder(regularFont).addFont(boldFont).build();
+        assertNotNull(family);
+        assertEquals(2, family.getSize());
+        assertNotSame(family.getFont(0), family.getFont(1));
+        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == boldFont);
+        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == boldFont);
+    }
+
+    @Test
+    public void testBuilder_MultipleFont_overrideItalic() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font italicFont = new Font.Builder(am, FONT_DIR + "ascii_g3em_weight400_upright.ttf")
+                .setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+        FontFamily family = new FontFamily.Builder(regularFont).addFont(italicFont).build();
+        assertNotNull(family);
+        assertEquals(2, family.getSize());
+        assertNotSame(family.getFont(0), family.getFont(1));
+        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == italicFont);
+        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == italicFont);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_MultipleFont_SameStyle() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font regularFont2 = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        new FontFamily.Builder(regularFont).addFont(regularFont2).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_MultipleFont_SameStyle_overrideWeight() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font regularFont2 = new Font.Builder(am, FONT_DIR + "ascii_m3em_weight700_upright.ttf")
+                .setWeight(400).build();
+        new FontFamily.Builder(regularFont).addFont(regularFont2).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_MultipleFont_SameStyle_overrideItalic() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Font regularFont = new Font.Builder(
+                am, FONT_DIR + "ascii_g3em_weight400_upright.ttf").build();
+        Font regularFont2 = new Font.Builder(am, FONT_DIR + "ascii_h3em_weight400_italic.ttf")
+                .setSlant(FontStyle.FONT_SLANT_UPRIGHT).build();
+        new FontFamily.Builder(regularFont).addFont(regularFont2).build();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontStyleTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontStyleTest.java
new file mode 100644
index 0000000..7d830a1
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontStyleTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.graphics.fonts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontStyleTest {
+    private static final String TAG = "FontStyleTest";
+
+    @Test
+    public void testBuilder_construct() {
+        new FontStyle();
+        new FontStyle(FontStyle.FONT_WEIGHT_THIN, FontStyle.FONT_SLANT_ITALIC);
+        new FontStyle(350, FontStyle.FONT_SLANT_ITALIC);
+    }
+
+    @Test
+    public void testBuilder_construct_withArgs() {
+        FontStyle fs = new FontStyle(FontStyle.FONT_WEIGHT_THIN, FontStyle.FONT_SLANT_ITALIC);
+        assertEquals(FontStyle.FONT_WEIGHT_THIN, fs.getWeight());
+        assertEquals(FontStyle.FONT_SLANT_ITALIC, fs.getSlant());
+
+        fs = new FontStyle(350, FontStyle.FONT_SLANT_UPRIGHT);
+        assertEquals(350, fs.getWeight());
+        assertEquals(FontStyle.FONT_SLANT_UPRIGHT, fs.getSlant());
+    }
+
+    @Test
+    public void testBuilder_defaultValues() {
+        assertEquals(FontStyle.FONT_WEIGHT_NORMAL, new FontStyle().getWeight());
+        assertEquals(FontStyle.FONT_SLANT_UPRIGHT, new FontStyle().getSlant());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_constructor_too_small_weight() {
+        new FontStyle(FontStyle.FONT_WEIGHT_MIN - 1, FontStyle.FONT_SLANT_UPRIGHT);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_constructor_too_large_weight() {
+        new FontStyle(FontStyle.FONT_WEIGHT_MAX + 1, FontStyle.FONT_SLANT_UPRIGHT);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_constructor_invalid_slant() {
+        new FontStyle(FontStyle.FONT_WEIGHT_THIN, -1);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
new file mode 100644
index 0000000..ce3b2d0
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
@@ -0,0 +1,990 @@
+/*
+ * 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.graphics.fonts;
+
+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 android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.cts.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.Pair;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontTest {
+    private static final String TAG = "FontFileUtilTest";
+    private static final String CACHE_FILE_PREFIX = ".font";
+
+    /**
+     * Create new temporary file.
+     *
+     * Caller must delete the file after used.
+     */
+    private static File getTempFile() {
+        Context ctx = InstrumentationRegistry.getTargetContext();
+        final String prefix = CACHE_FILE_PREFIX;
+        for (int i = 0; i < 100; ++i) {
+            final File file = new File(ctx.getCacheDir(), prefix + i);
+            try {
+                if (file.createNewFile()) {
+                    return file;
+                }
+            } catch (IOException e) {
+                // ignore. Try next file.
+            }
+        }
+        return null;
+    }
+
+    private static ByteBuffer mmap(AssetManager am, String path) {
+        File file = getTempFile();
+        try (InputStream is = am.open(path)) {
+            if (!copyToFile(file, is)) {
+                return null;
+            }
+            return mmap(file);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to open assets");
+            return null;
+        } finally {
+            file.delete();
+        }
+    }
+
+    private static ByteBuffer mmap(File file) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            FileChannel channel = fis.getChannel();
+            return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private static boolean copyToFile(File file, InputStream is) {
+        return copyToFile(file, is, null, null);
+    }
+
+    private static boolean copyToFile(File file, InputStream is, byte[] prepend, byte[] append) {
+        try (FileOutputStream os = new FileOutputStream(file, false)) {
+            byte[] buffer = new byte[1024];
+            int readLen;
+            if (prepend != null) {
+                os.write(prepend, 0, prepend.length);
+            }
+            while ((readLen = is.read(buffer)) != -1) {
+                os.write(buffer, 0, readLen);
+            }
+            if (append != null) {
+                os.write(append, 0, append.length);
+            }
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "Error copying resource contents to temp file: " + e.getMessage());
+            return false;
+        }
+    }
+
+    @Test
+    public void testBuilder_buffer() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+
+            ByteBuffer buffer = mmap(am, path);
+
+            Font font = new Font.Builder(buffer).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_buffer_ttc() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+            String path = FontTestUtil.getTtcFontFileInAsset();
+
+            ByteBuffer buffer = mmap(am, path);
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+
+            Font font = new Font.Builder(buffer).setTtcIndex(ttcIndex).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, ttcIndex, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_buffer_vf() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getVFFontInAsset();
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            ByteBuffer buffer = mmap(am, path);
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+
+            Font font = new Font.Builder(buffer).setFontVariationSettings(axes).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertEquals(path, axes, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_buffer_override() throws IOException {
+        int customWeight = 350;
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            ByteBuffer buffer = mmap(am, path);
+
+            Font font = new Font.Builder(buffer).setWeight(customWeight).build();
+            assertEquals(path, customWeight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+
+            ByteBuffer buffer = mmap(am, path);
+
+            Font font = new Font.Builder(buffer).setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_buffer_invalid_null() throws IOException {
+        ByteBuffer buf = null;
+        new Font.Builder(buf);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_buffer_invalid_not_direct() throws IOException {
+        ByteBuffer buf = ByteBuffer.allocate(1024);
+        new Font.Builder(buf);
+    }
+
+    @Test
+    public void testBuilder_file() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                Font font = new Font.Builder(file).build();
+                assertEquals(path, weight, font.getStyle().getWeight());
+                assertEquals(path, slant, font.getStyle().getSlant());
+                assertEquals(path, 0, font.getTtcIndex());
+                assertNull(path, font.getAxes());
+                assertNotNull(font.getBuffer());
+                assertNotNull(font.getFile());
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_file_ttc() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getTtcFontFileInAsset();
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                Font font = new Font.Builder(file).setTtcIndex(ttcIndex).build();
+                assertEquals(path, weight, font.getStyle().getWeight());
+                assertEquals(path, slant, font.getStyle().getSlant());
+                assertEquals(path, ttcIndex, font.getTtcIndex());
+                assertNull(path, font.getAxes());
+                assertNotNull(font.getBuffer());
+                assertNotNull(font.getFile());
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_file_vf() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getVFFontInAsset();
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                Font font = new Font.Builder(file).setFontVariationSettings(axes).build();
+                assertEquals(path, weight, font.getStyle().getWeight());
+                assertEquals(path, slant, font.getStyle().getSlant());
+                assertEquals(path, 0, font.getTtcIndex());
+                assertEquals(path, axes, font.getAxes());
+                assertNotNull(font.getBuffer());
+                assertNotNull(font.getFile());
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_file_override() throws IOException {
+        int customWeight = 350;
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                Font font = new Font.Builder(file).setWeight(customWeight).build();
+                assertEquals(path, customWeight, font.getStyle().getWeight());
+                assertEquals(path, slant, font.getStyle().getSlant());
+                assertEquals(path, 0, font.getTtcIndex());
+                assertNull(path, font.getAxes());
+                assertNotNull(font.getBuffer());
+                assertNotNull(font.getFile());
+            } finally {
+                file.delete();
+            }
+        }
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                Font font = new Font.Builder(file).setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+                assertEquals(path, weight, font.getStyle().getWeight());
+                assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
+                assertEquals(path, 0, font.getTtcIndex());
+                assertNull(path, font.getAxes());
+                assertNotNull(font.getBuffer());
+                assertNotNull(font.getFile());
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_file_invalid_null_file() throws IOException {
+        File file = null;
+        new Font.Builder(file);
+    }
+
+    @Test(expected = IOException.class)
+    public void testBuilder_file_invalid_not_found_file() throws IOException {
+        File file = new File("/no/such/file");
+        new Font.Builder(file).build();
+    }
+
+    @Test
+    public void testBuilder_fd() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(fis.getFD()).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_ttc() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getTtcFontFileInAsset();
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(fis.getFD()).setTtcIndex(ttcIndex).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, ttcIndex, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_vf() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getVFFontInAsset();
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(fis.getFD()).setFontVariationSettings(axes)
+                            .build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertEquals(path, axes, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_override() throws IOException {
+        int customWeight = 350;
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(fis.getFD()).setWeight(customWeight).build();
+                    assertEquals(path, customWeight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(fis.getFD()).setSlant(
+                            FontStyle.FONT_SLANT_ITALIC).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_subdata() throws IOException {
+        byte[] dummy = { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is, dummy, dummy));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(
+                            fis.getFD(), dummy.length, file.length() - dummy.length * 2).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_subdata_ttc() throws IOException {
+        byte[] dummy = { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getTtcFontFileInAsset();
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is, dummy, dummy));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(
+                            fis.getFD(), dummy.length, file.length() - dummy.length * 2)
+                            .setTtcIndex(ttcIndex).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, ttcIndex, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_subdata_vf() throws IOException {
+        byte[] dummy = { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getVFFontInAsset();
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is, dummy, dummy));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(
+                            fis.getFD(), dummy.length, file.length() - dummy.length * 2)
+                            .setFontVariationSettings(axes).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertEquals(path, axes, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_fd_subdata_override() throws IOException {
+        int customWeight = 350;
+        byte[] dummy = { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is, dummy, dummy));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(
+                            fis.getFD(), dummy.length, file.length() - dummy.length * 2)
+                            .setWeight(customWeight).build();
+                    assertEquals(path, customWeight, font.getStyle().getWeight());
+                    assertEquals(path, slant, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+
+            File file = getTempFile();
+            try (InputStream is = am.open(path)) {
+                assertTrue(copyToFile(file, is, dummy, dummy));
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    Font font = new Font.Builder(
+                            fis.getFD(), dummy.length, file.length() - dummy.length * 2)
+                            .setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+                    assertEquals(path, weight, font.getStyle().getWeight());
+                    assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
+                    assertEquals(path, 0, font.getTtcIndex());
+                    assertNull(path, font.getAxes());
+                    assertNotNull(font.getBuffer());
+                    assertNull(font.getFile());
+                }
+            } finally {
+                file.delete();
+            }
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_fd_invalid_null() throws IOException {
+        FileDescriptor fd = null;
+        new Font.Builder(fd);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_fd_subadata_invalid_null() throws IOException {
+        FileDescriptor fd = null;
+        new Font.Builder(fd, 0, -1);
+    }
+
+    @Test(expected = IOException.class)
+    public void testBuilder_fd_subadata_invalid_invalid_size() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        String path = FontTestUtil.getFontPathFromStyle(400, false);
+
+        File file = getTempFile();
+        try (InputStream is = am.open(path)) {
+            assertTrue(copyToFile(file, is));
+            try (FileInputStream fis = new FileInputStream(file)) {
+                new Font.Builder(fis.getFD(), 0, Integer.MAX_VALUE).build();
+            }
+        }
+    }
+
+    @Test(expected = IOException.class)
+    public void testBuilder_fd_subadata_invalid_invalid_offset() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        String path = FontTestUtil.getFontPathFromStyle(400, false);
+
+        File file = getTempFile();
+        try (InputStream is = am.open(path)) {
+            assertTrue(copyToFile(file, is));
+            try (FileInputStream fis = new FileInputStream(file)) {
+                new Font.Builder(fis.getFD(), Integer.MAX_VALUE, file.length()).build();
+            }
+        }
+    }
+
+    @Test
+    public void testBuilder_asset() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(am, path).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_asset_ttc() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getTtcFontFileInAsset();
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(am, path).setTtcIndex(ttcIndex).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, ttcIndex, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_asset_vf() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getVFFontInAsset();
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(am, path).setFontVariationSettings(axes).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertEquals(path, axes, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_asset_override() throws IOException {
+        int customWeight = 350;
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(am, path).setWeight(customWeight).build();
+            assertEquals(path, customWeight, font.getStyle().getWeight());
+            assertEquals(path, slant, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            String path = FontTestUtil.getFontPathFromStyle(weight, italic);
+
+            Font font = new Font.Builder(am, path).setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+            assertEquals(path, weight, font.getStyle().getWeight());
+            assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
+            assertEquals(path, 0, font.getTtcIndex());
+            assertNull(path, font.getAxes());
+            assertNotNull(font.getBuffer());
+            assertNull(font.getFile());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_asset_invalid_null_asset() throws IOException {
+        AssetManager am = null;
+        new Font.Builder(am, "/some/path");
+    }
+
+    @Test(expected = IOException.class)
+    public void testBuilder_asset_invalid_not_found() throws IOException {
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        new Font.Builder(am, "/no/such/file").build();
+    }
+
+    @Test
+    public void testBuilder_resource() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getFontResourceIdFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(res, resId).build();
+            assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
+            assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
+            assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
+            assertNull("ResId=#" + resId, font.getAxes());
+            assertNotNull("ResId=#" + resId, font.getBuffer());
+            assertNull("ResId=#" + resId, font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_resource_ttc() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getTtcFontFileResourceId();
+            int ttcIndex = FontTestUtil.getTtcIndexFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(res, resId).setTtcIndex(ttcIndex).build();
+            assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
+            assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
+            assertEquals("ResId=#" + resId, ttcIndex, font.getTtcIndex());
+            assertNull("ResId=#" + resId, font.getAxes());
+            assertNotNull("ResId=#" + resId, font.getBuffer());
+            assertNull("ResId=#" + resId, font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_resource_vf() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getVFFontResourceId();
+            FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(
+                    FontTestUtil.getVarSettingsFromStyle(weight, italic));
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(res, resId).setFontVariationSettings(axes).build();
+            assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
+            assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
+            assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
+            assertEquals("ResId=#" + resId, axes, font.getAxes());
+            assertNotNull("ResId=#" + font.getBuffer());
+            assertNull("ResId=#" + resId, font.getFile());
+        }
+    }
+
+    @Test
+    public void testBuilder_resource_override() throws IOException {
+        int customWeight = 350;
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getFontResourceIdFromStyle(weight, italic);
+            final int slant = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+
+            Font font = new Font.Builder(res, resId).setWeight(customWeight).build();
+            assertEquals("ResId=#" + resId, customWeight, font.getStyle().getWeight());
+            assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
+            assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
+            assertNull("ResId=#" + resId, font.getAxes());
+            assertNotNull("ResId=#" + resId, font.getBuffer());
+            assertNull("ResId=#" + resId, font.getFile());
+        }
+
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getFontResourceIdFromStyle(weight, italic);
+
+            Font font = new Font.Builder(res, resId).setSlant(FontStyle.FONT_SLANT_ITALIC).build();
+            assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
+            assertEquals("ResId=#" + resId, FontStyle.FONT_SLANT_ITALIC,
+                    font.getStyle().getSlant());
+            assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
+            assertNull("ResId=#" + resId, font.getAxes());
+            assertNotNull("ResId=#" + resId, font.getBuffer());
+            assertNull("ResId=#" + resId, font.getFile());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_resource_invalid_null_resource() throws IOException {
+        Resources res = null;
+        new Font.Builder(res, R.font.ascii);
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testBuilder_resource_invalid_res_id() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        new Font.Builder(res, -1).build();
+    }
+
+    @Test(expected = IOException.class)
+    public void testBuilder_asset_invalid_xml_font() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        new Font.Builder(res, R.font.multiweight_family /* XML font */).build();
+    }
+
+    @Test
+    public void testEquals() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        ArrayList<Font> fonts1 = new ArrayList<>();
+        ArrayList<Font> fonts2 = new ArrayList<>();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getFontResourceIdFromStyle(weight, italic);
+
+            fonts1.add(new Font.Builder(res, resId).build());
+            fonts2.add(new Font.Builder(res, resId).build());
+        }
+
+        for (int i = 0; i < fonts1.size(); ++i) {
+            assertTrue(fonts1.get(i).equals(fonts1.get(i)));
+            assertTrue(fonts1.get(i).equals(fonts2.get(i)));
+            assertTrue(fonts2.get(i).equals(fonts1.get(i)));
+        }
+
+        for (int i = 0; i < fonts1.size(); ++i) {
+            for (int j = i + 1; j < fonts1.size(); ++j) {
+                assertFalse(fonts1.get(i).equals(fonts1.get(j)));
+                assertFalse(fonts1.get(j).equals(fonts1.get(i)));
+            }
+        }
+    }
+
+    @Test
+    public void testHashCode() throws IOException {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        ArrayList<Font> fonts1 = new ArrayList<>();
+        ArrayList<Font> fonts2 = new ArrayList<>();
+        for (Pair<Integer, Boolean> style : FontTestUtil.getAllStyles()) {
+            int weight = style.first.intValue();
+            boolean italic = style.second.booleanValue();
+            int resId = FontTestUtil.getFontResourceIdFromStyle(weight, italic);
+
+            fonts1.add(new Font.Builder(res, resId).build());
+            fonts2.add(new Font.Builder(res, resId).build());
+        }
+
+        for (int i = 0; i < fonts1.size(); ++i) {
+            assertEquals(fonts1.get(i).hashCode(), fonts1.get(i).hashCode());
+            assertEquals(fonts1.get(i).hashCode(), fonts2.get(i).hashCode());
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMaxFontWeight() throws IOException {
+        final Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        new Font.Builder(res, R.font.ascii).setWeight(FontStyle.FONT_WEIGHT_MAX + 1).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMinFontWeight() throws IOException {
+        final Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        new Font.Builder(res, R.font.ascii).setWeight(FontStyle.FONT_WEIGHT_MIN - 1).build();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontTestUtil.java b/tests/tests/graphics/src/android/graphics/fonts/FontTestUtil.java
new file mode 100644
index 0000000..418a67c
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontTestUtil.java
@@ -0,0 +1,349 @@
+/*
+ * 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.graphics.fonts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Typeface;
+import android.graphics.cts.R;
+import android.text.TextPaint;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Provides a utility for testing fonts
+ *
+ * For the purpose of testing font selection of families or fallbacks, this class provies following
+ * regular font files.
+ *
+ * - ascii_a3em_weight100_upright.ttf
+ *   'a' has 3em width and others have 1em width. The metadata has weight=100, non-italic value.
+ * - ascii_b3em_weight100_italic.ttf
+ *   'b' has 3em width and others have 1em width. The metadata has weight=100, italic value.
+ * - ascii_c3em_weight200_upright.ttf
+ *   'c' has 3em width and others have 1em width. The metadata has weight=200, non-italic value.
+ * - ascii_d3em_weight200_italic.ttf
+ *   'd' has 3em width and others have 1em width. The metadata has weight=200, italic value.
+ * - ascii_e3em_weight300_upright.ttf
+ *   'e' has 3em width and others have 1em width. The metadata has weight=300, non-italic value.
+ * - ascii_f3em_weight300_italic.ttf
+ *   'f' has 3em width and others have 1em width. The metadata has weight=300, italic value.
+ * - ascii_g3em_weight400_upright.ttf
+ *   'g' has 3em width and others have 1em width. The metadata has weight=400, non-italic value.
+ * - ascii_h3em_weight400_italic.ttf
+ *   'h' has 3em width and others have 1em width. The metadata has weight=400, italic value.
+ * - ascii_i3em_weight500_upright.ttf
+ *   'i' has 3em width and others have 1em width. The metadata has weight=500, non-italic value.
+ * - ascii_j3em_weight500_italic.ttf
+ *   'j' has 3em width and others have 1em width. The metadata has weight=500, italic value.
+ * - ascii_k3em_weight600_upright.ttf
+ *   'k' has 3em width and others have 1em width. The metadata has weight=600, non-italic value.
+ * - ascii_l3em_weight600_italic.ttf
+ *   'l' has 3em width and others have 1em width. The metadata has weight=600, italic value.
+ * - ascii_m3em_weight700_upright.ttf
+ *   'm' has 3em width and others have 1em width. The metadata has weight=700, non-italic value.
+ * - ascii_n3em_weight700_italic.ttf
+ *   'n' has 3em width and others have 1em width. The metadata has weight=700, italic value.
+ * - ascii_o3em_weight800_upright.ttf
+ *   'o' has 3em width and others have 1em width. The metadata has weight=800, non-italic value.
+ * - ascii_p3em_weight800_italic.ttf
+ *   'p' has 3em width and others have 1em width. The metadata has weight=800, italic value.
+ * - ascii_q3em_weight900_upright.ttf
+ *   'q' has 3em width and others have 1em width. The metadata has weight=900, non-italic value.
+ * - ascii_r3em_weight900_italic.ttf
+ *   'r' has 3em width and others have 1em width. The metadata has weight=900, italic value.
+ *
+ * In addition to above font files, this class provides a font collection file and a variable font
+ * file.
+ * - ascii.ttc
+ *   The collection of above 18 fonts with above order.
+ * - ascii_vf.ttf
+ *   This font supports a-z characters and all characters has 1em width. This font supports 'wght',
+ *   'ital' axes but no effect for the glyph width. This font also supports 'Asc[a-z]' 26 axes which
+ *   makes glyph width 3em. For example, 'Asca 1.0' makes a glyph width of 'a' 3em, 'Ascb 1.0' makes
+ *   a glyph width of 'b' 3em. With these axes, above font can be replicated like
+ *   - 'Asca' 1.0, 'wght' 100.0' is equivalent with ascii_a3em_width100_upright.ttf
+ *   - 'Ascb' 1.0, 'wght' 100.0, 'ital' 1.0' is equivalent with ascii_b3em_width100_italic.ttf
+ */
+public class FontTestUtil {
+    private static final String FAMILY_SELECTION_FONT_PATH_IN_ASSET = "fonts/family_selection";
+    private static final List<Pair<Integer, Boolean>> sStyleList;
+    private static final Map<Pair<Integer, Boolean>, String> sFontMap;
+    private static final Map<Pair<Integer, Boolean>, Integer> sTtcMap;
+    private static final Map<Pair<Integer, Boolean>, String> sVariationSettingsMap;
+    private static final Map<Pair<Integer, Boolean>, Integer> sResourceMap;
+    private static final String[] sFontList = {  // Same order of ascii.ttc
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_a3em_weight100_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_b3em_weight100_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_c3em_weight200_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_d3em_weight200_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_e3em_weight300_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_f3em_weight300_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_g3em_weight400_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_h3em_weight400_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_i3em_weight500_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_j3em_weight500_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_k3em_weight600_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_l3em_weight600_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_m3em_weight700_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_n3em_weight700_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_o3em_weight800_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_p3em_weight800_italic.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_q3em_weight900_upright.ttf",
+            FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ttf/ascii_r3em_weight900_italic.ttf",
+    };
+
+    private static final String[] FONT_VARIATION_SETTING_LIST = {
+            "'Asca' 1.0, 'wght' 100.0",
+            "'Ascb' 1.0, 'wght' 100.0, 'ital' 1.0",
+            "'Ascc' 1.0, 'wght' 200.0",
+            "'Ascd' 1.0, 'wght' 200.0, 'ital' 1.0",
+            "'Asce' 1.0, 'wght' 300.0",
+            "'Ascf' 1.0, 'wght' 300.0, 'ital' 1.0",
+            "'Ascg' 1.0, 'wght' 400.0",
+            "'Asch' 1.0, 'wght' 400.0, 'ital' 1.0",
+            "'Asci' 1.0, 'wght' 500.0",
+            "'Ascj' 1.0, 'wght' 500.0, 'ital' 1.0",
+            "'Asck' 1.0, 'wght' 600.0",
+            "'Ascl' 1.0, 'wght' 600.0, 'ital' 1.0",
+            "'Ascm' 1.0, 'wght' 700.0",
+            "'Ascn' 1.0, 'wght' 700.0, 'ital' 1.0",
+            "'Asco' 1.0, 'wght' 800.0",
+            "'Ascp' 1.0, 'wght' 800.0, 'ital' 1.0",
+            "'Ascq' 1.0, 'wght' 900.0",
+            "'Ascr' 1.0, 'wght' 900.0, 'ital' 1.0",
+    };
+
+    private static final int[] FONT_RESOURCE_ID_LIST = {
+            R.font.ascii_a3em_weight100_upright,
+            R.font.ascii_b3em_weight100_italic,
+            R.font.ascii_c3em_weight200_upright,
+            R.font.ascii_d3em_weight200_italic,
+            R.font.ascii_e3em_weight300_upright,
+            R.font.ascii_f3em_weight300_italic,
+            R.font.ascii_g3em_weight400_upright,
+            R.font.ascii_h3em_weight400_italic,
+            R.font.ascii_i3em_weight500_upright,
+            R.font.ascii_j3em_weight500_italic,
+            R.font.ascii_k3em_weight600_upright,
+            R.font.ascii_l3em_weight600_italic,
+            R.font.ascii_m3em_weight700_upright,
+            R.font.ascii_n3em_weight700_italic,
+            R.font.ascii_o3em_weight800_upright,
+            R.font.ascii_p3em_weight800_italic,
+            R.font.ascii_q3em_weight900_upright,
+            R.font.ascii_r3em_weight900_italic,
+    };
+
+    private static final char[] CHAR_3EM_WIDTH = {
+            'a',
+            'b',
+            'c',
+            'd',
+            'e',
+            'f',
+            'g',
+            'h',
+            'i',
+            'j',
+            'k',
+            'l',
+            'm',
+            'n',
+            'o',
+            'p',
+            'q',
+            'r',
+    };
+
+    static {
+        // Style list with the same order of sFontList.
+        ArrayList<Pair<Integer, Boolean>> styles = new ArrayList<>();
+        styles.add(new Pair<>(100, false));
+        styles.add(new Pair<>(100, true));
+        styles.add(new Pair<>(200, false));
+        styles.add(new Pair<>(200, true));
+        styles.add(new Pair<>(300, false));
+        styles.add(new Pair<>(300, true));
+        styles.add(new Pair<>(400, false));
+        styles.add(new Pair<>(400, true));
+        styles.add(new Pair<>(500, false));
+        styles.add(new Pair<>(500, true));
+        styles.add(new Pair<>(600, false));
+        styles.add(new Pair<>(600, true));
+        styles.add(new Pair<>(700, false));
+        styles.add(new Pair<>(700, true));
+        styles.add(new Pair<>(800, false));
+        styles.add(new Pair<>(800, true));
+        styles.add(new Pair<>(900, false));
+        styles.add(new Pair<>(900, true));
+        sStyleList = Collections.unmodifiableList(styles);
+
+        HashMap<Pair<Integer, Boolean>, String> map = new HashMap<>();
+        HashMap<Pair<Integer, Boolean>, Integer> ttcMap = new HashMap<>();
+        HashMap<Pair<Integer, Boolean>, String> variationMap = new HashMap<>();
+        HashMap<Pair<Integer, Boolean>, Integer> resourceMap = new HashMap<>();
+        HashMap<Character, Pair<Integer, Boolean>> reverseMap = new HashMap<>();
+        for (int i = 0; i < sFontList.length; ++i) {
+            map.put(sStyleList.get(i), sFontList[i]);
+            ttcMap.put(sStyleList.get(i), i);
+            variationMap.put(sStyleList.get(i), FONT_VARIATION_SETTING_LIST[i]);
+            resourceMap.put(sStyleList.get(i), FONT_RESOURCE_ID_LIST[i]);
+        }
+        sFontMap = Collections.unmodifiableMap(map);
+        sTtcMap = Collections.unmodifiableMap(ttcMap);
+        sVariationSettingsMap = Collections.unmodifiableMap(variationMap);
+        sResourceMap = Collections.unmodifiableMap(resourceMap);
+    }
+
+    /**
+     * Measure a character with 100px text size
+     */
+    private static float measureChar(Typeface typeface, char c) {
+        final TextPaint tp = new TextPaint();
+        tp.setTextSize(100);
+        tp.setTypeface(typeface);
+        tp.setTextLocale(Locale.US);
+        return tp.measureText(new char[] { c }, 0, 1);
+    }
+
+    /**
+     * Returns a path to the font collection file in asset directory.
+     */
+    public static String getTtcFontFileInAsset() {
+        return FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ascii.ttc";
+    }
+
+    /**
+     * Returns a resource id for the font collection file.
+     */
+    public static int getTtcFontFileResourceId() {
+        return R.font.ascii;
+    }
+
+    /**
+     * Returns a path to the variable font file in asset directory.
+     */
+    public static String getVFFontInAsset() {
+        return FAMILY_SELECTION_FONT_PATH_IN_ASSET + "/ascii_vf.ttf";
+    }
+
+    /**
+     * Returns a resource id for the variable font.
+     */
+    public static int getVFFontResourceId() {
+        return R.font.ascii_vf;
+    }
+
+    /**
+     * Returns a ttc index of the specified style.
+     */
+    public static int getTtcIndexFromStyle(int weight, boolean italic) {
+        return sTtcMap.get(new Pair<>(weight, italic)).intValue();
+    }
+
+    /**
+     * Returns a variation settings string of the specified style.
+     */
+    public static String getVarSettingsFromStyle(int weight, boolean italic) {
+        return sVariationSettingsMap.get(new Pair<>(weight, italic));
+    }
+
+    /**
+     * Returns a font resource ID of the specific style.
+     */
+    public static int getFontResourceIdFromStyle(int weight, boolean italic) {
+        return sResourceMap.get(new Pair<>(weight, italic));
+    }
+
+    /**
+     * Returns a font path from the specified style.
+     */
+    public static String getFontPathFromStyle(int weight, boolean italic) {
+        return sFontMap.get(new Pair<>(weight, italic));
+    }
+
+    /**
+     * Returns all supported styles.
+     *
+     * @return a pair of weight and style(uplight/italic)
+     */
+    public static List<Pair<Integer, Boolean>> getAllStyles() {
+        return sStyleList;
+    }
+
+    /**
+     * Returns selected font index in the sStyleList array.
+     */
+    private static int getSelectedFontStyle(Typeface typeface) {
+        int indexOf3Em = -1;
+        for (int i = 0; i < CHAR_3EM_WIDTH.length; ++i) {
+            if (measureChar(typeface, CHAR_3EM_WIDTH[i]) == 300.0f) {
+                assertEquals("A font has two 3em width character. Likely the wrong test setup.",
+                        -1, indexOf3Em);
+                indexOf3Em = i;
+            }
+        }
+        assertNotEquals("No font has 3em width character. Likely the wrong test setup.",
+                -1, indexOf3Em);
+        return indexOf3Em;
+    }
+
+    /**
+     * Returns selected font's style.
+     */
+    public static Pair<Integer, Boolean> getSelectedStyle(Typeface typeface) {
+        return sStyleList.get(getSelectedFontStyle(typeface));
+    }
+
+    /**
+     * Returns selected font's file path.
+     *
+     * Note that this is valid only if the all Font objects in the FontFamily is created with
+     * AssetManager.
+     */
+    public static String getSelectedFontPathInAsset(Typeface typeface) {
+        return sFontList[getSelectedFontStyle(typeface)];
+    }
+
+    /**
+     * Returns selected font's ttc index.
+     *
+     * Note that this is valid only if the all Font objects in the FontFamily is created with
+     * TTC font with ttcIndex.
+     */
+    public static int getSelectedTtcIndex(Typeface typeface) {
+        return getSelectedFontStyle(typeface);
+    }
+
+    /**
+     * Returns selected font's variation settings.
+     *
+     * Note that this is valid only if the all Font objects in the FontFamily is created with
+     * variable fonts with font variation settings.
+     */
+    public static String getSelectedVariationSettings(Typeface typeface) {
+        return FONT_VARIATION_SETTING_LIST[getSelectedFontStyle(typeface)];
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
new file mode 100644
index 0000000..6a7906c
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.graphics.fonts;
+
+import android.util.Pair;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class NativeSystemFontHelper {
+    static {
+        System.loadLibrary("ctsgraphics_jni");
+    }
+
+    private static String tagToStr(int tag) {
+        char[] buf = new char[4];
+        buf[0] = (char) ((tag >> 24) & 0xFF);
+        buf[1] = (char) ((tag >> 16) & 0xFF);
+        buf[2] = (char) ((tag >> 8) & 0xFF);
+        buf[3] = (char) (tag & 0xFF);
+        return String.valueOf(buf);
+    }
+
+    public static Set<Font> getAvailableFonts() {
+        long iterPtr = nOpenIterator();
+        HashSet<Font> 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));
+                    }
+                    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);
+                } finally {
+                    nCloseFont(fontPtr);
+                }
+            }
+        } finally {
+            nCloseIterator(iterPtr);
+        }
+        return nativeFonts;
+    }
+
+    public static Pair<File, Integer> matchFamilyStyleCharacter(String familyName, int weight,
+            boolean italic, String languageTags, String text) {
+        final long fontPtr = nMatchFamilyStyleCharacter(familyName, weight, italic, languageTags,
+                text);
+        final int runLength = nMatchFamilyStyleCharacter_runLength(familyName, weight, italic,
+                languageTags, text);
+        try {
+            return new Pair<>(new File(nGetFilePath(fontPtr)), runLength);
+        } finally {
+            nCloseFont(fontPtr);
+        }
+    }
+
+    private static native long nOpenIterator();
+    private static native void nCloseIterator(long ptr);
+    private static native long nNext(long ptr);
+    private static native void nCloseFont(long ptr);
+    private static native String nGetFilePath(long ptr);
+    private static native int nGetWeight(long ptr);
+    private static native boolean nIsItalic(long ptr);
+    private static native String nGetLocale(long ptr);
+    private static native int nGetCollectionIndex(long ptr);
+    private static native int nGetAxisCount(long ptr);
+    private static native int nGetAxisTag(long ptr, int index);
+    private static native float nGetAxisValue(long ptr, int index);
+    private static native long nMatchFamilyStyleCharacter(String familyName, int weight,
+            boolean italic, String languageTags, String text);
+    private static native int nMatchFamilyStyleCharacter_runLength(String familyName, int weight,
+            boolean italic, String languageTags, String text);
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
new file mode 100644
index 0000000..4094955
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.graphics.fonts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NativeSystemFontTest {
+
+    @Test
+    public void testSameResultAsJava() {
+        Set<Font> javaFonts = SystemFonts.getAvailableFonts();
+        Set<Font> nativeFonts = NativeSystemFontHelper.getAvailableFonts();
+
+        assertEquals(javaFonts.size(), nativeFonts.size());
+
+        for (Font f : nativeFonts) {
+            assertTrue(javaFonts.contains(f));
+        }
+
+        for (Font f : javaFonts) {
+            assertTrue(nativeFonts.contains(f));
+        }
+    }
+
+    @Test
+    public void testMatchFamilyStyleCharacter() {
+        Pair<File, Integer> fontForA = NativeSystemFontHelper.matchFamilyStyleCharacter(
+                "sans", 400, false, "en-US", "A");
+        Pair<File, Integer> fontForB = NativeSystemFontHelper.matchFamilyStyleCharacter(
+                "sans", 400, false, "en-US", "B");
+        assertEquals(fontForA, fontForB);
+    }
+
+    @Test
+    public void testMatchFamilyStyleCharacter_fallback() {
+        Pair<File, Integer> fontForA = NativeSystemFontHelper.matchFamilyStyleCharacter(
+                "Unknown-Generic-Family", 400, false, "en-US", "A");
+        Pair<File, Integer> fontForB = NativeSystemFontHelper.matchFamilyStyleCharacter(
+                "Another-Unknown-Generic-Family", 400, false, "en-US", "B");
+        assertEquals(fontForA, fontForB);
+    }
+
+    @Test
+    public void testMatchFamilyStyleCharacter_notCrash() {
+        String[] genericFamilies = {
+            "sans", "sans-serif", "monospace", "cursive", "fantasy",  // generic families
+            "Helvetica", "Roboto", "Times",  // known family names but not supported by Android
+            "Unknown Families",  // Random string
+        };
+
+        int[] weights = {
+            0, 150, 400, 700, 1000, // valid weights
+            -100, 1100  // out-of-range
+        };
+
+        boolean[] italics = { false, true };
+
+        String[] languageTags = {
+            // Valid language tags
+            "", "en-US", "und", "ja-JP,zh-CN", "en-Latn", "en-Zsye-US", "en-GB", "en-GB,en-AU",
+            // Invalid language tags
+            "aaa", "100", "\u3042", "-"
+        };
+
+        String[] inputTexts = {
+            "A", "B", "abc", // Alphabet input
+            "\u3042", "\u3042\u3046\u3048", "\u4F60\u597D",  // CJK characters
+            "\u0627\u0644\u0639\u064E\u0631\u064E\u0628\u0650\u064A\u064E\u0651\u0629",  // Arabic
+            // Emoji, emoji sequence and surrogate pairs
+            "\uD83D\uDE00", "\uD83C\uDDFA\uD83C\uDDF8", "\uD83D\uDC68\u200D\uD83C\uDFA4",
+            // Unpaired surrogate pairs
+            "\uD83D", "\uDE00", "\uDE00\uD83D",
+
+        };
+
+        for (String familyName : genericFamilies) {
+            for (int weight : weights) {
+                for (boolean italic : italics) {
+                    for (String languageTag : languageTags) {
+                        for (String inputText : inputTexts) {
+                            Pair<File, Integer> result =
+                                    NativeSystemFontHelper.matchFamilyStyleCharacter(
+                                            familyName, weight, italic, languageTag, inputText);
+                            // We cannot expcet much here since OEM can change font configurations.
+                            // At least, a font must be assigned for the first character.
+                            assertTrue(result.second >= 1);
+
+                            final File fontFile = result.first;
+                            assertTrue(fontFile.exists());
+                            assertTrue(fontFile.isAbsolute());
+                            assertTrue(fontFile.isFile());
+                            assertTrue(fontFile.canRead());
+                            assertFalse(fontFile.canExecute());
+                            assertFalse(fontFile.canWrite());
+                            assertTrue(fontFile.length() > 0);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
new file mode 100644
index 0000000..eb2b32d
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.graphics.fonts;
+
+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.support.test.filters.SmallTest;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.nio.ReadOnlyBufferException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class SystemFontsTest {
+
+    @Parameterized.Parameter(0)
+    public Set<Font> availableFonts;
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> getParameters() {
+        ArrayList<Object[]> allParams = new ArrayList<>();
+        allParams.add(new Object[] { SystemFonts.getAvailableFonts() });
+        allParams.add(new Object[] { NativeSystemFontHelper.getAvailableFonts() });
+        return allParams;
+    }
+
+    @Test
+    public void testAvailableFonts_NotEmpty() {
+        assertNotEquals("System available fonts must not be empty", 0, availableFonts.size());
+    }
+
+    @Test
+    public void testAvailableFonts_ReadOnlyFile() throws ErrnoException {
+        for (Font font : availableFonts) {
+            assertNotNull("System font must provide file path to the font file.", font.getFile());
+
+            // The system font must be read-only file.
+            assertTrue(font.getFile().exists());
+            assertTrue(font.getFile().isFile());
+            assertTrue(font.getFile().canRead());
+            assertFalse(font.getFile().canExecute());
+            assertFalse(font.getFile().canWrite());
+
+            // The system font must be in read-only file system
+            final String absPath = font.getFile().getAbsolutePath();
+            assertTrue((Os.statvfs(absPath).f_flag & OsConstants.ST_RDONLY) != 0);
+        }
+    }
+
+    @Test
+    public void testAvailableFonts_ReadOnlyBuffer() {
+        for (Font font : availableFonts) {
+            try {
+                font.getBuffer().put((byte) 0);
+                fail("System font must be read only");
+            } catch (ReadOnlyBufferException e) {
+                // pass
+            }
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
new file mode 100644
index 0000000..83d71e7
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -0,0 +1,559 @@
+/*
+ * 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.graphics.text.cts;
+
+import static android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED;
+import static android.graphics.text.LineBreaker.BREAK_STRATEGY_HIGH_QUALITY;
+import static android.graphics.text.LineBreaker.BREAK_STRATEGY_SIMPLE;
+import static android.graphics.text.LineBreaker.HYPHENATION_FREQUENCY_FULL;
+import static android.graphics.text.LineBreaker.HYPHENATION_FREQUENCY_NONE;
+import static android.graphics.text.LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
+import static android.graphics.text.LineBreaker.JUSTIFICATION_MODE_INTER_WORD;
+import static android.graphics.text.LineBreaker.JUSTIFICATION_MODE_NONE;
+import static android.graphics.text.LineBreaker.ParagraphConstraints;
+import static android.graphics.text.LineBreaker.Result;
+
+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 android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.text.LineBreaker;
+import android.graphics.text.MeasuredText;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Hyphenator;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LineBreakerTest {
+    private static final String TAG = "LineBreakerTest";
+
+    private static final int NO_HYPHEN_EDIT = Hyphenator.packHyphenEdit(
+            Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+            Hyphenator.END_HYPHEN_EDIT_NO_EDIT);
+
+    private static Paint sPaint;
+    @BeforeClass
+    public static void classSetUp() {
+        sPaint = new Paint();
+        Context context = InstrumentationRegistry.getTargetContext();
+        AssetManager am = context.getAssets();
+        Typeface tf = new Typeface.Builder(am, "fonts/layout/linebreak.ttf").build();
+        sPaint.setTypeface(tf);
+        sPaint.setTextSize(10.0f);  // Make 1em = 10px
+    }
+
+    @Test
+    public void testLineBreak_construct() {
+        assertNotNull(new LineBreaker.Builder().build());
+    }
+
+    @Test
+    public void testSetBreakStrategy_shoulNotThrowExceptions() {
+        assertNotNull(new LineBreaker.Builder().setBreakStrategy(BREAK_STRATEGY_SIMPLE).build());
+        assertNotNull(new LineBreaker.Builder().setBreakStrategy(BREAK_STRATEGY_HIGH_QUALITY)
+                .build());
+        assertNotNull(new LineBreaker.Builder().setBreakStrategy(BREAK_STRATEGY_BALANCED).build());
+    }
+
+    @Test
+    public void testSetHyphenationFrequency_shouldNotThrowExceptions() {
+        assertNotNull(new LineBreaker.Builder()
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL).build());
+        assertNotNull(new LineBreaker.Builder()
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL).build());
+        assertNotNull(new LineBreaker.Builder()
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_NONE).build());
+    }
+
+    @Test
+    public void testSetJustification_shouldNotThrowExceptions() {
+        assertNotNull(new LineBreaker.Builder().setJustified(JUSTIFICATION_MODE_NONE).build());
+        assertNotNull(new LineBreaker.Builder().setJustified(JUSTIFICATION_MODE_INTER_WORD)
+                .build());
+    }
+
+    @Test
+    public void testSetIntent_shouldNotThrowExceptions() {
+        assertNotNull(new LineBreaker.Builder().setIndents(null).build());
+        assertNotNull(new LineBreaker.Builder().setIndents(new int[] {}).build());
+        assertNotNull(new LineBreaker.Builder().setIndents(new int[] { 100 }).build());
+    }
+
+    @Test
+    public void testSetGetWidth() {
+        ParagraphConstraints c = new ParagraphConstraints();
+        assertEquals(0, c.getWidth(), 0.0f);  // 0 by default
+        c.setWidth(100);
+        assertEquals(100, c.getWidth(), 0.0f);
+        c.setWidth(200);
+        assertEquals(200, c.getWidth(), 0.0f);
+    }
+
+    @Test
+    public void testSetGetIndent() {
+        ParagraphConstraints c = new ParagraphConstraints();
+        assertEquals(0.0f, c.getFirstWidth(), 0.0f);  // 0 by default
+        assertEquals(0, c.getFirstWidthLineCount());  // 0 by default
+        c.setIndent(100.0f, 1);
+        assertEquals(100.0f, c.getFirstWidth(), 0.0f);
+        assertEquals(1, c.getFirstWidthLineCount());
+        c.setIndent(200.0f, 5);
+        assertEquals(200.0f, c.getFirstWidth(), 0.0f);
+        assertEquals(5, c.getFirstWidthLineCount());
+    }
+
+    @Test
+    public void testSetGetTabStops() {
+        ParagraphConstraints c = new ParagraphConstraints();
+        assertNull(c.getTabStops());  // null by default
+        assertEquals(0, c.getDefaultTabStop());  // 0 by default
+        c.setTabStops(new int[] { 120 }, 240);
+        assertEquals(1, c.getTabStops().length);
+        assertEquals(120, c.getTabStops()[0]);
+        assertEquals(240, c.getDefaultTabStop());
+    }
+
+    @Test
+    public void testLineBreak_Simple() {
+        final String text = "Hello, World.";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(Float.MAX_VALUE);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false).build(), c, 0);
+        assertEquals(1, r.getLineCount());
+        assertEquals(13, r.getLineBreakOffset(0));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertFalse(r.hasLineTab(0));
+        assertEquals(130.0f, r.getLineWidth(0), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple2() {
+        // The visual line break output is like
+        // |abc defg|
+        // |hijkl   |
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(80.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false).build(), c, 0);
+        assertEquals(2, r.getLineCount());
+        assertEquals(9, r.getLineBreakOffset(0));
+        assertEquals(14, r.getLineBreakOffset(1));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertEquals(80.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(1), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple3() {
+        // The visual line break output is like
+        // |abc |
+        // |defg|
+        // |hijk|
+        // |l   |
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(40.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false).build(), c, 0);
+        assertEquals(4, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(9, r.getLineBreakOffset(1));
+        assertEquals(13, r.getLineBreakOffset(2));
+        assertEquals(14, r.getLineBreakOffset(3));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(3), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(3), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(3));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertFalse(r.hasLineTab(3));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(40.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(40.0f, r.getLineWidth(2), 0.0f);
+        assertEquals(10.0f, r.getLineWidth(3), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_NotRectangle() {
+        // The visual line break output is like
+        // |abc  |
+        // |defg hijkl|
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(100.0f);
+        c.setIndent(50.0f, 1);  // Make the first line width 50 px.
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false).build(), c, 0);
+        assertEquals(2, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(14, r.getLineBreakOffset(1));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(100.0f, r.getLineWidth(1), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_Hyphenation() {
+        // The visual line break output is like
+        // |abc |
+        // |defg|
+        // |hi- |
+        // |jkl |
+        final String text = "ab\u00ADc de\u00ADfg hi\u00ADjkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(40.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false).build(), c, 0);
+        assertEquals(4, r.getLineCount());
+        assertEquals(5, r.getLineBreakOffset(0));
+        assertEquals(11, r.getLineBreakOffset(1));
+        assertEquals(14, r.getLineBreakOffset(2));
+        assertEquals(17, r.getLineBreakOffset(3));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(3), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(3), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+                Hyphenator.unpackStartHyphenEdit(r.getLineHyphenEdit(2)));
+        assertEquals(Hyphenator.END_HYPHEN_EDIT_INSERT_HYPHEN,
+                Hyphenator.unpackEndHyphenEdit(r.getLineHyphenEdit(2)));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(3));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertFalse(r.hasLineTab(3));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(40.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(30.0f, r.getLineWidth(2), 0.0f);
+        assertEquals(30.0f, r.getLineWidth(3), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_Styled() {
+        // The visual line break output is like
+        // |abc      |
+        // |ddeeffgg | (Make text size of "defg" doubled)
+        // |hijkl    |
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(90.0f);
+        final Paint biggerPaint = new Paint(sPaint);
+        biggerPaint.setTextSize(sPaint.getTextSize() * 2.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, 4, false)
+                .appendStyleRun(biggerPaint, 5, false)
+                .appendStyleRun(sPaint, 5, false).build(), c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(9, r.getLineBreakOffset(1));
+        assertEquals(14, r.getLineBreakOffset(2));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-20.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(4.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(80.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_Styled2() {
+        // The visual line break output is like
+        // |abc deffg| (Make text size of "f" doubled)
+        // |hijkl    |
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(90.0f);
+        final Paint biggerPaint = new Paint(sPaint);
+        biggerPaint.setTextSize(sPaint.getTextSize() * 2.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, 6, false)
+                .appendStyleRun(biggerPaint, 1, false)
+                .appendStyleRun(sPaint, 7, false)
+                .build(), c, 0);
+        assertEquals(2, r.getLineCount());
+        assertEquals(9, r.getLineBreakOffset(0));
+        assertEquals(14, r.getLineBreakOffset(1));
+        assertEquals(-20.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(4.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertEquals(90.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(1), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_indents() {
+        // The visual line break output is like
+        // |abc  |
+        // |defg hijkl|
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .setIndents(new int[] { 50, 0 })  // The first line indent is 50 and 0 for others.
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(100.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        assertEquals(2, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(14, r.getLineBreakOffset(1));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(100.0f, r.getLineWidth(1), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_indents2() {
+        // The visual line break output is like
+        // |abc |
+        // |defg|
+        // |hijkl     |
+        final String text = "abc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .setIndents(new int[] { 60, 60, 0 })
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(100.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(9, r.getLineBreakOffset(1));
+        assertEquals(14, r.getLineBreakOffset(2));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(40.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_tabStop() {
+        // The visual line break output is like
+        // |abc    |
+        // |de  fg |
+        // |hijkl  |
+        final String text = "abc de\tfg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(70.0f);
+        c.setTabStops(null, 40);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(4, r.getLineBreakOffset(0));
+        assertEquals(10, r.getLineBreakOffset(1));
+        assertEquals(15, r.getLineBreakOffset(2));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertTrue(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(30.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(60.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Simple_tabStop2() {
+        // The visual line break output is like
+        // |a b  c |
+        // |defg   |
+        // |hijkl  |
+        final String text = "a\tb\tc defg hijkl";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(70.0f);
+        c.setTabStops(new int[] { 20 }, 50);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(6, r.getLineBreakOffset(0));
+        assertEquals(11, r.getLineBreakOffset(1));
+        assertEquals(16, r.getLineBreakOffset(2));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertTrue(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(60.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(40.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Balanced() {
+        // The visual BALANCED line break output is like
+        // |This   |
+        // |is an  |
+        // |example|
+        //
+        // FYI, SIMPLE line breaker breaks this string to
+        // |This is|
+        // |an     |
+        // |example|
+        final String text = "This is an example";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(70.0f);
+        final Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(5, r.getLineBreakOffset(0));
+        assertEquals(11, r.getLineBreakOffset(1));
+        assertEquals(18, r.getLineBreakOffset(2));
+        assertEquals(-10.0f, r.getLineAscent(0), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(1), 0.0f);
+        assertEquals(-10.0f, r.getLineAscent(2), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(0), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(1), 0.0f);
+        assertEquals(2.0f, r.getLineDescent(2), 0.0f);
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(0));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(1));
+        assertEquals(NO_HYPHEN_EDIT, r.getLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(40.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(50.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(70.0f, r.getLineWidth(2), 0.0f);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
new file mode 100644
index 0000000..8478834
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -0,0 +1,424 @@
+/*
+ * 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.graphics.text.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 android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.text.MeasuredText;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.PrecomputedText;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MeasuredTextTest {
+    private static Paint sPaint;
+
+    @BeforeClass
+    public static void classSetUp() {
+        sPaint = new Paint();
+        Context context = InstrumentationRegistry.getTargetContext();
+        AssetManager am = context.getAssets();
+        Typeface tf = new Typeface.Builder(am, "fonts/layout/linebreak.ttf").build();
+        sPaint.setTypeface(tf);
+        sPaint.setTextSize(10.0f);  // Make 1em = 10px
+    }
+
+    @Test
+    public void testBuilder() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+    }
+
+    @Test
+    public void testBuilder_FromExistingMeasuredText() {
+        String text = "Hello, World";
+        final MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        assertNotNull(new MeasuredText.Builder(mt)
+                .appendStyleRun(sPaint, text.length(), true /* isRtl */).build());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_FromExistingMeasuredText_differentLayoutParam() {
+        String text = "Hello, World";
+        final MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .setComputeLayout(false)
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        new MeasuredText.Builder(mt)
+                .appendStyleRun(sPaint, text.length(), true /* isRtl */).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_FromExistingMeasuredText_differentHyphenationParam() {
+        String text = "Hello, World";
+        final MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .setComputeHyphenation(false)
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        new MeasuredText.Builder(mt)
+                .setComputeHyphenation(true)
+                .appendStyleRun(sPaint, text.length(), true /* isRtl */).build();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_NullText() {
+        new MeasuredText.Builder((char[]) null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_NullMeasuredText() {
+        new MeasuredText.Builder((MeasuredText) null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_NullPaint() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray()).appendStyleRun(null, text.length(), false);
+    }
+
+    @Test
+    public void testGetWidth() {
+        String text = "Hello, World";
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        assertEquals(0.0f, mt.getWidth(0, 0), 0.0f);
+        assertEquals(10.0f, mt.getWidth(0, 1), 0.0f);
+        assertEquals(20.0f, mt.getWidth(0, 2), 0.0f);
+        assertEquals(10.0f, mt.getWidth(1, 2), 0.0f);
+        assertEquals(20.0f, mt.getWidth(1, 3), 0.0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetWidth_StartSmallerThanZero() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getWidth(-1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetWidth_StartLargerThanLength() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getWidth(text.length() + 1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetWidth_EndSmallerThanZero() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getWidth(0, -1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetWidth_EndLargerThanLength() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getWidth(0, text.length() + 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetWidth_StartLargerThanEnd() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getWidth(1, 0);
+    }
+
+    @Test
+    public void testGetBounds() {
+        String text = "Hello, World";
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        final Rect emptyRect = new Rect(0, 0, 0, 0);
+        final Rect singleCharRect = new Rect(0, -10, 10, 0);
+        final Rect twoCharRect = new Rect(0, -10, 20, 0);
+        Rect out = new Rect();
+        mt.getBounds(0, 0, out);
+        assertEquals(emptyRect, out);
+        mt.getBounds(0, 1, out);
+        assertEquals(singleCharRect, out);
+        mt.getBounds(0, 2, out);
+        assertEquals(twoCharRect, out);
+        mt.getBounds(1, 2, out);
+        assertEquals(singleCharRect, out);
+        mt.getBounds(1, 3, out);
+        assertEquals(twoCharRect, out);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBounds_StartSmallerThanZero() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(-1, 0, rect);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBounds_StartLargerThanLength() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(text.length() + 1, 0, rect);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBounds_EndSmallerThanZero() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(0, -1, rect);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBounds_EndLargerThanLength() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(0, text.length() + 1, rect);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBounds_StartLargerThanEnd() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(1, 0, rect);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testGetBounds_NullRect() {
+        String text = "Hello, World";
+        Rect rect = new Rect();
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getBounds(0, 0, null);
+    }
+
+    @Test
+    public void testGetCharWidthAt() {
+        String text = "Hello, World";
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+        assertEquals(10.0f, mt.getCharWidthAt(0), 0.0f);
+        assertEquals(10.0f, mt.getCharWidthAt(1), 0.0f);
+        assertEquals(10.0f, mt.getCharWidthAt(2), 0.0f);
+        assertEquals(10.0f, mt.getCharWidthAt(3), 0.0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetCharWidthAt_OffsetSmallerThanZero() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getCharWidthAt(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetCharWidthAt_OffsetLargerThanLength() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */)
+                .build()
+                .getCharWidthAt(text.length());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuilder_reuse_throw_exception() {
+        String text = "Hello, World";
+        MeasuredText.Builder b = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false /* isRtl */);
+        b.build();
+        b.build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_tooSmallLengthStyle() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray()).appendStyleRun(sPaint, -1, false /* isRtl */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_tooLargeLengthStyle() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length() + 1, false /* isRtl */);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_tooSmallLengthReplacement() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray()).appendReplacementRun(sPaint, -1, 1.0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_tooLargeLengthReplacement() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendReplacementRun(sPaint, text.length() + 1, 1.0f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuilder_notEnoughStyle() {
+        String text = "Hello, World";
+        new MeasuredText.Builder(text.toCharArray())
+                .appendReplacementRun(sPaint, text.length() - 1, 1.0f).build();
+    }
+
+    public Bitmap makeBitmapFromSsb(String text, TextPaint paint, boolean isRtl) {
+        // drawTetRun is not aware of multiple style text.
+        final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+        final Bitmap ssbBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Canvas ssbCanvas = new Canvas(ssbBitmap);
+        ssbCanvas.save();
+        ssbCanvas.translate(0, 0);
+        ssbCanvas.drawTextRun(ssb, 0, ssb.length(), 0, ssb.length(),
+                0.0f /* x */, 240.0f /* y */, isRtl, paint);
+        ssbCanvas.restore();
+        return ssbBitmap;
+    }
+
+    public Bitmap makeBitmapFromMtWithSamePaint(String text, TextPaint paint, boolean isRtl) {
+        // MeasuredText uses measured result if the given paint is equal to the applied one.
+        final MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(paint, text.length(), isRtl)
+                .build();
+        final Bitmap mtBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Canvas mtCanvas = new Canvas(mtBitmap);
+        mtCanvas.save();
+        mtCanvas.translate(0, 0);
+        mtCanvas.drawTextRun(mt, 0, text.length(), 0, text.length(),
+                0.0f /* x */, 240.0f /* y */, isRtl, paint);
+        mtCanvas.restore();
+        return mtBitmap;
+    }
+
+    public Bitmap makeBitmapFromMtWithDifferentPaint(String text, TextPaint paint, boolean isRtl) {
+        // If different paint is provided when drawing, MeasuredText discards the measured result
+        // and recompute immediately. Thus the final output must be the same with given one.
+        final MeasuredText mt2 = new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(new Paint(), text.length(), isRtl)
+                .build();
+        final Bitmap mt2Bitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Canvas mt2Canvas = new Canvas(mt2Bitmap);
+        mt2Canvas.save();
+        mt2Canvas.translate(0, 0);
+        mt2Canvas.drawTextRun(mt2, 0, text.length(), 0, text.length(),
+                0.0f /* x */, 240.0f /* y */, isRtl, paint);
+        mt2Canvas.restore();
+        return mt2Bitmap;
+    }
+
+    public Bitmap makeBitmapFromPct(String text, TextPaint paint, boolean isRtl) {
+        final PrecomputedText pct = PrecomputedText.create(
+                text, new PrecomputedText.Params.Builder(paint).build());
+        final Bitmap pctBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Canvas pctCanvas = new Canvas(pctBitmap);
+        pctCanvas.save();
+        pctCanvas.translate(0, 0);
+        pctCanvas.drawTextRun(pct, 0, text.length(), 0, text.length(),
+                0.0f /* x */, 240.0f /* y */, isRtl, paint);
+        pctCanvas.restore();
+        return pctBitmap;
+    }
+
+    @Test
+    public void testCanvasDrawTextRun_sameOutputTestForLatinText() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(30.0f);
+
+        final String text = "Hello, World";
+
+        final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, false);
+        assertFalse(blankBitmap.sameAs(ssbBitmap));
+
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, false)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, false)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, false)));
+    }
+
+    @Test
+    public void testCanvasDrawTextRun_sameOutputTestForCJKText() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(30.0f);
+
+        final String text = "\u3042\u3044\u3046\u3048\u304A";
+
+        final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, false);
+        assertFalse(blankBitmap.sameAs(ssbBitmap));
+
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, false)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, false)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, false)));
+    }
+
+    @Test
+    public void testCanvasDrawTextRun_sameOutputTestForRTLText() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(30.0f);
+
+        final String text = "\u05D0\u05D1\u05D2\u05D3\u05D4";
+
+        final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
+        final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, true);
+        assertFalse(blankBitmap.sameAs(ssbBitmap));
+
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, true)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, true)));
+        assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, true)));
+    }
+}
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index f4d23de..a2ac3bd 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -43,7 +43,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsHardwareTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/hardware/jni/Android.mk b/tests/tests/hardware/jni/Android.mk
index 0cd95e7..6df6667 100644
--- a/tests/tests/hardware/jni/Android.mk
+++ b/tests/tests/hardware/jni/Android.mk
@@ -30,7 +30,9 @@
 
 LOCAL_SHARED_LIBRARIES := libandroid libnativehelper_compat_libc++ liblog
 
-LOCAL_CXX_STL := libc++_static
+LOCAL_NDK_STL_VARIANT := none
+
+LOCAL_SDK_VERSION := current
 
 LOCAL_CLANG := true
 
diff --git a/tests/tests/hardware/jni/android_hardware_cts_HardwareBufferTest.cpp b/tests/tests/hardware/jni/android_hardware_cts_HardwareBufferTest.cpp
index c5b5c35..8df230f 100644
--- a/tests/tests/hardware/jni/android_hardware_cts_HardwareBufferTest.cpp
+++ b/tests/tests/hardware/jni/android_hardware_cts_HardwareBufferTest.cpp
@@ -20,7 +20,6 @@
 #include <jni.h>
 
 #include <android/hardware_buffer_jni.h>
-#include <utils/Errors.h>
 
 #define LOG_TAG "HardwareBufferTest"
 
@@ -35,7 +34,9 @@
     desc.usage = usage;
     desc.format = format;
     int res = AHardwareBuffer_allocate(&desc, &buffer);
-    if (res == android::NO_ERROR) {
+
+    // TODO: Change this to res == NO_ERROR after b/77153085 is fixed
+    if (res == 0) {
         return AHardwareBuffer_toHardwareBuffer(env, buffer);
     } else {
         return 0;
diff --git a/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java
new file mode 100644
index 0000000..44f9b20
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.hardware.biometrics.cts;
+
+import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
+import android.platform.test.annotations.Presubmit;
+import android.test.AndroidTestCase;
+
+/**
+ * Basic test cases for BiometricManager
+ */
+public class BiometricManagerTest extends AndroidTestCase {
+
+    private BiometricManager mBiometricManager;
+    private boolean mHasBiometric;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        PackageManager pm = getContext().getPackageManager();
+
+        mHasBiometric |= pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
+        mHasBiometric |= pm.hasSystemFeature(PackageManager.FEATURE_FACE);
+        mHasBiometric |= pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
+
+        mBiometricManager = getContext().getSystemService(BiometricManager.class);
+    }
+
+    @Presubmit
+    public void test_canAuthenticate() {
+        if (!mHasBiometric) {
+            assertTrue(mBiometricManager.canAuthenticate()
+                    == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE);
+        } else {
+            // No biometrics are enrolled. CTSVerifier should test the other error cases.
+            assertTrue(mBiometricManager.canAuthenticate()
+                    == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED);
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
index 74e6857..b36d607 100644
--- a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
@@ -17,9 +17,9 @@
 package android.hardware.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.graphics.PixelFormat;
 import android.hardware.HardwareBuffer;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -110,4 +110,14 @@
         assertEquals(HardwareBuffer.USAGE_CPU_READ_RARELY, buffer.getUsage());
         nativeReleaseHardwareBuffer(buffer);
     }
+
+    @Test
+    public void testIsSupported() {
+        assertTrue(HardwareBuffer.isSupported(1, 1, HardwareBuffer.RGBA_8888,
+                1, HardwareBuffer.USAGE_CPU_READ_RARELY));
+        assertTrue(HardwareBuffer.isSupported(1, 1, HardwareBuffer.RGBA_8888,
+                1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE));
+        assertFalse(HardwareBuffer.isSupported(1, 1, HardwareBuffer.BLOB,
+                1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT));
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
index f11b6bc..ec8e634 100644
--- a/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
@@ -67,6 +67,18 @@
     }
 
     @Presubmit
+    public void test_managerAndFeature() {
+        final boolean hasFingerprintFeature = getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
+        final FingerprintManager fpm = getContext().getSystemService(FingerprintManager.class);
+
+        if (hasFingerprintFeature || fpm != null) {
+            assertTrue(hasFingerprintFeature);
+            assertTrue(fpm != null);
+        }
+    }
+
+    @Presubmit
     public void test_hasFingerprintHardware() {
         if (!mHasFingerprintManager) {
             return; // skip test if no fingerprint feature
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 a4edc8b..19d0b07 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
@@ -34,6 +34,7 @@
 import com.android.cts.input.HidJsonParser;
 import com.android.cts.input.HidTestData;
 
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -97,9 +98,7 @@
     private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
         KeyEvent receivedKeyEvent = waitForKey();
         if (receivedKeyEvent == null) {
-            fail(mCurrentTestCase + ": timed out waiting for "
-                    + KeyEvent.keyCodeToString(expectedKeyEvent.getKeyCode())
-                    + " with action " + KeyEvent.actionToString(expectedKeyEvent.getAction()));
+            failWithMessage("timed out waiting for " + expectedKeyEvent);
         }
         assertEquals(mCurrentTestCase, expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
         assertEquals(mCurrentTestCase,
@@ -118,10 +117,10 @@
           */
 
         if (event == null) {
-            fail(mCurrentTestCase + ": timed out waiting for MotionEvent");
+            failWithMessage("timed out waiting for " + expectedEvent);
         }
         if (event.getHistorySize() > 0) {
-            fail(mCurrentTestCase + ": expected each MotionEvent to only have a single entry");
+            failWithMessage("expected each MotionEvent to only have a single entry");
         }
         assertEquals(mCurrentTestCase, expectedEvent.getAction(), event.getAction());
         for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
@@ -141,7 +140,7 @@
         if (event == null) {
             return;
         }
-        fail(mCurrentTestCase + ": extraneous events generated: " + event);
+        failWithMessage("extraneous events generated: " + event);
     }
 
     protected void testInputEvents(int resourceId) {
@@ -175,7 +174,7 @@
         try {
             return mEvents.poll(5, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
-            fail(mCurrentTestCase + ": unexpectedly interrupted while waiting for InputEvent");
+            failWithMessage("unexpectedly interrupted while waiting for InputEvent");
             return null;
         }
     }
@@ -185,7 +184,8 @@
         if (event instanceof KeyEvent) {
             return (KeyEvent) event;
         }
-        fail("Expected a KeyEvent, but received: " + event);
+        failWithMessage("expected a KeyEvent, but received: " + event
+                + ". So far received " + mEvents.size() + " events.");
         return null;
     }
 
@@ -194,7 +194,8 @@
         if (event instanceof MotionEvent) {
             return (MotionEvent) event;
         }
-        fail("Expected a MotionEvent, but received: " + event);
+        failWithMessage("expected a MotionEvent, but received: " + event
+                + ". So far received " + mEvents.size() + " events.");
         return null;
     }
 
@@ -254,13 +255,20 @@
         return events;
     }
 
+    /**
+     * Append the name of the currently executing test case to the fail message.
+     */
+    private void failWithMessage(String message) {
+        fail(mCurrentTestCase + ": " + message);
+    }
+
     private class InputListener implements InputCallback {
         @Override
         public void onKeyEvent(KeyEvent ev) {
             try {
                 mEvents.put(new KeyEvent(ev));
             } catch (InterruptedException ex) {
-                fail(mCurrentTestCase + ": interrupted while adding a KeyEvent to the queue");
+                failWithMessage("interrupted while adding a KeyEvent to the queue");
             }
         }
 
@@ -271,7 +279,7 @@
                     mEvents.put(event);
                 }
             } catch (InterruptedException ex) {
-                fail(mCurrentTestCase + ": interrupted while adding a MotionEvent to the queue");
+                failWithMessage("interrupted while adding a MotionEvent to the queue");
             }
         }
     }
diff --git a/tests/tests/jni/AndroidManifest.xml b/tests/tests/jni/AndroidManifest.xml
index 2724573..18ef4c6 100644
--- a/tests/tests/jni/AndroidManifest.xml
+++ b/tests/tests/jni/AndroidManifest.xml
@@ -18,7 +18,7 @@
     package="android.jni.cts">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <application>
+    <application android:extractNativeLibs="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index bdbafa3..59f6cb1 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -45,6 +45,7 @@
     private final static String VENDOR_CONFIG_FILE = "/vendor/etc/public.libraries.txt";
     private final static String[] PUBLIC_SYSTEM_LIBRARIES = {
         "libaaudio.so",
+        "libamidi.so",
         "libandroid.so",
         "libbinder_ndk.so",
         "libc.so",
diff --git a/tests/tests/keystore/AndroidManifest.xml b/tests/tests/keystore/AndroidManifest.xml
index 1816f2e..034c871 100644
--- a/tests/tests/keystore/AndroidManifest.xml
+++ b/tests/tests/keystore/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.keystore.cts">
 
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
     <uses-permission android:name="android.permission.INTERNET" />
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/keystore/AndroidTest.xml b/tests/tests/keystore/AndroidTest.xml
index e2b4986..4a2dd9e 100644
--- a/tests/tests/keystore/AndroidTest.xml
+++ b/tests/tests/keystore/AndroidTest.xml
@@ -25,5 +25,6 @@
         <option name="runtime-hint" value="16m39s" />
         <!-- test-timeout unit is ms, value = 10 min -->
         <option name="test-timeout" value="600000" />
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/tests/keystore/src/android/server/am/ActivityAndWindowManagersState.java b/tests/tests/keystore/src/android/server/am/ActivityAndWindowManagersState.java
index be769cf..0952f90 100644
--- a/tests/tests/keystore/src/android/server/am/ActivityAndWindowManagersState.java
+++ b/tests/tests/keystore/src/android/server/am/ActivityAndWindowManagersState.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -28,6 +28,7 @@
 import static android.server.am.StateLogger.log;
 import static android.server.am.StateLogger.logAlways;
 import static android.server.am.StateLogger.logE;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.lessThan;
@@ -143,7 +144,7 @@
 
     public void waitForKeyguardShowingAndNotOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
-                        && !state.getKeyguardControllerState().keyguardOccluded,
+                        && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "***Waiting for Keyguard showing...");
     }
 
diff --git a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
index 0ff8e40..977cd59 100644
--- a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
+++ b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
@@ -27,6 +27,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
+import android.util.SparseArray;
 
 import com.android.server.am.nano.ActivityDisplayProto;
 import com.android.server.am.nano.ActivityManagerServiceDumpActivitiesProto;
@@ -380,13 +381,23 @@
     static class KeyguardControllerState {
 
         boolean keyguardShowing = false;
-        boolean keyguardOccluded = false;
+        SparseArray<Boolean> mKeyguardOccludedStates = new SparseArray<>();
 
         KeyguardControllerState(KeyguardControllerProto proto) {
             if (proto != null) {
                 keyguardShowing = proto.keyguardShowing;
-                keyguardOccluded = proto.keyguardOccluded;
+                for (int i = 0;  i < proto.keyguardOccludedStates.length; i++) {
+                    mKeyguardOccludedStates.append(proto.keyguardOccludedStates[i].displayId,
+                            proto.keyguardOccludedStates[i].keyguardOccluded);
+                }
             }
         }
+
+        boolean isKeyguardOccluded(int displayId) {
+            if (mKeyguardOccludedStates.get(displayId) != null) {
+                return mKeyguardOccludedStates.get(displayId);
+            }
+            return false;
+        }
     }
 }
diff --git a/tests/tests/keystore/src/android/server/am/ActivityManagerTestBase.java b/tests/tests/keystore/src/android/server/am/ActivityManagerTestBase.java
index b960d2f..6847d31 100644
--- a/tests/tests/keystore/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/tests/keystore/src/android/server/am/ActivityManagerTestBase.java
@@ -35,6 +35,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.SystemClock;
@@ -77,6 +78,7 @@
 
     protected Context mContext;
     protected ActivityManager mAm;
+    protected ActivityTaskManager mAtm;
 
     /**
      * @return the am command to start the given activity with the following extra key/value pairs.
@@ -117,11 +119,11 @@
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
         mAm = mContext.getSystemService(ActivityManager.class);
+        mAtm = mContext.getSystemService(ActivityTaskManager.class);
 
         pressWakeupButton();
         pressUnlockButton();
         pressHomeButton();
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
     }
 
     @After
@@ -129,18 +131,12 @@
         // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
         // might be asynchronous and could interrupt the stack cleanup process if executed first.
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
         executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
         executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
         executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
         pressHomeButton();
     }
 
-    protected void removeStacksWithActivityTypes(int... activityTypes) {
-        mAm.removeStacksWithActivityTypes(activityTypes);
-        waitForIdle();
-    }
-
     public static String executeShellCommand(String command) {
         log("Shell command: " + command);
         try {
@@ -178,7 +174,7 @@
 
     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
         final int taskId = getActivityTaskId(activityName);
-        mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+        mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
                 .setWindowingMode(windowingMode)
@@ -238,7 +234,7 @@
     }
 
     protected boolean supportsSplitScreenMultiWindow() {
-        return ActivityManager.supportsSplitScreenMultiWindow(mContext);
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
     }
 
     protected boolean hasDeviceFeature(final String requiredFeature) {
diff --git a/tests/tests/keystore/src/android/server/am/WaitForValidActivityState.java b/tests/tests/keystore/src/android/server/am/WaitForValidActivityState.java
index 05806a7..5fac1c0 100644
--- a/tests/tests/keystore/src/android/server/am/WaitForValidActivityState.java
+++ b/tests/tests/keystore/src/android/server/am/WaitForValidActivityState.java
@@ -16,7 +16,7 @@
 
 package android.server.am;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
diff --git a/tests/tests/keystore/src/android/server/am/WindowManagerState.java b/tests/tests/keystore/src/android/server/am/WindowManagerState.java
index ae6e547..b4a8e9e 100644
--- a/tests/tests/keystore/src/android/server/am/WindowManagerState.java
+++ b/tests/tests/keystore/src/android/server/am/WindowManagerState.java
@@ -33,16 +33,16 @@
 import android.support.test.InstrumentationRegistry;
 import android.view.nano.DisplayInfoProto;
 
-import com.android.server.wm.nano.AppTransitionProto;
 import com.android.server.wm.nano.AppWindowTokenProto;
 import com.android.server.wm.nano.ConfigurationContainerProto;
 import com.android.server.wm.nano.DisplayFramesProto;
-import com.android.server.wm.nano.DisplayProto;
+import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.IdentifierProto;
 import com.android.server.wm.nano.PinnedStackControllerProto;
 import com.android.server.wm.nano.StackProto;
 import com.android.server.wm.nano.TaskProto;
 import com.android.server.wm.nano.WindowContainerProto;
+import com.android.server.wm.nano.WindowFramesProto;
 import com.android.server.wm.nano.WindowManagerServiceDumpProto;
 import com.android.server.wm.nano.WindowStateAnimatorProto;
 import com.android.server.wm.nano.WindowStateProto;
@@ -152,7 +152,7 @@
         }
         mFocusedApp = state.focusedApp;
         for (int i = 0; i < state.rootWindowContainer.displays.length; i++) {
-            DisplayProto displayProto = state.rootWindowContainer.displays[i];
+            DisplayContentProto displayProto = state.rootWindowContainer.displays[i];
             final Display display = new Display(displayProto);
             mDisplays.add(display);
             allWindows.addAll(display.getWindows());
@@ -188,7 +188,6 @@
             mWindowStates.add(windowMap.get(hash_code));
         }
         mRotation = state.rotation;
-        AppTransitionProto appTransitionProto = state.appTransition;
     }
 
     public void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
@@ -340,7 +339,6 @@
     static class WindowTask extends WindowContainer {
 
         int mTaskId;
-        Rect mTempInsetBounds;
         List<String> mAppTokens = new ArrayList<>();
 
         WindowTask(TaskProto proto) {
@@ -359,7 +357,6 @@
                     mSubWindows.addAll(window.getWindows());
                 }
             }
-            mTempInsetBounds = extract(proto.tempInsetBounds);
         }
     }
 
@@ -425,7 +422,7 @@
         private int mDpi;
         private String mName;
 
-        public Display(DisplayProto proto) {
+        public Display(DisplayContentProto proto) {
             super(proto.windowContainer);
             mDisplayId = proto.id;
             for (int i = 0; i < proto.aboveAppWindows.length; i++) {
@@ -491,10 +488,10 @@
         private int mDisplayId;
         private int mStackId;
         private boolean mShown;
-        private Rect mContainingFrame = new Rect();
-        private Rect mParentFrame = new Rect();
-        private Rect mContentFrame = new Rect();
-        private Rect mFrame = new Rect();
+        private Rect mContainingFrame;
+        private Rect mParentFrame;
+        private Rect mContentFrame;
+        private Rect mFrame;
         private Rect mCrop = new Rect();
 
         WindowState(WindowStateProto proto) {
@@ -515,10 +512,13 @@
                 }
                 mCrop = extract(animatorProto.lastClipRect);
             }
-            mFrame = extract(proto.frame);
-            mContainingFrame = extract(proto.containingFrame);
-            mParentFrame = extract(proto.parentFrame);
-            mContentFrame = extract(proto.contentFrame);
+            WindowFramesProto windowFramesProto = proto.windowFrames;
+            if (windowFramesProto != null) {
+                mFrame = extract(windowFramesProto.frame);
+                mContainingFrame = extract(windowFramesProto.containingFrame);
+                mParentFrame = extract(windowFramesProto.parentFrame);
+                mContentFrame = extract(windowFramesProto.contentFrame);
+            }
             if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
                 mWindowType = WINDOW_TYPE_STARTING;
                 // Existing code depends on the prefix being removed
diff --git a/tests/tests/location/AndroidTest.xml b/tests/tests/location/AndroidTest.xml
index c69e224..47c105c 100644
--- a/tests/tests/location/AndroidTest.xml
+++ b/tests/tests/location/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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" />
     <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" />
@@ -24,6 +25,7 @@
     <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/src/android/location/cts/EmergencyCallMessageTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
index d7dbc89..4c2c36f 100644
--- a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
+++ b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
@@ -16,19 +16,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.google.android.mms.ContentType;
-import com.google.android.mms.InvalidHeaderValueException;
-import com.google.android.mms.pdu.CharacterSets;
-import com.google.android.mms.pdu.EncodedStringValue;
-import com.google.android.mms.pdu.GenericPdu;
-import com.google.android.mms.pdu.PduBody;
-import com.google.android.mms.pdu.PduComposer;
-import com.google.android.mms.pdu.PduHeaders;
-import com.google.android.mms.pdu.PduParser;
-import com.google.android.mms.pdu.PduPart;
-import com.google.android.mms.pdu.SendConf;
-import com.google.android.mms.pdu.SendReq;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -43,96 +30,20 @@
 
     private static final String TAG = "EmergencyCallMSGTest";
 
-    private static final String ACTION_MMS_SENT = "CTS_MMS_SENT_ACTION";
     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 int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
     private static final short DEFAULT_DATA_SMS_PORT = 8091;
     private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
-    private static final String SUBJECT = "CTS Emergency Call MMS Test";
-    private static final String MMS_MESSAGE_BODY = "CTS Emergency Call MMS test message body";
     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 String TEXT_PART_FILENAME = "text_0.txt";
-    private static final String SMIL_TEXT =
-            "<smil>" +
-                    "<head>" +
-                        "<layout>" +
-                            "<root-layout/>" +
-                            "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" +
-                        "</layout>" +
-                    "</head>" +
-                    "<body>" +
-                        "<par dur=\"8000ms\">" +
-                            "<text src=\"%s\" region=\"Text\"/>" +
-                        "</par>" +
-                    "</body>" +
-            "</smil>";
-
     private static final long SENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); // 5 minutes
-
     private static final String PROVIDER_AUTHORITY = "emergencycallverifier";
 
     private Random mRandom;
-    private SentReceiver mSentReceiver;
     private TelephonyManager mTelephonyManager;
     private PackageManager mPackageManager;
 
-    private static class SentReceiver extends BroadcastReceiver {
-        private boolean mSuccess;
-        private boolean mDone;
-        private final CountDownLatch mLatch;
-        public SentReceiver() {
-            mLatch =  new CountDownLatch(1);
-            mSuccess = false;
-            mDone = false;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Log.i(TAG, "Action " + intent.getAction());
-            if (!ACTION_MMS_SENT.equals(intent.getAction())) {
-                return;
-            }
-            final int resultCode = getResultCode();
-            if (resultCode == Activity.RESULT_OK) {
-                final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
-                if (response != null) {
-                    final GenericPdu pdu = new PduParser(
-                            response, shouldParseContentDisposition()).parse();
-                    if (pdu != null && pdu instanceof SendConf) {
-                        final SendConf sendConf = (SendConf) pdu;
-                        if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
-                            mSuccess = true;
-                        } else {
-                            Log.e(TAG, "SendConf response status=" + sendConf.getResponseStatus());
-                        }
-                    } else {
-                        Log.e(TAG, "Not a SendConf: " +
-                                (pdu != null ? pdu.getClass().getCanonicalName() : "NULL"));
-                    }
-                } else {
-                    Log.e(TAG, "Empty response");
-                }
-            } else {
-                Log.e(TAG, "Failure result=" + resultCode);
-                if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
-                    final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
-                    Log.e(TAG, "HTTP failure=" + httpError);
-                }
-            }
-            mDone = true;
-            mLatch.countDown();
-        }
-
-        public boolean waitForSuccess(long timeoutMs) throws Exception {
-            mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
-            return mDone && mSuccess;
-        }
-    }
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -164,153 +75,11 @@
             SMS_DATA_MESSAGE_BODY.getBytes(), null, null);
     }
 
-    public void testSendMmsMessage() throws Exception {
-        // this test is only for cts verifier
-        if (!isCtsVerifierTest()) {
-            return;
-        }
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-             || !doesSupportMMS()) {
-            Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
-            return;
-        }
-
-        Log.i(TAG, "testSendMmsMessage");
-        // Prime the MmsService so that MMS config is loaded
-        final SmsManager smsManager = SmsManager.getDefault();
-        // MMS config is loaded asynchronously. Wait a bit so it will be loaded.
-        try {
-            Thread.sleep(MMS_CONFIG_DELAY_MILLIS);
-        } catch (InterruptedException e) {
-            // Ignore
-        }
-
-        final Context context = getContext();
-        // Register sent receiver
-        mSentReceiver = new SentReceiver();
-        context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT));
-        // Create local provider file for sending PDU
-        final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
-        final File sendFile = new File(context.getCacheDir(), fileName);
-        final String selfNumber = getPhoneNumber(context);
-        assertTrue(!TextUtils.isEmpty(selfNumber));
-        final byte[] pdu = buildPdu(context, selfNumber, SUBJECT, MMS_MESSAGE_BODY);
-        assertNotNull(pdu);
-        assertTrue(writePdu(sendFile, pdu));
-        final Uri contentUri = (new Uri.Builder())
-                .authority(PROVIDER_AUTHORITY)
-                .path(fileName)
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .build();
-        // Send
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                context, 0, new Intent(ACTION_MMS_SENT), 0);
-        smsManager.sendMultimediaMessage(context,
-                contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
-        assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT_MILLIS));
-        sendFile.delete();
-    }
-
-    private static boolean writePdu(File file, byte[] pdu) {
-        FileOutputStream writer = null;
-        try {
-            writer = new FileOutputStream(file);
-            writer.write(pdu);
-            return true;
-        } catch (final IOException e) {
-            String stackTrace = Log.getStackTraceString(e);
-            Log.i(TAG, stackTrace);
-            return false;
-        } finally {
-            if (writer != null) {
-                try {
-                    writer.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    private static byte[] buildPdu(Context context, String selfNumber, String subject,
-       String text) {
-        final SendReq req = new SendReq();
-        // From, per spec
-        req.setFrom(new EncodedStringValue(selfNumber));
-        // To
-        final String[] recipients = new String[1];
-        recipients[0] = selfNumber;
-        final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
-        if (encodedNumbers != null) {
-            req.setTo(encodedNumbers);
-        }
-        // Subject
-        if (!TextUtils.isEmpty(subject)) {
-            req.setSubject(new EncodedStringValue(subject));
-        }
-        // Date
-        req.setDate(System.currentTimeMillis() / 1000);
-        // Body
-        final PduBody body = new PduBody();
-        // Add text part. Always add a smil part for compatibility, without it there
-        // may be issues on some carriers/client apps
-        final int size = addTextPart(body, text, true/* add text smil */);
-        req.setBody(body);
-        // Message size
-        req.setMessageSize(size);
-        // Message class
-        req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
-        // Expiry
-        req.setExpiry(DEFAULT_EXPIRY_TIME_SECS);
-        // The following set methods throw InvalidHeaderValueException
-        try {
-            // Priority
-            req.setPriority(DEFAULT_PRIORITY);
-            // Delivery report
-            req.setDeliveryReport(PduHeaders.VALUE_NO);
-            // Read report
-            req.setReadReport(PduHeaders.VALUE_NO);
-        } catch (InvalidHeaderValueException e) {
-            return null;
-        }
-
-        return new PduComposer(context, req).make();
-    }
-
-    private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
-        final PduPart part = new PduPart();
-        // Set Charset if it's a text media.
-        part.setCharset(CharacterSets.UTF_8);
-        // Set Content-Type.
-        part.setContentType(ContentType.TEXT_PLAIN.getBytes());
-        // Set Content-Location.
-        part.setContentLocation(TEXT_PART_FILENAME.getBytes());
-        int index = TEXT_PART_FILENAME.lastIndexOf(".");
-        String contentId = (index == -1) ? TEXT_PART_FILENAME
-                : TEXT_PART_FILENAME.substring(0, index);
-        part.setContentId(contentId.getBytes());
-        part.setData(message.getBytes());
-        pb.addPart(part);
-        if (addTextSmil) {
-            final String smil = String.format(SMIL_TEXT, TEXT_PART_FILENAME);
-            addSmilPart(pb, smil);
-        }
-        return part.getData().length;
-    }
-
-    private static void addSmilPart(PduBody pb, String smil) {
-        final PduPart smilPart = new PduPart();
-        smilPart.setContentId("smil".getBytes());
-        smilPart.setContentLocation("smil.xml".getBytes());
-        smilPart.setContentType(ContentType.APP_SMIL.getBytes());
-        smilPart.setData(smil.getBytes());
-        pb.addPart(0, smilPart);
-    }
-
     private static String getPhoneNumber(Context context) {
         final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
                 Context.TELEPHONY_SERVICE);
         String phoneNumber = telephonyManager.getLine1Number();
-        if (phoneNumber.trim().isEmpty()) {
+        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
             phoneNumber = System.getProperty(PHONE_NUMBER_KEY);
         }
         return phoneNumber;
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
index 216989f..5e8d99b 100644
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
@@ -43,7 +43,7 @@
 public class GnssMeasurementValuesTest extends GnssTestCase {
 
     private static final String TAG = "GnssMeasValuesTest";
-    private static final int LOCATION_TO_COLLECT_COUNT = 5;
+    private static final int LOCATION_TO_COLLECT_COUNT = 20;
     private static final int YEAR_2017 = 2017;
 
     private TestGnssMeasurementListener mMeasurementListener;
@@ -147,6 +147,8 @@
                 }
             }
         }
+        TestMeasurementUtil.assertGnssClockHasConsistentFullBiasNanos(softAssert, events);
+
         softAssert.assertOrWarnTrue(isMeasurementTestStrict(),
                 "GNSS Measurements PRRs with Carrier Phase "
                         + "level uncertainties.  If failed, retry near window or outdoors?",
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index d58ebb6..5d17e80 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -255,64 +255,6 @@
     }
 
     /**
-     * Tests that location mode is consistent with which providers are enabled. Sadly we can only
-     * passively test whatever mode happens to be selected--actually changing the mode would require
-     * the test to be system-signed, and CTS tests aren't. Also mode changes that enable NLP require
-     * user consent. Thus we will have a manual CTS verifier test that is similar to this test but
-     * tests every location mode. This test is just a "backup" for that since verifier tests are
-     * less reliable.
-     */
-    public void testModeAndProviderApisConsistent() {
-        ContentResolver cr = mContext.getContentResolver();
-
-        // Find out what the settings say about which providers are enabled
-        int mode = Settings.Secure.getInt(
-                cr, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
-        boolean gps = Settings.Secure.isLocationProviderEnabled(cr, LocationManager.GPS_PROVIDER);
-        boolean nlp = Settings.Secure.isLocationProviderEnabled(
-                cr, LocationManager.NETWORK_PROVIDER);
-
-        // Find out location manager's opinion on the matter, making sure we dont' get spurious
-        // results from test versions of the two providers.
-        forceRemoveTestProvider(LocationManager.GPS_PROVIDER);
-        forceRemoveTestProvider(LocationManager.NETWORK_PROVIDER);
-        boolean lmGps = mManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
-        boolean lmNlp = mManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
-
-        // Location Manager will report provider as off if it doesn't know about it
-        boolean expectedGps = gps && deviceHasProvider(LocationManager.GPS_PROVIDER);
-        boolean expectedNlp = nlp && deviceHasProvider(LocationManager.NETWORK_PROVIDER);
-
-        // Assert LocationManager returned the values from Settings.Secure (assuming the device has
-        // the appropriate hardware).
-        assertEquals("Inconsistent GPS values", expectedGps, lmGps);
-        assertEquals("Inconsistent NLP values", expectedNlp, lmNlp);
-
-        switch (mode) {
-            case Settings.Secure.LOCATION_MODE_OFF:
-                expectedGps = false;
-                expectedNlp = false;
-                break;
-            case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
-                expectedGps = true;
-                expectedNlp = false;
-                break;
-            case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
-                expectedGps = false;
-                expectedNlp = true;
-                break;
-            case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
-                expectedGps = true;
-                expectedNlp = true;
-                break;
-        }
-
-        // Assert that isLocationProviderEnabled() values are consistent with the location mode
-        assertEquals("Bad GPS for mode " + mode, expectedGps, gps);
-        assertEquals("Bad NLP for mode " + mode, expectedNlp, nlp);
-    }
-
-    /**
      * Returns true if the {@link LocationManager} reports that the device includes this flavor
      * of location provider.
      */
@@ -360,13 +302,6 @@
         } catch (IllegalArgumentException e) {
             // expected
         }
-
-        try {
-            mManager.clearTestProviderLocation(UNKNOWN_PROVIDER_NAME);
-            fail("Should throw IllegalArgumentException if provider is unknown!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
     }
 
     /**
@@ -943,13 +878,6 @@
         }
 
         try {
-            mManager.clearTestProviderEnabled(UNKNOWN_PROVIDER_NAME);
-            fail("Should throw IllegalArgumentException if provider is unknown!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
             mManager.setTestProviderEnabled(UNKNOWN_PROVIDER_NAME, false);
             fail("Should throw IllegalArgumentException if provider is unknown!");
         } catch (IllegalArgumentException e) {
@@ -1047,51 +975,6 @@
         mManager.unregisterGnssStatusCallback(callback);
     }
 
-    @AppModeFull(reason = "Requires use of extra LocationManager commands")
-    public void testSendExtraCommand() {
-        // this test assumes TEST_MOCK_PROVIDER_NAME was created in setUp.
-        assertNotNull(mManager.getProvider(TEST_MOCK_PROVIDER_NAME));
-        // Unknown command
-        assertFalse(mManager.sendExtraCommand(TEST_MOCK_PROVIDER_NAME, "unknown", new Bundle()));
-
-        assertNull(mManager.getProvider(UNKNOWN_PROVIDER_NAME));
-        assertFalse(mManager.sendExtraCommand(UNKNOWN_PROVIDER_NAME, "unknown", new Bundle()));
-    }
-
-    public void testSetTestProviderStatus() throws InterruptedException {
-        final int status = LocationProvider.TEMPORARILY_UNAVAILABLE;
-        final long updateTime = 1000;
-        final MockLocationListener listener = new MockLocationListener();
-
-        HandlerThread handlerThread = new HandlerThread("testStatusUpdates");
-        handlerThread.start();
-
-        // set status successfully
-        mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, listener,
-                handlerThread.getLooper());
-        mManager.setTestProviderStatus(TEST_MOCK_PROVIDER_NAME, status, null, updateTime);
-        // setting the status alone is not sufficient to trigger a status update
-        updateLocation(10, 30);
-        assertTrue(listener.hasCalledOnStatusChanged(TEST_TIME_OUT));
-        assertEquals(TEST_MOCK_PROVIDER_NAME, listener.getProvider());
-        assertEquals(status, listener.getStatus());
-
-        try {
-            mManager.setTestProviderStatus(UNKNOWN_PROVIDER_NAME, 0, null,
-                    System.currentTimeMillis());
-            fail("Should throw IllegalArgumentException if provider is unknown!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.clearTestProviderStatus(UNKNOWN_PROVIDER_NAME);
-            fail("Should throw IllegalArgumentException if provider is unknown!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
     /**
      * Tests basic proximity alert when entering proximity
      */
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
index 26aedf8..c9ffe61 100644
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
@@ -16,10 +16,9 @@
 
 package android.location.cts;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
-
 import android.location.GnssClock;
 import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
 import android.location.LocationManager;
@@ -27,11 +26,16 @@
 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.concurrent.TimeUnit;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Helper class for GnssMeasurement Tests.
@@ -170,6 +174,46 @@
     }
 
     /**
+     * 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}.
      *
diff --git a/tests/tests/location2/AndroidTest.xml b/tests/tests/location2/AndroidTest.xml
index 412fd1d..e193a7e 100644
--- a/tests/tests/location2/AndroidTest.xml
+++ b/tests/tests/location2/AndroidTest.xml
@@ -16,6 +16,7 @@
 <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" />
     <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/tests/location2/src/android/location2/cts/LocationManagerTest.java b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
index 96c41aa..aab7c15 100644
--- a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
+++ b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
@@ -266,9 +266,6 @@
         addTestProvider(LocationManager.NETWORK_PROVIDER, Criteria.ACCURACY_COARSE, true, false, true);
         addTestProvider(LocationManager.GPS_PROVIDER, Criteria.ACCURACY_FINE, false, true, false);
 
-        // Unknown command
-        assertFalse(mManager.sendExtraCommand(LocationManager.NETWORK_PROVIDER, "unknown", new Bundle()));
-
         try {
             mManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "unknown", new Bundle());
             fail("Should have failed to send a command to the gps provider");
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 78eb8ed..dc77eed 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -56,7 +56,7 @@
     truth-prebuilt \
     mockito-target-minus-junit4 \
     androidx.heifwriter_heifwriter \
-    androidx.media_media \
+    androidx.media2_media2 \
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libaudio_jni \
@@ -72,6 +72,7 @@
 LOCAL_AAPT_FLAGS := -0 .vp9
 LOCAL_AAPT_FLAGS += -0 .ts
 LOCAL_AAPT_FLAGS += -0 .heic
+LOCAL_AAPT_FLAGS += -0 .trp
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -84,7 +85,8 @@
 LOCAL_JAVA_LIBRARIES += \
     org.apache.http.legacy \
     android.test.base.stubs \
-    android.test.runner.stubs
+    android.test.runner.stubs \
+    updatable-media # TODO(b/112766913): replace with stub to prevent private API use.
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index eca5dd6..1b816d3 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -31,6 +31,8 @@
     <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
     <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
 
+    <permission android:name="android.media.cts" android:protectionLevel="normal" />
+
     <application android:networkSecurityConfig="@xml/network_security_config">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
@@ -100,19 +102,11 @@
                 <action android:name="android.media.browse.MediaBrowserService" />
             </intent-filter>
         </service>
-        <!-- Keep the test services synced together with the TestUtils.java -->
-        <service android:name="android.media.cts.MockMediaSessionService2">
+        <service android:name="android.media.cts.StubMediaSession2Service"
+            android:permission="android.media.cts">
             <intent-filter>
-                <action android:name="android.media.MediaSessionService2" />
+                <action android:name="android.media.MediaSession2Service" />
             </intent-filter>
-            <meta-data android:name="android.media.session" android:value="TestSession" />
-        </service>
-        <!-- Keep the test services synced together with the MockMediaLibraryService -->
-        <service android:name="android.media.cts.MockMediaLibraryService2">
-            <intent-filter>
-                <action android:name="android.media.MediaLibraryService2" />
-            </intent-filter>
-            <meta-data android:name="android.media.session" android:value="TestLibrary" />
         </service>
     </application>
 
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index af3b082..d9a834c 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Media 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" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
         <option name="target" value="host" />
         <option name="config-filename" value="cts" />
diff --git a/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp b/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp
index bf7aacc..ea35540 100644
--- a/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp
+++ b/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp
@@ -64,6 +64,7 @@
     {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
     {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE},
     {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_VIDEO_ENCODE},
+    {AIMAGE_FORMAT_Y8, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
 };
 
 class CameraHelper {
@@ -392,7 +393,8 @@
             mFormat == AIMAGE_FORMAT_RGB_888 ||
             mFormat == AIMAGE_FORMAT_RGB_565 ||
             mFormat == AIMAGE_FORMAT_RGBA_FP16 ||
-            mFormat == AIMAGE_FORMAT_YUV_420_888) {
+            mFormat == AIMAGE_FORMAT_YUV_420_888 ||
+            mFormat == AIMAGE_FORMAT_Y8) {
             // Check output buffer dimension for certain formats. Don't do this for blob based
             // formats.
             if (bufferWidth != mWidth || bufferHeight != mHeight) {
diff --git a/tests/tests/media/libimagereaderjni/OWNERS b/tests/tests/media/libimagereaderjni/OWNERS
new file mode 100644
index 0000000..497b490
--- /dev/null
+++ b/tests/tests/media/libimagereaderjni/OWNERS
@@ -0,0 +1,5 @@
+epeev@google.com
+etalvala@google.com
+jchowdhary@google.com
+shuzhenwang@google.com
+yinchiayeh@google.com
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
index 0e3b3a5..9a2a9df 100644
--- a/tests/tests/media/libmediandkjni/native-media-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-media-jni.cpp
@@ -17,7 +17,7 @@
 /* Original code copied from NDK Native-media sample code */
 
 //#define LOG_NDEBUG 0
-#define TAG "NativeMedia"
+#define LOG_TAG "NativeMedia"
 #include <log/log.h>
 
 #include <assert.h>
@@ -82,16 +82,19 @@
 struct FdDataSource {
 
     FdDataSource(int fd, jlong offset, jlong size)
-        : mFd(fd),
+        : mFd(dup(fd)),
           mOffset(offset),
           mSize(size) {
     }
 
     ssize_t readAt(off64_t offset, void *data, size_t size) {
         ssize_t ssize = size;
-        if (!data || offset < 0 || offset >= mSize || offset + ssize < offset) {
+        if (!data || offset < 0 || offset + ssize < offset) {
             return -1;
         }
+        if (offset >= mSize) {
+            return 0; // EOS
+        }
         if (offset + ssize > mSize) {
             ssize = mSize - offset;
         }
@@ -242,6 +245,21 @@
     ALOGV("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);
 }
 
+static int adler32(const uint8_t *input, int len) {
+
+    int a = 1;
+    int b = 0;
+    for (int i = 0; i < len; i++) {
+        a += input[i];
+        b += a;
+        a = a % 65521;
+        b = b % 65521;
+    }
+    int ret = b * 65536 + a;
+    ALOGV("adler %d/%d", len, ret);
+    return ret;
+}
+
 jobject testExtractor(AMediaExtractor *ex, JNIEnv *env) {
 
     simplevector<int> sizes;
@@ -293,6 +311,7 @@
         sizes.add(AMediaExtractor_getSampleTrackIndex(ex));
         sizes.add(AMediaExtractor_getSampleFlags(ex));
         sizes.add(AMediaExtractor_getSampleTime(ex));
+        sizes.add(adler32(buf, n));
         AMediaExtractor_advance(ex);
     }
 
@@ -328,7 +347,8 @@
 
 // get the sample sizes for the path
 extern "C" jobject Java_android_media_cts_NativeDecoderTest_getSampleSizesNativePath(JNIEnv *env,
-        jclass /*clazz*/, jstring jpath)
+        jclass /*clazz*/, jstring jpath, jobjectArray jkeys, jobjectArray jvalues,
+        jboolean testNativeSource)
 {
     AMediaExtractor *ex = AMediaExtractor_new();
 
@@ -337,29 +357,47 @@
         return NULL;
     }
 
-    int err = AMediaExtractor_setDataSource(ex, tmp);
+    int numkeys = jkeys ? env->GetArrayLength(jkeys) : 0;
+    int numvalues = jvalues ? env->GetArrayLength(jvalues) : 0;
+    int numheaders = numkeys < numvalues ? numkeys : numvalues;
+    const char **key_values = numheaders ? new const char *[numheaders * 2] : NULL;
+    for (int i = 0; i < numheaders; i++) {
+        jstring jkey = (jstring) (env->GetObjectArrayElement(jkeys, i));
+        jstring jvalue = (jstring) (env->GetObjectArrayElement(jvalues, i));
+        const char *key = env->GetStringUTFChars(jkey, NULL);
+        const char *value = env->GetStringUTFChars(jvalue, NULL);
+        key_values[i * 2] = key;
+        key_values[i * 2 + 1] = value;
+    }
+
+    int err;
+    AMediaDataSource *src = NULL;
+    if (testNativeSource) {
+        src = AMediaDataSource_newUri(tmp, numheaders, key_values);
+        err = src ? AMediaExtractor_setDataSourceCustom(ex, src) : -1;
+    } else {
+        err = AMediaExtractor_setDataSource(ex, tmp);
+    }
+
+    for (int i = 0; i < numheaders; i++) {
+        jstring jkey = (jstring) (env->GetObjectArrayElement(jkeys, i));
+        jstring jvalue = (jstring) (env->GetObjectArrayElement(jvalues, i));
+        env->ReleaseStringUTFChars(jkey, key_values[i * 2]);
+        env->ReleaseStringUTFChars(jvalue, key_values[i * 2 + 1]);
+    }
 
     env->ReleaseStringUTFChars(jpath, tmp);
+    delete[] key_values;
 
     if (err != 0) {
         ALOGE("setDataSource error: %d", err);
+        AMediaExtractor_delete(ex);
+        AMediaDataSource_delete(src);
         return NULL;
     }
-    return testExtractor(ex, env);
-}
 
-static int adler32(const uint8_t *input, int len) {
-
-    int a = 1;
-    int b = 0;
-    for (int i = 0; i < len; i++) {
-        a += input[i];
-        b += a;
-    }
-    a = a % 65521;
-    b = b % 65521;
-    int ret = b * 65536 + a;
-    ALOGV("adler %d/%d", len, ret);
+    jobject ret = testExtractor(ex, env);
+    AMediaDataSource_delete(src);
     return ret;
 }
 
@@ -403,7 +441,7 @@
 }
 
 extern "C" jlong Java_android_media_cts_NativeDecoderTest_getExtractorCachedDurationNative(
-        JNIEnv * env, jclass /*clazz*/, jstring jpath)
+        JNIEnv * env, jclass /*clazz*/, jstring jpath, jboolean testNativeSource)
 {
     AMediaExtractor *ex = AMediaExtractor_new();
 
@@ -413,18 +451,27 @@
         return -1;
     }
 
-    int err = AMediaExtractor_setDataSource(ex, tmp);
+    int err;
+    AMediaDataSource *src = NULL;
+    if (testNativeSource) {
+        src = AMediaDataSource_newUri(tmp, 0, NULL);
+        err = src ? AMediaExtractor_setDataSourceCustom(ex, src) : -1;
+    } else {
+        err = AMediaExtractor_setDataSource(ex, tmp);
+    }
 
     env->ReleaseStringUTFChars(jpath, tmp);
 
     if (err != 0) {
         ALOGE("setDataSource error: %d", err);
         AMediaExtractor_delete(ex);
+        AMediaDataSource_delete(src);
         return -1;
     }
 
     int64_t cachedDurationUs = AMediaExtractor_getCachedDuration(ex);
     AMediaExtractor_delete(ex);
+    AMediaDataSource_delete(src);
     return cachedDurationUs;
 
 }
@@ -918,6 +965,47 @@
     return true;
 }
 
+extern "C" jlong Java_android_media_cts_NativeDecoderTest_createAMediaExtractor(JNIEnv * /*env*/,
+        jclass /*clazz*/) {
+    AMediaExtractor *ex = AMediaExtractor_new();
+    return reinterpret_cast<jlong>(ex);
+}
+
+extern "C" jlong Java_android_media_cts_NativeDecoderTest_createAMediaDataSource(JNIEnv * env,
+        jclass /*clazz*/, jstring jurl) {
+    const char *url = env->GetStringUTFChars(jurl, NULL);
+    if (url == NULL) {
+        ALOGE("GetStringUTFChars error");
+        return 0;
+    }
+
+    AMediaDataSource *ds = AMediaDataSource_newUri(url, 0, NULL);
+    env->ReleaseStringUTFChars(jurl, url);
+    return reinterpret_cast<jlong>(ds);
+}
+
+extern "C" jint Java_android_media_cts_NativeDecoderTest_setAMediaExtractorDataSource(JNIEnv * /*env*/,
+        jclass /*clazz*/, jlong jex, jlong jds) {
+    AMediaExtractor *ex = reinterpret_cast<AMediaExtractor *>(jex);
+    AMediaDataSource *ds = reinterpret_cast<AMediaDataSource *>(jds);
+    return AMediaExtractor_setDataSourceCustom(ex, ds);
+}
+
+extern "C" void Java_android_media_cts_NativeDecoderTest_closeAMediaDataSource(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
+    AMediaDataSource_close(reinterpret_cast<AMediaDataSource *>(ds));
+}
+
+extern "C" void Java_android_media_cts_NativeDecoderTest_deleteAMediaExtractor(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ex) {
+    AMediaExtractor_delete(reinterpret_cast<AMediaExtractor *>(ex));
+}
+
+extern "C" void Java_android_media_cts_NativeDecoderTest_deleteAMediaDataSource(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
+    AMediaDataSource_delete(reinterpret_cast<AMediaDataSource *>(ds));
+}
+//
 // === NdkMediaCodec
 
 extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreateCodecByName(
@@ -1382,3 +1470,40 @@
     ANativeWindow_release(reinterpret_cast<ANativeWindow *>(nativeWindow));
 
 }
+
+extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testMediaFormatNative(
+        JNIEnv * /*env*/, jclass /*clazz*/) {
+
+    AMediaFormat *original = AMediaFormat_new();
+    AMediaFormat *copy = AMediaFormat_new();
+    jboolean ret = false;
+    while (true) {
+        AMediaFormat_setInt64(original, AMEDIAFORMAT_KEY_DURATION, 1234ll);
+        int64_t value = 0;
+        if (!AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("format missing expected entry");
+            break;
+        }
+        AMediaFormat_copy(copy, original);
+        value = 0;
+        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("copied format missing expected entry");
+            break;
+        }
+        AMediaFormat_clear(original);
+        if (AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value)) {
+            ALOGE("format still has entry after clear");
+            break;
+        }
+        value = 0;
+        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("copied format missing expected entry");
+            break;
+        }
+        ret = true;
+        break;
+    }
+    AMediaFormat_delete(original);
+    AMediaFormat_delete(copy);
+    return ret;
+}
diff --git a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
index ea95574..2c119cc 100644
--- a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
@@ -56,6 +56,8 @@
 
 static fields_t gFieldIds;
 static bool gGotVendorDefinedEvent = false;
+static bool gListenerGotValidExpiryTime = false;
+static bool gOnKeyChangeListenerOK = false;
 
 static const size_t kPlayTimeSeconds = 30;
 static const size_t kUuidSize = 16;
@@ -206,6 +208,57 @@
     return JNI_TRUE;
 }
 
+extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(
+        JNIEnv* env, jclass /* clazz */, jbyteArray uuid) {
+
+    if (NULL == uuid) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "uuid is NULL");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaDrm* drm = AMediaDrm_createByUUID(&juuid[0]);
+    const char *propertyName = "clientId";
+    const uint8_t value[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    media_status_t status = AMediaDrm_setPropertyByteArray(drm, propertyName, value, sizeof(value));
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException", "setPropertyByteArray failed");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    AMediaDrmByteArray array;
+    status = AMediaDrm_getPropertyByteArray(drm, propertyName, &array);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException", "getPropertyByteArray failed");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (array.length != sizeof(value)) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array size differs");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (!array.ptr) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array pointer is null");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (memcmp(array.ptr, value, sizeof(value)) != 0) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array content differs");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    AMediaDrm_release(drm);
+    return JNI_TRUE;
+}
+
 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(
     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
 
@@ -490,12 +543,57 @@
             gGotVendorDefinedEvent = true;
             ALOGD("EVENT_VENDOR_DEFINED received");
             break;
+        case EVENT_SESSION_RECLAIMED:
+            ALOGD("EVENT_SESSION_RECLAIMED received");
+            break;
         default:
             ALOGD("Unknown event received");
             break;
     }
 }
 
+static void onExpirationUpdateListener(
+    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
+    int64_t expiryTimeInMS) {
+
+    if (expiryTimeInMS == 100) {
+        ALOGD("Updates new expiration time to %" PRId64 " ms", expiryTimeInMS);
+        gListenerGotValidExpiryTime = true;
+    } else {
+        ALOGE("Expects 100 ms for expiry time, received: %" PRId64 " ms", expiryTimeInMS);
+        gListenerGotValidExpiryTime = false;
+    }
+}
+
+static void onKeysChangeListener(
+    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
+    const AMediaDrmKeyStatus* keysStatus, size_t numKeys, bool hasNewUsableKey) {
+
+    gOnKeyChangeListenerOK = false;
+    if (numKeys != 2) {
+        ALOGE("Expects 2 keys, received %zd keys", numKeys);
+        return;
+    }
+
+    if (!hasNewUsableKey) {
+        ALOGE("Expects hasNewUsableKey to be true");
+        return;
+    }
+
+    ALOGD("Number of keys changed=%zd", numKeys);
+    AMediaDrmKeyStatus keyStatus;
+    for (size_t i = 0; i < numKeys; ++i) {
+        keyStatus.keyId.ptr = keysStatus[i].keyId.ptr;
+        keyStatus.keyId.length = keysStatus[i].keyId.length;
+        keyStatus.keyType = keysStatus[i].keyType;
+
+        ALOGD("key[%zd]: key: %0x, %0x, %0x", i, keyStatus.keyId.ptr[0], keyStatus.keyId.ptr[1],
+                keyStatus.keyId.ptr[2]);
+        ALOGD("key[%zd]: key type=%d", i, keyStatus.keyType);
+    }
+    gOnKeyChangeListenerOK = true;
+}
+
 static void acquireLicense(
     JNIEnv* env, const AMediaObjects& aMediaObjects, const AMediaDrmSessionId& sessionId,
     AMediaDrmKeyType keyType) {
@@ -530,6 +628,8 @@
 
     AMediaDrmKeySetId keySetId;
     gGotVendorDefinedEvent = false;
+    gListenerGotValidExpiryTime = false;
+    gOnKeyChangeListenerOK = false;
     status = AMediaDrm_provideKeyResponse(aMediaObjects.getDrm(), &sessionId,
             reinterpret_cast<const uint8_t*>(kResponse),
             sizeof(kResponse), &keySetId);
@@ -577,6 +677,22 @@
         return JNI_FALSE;
     }
 
+    status = AMediaDrm_setOnExpirationUpdateListener(aMediaObjects.getDrm(),
+            onExpirationUpdateListener);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "setOnExpirationUpdateListener failed");
+        return JNI_FALSE;
+    }
+
+    status = AMediaDrm_setOnKeysChangeListener(aMediaObjects.getDrm(),
+            onKeysChangeListener);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "setOnKeysChangeListener failed");
+        return JNI_FALSE;
+    }
+
     aMediaObjects.setAudioExtractor(AMediaExtractor_new());
     const char* url = env->GetStringUTFChars(params.audioUrl, 0);
     if (url) {
@@ -615,7 +731,7 @@
 
     acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
 
-    // Check if the event listener has received the expected event sent by
+    // Checks if the event listener has received the expected event sent by
     // provideKeyResponse. This is for testing AMediaDrm_setOnEventListener().
     const char *utf8_outValue = NULL;
     status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
@@ -624,10 +740,13 @@
         std::string eventType(utf8_outValue);
         if (eventType.compare("true") == 0) {
             int count = 0;
-            while (!gGotVendorDefinedEvent && count++ < 5) {
+            while ((!gGotVendorDefinedEvent ||
+                    !gListenerGotValidExpiryTime ||
+                    !gOnKeyChangeListenerOK) && count++ < 5) {
                // Prevents race condition when the event arrives late
                usleep(2000);
             }
+
             if (!gGotVendorDefinedEvent) {
                 ALOGE("Event listener did not receive the expected event.");
                 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
@@ -635,6 +754,22 @@
                 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
                 return JNI_FALSE;
            }
+
+          // Checks if onExpirationUpdateListener received the correct expiry time.
+           if (!gListenerGotValidExpiryTime) {
+               jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                       "onExpirationUpdateListener received incorrect expiry time.");
+               AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+               return JNI_FALSE;
+           }
+
+          // Checks if onKeysChangeListener succeeded.
+          if (!gOnKeyChangeListenerOK) {
+              jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                      "onKeysChangeListener failed");
+              AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+              return JNI_FALSE;
+          }
         }
     }
 
@@ -726,7 +861,7 @@
         ALOGI("AMediaDrm_queryKeyStatus: key=%s, value=%s", keyStatus[i].mKey, keyStatus[i].mValue);
     }
 
-    if (numPairs !=  3) {
+    if (numPairs != 3) {
         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
                 "AMediaDrm_queryKeyStatus returns %zd key status, expecting 3", numPairs);
         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
@@ -810,6 +945,10 @@
             "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative },
 
+    { "testPropertyByteArrayNative",
+            "([B)Z",
+            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative },
+
     { "testPsshNative", "([BLjava/lang/String;)Z",
             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative },
 
diff --git a/tests/tests/media/libndkaudio/Android.mk b/tests/tests/media/libndkaudio/Android.mk
index 982c630..9480dcd 100644
--- a/tests/tests/media/libndkaudio/Android.mk
+++ b/tests/tests/media/libndkaudio/Android.mk
@@ -57,5 +57,6 @@
 LOCAL_CERTIFICATE := platform
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 23
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
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
new file mode 100644
index 0000000..e39e5c4
--- /dev/null
+++ b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp
Binary files differ
diff --git a/tests/tests/media/res/raw/a_4_haptic.ogg b/tests/tests/media/res/raw/a_4_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/a_4_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_haptic.ogg b/tests/tests/media/res/raw/b_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/b_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mkv b/tests/tests/media/res/raw/bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mkv
new file mode 100644
index 0000000..8963f2c
--- /dev/null
+++ b/tests/tests/media/res/raw/bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mkv
Binary files differ
diff --git a/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames.mp4 b/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames.mp4
index 6609614..8c0d787 100644
--- a/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames.mp4
+++ b/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames_editlist.mp4 b/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames_editlist.mp4
new file mode 100644
index 0000000..6609614
--- /dev/null
+++ b/tests/tests/media/res/raw/binary_counter_320x240_30fps_600frames_editlist.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_haptic.ogg b/tests/tests/media/res/raw/e_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/e_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
new file mode 100644
index 0000000..bae9cd8
--- /dev/null
+++ b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/testamr.amr b/tests/tests/media/res/raw/testamr.amr
new file mode 100644
index 0000000..36c259c
--- /dev/null
+++ b/tests/tests/media/res/raw/testamr.amr
Binary files differ
diff --git a/tests/tests/media/res/raw/testopus.opus b/tests/tests/media/res/raw/testopus.opus
new file mode 100644
index 0000000..f2d598a
--- /dev/null
+++ b/tests/tests/media/res/raw/testopus.opus
Binary files differ
diff --git a/tests/tests/media/res/raw/timestamps_binary_counter_320x240_30fps_600frames.txt b/tests/tests/media/res/raw/timestamps_binary_counter_320x240_30fps_600frames.txt
new file mode 100644
index 0000000..388bacc
--- /dev/null
+++ b/tests/tests/media/res/raw/timestamps_binary_counter_320x240_30fps_600frames.txt
@@ -0,0 +1,600 @@
+66666
+166666
+100000
+133333
+300000
+233333
+200000
+266666
+433333
+366666
+333333
+400000
+566666
+500000
+466666
+533333
+666666
+600000
+633333
+800000
+733333
+700000
+766666
+900000
+833333
+866666
+1033333
+966666
+933333
+1000000
+1066666
+1166666
+1100000
+1133333
+1300000
+1233333
+1200000
+1266666
+1433333
+1366666
+1333333
+1400000
+1566666
+1500000
+1466666
+1533333
+1666666
+1600000
+1633333
+1800000
+1733333
+1700000
+1766666
+1900000
+1833333
+1866666
+2000000
+1933333
+1966666
+2033333
+2066666
+2166666
+2100000
+2133333
+2300000
+2233333
+2200000
+2266666
+2433333
+2366666
+2333333
+2400000
+2566666
+2500000
+2466666
+2533333
+2700000
+2633333
+2600000
+2666666
+2833333
+2766666
+2733333
+2800000
+2966666
+2900000
+2866666
+2933333
+3033333
+3000000
+3066666
+3166666
+3100000
+3133333
+3300000
+3233333
+3200000
+3266666
+3433333
+3366666
+3333333
+3400000
+3566666
+3500000
+3466666
+3533333
+3666666
+3600000
+3633333
+3800000
+3733333
+3700000
+3766666
+3900000
+3833333
+3866666
+4000000
+3933333
+3966666
+4033333
+4066666
+4166666
+4100000
+4133333
+4300000
+4233333
+4200000
+4266666
+4433333
+4366666
+4333333
+4400000
+4566666
+4500000
+4466666
+4533333
+4666666
+4600000
+4633333
+4733333
+4700000
+4833333
+4766666
+4800000
+4966666
+4900000
+4866666
+4933333
+5033333
+5000000
+5066666
+5166666
+5100000
+5133333
+5300000
+5233333
+5200000
+5266666
+5433333
+5366666
+5333333
+5400000
+5566666
+5500000
+5466666
+5533333
+5666666
+5600000
+5633333
+5800000
+5733333
+5700000
+5766666
+5900000
+5833333
+5866666
+6033333
+5966666
+5933333
+6000000
+6066666
+6166666
+6100000
+6133333
+6300000
+6233333
+6200000
+6266666
+6433333
+6366666
+6333333
+6400000
+6566666
+6500000
+6466666
+6533333
+6666666
+6600000
+6633333
+6800000
+6733333
+6700000
+6766666
+6900000
+6833333
+6866666
+7000000
+6933333
+6966666
+7033333
+7066666
+7166666
+7100000
+7133333
+7300000
+7233333
+7200000
+7266666
+7433333
+7366666
+7333333
+7400000
+7566666
+7500000
+7466666
+7533333
+7700000
+7633333
+7600000
+7666666
+7833333
+7766666
+7733333
+7800000
+7966666
+7900000
+7866666
+7933333
+8033333
+8000000
+8066666
+8166666
+8100000
+8133333
+8300000
+8233333
+8200000
+8266666
+8433333
+8366666
+8333333
+8400000
+8566666
+8500000
+8466666
+8533333
+8666666
+8600000
+8633333
+8800000
+8733333
+8700000
+8766666
+8900000
+8833333
+8866666
+9000000
+8933333
+8966666
+9033333
+9066666
+9166666
+9100000
+9133333
+9300000
+9233333
+9200000
+9266666
+9366666
+9333333
+9500000
+9433333
+9400000
+9466666
+9633333
+9566666
+9533333
+9600000
+9766666
+9700000
+9666666
+9733333
+9900000
+9833333
+9800000
+9866666
+10000000
+9933333
+9966666
+10033333
+10066666
+10166666
+10100000
+10133333
+10300000
+10233333
+10200000
+10266666
+10433333
+10366666
+10333333
+10400000
+10566666
+10500000
+10466666
+10533333
+10666666
+10600000
+10633333
+10800000
+10733333
+10700000
+10766666
+10900000
+10833333
+10866666
+11033333
+10966666
+10933333
+11000000
+11066666
+11166666
+11100000
+11133333
+11300000
+11233333
+11200000
+11266666
+11433333
+11366666
+11333333
+11400000
+11566666
+11500000
+11466666
+11533333
+11666666
+11600000
+11633333
+11800000
+11733333
+11700000
+11766666
+11900000
+11833333
+11866666
+12000000
+11933333
+11966666
+12033333
+12066666
+12166666
+12100000
+12133333
+12300000
+12233333
+12200000
+12266666
+12433333
+12366666
+12333333
+12400000
+12566666
+12500000
+12466666
+12533333
+12700000
+12633333
+12600000
+12666666
+12833333
+12766666
+12733333
+12800000
+12966666
+12900000
+12866666
+12933333
+13033333
+13000000
+13066666
+13166666
+13100000
+13133333
+13300000
+13233333
+13200000
+13266666
+13433333
+13366666
+13333333
+13400000
+13566666
+13500000
+13466666
+13533333
+13666666
+13600000
+13633333
+13800000
+13733333
+13700000
+13766666
+13900000
+13833333
+13866666
+14000000
+13933333
+13966666
+14033333
+14066666
+14166666
+14100000
+14133333
+14300000
+14233333
+14200000
+14266666
+14433333
+14366666
+14333333
+14400000
+14566666
+14500000
+14466666
+14533333
+14666666
+14600000
+14633333
+14733333
+14700000
+14833333
+14766666
+14800000
+14966666
+14900000
+14866666
+14933333
+15033333
+15000000
+15066666
+15166666
+15100000
+15133333
+15300000
+15233333
+15200000
+15266666
+15433333
+15366666
+15333333
+15400000
+15566666
+15500000
+15466666
+15533333
+15666666
+15600000
+15633333
+15800000
+15733333
+15700000
+15766666
+15900000
+15833333
+15866666
+16033333
+15966666
+15933333
+16000000
+16066666
+16166666
+16100000
+16133333
+16300000
+16233333
+16200000
+16266666
+16433333
+16366666
+16333333
+16400000
+16566666
+16500000
+16466666
+16533333
+16666666
+16600000
+16633333
+16800000
+16733333
+16700000
+16766666
+16900000
+16833333
+16866666
+17000000
+16933333
+16966666
+17033333
+17066666
+17166666
+17100000
+17133333
+17300000
+17233333
+17200000
+17266666
+17433333
+17366666
+17333333
+17400000
+17566666
+17500000
+17466666
+17533333
+17700000
+17633333
+17600000
+17666666
+17833333
+17766666
+17733333
+17800000
+17966666
+17900000
+17866666
+17933333
+18033333
+18000000
+18066666
+18166666
+18100000
+18133333
+18300000
+18233333
+18200000
+18266666
+18433333
+18366666
+18333333
+18400000
+18566666
+18500000
+18466666
+18533333
+18666666
+18600000
+18633333
+18800000
+18733333
+18700000
+18766666
+18900000
+18833333
+18866666
+19000000
+18933333
+18966666
+19033333
+19066666
+19166666
+19100000
+19133333
+19300000
+19233333
+19200000
+19266666
+19366666
+19333333
+19500000
+19433333
+19400000
+19466666
+19633333
+19566666
+19533333
+19600000
+19766666
+19700000
+19666666
+19733333
+19900000
+19833333
+19800000
+19866666
+20000000
+19933333
+19966666
+20033333
diff --git a/tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp b/tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp
new file mode 100644
index 0000000..6ce3a29
--- /dev/null
+++ b/tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro.3gp b/tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp
similarity index 100%
rename from tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro.3gp
rename to tests/tests/media/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4
new file mode 100644
index 0000000..f91b80d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/volantis.jpg b/tests/tests/media/res/raw/volantis.jpg
new file mode 100644
index 0000000..cfe300f
--- /dev/null
+++ b/tests/tests/media/res/raw/volantis.jpg
Binary files differ
diff --git a/tests/tests/media/res/values/exifinterface.xml b/tests/tests/media/res/values/exifinterface.xml
index 23fbb93..3443f33 100644
--- a/tests/tests/media/res/values/exifinterface.xml
+++ b/tests/tests/media/res/values/exifinterface.xml
@@ -17,10 +17,14 @@
 <resources>
     <array name="exifbyteorderii_jpg">
         <item>true</item>
+        <item>3112</item>
+        <item>6265</item>
         <item>512</item>
         <item>288</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -48,10 +52,14 @@
     </array>
     <array name="exifbyteordermm_jpg">
         <item>false</item>
+        <item />
+        <item />
         <item>0</item>
         <item>0</item>
         <item>false</item>
         <item>true</item>
+        <item>675</item>
+        <item>24</item>
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -79,10 +87,14 @@
     </array>
     <array name="lg_g4_iso_800_dng">
         <item>true</item>
+        <item>0</item>
+        <item>15179</item>
         <item>256</item>
         <item>144</item>
         <item>true</item>
         <item>true</item>
+        <item>12486</item>
+        <item>24</item>
         <item>53.834507</item>
         <item>10.69585</item>
         <item>0.0</item>
@@ -110,10 +122,14 @@
     </array>
     <array name="volantis_jpg">
         <item>false</item>
+        <item />
+        <item />
         <item>0</item>
         <item>0</item>
         <item>false</item>
         <item>true</item>
+        <item>3081</item>
+        <item>24</item>
         <item>37.423</item>
         <item>-122.162</item>
         <item>0.0</item>
@@ -141,10 +157,14 @@
     </array>
     <array name="sony_rx_100_arw">
         <item>true</item>
+        <item>32176</item>
+        <item>7423</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -172,10 +192,14 @@
     </array>
     <array name="canon_g7x_cr2">
         <item>true</item>
+        <item>22528</item>
+        <item>14161</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -203,10 +227,14 @@
     </array>
     <array name="fuji_x20_raf">
         <item>true</item>
+        <item>2080</item>
+        <item>9352</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -234,10 +262,14 @@
     </array>
     <array name="nikon_1aw1_nef">
         <item>true</item>
+        <item>0</item>
+        <item>57600</item>
         <item>160</item>
         <item>120</item>
         <item>false</item>
         <item>true</item>
+        <item>962700</item>
+        <item>24</item>
         <item>53.83652</item>
         <item>10.69828</item>
         <item>0.0</item>
@@ -265,10 +297,14 @@
     </array>
     <array name="nikon_p330_nrw">
         <item>true</item>
+        <item>32791</item>
+        <item>4875</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>true</item>
+        <item>1620</item>
+        <item>24</item>
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -296,10 +332,14 @@
     </array>
     <array name="pentax_k5_pef">
         <item>true</item>
+        <item>103520</item>
+        <item>6532</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -327,10 +367,14 @@
     </array>
     <array name="olympus_e_pl3_orf">
         <item>true</item>
+        <item>19264</item>
+        <item>3698</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -358,10 +402,14 @@
     </array>
     <array name="panasonic_gm5_rw2">
         <item>true</item>
+        <item>18944</item>
+        <item>6435</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
@@ -389,10 +437,14 @@
     </array>
     <array name="samsung_nx3000_srw">
         <item>true</item>
+        <item>317560</item>
+        <item>3059</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
         <item>false</item>
+        <item />
+        <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index da99a12..3391aa3 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -47,6 +47,7 @@
 import java.util.Vector;
 import java.util.zip.CRC32;
 
+@MediaHeavyPresubmitTest
 @AppModeFull
 public class AdaptivePlaybackTest extends MediaPlayerTestBase {
     private static final String TAG = "AdaptivePlaybackTest";
diff --git a/tests/tests/media/src/android/media/cts/AudioFormatTest.java b/tests/tests/media/src/android/media/cts/AudioFormatTest.java
index e37f64e..221dcfa 100644
--- a/tests/tests/media/src/android/media/cts/AudioFormatTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioFormatTest.java
@@ -54,6 +54,10 @@
                 TEST_CONF_POS, copiedFormat.getChannelMask());
         assertEquals("New AudioFormat has wrong channel index mask",
                 TEST_CONF_IDX, copiedFormat.getChannelIndexMask());
+        assertEquals("New AudioFormat has wrong channel count",
+                6, copiedFormat.getChannelCount());
+        assertEquals("New AudioFormat has the wrong frame size",
+                6 /* channels */ * 2 /* bytes per sample */, copiedFormat.getFrameSizeInBytes());
     }
 
     // Test case 2: Use Builder to duplicate an AudioFormat with only encoding supplied
@@ -169,4 +173,25 @@
         assertEquals("Source and destination AudioFormat not equal",
                 formatToMarshall, unmarshalledFormat);
     }
+
+    // Test case 7: Check frame size for compressed, float formats.
+    public void testFrameSize() throws Exception {
+        final AudioFormat formatMp3 = new AudioFormat.Builder()
+            .setEncoding(AudioFormat.ENCODING_MP3)
+            .setSampleRate(44100)
+            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+            .build();
+
+        assertEquals("MP3 AudioFormat has the wrong frame size",
+                1, formatMp3.getFrameSizeInBytes());
+
+        final AudioFormat formatPcmFloat = new AudioFormat.Builder()
+            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+            .setSampleRate(192000)
+            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+            .build();
+
+        assertEquals("Float AudioFormat has the wrong frame size",
+            2 /* channels */ * 4 /* bytes per sample */, formatPcmFloat.getFrameSizeInBytes());
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 4c46420..c10bfbe 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -194,29 +194,6 @@
         }
     }
 
-    // helper class to simplify that abstracts out the handling of spurious wakeups in Object.wait()
-    private static final class SafeWaitObject {
-        private boolean mQuit = false;
-
-        public void safeNotify() {
-            synchronized (this) {
-                mQuit = true;
-                this.notify();
-            }
-        }
-
-        public void safeWait(long millis) throws InterruptedException {
-            final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
-            synchronized (this) {
-                while (!mQuit) {
-                    final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
-                    if (timeToWait < 0) { break; }
-                    this.wait(timeToWait);
-                }
-            }
-        }
-    }
-
     private static final class MyBlockingIntentReceiver extends BroadcastReceiver {
         private final SafeWaitObject mLock = new SafeWaitObject();
         // state protected by mLock
@@ -1486,6 +1463,11 @@
         }
     }
 
+    public void testIsHapticPlaybackSupported() throws Exception {
+        // Calling the API to make sure it doesn't crash.
+        Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
+    }
+
     private void setInterruptionFilter(int filter) throws Exception {
         mNm.setInterruptionFilter(filter);
         for (int i = 0; i < 5; i++) {
diff --git a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java b/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
index 227fca5..f9df727 100644
--- a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
@@ -16,6 +16,9 @@
 
 package android.media.cts;
 
+import static org.junit.Assert.assertNotEquals;
+
+import android.icu.util.ULocale;
 import android.media.AudioPresentation;
 import android.util.Log;
 
@@ -39,27 +42,145 @@
         final boolean HAS_SPOKEN_SUBTITLES = true;
         final boolean HAS_DIALOGUE_ENHANCEMENT = true;
 
-        AudioPresentation presentation = new AudioPresentation(
-                PRESENTATION_ID,
-                PROGRAM_ID,
-                labelsLocaleToString(LABELS),
-                LOCALE.toString(),
-                MASTERING_INDICATION,
-                HAS_AUDIO_DESCRIPTION,
-                HAS_SPOKEN_SUBTITLES,
-                HAS_DIALOGUE_ENHANCEMENT);
+        AudioPresentation presentation = (new AudioPresentation.Builder(PRESENTATION_ID)
+                .setProgramId(PROGRAM_ID)
+                .setLocale(ULocale.forLocale(LOCALE))
+                .setLabels(localeToULocale(LABELS))
+                .setMasteringIndication(MASTERING_INDICATION)
+                .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)
+                .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)
+                .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
         assertEquals(PRESENTATION_ID, presentation.getPresentationId());
         assertEquals(PROGRAM_ID, presentation.getProgramId());
-        assertEquals(LABELS.toString().toLowerCase(),
-                presentation.getLabels().toString().toLowerCase());
-        assertEquals(LOCALE.toString().toLowerCase(),
-                presentation.getLocale().toString().toLowerCase());
+        assertEquals(LABELS, presentation.getLabels());
+        assertEquals(LOCALE, presentation.getLocale());
         assertEquals(MASTERING_INDICATION, presentation.getMasteringIndication());
         assertEquals(HAS_AUDIO_DESCRIPTION, presentation.hasAudioDescription());
         assertEquals(HAS_SPOKEN_SUBTITLES, presentation.hasSpokenSubtitles());
         assertEquals(HAS_DIALOGUE_ENHANCEMENT, presentation.hasDialogueEnhancement());
     }
 
+    public void testEqualsAndHashCode() throws Exception {
+        final int PRESENTATION_ID = 42;
+        final int PROGRAM_ID = 43;
+        final Map<Locale, String> LABELS = generateLabels();
+        final Locale LOCALE = Locale.US;
+        final Locale LOCALE_3 = Locale.FRENCH;
+        final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
+        final int MASTERING_INDICATION_3 = AudioPresentation.MASTERED_FOR_HEADPHONE;
+        final boolean HAS_AUDIO_DESCRIPTION = false;
+        final boolean HAS_SPOKEN_SUBTITLES = true;
+        final boolean HAS_DIALOGUE_ENHANCEMENT = true;
+
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID))
+                    .build();
+            assertEquals(presentation1, presentation1);
+            assertNotEquals(presentation1, null);
+            assertNotEquals(presentation1, new Object());
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID))
+                    .build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID + 1))
+                    .build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID + 1)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE))).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE))).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE_3))).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(localeToULocale(LABELS))).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(localeToULocale(LABELS))).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(new HashMap<ULocale, String>())).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION_3)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(!HAS_AUDIO_DESCRIPTION)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(!HAS_SPOKEN_SUBTITLES)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(!HAS_DIALOGUE_ENHANCEMENT)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+    }
+
     private static Map<Locale, String> generateLabels() {
         Map<Locale, String> result = new HashMap<Locale, String>();
         result.put(Locale.US, Locale.US.getDisplayLanguage());
@@ -68,11 +189,11 @@
         return result;
     }
 
-    private static Map<String, String> labelsLocaleToString(Map<Locale, String> localesMap) {
-        Map<String, String> result = new HashMap<String, String>();
-        for (Map.Entry<Locale, String> entry : localesMap.entrySet()) {
-            result.put(entry.getKey().toString(), entry.getValue());
+    private static Map<ULocale, String> localeToULocale(Map<Locale, String> locales) {
+        Map<ULocale, String> ulocaleLabels = new HashMap<ULocale, String>();
+        for (Map.Entry<Locale, String> entry : locales.entrySet()) {
+            ulocaleLabels.put(ULocale.forLocale(entry.getKey()), entry.getValue());
         }
-        return result;
+        return ulocaleLabels;
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
new file mode 100644
index 0000000..0a87f31
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
@@ -0,0 +1,182 @@
+/*
+ **
+ ** 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.content.res.AssetFileDescriptor;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+
+public class AudioTrackOffloadTest extends CtsAndroidTestCase {
+    private static final String TAG = "AudioTrackOffloadTest";
+
+    private static final int MP3_BUFF_SIZE = 192 * 1024 * 3 / 8; // 3s for 192kbps MP3
+
+    private static final int PRESENTATION_END_TIMEOUT_MS = 8 * 1000; // 8s
+
+
+    public void testIsOffloadSupportedNullFormat() throws Exception {
+        try {
+            final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(null);
+            fail("Shouldn't be able to use null AudioFormat in isOffloadedPlaybackSupported()");
+        } catch (IllegalArgumentException e) {
+            // ok, IAE is expected here
+        }
+    }
+
+
+    public void testExerciseIsOffloadSupported() throws Exception {
+        final AudioFormat af = new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_MP3)
+                .setSampleRate(44100)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .build();
+        final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(af);
+    }
+
+
+    public void testAudioTrackOffload() throws Exception {
+        final AudioFormat formatToOffload = new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_MP3)
+                .setSampleRate(44100)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .build();
+        AudioTrack track = null;
+
+        try (AssetFileDescriptor mp3ToOffload = getContext().getResources()
+                .openRawResourceFd(R.raw.sine1khzs40dblong);
+             InputStream mp3InputStream = mp3ToOffload.createInputStream()) {
+
+            long mp3ToOffloadLength = mp3ToOffload.getLength();
+            if (!AudioManager.isOffloadedPlaybackSupported(formatToOffload)) {
+                Log.i(TAG, "skipping test testPlayback");
+                // cannot test if offloading is not supported
+                return;
+            }
+
+            // format is offloadable, test playback head is progressing
+            track = new AudioTrack.Builder()
+                    .setAudioFormat(formatToOffload)
+                    .setTransferMode(AudioTrack.MODE_STREAM)
+                    .setBufferSizeInBytes(MP3_BUFF_SIZE)
+                    .setOffloadedPlayback(true).build();
+            assertNotNull("Couldn't create offloaded AudioTrack", track);
+            assertEquals("Unexpected track sample rate", 44100, track.getSampleRate());
+            assertEquals("Unexpected track channel config", AudioFormat.CHANNEL_OUT_STEREO,
+                    track.getChannelConfiguration());
+
+            try {
+                track.registerStreamEventCallback(mExec, null);
+                fail("Shouldn't be able to register null StreamEventCallback");
+            } catch (Exception e) { }
+            track.registerStreamEventCallback(mExec, mCallback);
+
+            final byte[] data = new byte[MP3_BUFF_SIZE];
+            final int read = mp3InputStream.read(data);
+
+            track.play();
+            int written = 0;
+            while (written < read) {
+                int wrote = track.write(data, written, read - written,
+                        AudioTrack.WRITE_BLOCKING);
+                if (wrote < 0) {
+                    fail("Unable to write all read data, wrote " + written + " bytes");
+                }
+                written += wrote;
+            }
+            try {
+                Thread.sleep(1 * 1000);
+                synchronized(mPresEndLock) {
+                    track.stop();
+                    mPresEndLock.safeWait(PRESENTATION_END_TIMEOUT_MS);
+                }
+            } catch (InterruptedException e) { fail("Error while sleeping"); }
+            synchronized (mEventCallbackLock) {
+                assertTrue("onDataRequest not called", mCallback.mDataRequestCount > 0);
+            }
+            synchronized (mPresEndLock) {
+                // we are at most PRESENTATION_END_TIMEOUT_MS + 1s after about 3s of data was
+                // supplied, presentation should have ended
+                assertEquals("onPresentationEnded not called one time",
+                        1, mCallback.mPresentationEndedCount);
+            }
+
+        } finally {
+            if (track != null) {
+                Log.i(TAG, "pause");
+                track.pause();
+                track.unregisterStreamEventCallback(mCallback);
+                track.release();
+            }
+        }
+    }
+
+    private Executor mExec = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    private final Object mEventCallbackLock = new Object();
+    private final SafeWaitObject mPresEndLock = new SafeWaitObject();
+
+    private EventCallback mCallback = new EventCallback();
+
+    private class EventCallback extends AudioTrack.StreamEventCallback {
+        @GuardedBy("mEventCallbackLock")
+        int mTearDownCount;
+        @GuardedBy("mPresEndLock")
+        int mPresentationEndedCount;
+        @GuardedBy("mEventCallbackLock")
+        int mDataRequestCount;
+
+        @Override
+        public void onTearDown(AudioTrack track) {
+            synchronized (mEventCallbackLock) {
+                Log.i(TAG, "onTearDown");
+                mTearDownCount++;
+            }
+        }
+
+        @Override
+        public void onPresentationEnded(AudioTrack track) {
+            synchronized (mPresEndLock) {
+                Log.i(TAG, "onPresentationEnded");
+                mPresentationEndedCount++;
+                mPresEndLock.safeNotify();
+            }
+        }
+
+        @Override
+        public void onDataRequest(AudioTrack track, int size) {
+            synchronized (mEventCallbackLock) {
+                Log.i(TAG, "onDataRequest size:"+size);
+                mDataRequestCount++;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
index 9de3bd2..4c2c164 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
@@ -290,7 +290,8 @@
             assertTrue("expect many timestamps, got " + mTimestamps.size(),
                     mTimestamps.size() > 10);
             // Use first and last timestamp to get the most accurate rate.
-            AudioTimestamp first = mTimestamps.get(0);
+            // TODO: Actually we're using the second timestamp since the first one is flaky.
+            AudioTimestamp first = mTimestamps.get(1);
             AudioTimestamp last = mTimestamps.get(mTimestamps.size() - 1);
             return calculateSampleRate(first, last);
         }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 47a3a31..762bc44 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static org.testng.Assert.assertThrows;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -358,24 +360,29 @@
         //     use a stream type documented in AudioAttributes.Builder.setLegacyStreamType(int)
         final int expectedStreamType = AudioManager.STREAM_ALARM;
         final int expectedContentType = AudioAttributes.CONTENT_TYPE_SPEECH;
-        final AudioAttributes aa = new AudioAttributes.Builder()
+        final AudioAttributes attributes = new AudioAttributes.Builder()
                 .setLegacyStreamType(expectedStreamType)
                 .setContentType(expectedContentType)
                 .build();
         // use builder
         final AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(aa)
+                .setAudioAttributes(attributes)
                 .build();
         // save results
         final int observedStreamType = track.getStreamType();
+        final AudioAttributes observedAttributes = track.getAudioAttributes();
+
         // release track before the test exits (either successfully or with an exception)
         track.release();
         // compare results
         assertEquals(TEST_NAME + ": track stream type", expectedStreamType, observedStreamType);
+        // attributes and observedAttributes should satisfy the overloaded equals.
+        assertEquals(TEST_NAME + ": observed attributes must match",
+                attributes, observedAttributes);
         //    also test content type was preserved in the attributes even though they
         //     were first configured with a legacy stream type
         assertEquals(TEST_NAME + ": attributes content type", expectedContentType,
-                aa.getContentType());
+                attributes.getContentType());
     }
 
     // Test case 5: build AudioTrack with attributes and performance mode
@@ -2516,11 +2523,38 @@
         }
     }
 
+    public void testSetNullPresentation() throws Exception {
+        final AudioTrack track = new AudioTrack.Builder().build();
+        assertThrows(IllegalArgumentException.class, () -> {
+            track.setPresentation(null);
+        });
+    }
+
     public void testSetPresentationDefaultTrack() throws Exception {
         final AudioTrack track = new AudioTrack.Builder().build();
         assertEquals(AudioTrack.ERROR, track.setPresentation(createAudioPresentation()));
     }
 
+    public void testIsDirectPlaybackSupported() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testIsDirectPlaybackSupported";
+        // Default format leaves everything unspecified
+        assertFalse(AudioTrack.isDirectPlaybackSupported(
+                        new AudioFormat.Builder().build(),
+                        new AudioAttributes.Builder().build()));
+        // There is no requirement to support direct playback for this format,
+        // so it's not possible to assert on the result, but at least the method
+        // must execute with no exceptions.
+        boolean isPcmStereo48kSupported = AudioTrack.isDirectPlaybackSupported(
+                new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .setSampleRate(48000)
+                .build(),
+                new AudioAttributes.Builder().build());
+        log(TEST_NAME, "PCM Stereo 48 kHz: " + isPcmStereo48kSupported);
+    }
+
 /* 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;
@@ -2562,14 +2596,6 @@
     }
 
     private static AudioPresentation createAudioPresentation() {
-        return new AudioPresentation(
-                42 /*presentationId*/,
-                43 /*programId*/,
-                new HashMap<String, String>(),
-                Locale.US.toString(),
-                AudioPresentation.MASTERING_NOT_INDICATED,
-                false /*audioDescriptionAvailable*/,
-                false /*spokenSubtitlesAvailable*/,
-                false /*dialogueEnhancementAvailable*/);
+        return (new AudioPresentation.Builder(42 /*presentationId*/)).build();
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 12ebeae..d55dc92 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -82,7 +82,7 @@
         mPresentationTimeUs = 0;
 
         String mime = mFormat.getString(MediaFormat.KEY_MIME);
-        Log.d(TAG, "CodecState::onOutputFormatChanged " + mime);
+        Log.d(TAG, "CodecState::CodecState " + mime);
         mIsAudio = mime.startsWith("audio/");
     }
 
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
index 2a056e4..0f404d1 100644
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
@@ -49,6 +49,7 @@
 
 @TargetApi(24)
 @RunWith(Parameterized.class)
+@MediaHeavyPresubmitTest
 @AppModeFull(reason = "There should be no instant apps specific behavior related to accuracy")
 public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 022b853..8cb2a27 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -59,6 +59,7 @@
 
 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 
+@MediaHeavyPresubmitTest
 @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
 public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
@@ -1948,6 +1949,14 @@
         testDecode(
                 R.raw.bbb_s2_1920x1080_mp4_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz,
                 300);
+        testDecode(
+                R.raw.bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz,
+                300);
+    }
+
+    public void testH265Decode25fps1280x720() throws Exception {
+        testDecode(
+                R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz, 240);
     }
 
     public void testVP8Decode320x180() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
index cf2e8f6..2319113 100644
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -24,6 +24,8 @@
 import android.media.MediaCodecList;
 import android.media.MediaFormat;
 import android.media.MediaMuxer;
+import android.platform.test.annotations.RequiresDevice;
+import android.support.test.filters.SmallTest;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -41,6 +43,8 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
+@SmallTest
+@RequiresDevice
 public class EncoderTest extends AndroidTestCase {
     private static final String TAG = "EncoderTest";
     private static final boolean VERBOSE = false;
@@ -103,6 +107,24 @@
         testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_WB, formats);
     }
 
+    public void testOpusEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kBitRates[] =
+            { 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 };
+
+        for (int j = 0; j < kBitRates.length; ++j) {
+            MediaFormat format  = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_OPUS);
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+            formats.push(format);
+        }
+
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_OPUS, formats);
+    }
+
     public void testAACEncoders() {
         LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
 
@@ -151,18 +173,28 @@
             MediaUtils.skipTest("no encoders found for " + Arrays.toString(formats));
             return;
         }
-        ExecutorService pool = Executors.newFixedThreadPool(3);
+
+        int ThreadCount = 3;
+        int testsStarted = 0;
+        int allowPerTest = 30;
+
+        ExecutorService pool = Executors.newFixedThreadPool(ThreadCount);
 
         for (String componentName : componentNames) {
             for (MediaFormat format : formats) {
                 assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
                 pool.execute(new EncoderRun(componentName, format));
+                testsStarted++;
             }
         }
         try {
             pool.shutdown();
+            int waitingSeconds = ((testsStarted + ThreadCount - 1) / ThreadCount) * allowPerTest;
+            waitingSeconds += 300;
+            Log.i(TAG, "waiting up to " + waitingSeconds + " seconds for "
+                            + testsStarted + " sub-tests to finish");
             assertTrue("timed out waiting for encoder threads",
-                    pool.awaitTermination(10, TimeUnit.MINUTES));
+                    pool.awaitTermination(waitingSeconds, TimeUnit.SECONDS));
         } catch (InterruptedException e) {
             fail("interrupted while waiting for encoder threads");
         }
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index f75a6fd..305a374 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -18,30 +18,25 @@
 
 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.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import libcore.io.IoUtils;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
 import java.io.IOException;
-import java.lang.reflect.Type;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
+import java.io.InputStream;
 
 @AppModeFull(reason = "Instant apps cannot access the SD card")
 public class ExifInterfaceTest extends AndroidTestCase {
@@ -99,10 +94,14 @@
         public final int thumbnailWidth;
         public final int thumbnailHeight;
         public final boolean isThumbnailCompressed;
+        public final int thumbnailOffset;
+        public final int thumbnailLength;
 
         // GPS information.
         public final boolean hasLatLong;
         public final float latitude;
+        public final int latitudeOffset;
+        public final int latitudeLength;
         public final float longitude;
         public final float altitude;
 
@@ -142,12 +141,16 @@
 
             // Reads thumbnail information.
             hasThumbnail = typedArray.getBoolean(index++, false);
+            thumbnailOffset = typedArray.getInt(index++, -1);
+            thumbnailLength = typedArray.getInt(index++, -1);
             thumbnailWidth = typedArray.getInt(index++, 0);
             thumbnailHeight = typedArray.getInt(index++, 0);
             isThumbnailCompressed = typedArray.getBoolean(index++, false);
 
             // Reads GPS information.
             hasLatLong = typedArray.getBoolean(index++, false);
+            latitudeOffset = typedArray.getInt(index++, -1);
+            latitudeLength = typedArray.getInt(index++, -1);
             latitude = typedArray.getFloat(index++, 0f);
             longitude = typedArray.getFloat(index++, 0f);
             altitude = typedArray.getFloat(index++, 0f);
@@ -244,13 +247,18 @@
     }
 
     private void compareWithExpectedValue(ExifInterface exifInterface,
-            ExpectedValue expectedValue, String verboseTag) {
+            ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
         if (VERBOSE) {
             printExifTagsAndValues(verboseTag, exifInterface);
         }
         // Checks a thumbnail image.
         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
         if (expectedValue.hasThumbnail) {
+            if (assertRanges) {
+                final long[] thumbnailRange = exifInterface.getThumbnailRange();
+                assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
+                assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
+            }
             byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
             assertNotNull(thumbnailBytes);
             Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
@@ -267,8 +275,19 @@
         float[] latLong = new float[2];
         assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
         if (expectedValue.hasLatLong) {
+            if (assertRanges) {
+                final long[] latitudeRange = exifInterface
+                        .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
+                assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
+                assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
+            }
             assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
             assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
+            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        } else {
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
         }
         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
 
@@ -309,14 +328,18 @@
         // Creates via path.
         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         assertNotNull(exifInterface);
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+
+        // Creates via file.
+        exifInterface = new ExifInterface(imageFile);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
 
         InputStream in = null;
         // Creates via InputStream.
         try {
             in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
             exifInterface = new ExifInterface(in);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
         } finally {
             IoUtils.closeQuietly(in);
         }
@@ -326,7 +349,7 @@
         try {
             fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
             exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
         } finally {
@@ -342,7 +365,7 @@
         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         exifInterface.saveAttributes();
         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
 
         // Test for modifying one attribute.
         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
@@ -354,7 +377,7 @@
         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
         exifInterface.saveAttributes();
         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
     }
 
     private void testSaveAttributes_withFileDescriptor(String fileName, ExpectedValue expectedValue)
@@ -369,7 +392,7 @@
             exifInterface.saveAttributes();
             Os.lseek(fd, 0, OsConstants.SEEK_SET);
             exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
 
             // Test for modifying one attribute.
             String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
@@ -383,7 +406,7 @@
             exifInterface.saveAttributes();
             Os.lseek(fd, 0, OsConstants.SEEK_SET);
             exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
         } finally {
diff --git a/tests/tests/media/src/android/media/cts/HandlerExecutor.java b/tests/tests/media/src/android/media/cts/HandlerExecutor.java
new file mode 100644
index 0000000..afeef51
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/HandlerExecutor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.media.cts;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+public class HandlerExecutor extends Handler implements Executor {
+    public HandlerExecutor(@NonNull Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!post(command)) {
+            throw new RejectedExecutionException(this + " is shutting down");
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 1104f9b..12be072 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -21,6 +21,8 @@
 import android.graphics.Color;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
@@ -67,6 +69,8 @@
     private static final String TAG = HeifWriterTest.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final boolean DUMP_YUV_INPUT = false;
+    private static final int GRID_WIDTH = 512;
+    private static final int GRID_HEIGHT = 512;
 
     private static byte[][] TEST_YUV_COLORS = {
             {(byte) 255, (byte) 0, (byte) 0},
@@ -213,6 +217,35 @@
         }
     }
 
+    /**
+     * This test is to ensure that if the device advertises support for {@link
+     * MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} (which encodes full-frame image
+     * with tiling), it must also support {@link MediaFormat#MIMETYPE_VIDEO_HEVC}
+     * at a specific tile size (512x512) with bitrate control mode {@link
+     * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CQ}, so that a fallback
+     * could be implemented for image resolutions that's not supported by the
+     * {@link MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} encoder.
+     */
+    public void testHeicFallbackAvailable() throws Throwable {
+        if (!MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
+            MediaUtils.skipTest("HEIC full-frame image encoder is not supported on this device");
+            return;
+        }
+
+        MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        assertNotNull("HEIC full-frame image encoder without HEVC fallback");
+
+        MediaCodecInfo.CodecCapabilities caps =
+                encoder.getCodecInfo().getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        assertTrue("HEVC fallback doesn't support tile size " + GRID_WIDTH + "x" + GRID_HEIGHT,
+                caps.getVideoCapabilities().isSizeSupported(GRID_WIDTH, GRID_HEIGHT));
+
+        MediaCodecInfo.EncoderCapabilities encoderCaps = caps.getEncoderCapabilities();
+        assertTrue("HEVC fallback doesn't support CQ mode",
+                encoderCaps.isBitrateModeSupported(
+                        MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ));
+    }
+
     private static boolean canEncodeHeic() {
         return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
             || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
index 540e3ff..1dd77d9 100644
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -38,6 +38,8 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.platform.test.annotations.RequiresDevice;
+import android.support.test.filters.SmallTest;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.view.Surface;
@@ -66,6 +68,8 @@
  * test. For decoder test, hw and sw decoders are tested,
  * </p>
  */
+@SmallTest
+@RequiresDevice
 public class ImageReaderDecoderTest extends AndroidTestCase {
     private static final String TAG = "ImageReaderDecoderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowser2Test.java b/tests/tests/media/src/android/media/cts/MediaBrowser2Test.java
deleted file mode 100644
index 4c10cf9..0000000
--- a/tests/tests/media/src/android/media/cts/MediaBrowser2Test.java
+++ /dev/null
@@ -1,671 +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 static android.media.cts.MockMediaLibraryService2.EXTRAS;
-import static android.media.cts.MockMediaLibraryService2.ROOT_ID;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.media.MediaBrowser2;
-import android.media.MediaBrowser2.BrowserCallback;
-import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaItem2;
-import android.media.MediaLibraryService2.MediaLibrarySession;
-import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
-import android.media.MediaMetadata2;
-import android.media.MediaSession2;
-import android.media.SessionCommand2;
-import android.media.MediaSession2.CommandButton;
-import android.media.SessionCommandGroup2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.SessionToken2;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.ResultReceiver;
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import junit.framework.Assert;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaBrowser2}.
- * <p>
- * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
- * {@link MediaController2} works cleanly.
- */
-// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Ignore
-public class MediaBrowser2Test extends MediaController2Test {
-    private static final String TAG = "MediaBrowser2Test";
-
-    @Override
-    TestControllerInterface onCreateController(@NonNull SessionToken2 token,
-            @Nullable ControllerCallback callback) {
-        if (callback == null) {
-            callback = new BrowserCallback() {};
-        }
-        return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
-    }
-
-    /**
-     * Test if the {@link TestBrowserCallback} wraps the callback proxy without missing any method.
-     */
-    @Test
-    public void testTestBrowserCallback() {
-        Method[] methods = TestBrowserCallback.class.getMethods();
-        assertNotNull(methods);
-        for (int i = 0; i < methods.length; i++) {
-            // For any methods in the controller callback, TestControllerCallback should have
-            // overriden the method and call matching API in the callback proxy.
-            assertNotEquals("TestBrowserCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    BrowserCallback.class, methods[i].getDeclaringClass());
-        }
-    }
-
-    @Test
-    public void testGetLibraryRoot() throws InterruptedException {
-        final Bundle param = new Bundle();
-        param.putString(TAG, TAG);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetLibraryRootDone(MediaBrowser2 browser,
-                    Bundle rootHints, String rootMediaId, Bundle rootExtra) {
-                assertTrue(TestUtils.equals(param, rootHints));
-                assertEquals(ROOT_ID, rootMediaId);
-                assertTrue(TestUtils.equals(EXTRAS, rootExtra));
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser =
-                (MediaBrowser2) createController(token,true, callback);
-        browser.getLibraryRoot(param);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetItem() throws InterruptedException {
-        final String mediaId = MockMediaLibraryService2.MEDIA_ID_GET_ITEM;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetItemDone(MediaBrowser2 browser, String mediaIdOut, MediaItem2 result) {
-                assertEquals(mediaId, mediaIdOut);
-                assertNotNull(result);
-                assertEquals(mediaId, result.getMediaId());
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getItem(mediaId);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetItemNullResult() throws InterruptedException {
-        final String mediaId = "random_media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetItemDone(MediaBrowser2 browser, String mediaIdOut, MediaItem2 result) {
-                assertEquals(mediaId, mediaIdOut);
-                assertNull(result);
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getItem(mediaId);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetChildren() throws InterruptedException {
-        final String parentId = MockMediaLibraryService2.PARENT_ID;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle extras = new Bundle();
-        extras.putString(TAG, TAG);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetChildrenDone(MediaBrowser2 browser, String parentIdOut, int pageOut,
-                    int pageSizeOut, List<MediaItem2> result, Bundle extrasOut) {
-                assertEquals(parentId, parentIdOut);
-                assertEquals(page, pageOut);
-                assertEquals(pageSize, pageSizeOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                assertNotNull(result);
-
-                int fromIndex = (page - 1) * pageSize;
-                int toIndex = Math.min(page * pageSize, MockMediaLibraryService2.CHILDREN_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    Assert.assertEquals(
-                            MockMediaLibraryService2.GET_CHILDREN_RESULT.get(originalIndex)
-                                    .getMediaId(),
-                            result.get(relativeIndex).getMediaId());
-                }
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getChildren(parentId, page, pageSize, extras);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetChildrenEmptyResult() throws InterruptedException {
-        final String parentId = MockMediaLibraryService2.PARENT_ID_NO_CHILDREN;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetChildrenDone(MediaBrowser2 browser, String parentIdOut,
-                    int pageOut, int pageSizeOut, List<MediaItem2> result, Bundle extrasOut) {
-                assertNotNull(result);
-                assertEquals(0, result.size());
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getChildren(parentId, 1, 1, null);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetChildrenNullResult() throws InterruptedException {
-        final String parentId = MockMediaLibraryService2.PARENT_ID_ERROR;
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onGetChildrenDone(MediaBrowser2 browser, String parentIdOut,
-                    int pageOut, int pageSizeOut, List<MediaItem2> result, Bundle extrasOut) {
-                assertNull(result);
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.getChildren(parentId, 1, 1, null);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore
-    @Test
-    public void testSearch() throws InterruptedException {
-        final String query = MockMediaLibraryService2.SEARCH_QUERY;
-        final int page = 4;
-        final int pageSize = 10;
-        final Bundle extras = new Bundle();
-        extras.putString(TAG, TAG);
-
-        final CountDownLatch latchForSearch = new CountDownLatch(1);
-        final CountDownLatch latchForGetSearchResult = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(MediaBrowser2 browser,
-                    String queryOut, int itemCount, Bundle extrasOut) {
-                assertEquals(query, queryOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
-                latchForSearch.countDown();
-            }
-
-            @Override
-            public void onGetSearchResultDone(MediaBrowser2 browser, String queryOut,
-                    int pageOut, int pageSizeOut, List<MediaItem2> result, Bundle extrasOut) {
-                assertEquals(query, queryOut);
-                assertEquals(page, pageOut);
-                assertEquals(pageSize, pageSizeOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                assertNotNull(result);
-
-                int fromIndex = (page - 1) * pageSize;
-                int toIndex = Math.min(
-                        page * pageSize, MockMediaLibraryService2.SEARCH_RESULT_COUNT);
-
-                // Compare the given results with originals.
-                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
-                    int relativeIndex = originalIndex - fromIndex;
-                    Assert.assertEquals(
-                            MockMediaLibraryService2.SEARCH_RESULT.get(originalIndex).getMediaId(),
-                            result.get(relativeIndex).getMediaId());
-                }
-                latchForGetSearchResult.countDown();
-            }
-        };
-
-        // Request the search.
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.search(query, extras);
-        assertTrue(latchForSearch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        // Get the search result.
-        browser.getSearchResult(query, page, pageSize, extras);
-        assertTrue(latchForGetSearchResult.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testSearchTakesTime() throws InterruptedException {
-        final String query = MockMediaLibraryService2.SEARCH_QUERY_TAKES_TIME;
-        final Bundle extras = new Bundle();
-        extras.putString(TAG, TAG);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser2 browser, String queryOut, int itemCount, Bundle extrasOut) {
-                assertEquals(query, queryOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.search(query, extras);
-        assertTrue(latch.await(
-                MockMediaLibraryService2.SEARCH_TIME_IN_MS + WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testSearchEmptyResult() throws InterruptedException {
-        final String query = MockMediaLibraryService2.SEARCH_QUERY_EMPTY_RESULT;
-        final Bundle extras = new Bundle();
-        extras.putString(TAG, TAG);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final BrowserCallback callback = new BrowserCallback() {
-            @Override
-            public void onSearchResultChanged(
-                    MediaBrowser2 browser, String queryOut, int itemCount, Bundle extrasOut) {
-                assertEquals(query, queryOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                assertEquals(0, itemCount);
-                latch.countDown();
-            }
-        };
-
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
-        browser.search(query, extras);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testSubscribe() throws InterruptedException {
-        final String testParentId = "testSubscribeId";
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testParentId, testParentId);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-            @Override
-            public void onSubscribe(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo info, @NonNull String parentId,
-                    @Nullable Bundle extras) {
-                if (Process.myUid() == info.getUid()) {
-                    assertEquals(testParentId, parentId);
-                    assertTrue(TestUtils.equals(testExtras, extras));
-                    latch.countDown();
-                }
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(callback);
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token);
-        browser.subscribe(testParentId, testExtras);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Ignore
-    @Test
-    public void testUnsubscribe() throws InterruptedException {
-        final String testParentId = "testUnsubscribeId";
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaLibrarySessionCallback callback = new MediaLibrarySessionCallback() {
-            @Override
-            public void onUnsubscribe(@NonNull MediaLibrarySession session,
-                    @NonNull ControllerInfo info, @NonNull String parentId) {
-                if (Process.myUid() == info.getUid()) {
-                    assertEquals(testParentId, parentId);
-                    latch.countDown();
-                }
-            }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(callback);
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        MediaBrowser2 browser = (MediaBrowser2) createController(token);
-        browser.unsubscribe(testParentId);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testBrowserCallback_notifyChildrenChanged() throws InterruptedException {
-        // TODO(jaewan): Add test for the notifyChildrenChanged itself.
-        final String testParentId1 = "testBrowserCallback_notifyChildrenChanged_unexpectedParent";
-        final String testParentId2 = "testBrowserCallback_notifyChildrenChanged";
-        final int testChildrenCount = 101;
-        final Bundle testExtras = new Bundle();
-        testExtras.putString(testParentId1, testParentId1);
-
-        final CountDownLatch latch = new CountDownLatch(3);
-        final MediaLibrarySessionCallback sessionCallback =
-                new MediaLibrarySessionCallback() {
-                    @Override
-                    public SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
-                            @NonNull ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            assertTrue(session instanceof MediaLibrarySession);
-                            if (mSession != null) {
-                                mSession.close();
-                            }
-                            mSession = session;
-                            // Shouldn't trigger onChildrenChanged() for the browser, because it
-                            // hasn't subscribed.
-                            ((MediaLibrarySession) session).notifyChildrenChanged(
-                                    testParentId1, testChildrenCount, null);
-                            ((MediaLibrarySession) session).notifyChildrenChanged(
-                                    controller, testParentId1, testChildrenCount, null);
-                        }
-                        return super.onConnect(session, controller);
-                    }
-
-                    @Override
-                    public void onSubscribe(@NonNull MediaLibrarySession session,
-                            @NonNull ControllerInfo info, @NonNull String parentId,
-                            @Nullable Bundle extras) {
-                        if (Process.myUid() == info.getUid()) {
-                            session.notifyChildrenChanged(testParentId2, testChildrenCount, null);
-                            session.notifyChildrenChanged(info, testParentId2, testChildrenCount,
-                                    testExtras);
-                        }
-                    }
-        };
-        final BrowserCallback controllerCallbackProxy =
-                new BrowserCallback() {
-                    @Override
-                    public void onChildrenChanged(MediaBrowser2 browser, String parentId,
-                            int itemCount, Bundle extras) {
-                        switch ((int) latch.getCount()) {
-                            case 3:
-                                assertEquals(testParentId2, parentId);
-                                assertEquals(testChildrenCount, itemCount);
-                                assertNull(extras);
-                                latch.countDown();
-                                break;
-                            case 2:
-                                assertEquals(testParentId2, parentId);
-                                assertEquals(testChildrenCount, itemCount);
-                                assertTrue(TestUtils.equals(testExtras, extras));
-                                latch.countDown();
-                                break;
-                            default:
-                                // Unexpected call.
-                                fail();
-                        }
-                    }
-                };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
-        final MediaBrowser2 browser = (MediaBrowser2) createController(
-                token, true, controllerCallbackProxy);
-        assertTrue(mSession instanceof MediaLibrarySession);
-        browser.subscribe(testParentId2, null);
-        // This ensures that onChildrenChanged() is only called for the expected reasons.
-        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    public static class TestBrowserCallback extends BrowserCallback
-            implements WaitForConnectionInterface {
-        private final ControllerCallback mCallbackProxy;
-        public final CountDownLatch connectLatch = new CountDownLatch(1);
-        public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-
-        TestBrowserCallback(ControllerCallback callbackProxy) {
-            if (callbackProxy == null) {
-                throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
-            }
-            mCallbackProxy = callbackProxy;
-        }
-
-        @CallSuper
-        @Override
-        public void onConnected(MediaController2 controller, SessionCommandGroup2 commands) {
-            connectLatch.countDown();
-        }
-
-        @CallSuper
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            disconnectLatch.countDown();
-        }
-
-        @Override
-        public void waitForConnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-
-        @Override
-        public void waitForDisconnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-
-        @Override
-        public void onPlaybackInfoChanged(MediaController2 controller,
-                MediaController2.PlaybackInfo info) {
-            mCallbackProxy.onPlaybackInfoChanged(controller, info);
-        }
-
-        @Override
-        public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
-                Bundle args, ResultReceiver receiver) {
-            mCallbackProxy.onCustomCommand(controller, command, args, receiver);
-        }
-
-        @Override
-        public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
-            mCallbackProxy.onCustomLayoutChanged(controller, layout);
-        }
-
-        @Override
-        public void onAllowedCommandsChanged(MediaController2 controller,
-                SessionCommandGroup2 commands) {
-            mCallbackProxy.onAllowedCommandsChanged(controller, commands);
-        }
-
-        @Override
-        public void onPlayerStateChanged(MediaController2 controller, int state) {
-            mCallbackProxy.onPlayerStateChanged(controller, state);
-        }
-
-        @Override
-        public void onSeekCompleted(MediaController2 controller, long position) {
-            mCallbackProxy.onSeekCompleted(controller, position);
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(MediaController2 controller, float speed) {
-            mCallbackProxy.onPlaybackSpeedChanged(controller, speed);
-        }
-
-        @Override
-        public void onBufferingStateChanged(MediaController2 controller, MediaItem2 item,
-                int state) {
-            mCallbackProxy.onBufferingStateChanged(controller, item, state);
-        }
-
-        @Override
-        public void onError(MediaController2 controller, int errorCode, Bundle extras) {
-            mCallbackProxy.onError(controller, errorCode, extras);
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(MediaController2 controller, MediaItem2 item) {
-            mCallbackProxy.onCurrentMediaItemChanged(controller, item);
-        }
-
-        @Override
-        public void onPlaylistChanged(MediaController2 controller,
-                List<MediaItem2> list, MediaMetadata2 metadata) {
-            mCallbackProxy.onPlaylistChanged(controller, list, metadata);
-        }
-
-        @Override
-        public void onPlaylistMetadataChanged(MediaController2 controller,
-                MediaMetadata2 metadata) {
-            mCallbackProxy.onPlaylistMetadataChanged(controller, metadata);
-        }
-
-        @Override
-        public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) {
-            mCallbackProxy.onShuffleModeChanged(controller, shuffleMode);
-        }
-
-        @Override
-        public void onRepeatModeChanged(MediaController2 controller, int repeatMode) {
-            mCallbackProxy.onRepeatModeChanged(controller, repeatMode);
-        }
-
-        @Override
-        public void onGetLibraryRootDone(MediaBrowser2 browser, Bundle rootHints,
-                String rootMediaId, Bundle rootExtra) {
-            super.onGetLibraryRootDone(browser, rootHints, rootMediaId, rootExtra);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy)
-                        .onGetLibraryRootDone(browser, rootHints, rootMediaId, rootExtra);
-            }
-        }
-
-        @Override
-        public void onGetItemDone(MediaBrowser2 browser, String mediaId, MediaItem2 result) {
-            super.onGetItemDone(browser, mediaId, result);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy).onGetItemDone(browser, mediaId, result);
-            }
-        }
-
-        @Override
-        public void onGetChildrenDone(MediaBrowser2 browser, String parentId, int page,
-                int pageSize, List<MediaItem2> result, Bundle extras) {
-            super.onGetChildrenDone(browser, parentId, page, pageSize, result, extras);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy)
-                        .onGetChildrenDone(browser, parentId, page, pageSize, result, extras);
-            }
-        }
-
-        @Override
-        public void onSearchResultChanged(MediaBrowser2 browser, String query, int itemCount,
-                Bundle extras) {
-            super.onSearchResultChanged(browser, query, itemCount, extras);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy)
-                        .onSearchResultChanged(browser, query, itemCount, extras);
-            }
-        }
-
-        @Override
-        public void onGetSearchResultDone(MediaBrowser2 browser, String query, int page,
-                int pageSize, List<MediaItem2> result, Bundle extras) {
-            super.onGetSearchResultDone(browser, query, page, pageSize, result, extras);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy)
-                        .onGetSearchResultDone(browser, query, page, pageSize, result, extras);
-            }
-        }
-
-        @Override
-        public void onChildrenChanged(MediaBrowser2 browser, String parentId, int itemCount,
-                Bundle extras) {
-            super.onChildrenChanged(browser, parentId, itemCount, extras);
-            if (mCallbackProxy instanceof BrowserCallback) {
-                ((BrowserCallback) mCallbackProxy)
-                        .onChildrenChanged(browser, parentId, itemCount, extras);
-            }
-        }
-    }
-
-    public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
-        private final BrowserCallback mCallback;
-
-        public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken2 token,
-                @NonNull ControllerCallback callback) {
-            super(context, token, sHandlerExecutor, (BrowserCallback) callback);
-            mCallback = (BrowserCallback) callback;
-        }
-
-        @Override
-        public BrowserCallback getCallback() {
-            return mCallback;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java
index bd62d87..5b2ad2f 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java
@@ -78,7 +78,8 @@
     private Map<UUID, byte[]> mPsshInitData;
     private MediaCrypto mCrypto;
     private MediaCas mMediaCas;
-    private MediaDescrambler mDescrambler;
+    private MediaDescrambler mAudioDescrambler;
+    private MediaDescrambler mVideoDescrambler;
     private MediaExtractor mAudioExtractor;
     private MediaExtractor mVideoExtractor;
     private SurfaceHolder mSurfaceHolder;
@@ -193,7 +194,7 @@
         return PSSH;
     }
 
-    private void prepareAudio() throws IOException {
+    private void prepareAudio() throws IOException, MediaCasException {
         boolean hasAudio = false;
         for (int i = mAudioExtractor.getTrackCount(); i-- > 0;) {
             MediaFormat format = mAudioExtractor.getTrackFormat(i);
@@ -208,6 +209,14 @@
                   " Channel count:" +
                   getMediaFormatInteger(format, MediaFormat.KEY_CHANNEL_COUNT));
 
+            if (mScrambled) {
+                MediaExtractor.CasInfo casInfo = mAudioExtractor.getCasInfo(i);
+                if (casInfo != null && casInfo.getSession() != null) {
+                    mAudioDescrambler = new MediaDescrambler(casInfo.getSystemId());
+                    mAudioDescrambler.setMediaCasSession(casInfo.getSession());
+                }
+            }
+
             if (!hasAudio) {
                 mAudioExtractor.selectTrack(i);
                 addTrack(i, format, mEncryptedAudio);
@@ -230,7 +239,7 @@
         }
     }
 
-    private void prepareVideo() throws IOException {
+    private void prepareVideo() throws IOException, MediaCasException {
         boolean hasVideo = false;
 
         for (int i = mVideoExtractor.getTrackCount(); i-- > 0;) {
@@ -245,10 +254,11 @@
             Log.d(TAG, "video track #" + i + " " + format + " " + mime +
                   " Width:" + mMediaFormatWidth + ", Height:" + mMediaFormatHeight);
 
-            if (mScrambled && mime.startsWith("video/")) {
+            if (mScrambled) {
                 MediaExtractor.CasInfo casInfo = mVideoExtractor.getCasInfo(i);
                 if (casInfo != null && casInfo.getSession() != null) {
-                    mDescrambler.setMediaCasSession(casInfo.getSession());
+                    mVideoDescrambler = new MediaDescrambler(casInfo.getSystemId());
+                    mVideoDescrambler.setMediaCasSession(casInfo.getSession());
                 }
             }
 
@@ -295,13 +305,14 @@
             android.media.MediaFormat format = extractor.getTrackFormat(trackId);
             String mime = format.getString(android.media.MediaFormat.KEY_MIME);
             Log.d(TAG, "track "+ trackId + ": " + mime);
-            if ("video/scrambled".equals(mime) || "audio/scrambled".equals(mime)) {
+            if (MediaFormat.MIMETYPE_VIDEO_SCRAMBLED.equals(mime) ||
+                    MediaFormat.MIMETYPE_AUDIO_SCRAMBLED.equals(mime)) {
                 MediaExtractor.CasInfo casInfo = extractor.getCasInfo(trackId);
                 if (casInfo != null) {
                     mMediaCas = new MediaCas(casInfo.getSystemId());
-                    mDescrambler = new MediaDescrambler(casInfo.getSystemId());
                     mMediaCas.provision(sProvisionStr);
                     extractor.setMediaCas(mMediaCas);
+                    break;
                 }
             }
         }
@@ -389,7 +400,7 @@
                     format,
                     isVideo ? mSurfaceHolder.getSurface() : null,
                     0,
-                    isVideo ? mDescrambler : null);
+                    isVideo ? mVideoDescrambler : mAudioDescrambler);
         }
 
         CodecState state;
@@ -549,9 +560,14 @@
             mMediaCas = null;
         }
 
-        if (mDescrambler != null) {
-            mDescrambler.close();
-            mDescrambler = null;
+        if (mAudioDescrambler != null) {
+            mAudioDescrambler.close();
+            mAudioDescrambler = null;
+        }
+
+        if (mVideoDescrambler != null) {
+            mVideoDescrambler.close();
+            mVideoDescrambler = null;
         }
 
         mDurationUs = -1;
@@ -596,7 +612,6 @@
         } catch (IllegalStateException e) {
             throw new Error("Aduio CodecState.feedInputBuffer IllegalStateException " + e);
         }
-
     }
 
     public long getNowUs() {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index a4e3a19..ec0c8f0 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -19,6 +19,7 @@
 import android.media.cts.R;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.media.AudioFormat;
 import android.media.MediaCodec;
 import android.media.MediaCodec.BufferInfo;
 import android.media.MediaCodec.CodecException;
@@ -28,26 +29,33 @@
 import android.media.MediaCodecList;
 import android.media.MediaCrypto;
 import android.media.MediaDrm;
+import android.media.MediaDrm.MediaDrmStateException;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.opengl.GLES20;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PersistableBundle;
+import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.platform.test.annotations.RequiresDevice;
 import android.test.AndroidTestCase;
 import android.util.Log;
+import android.util.Range;
 import android.view.Surface;
 
 import com.android.compatibility.common.util.MediaUtils;
 
 import java.io.BufferedInputStream;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -1807,14 +1815,206 @@
         }
     }
 
-    abstract class ByteBufferStream {
+    /**
+     * PCM encoding configuration test.
+     *
+     * If not specified in configure(), PCM encoding if it exists must be 16 bit.
+     * If specified float in configure(), PCM encoding if it exists must be 16 bit, or float.
+     *
+     * As of Q, any codec of type "audio/raw" must support PCM encoding float.
+     */
+    @MediumTest
+    public void testPCMEncoding() throws Exception {
+        final MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            final boolean isEncoder = codecInfo.isEncoder();
+            final String name = codecInfo.getName();
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                final MediaCodecInfo.CodecCapabilities ccaps =
+                        codecInfo.getCapabilitiesForType(type);
+                final MediaCodecInfo.AudioCapabilities acaps =
+                        ccaps.getAudioCapabilities();
+                if (acaps == null) {
+                    break; // not an audio codec
+                }
+
+                // Deduce the minimum channel count (though prefer stereo over mono).
+                final int channelCount = Math.min(acaps.getMaxInputChannelCount(), 2);
+
+                // Deduce the minimum sample rate.
+                final int[] sampleRates = acaps.getSupportedSampleRates();
+                final Range<Integer>[] sampleRateRanges = acaps.getSupportedSampleRateRanges();
+                assertNotNull("supported sample rate ranges must be non-null", sampleRateRanges);
+                final int sampleRate = sampleRateRanges[0].getLower();
+
+                // If sample rate array exists (it may not),
+                // the minimum value must be equal with the minimum from the sample rate range.
+                if (sampleRates != null) {
+                    assertEquals("sample rate range and array should have equal minimum",
+                            sampleRate, sampleRates[0]);
+                    Log.d(TAG, "codec: " + name + " type: " + type
+                            + " has both sampleRate array and ranges");
+                } else {
+                    Log.d(TAG, "codec: " + name + " type: " + type
+                            + " returns null getSupportedSampleRates()");
+                }
+
+                // We create one format here for both tests below.
+                final MediaFormat format = MediaFormat.createAudioFormat(
+                    type, sampleRate, channelCount);
+
+                // Bitrate field is mandatory for encoders (except FLAC).
+                if (isEncoder) {
+                    final int bitRate = acaps.getBitrateRange().getLower();
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+                }
+
+                // First test: An audio codec must be createable from a format
+                // with the minimum sample rate and channel count.
+                // The PCM encoding must be null (doesn't exist) or 16 bit.
+                {
+                    // Check encoding of codec.
+                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
+                    if (actualEncoding != null) {
+                        assertEquals("returned audio encoding must be 16 bit for codec: "
+                                + name + " type: " + type + " encoding: " + actualEncoding,
+                                AudioFormat.ENCODING_PCM_16BIT, actualEncoding.intValue());
+                    }
+                }
+
+                // Second test: An audio codec configured with PCM encoding float must return
+                // either an encoding of null (doesn't exist), 16 bit, or float.
+                {
+                    // Reuse the original format, and add float specifier.
+                    format.setInteger(
+                            MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
+
+                    // Check encoding of codec.
+                    // The KEY_PCM_ENCODING key is advisory, so should not cause configuration
+                    // failure.  The actual PCM encoding is returned from
+                    // the input format (encoder) or output format (decoder).
+                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
+                    if (actualEncoding != null) {
+                        assertTrue(
+                                "returned audio encoding must be 16 bit or float for codec: "
+                                + name + " type: " + type + " encoding: " + actualEncoding,
+                                actualEncoding == AudioFormat.ENCODING_PCM_16BIT
+                                || actualEncoding == AudioFormat.ENCODING_PCM_FLOAT);
+                        if (actualEncoding == AudioFormat.ENCODING_PCM_FLOAT) {
+                            Log.d(TAG, "codec: " + name + " type: " + type + " supports float");
+                        }
+                    }
+
+                    // As of Q, all codecs of type "audio/raw" must support float.
+                    if (type.equals("audio/raw")) {
+                        assertTrue(type + " must support float",
+                                actualEncoding != null &&
+                                actualEncoding.intValue() == AudioFormat.ENCODING_PCM_FLOAT);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the PCM encoding of an audio codec, or null if codec doesn't exist,
+     * or not an audio codec, or PCM encoding key doesn't exist.
+     */
+    private Integer encodingOfAudioCodec(String name, MediaFormat format, boolean encode)
+            throws IOException {
+        final int flagEncoder = encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
+        final MediaCodec codec = MediaCodec.createByCodecName(name);
+        Integer actualEncoding = null;
+
+        try {
+            codec.configure(format, null /* surface */, null /* crypto */, flagEncoder);
+
+            // Check input/output format - this must exist.
+            final MediaFormat actualFormat =
+                    encode ? codec.getInputFormat() : codec.getOutputFormat();
+            assertNotNull("cannot get format for " + name, actualFormat);
+
+            // Check actual encoding - this may or may not exist
+            try {
+                actualEncoding = actualFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
+            } catch (Exception e) {
+                ; // trying to get a non-existent key throws exception
+            }
+        } finally {
+            codec.release();
+        }
+        return actualEncoding;
+    }
+
+    /*
+     * Simulate ERROR_LOST_STATE error during decryption, expected
+     * result is MediaCodec.CryptoException with errorCode == ERROR_LOST_STATE
+     */
+    public void testCryptoErrorLostSessionState() throws Exception {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        drm.setPropertyString("drmErrorTest", "lostState");
+
+        byte[] sessionId = drm.openSession();
+        MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]);
+        MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE);
+
+        try {
+            crypto.setMediaDrmSession(sessionId);
+
+            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
+            MediaFormat format = createMediaFormat();
+
+            codec.configure(format, null, crypto, 0);
+            codec.start();
+            int index = codec.dequeueInputBuffer(-1);
+            assertTrue(index >= 0);
+            ByteBuffer buffer = codec.getInputBuffer(index);
+            cryptoInfo.set(
+                    1,
+                    new int[] { 0 },
+                    new int[] { buffer.capacity() },
+                            new byte[16],
+                    new byte[16],
+                    MediaCodec.CRYPTO_MODE_AES_CTR);
+            try {
+                codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0);
+                fail("queueSecureInputBuffer should fail when trying to decrypt " +
+                        "after session lost state error.");
+            } catch (MediaCodec.CryptoException e) {
+                if (e.getErrorCode() != MediaCodec.CryptoException.ERROR_LOST_STATE) {
+                    fail("expected MediaCodec.CryptoException.ERROR_LOST_STATE: " +
+                            e.getErrorCode() + ": " + e.getMessage());
+                }
+                // received expected lost state exception
+            }
+            buffer = codec.getInputBuffer(index);
+            codec.stop();
+        } finally {
+            codec.release();
+            crypto.release();
+            try {
+                drm.closeSession(sessionId);
+            } catch (MediaDrmStateException e) {
+                // expected since session lost state
+            }
+        }
+    }
+
+    /* package */ static abstract class ByteBufferStream {
         public abstract ByteBuffer read() throws IOException;
     }
 
-    class MediaCodecStream extends ByteBufferStream {
+    /* package */ static class MediaCodecStream extends ByteBufferStream implements Closeable {
         private ByteBufferStream mBufferInputStream;
         private InputStream mInputStream;
         private MediaCodec mCodec;
+        public boolean mIsFloat;
         BufferInfo mInfo = new BufferInfo();
         boolean mSawOutputEOS;
         boolean mSawInputEOS;
@@ -1842,6 +2042,18 @@
                 mCodec = MediaCodec.createDecoderByType(mime);
             }
             mCodec.configure(format,null, null, encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+
+            // check if float
+            final MediaFormat actualFormat =
+                    encode ? mCodec.getInputFormat() : mCodec.getOutputFormat();
+            Integer actualEncoding = null;
+            try {
+                actualEncoding = actualFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
+            } catch (Exception e) {
+                ; // trying to get a non-existent key throws exception
+            }
+            mIsFloat = actualEncoding != null && actualEncoding == AudioFormat.ENCODING_PCM_FLOAT;
+
             mCodec.start();
         }
 
@@ -1861,6 +2073,7 @@
             mBufferInputStream = input;
         }
 
+        @Override
         public ByteBuffer read() throws IOException {
 
             if (mSawOutputEOS) {
@@ -1884,11 +2097,11 @@
                         ByteBuffer in = null;
                         do {
                             in = mBufferInputStream.read();
-                        } while (in != null && in.limit() == 0);
+                        } while (in != null && in.limit() - in.position() == 0);
                         if (in == null) {
                             mSawInputEOS = true;
                         } else {
-                            int n = in.limit();
+                            final int n = in.limit() - in.position();
                             numRead += n;
                             buf.put(in);
                         }
@@ -1951,6 +2164,7 @@
             return ByteBuffer.allocate(0);
         }
 
+        @Override
         public void close() throws IOException {
             try {
                 if (mInputStream != null) {
@@ -1977,7 +2191,7 @@
         }
     };
 
-    class ByteBufferInputStream extends InputStream {
+    /* package */ static class ByteBufferInputStream extends InputStream {
         ByteBufferStream mInput;
         ByteBuffer mBuffer;
 
@@ -2001,7 +2215,55 @@
         }
     };
 
-    private int compareStreams(InputStream test, InputStream reference) {
+    /* package */ static class PcmAudioBufferStream extends ByteBufferStream {
+
+        public int mCount;         // either 0 or 1 if the buffer has been delivered
+        public ByteBuffer mBuffer; // the audio buffer (furnished duplicated, read only).
+
+        public PcmAudioBufferStream(
+            int samples, int sampleRate, double frequency, double sweep, boolean useFloat) {
+            final int sampleSize = useFloat ? 4 : 2;
+            final int sizeInBytes = samples * sampleSize;
+            mBuffer = ByteBuffer.allocate(sizeInBytes);
+            mBuffer.order(java.nio.ByteOrder.nativeOrder());
+            if (useFloat) {
+                FloatBuffer fb = mBuffer.asFloatBuffer();
+                float[] fa = AudioHelper.createSoundDataInFloatArray(
+                    samples, sampleRate, frequency, sweep);
+                for (int i = 0; i < fa.length; ++i) {
+                    // quantize to a Q.23 integer so that identity is preserved
+                    fa[i] = (float)((int)(fa[i] * ((1 << 23) - 1))) / (1 << 23);
+                }
+                fb.put(fa);
+            } else {
+                ShortBuffer sb = mBuffer.asShortBuffer();
+                sb.put(AudioHelper.createSoundDataInShortArray(
+                    samples, sampleRate, frequency, sweep));
+            }
+            mBuffer.limit(sizeInBytes);
+        }
+
+        // duplicating constructor
+        public PcmAudioBufferStream(PcmAudioBufferStream other) {
+            mCount = 0;
+            mBuffer = other.mBuffer; // ok to copy, furnished read-only
+        }
+
+        public int sizeInBytes() {
+            return mBuffer.capacity();
+        }
+
+        @Override
+        public ByteBuffer read() throws IOException {
+            if (mCount < 1 /* only one buffer */) {
+                ++mCount;
+                return mBuffer.asReadOnlyBuffer();
+            }
+            return null;
+        }
+    }
+
+    /* package */ static int compareStreams(InputStream test, InputStream reference) {
         Log.i(TAG, "compareStreams");
         BufferedInputStream buffered_test = new BufferedInputStream(test);
         BufferedInputStream buffered_reference = new BufferedInputStream(reference);
@@ -2028,35 +2290,93 @@
         return numread;
     }
 
+    @SmallTest
     public void testFlacIdentity() throws Exception {
-        Resources res = mContext.getResources();
-        InputStream pcmStream1 = res.openRawResource(R.raw.sinesweepraw);
+        final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
+        final int SAMPLES = PCM_FRAMES * AUDIO_CHANNEL_COUNT;
+        final int[] SAMPLE_RATES = {AUDIO_SAMPLE_RATE, 192000}; // ensure 192kHz supported.
 
-        MediaFormat encodeFormat = new MediaFormat();
-        encodeFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
-        encodeFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
-        encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
-        encodeFormat.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
+        for (int sampleRate : SAMPLE_RATES) {
+            final MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, AUDIO_CHANNEL_COUNT);
 
-        MediaFormat decodeFormat = new MediaFormat();
-        decodeFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
-        decodeFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
-        decodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+            Log.d(TAG, "Trying sample rate: " + sampleRate
+                    + " channel count: " + AUDIO_CHANNEL_COUNT);
+            // this key is only needed for encode, ignored for decode
+            format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
 
-        MediaCodecStream rawToAmr = new MediaCodecStream(pcmStream1, encodeFormat, true);
-        MediaCodecStream amrToRaw = new MediaCodecStream(rawToAmr, decodeFormat, false);
+            for (int i = 0; i < 2; ++i) {
+                final boolean useFloat = (i == 1);
+                final PcmAudioBufferStream audioStream = new PcmAudioBufferStream(
+                    SAMPLES, sampleRate, 1000 /* frequency */, 100 /* sweep */, useFloat);
 
-        AssetFileDescriptor masterFd = res.openRawResourceFd(R.raw.sinesweepraw);
-        long masterLength = masterFd.getLength();
-        Log.i(TAG, "source length: " + masterLength);
-        masterFd.close();
+                if (useFloat) {
+                    format.setInteger(
+                        MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
+                }
 
-        InputStream pcmStream2 = res.openRawResource(R.raw.sinesweepraw);
+                final MediaCodecStream rawToFlac = new MediaCodecStream(
+                    new ByteBufferInputStream(audioStream), format, true /* encode */);
+                final MediaCodecStream flacToRaw = new MediaCodecStream(
+                    rawToFlac, format, false /* encode */);
 
-        assertEquals("not identical after compression",
-                masterLength,
-                compareStreams(new ByteBufferInputStream(amrToRaw), pcmStream2));
-        pcmStream1.close();
-        pcmStream2.close();
+                if (useFloat) { // ensure float precision supported at the sample rate.
+                    assertTrue("No float FLAC encoder at " + sampleRate,
+                            rawToFlac.mIsFloat);
+                    assertTrue("No float FLAC decoder at " + sampleRate,
+                            flacToRaw.mIsFloat);
+                }
+
+                // Note: the existence of signed zero (as well as NAN) may make byte
+                // comparisons invalid for floating point output. In our case, since the
+                // floats come through integer to float conversion, it does not matter.
+                assertEquals("Audio data not identical after compression",
+                    audioStream.sizeInBytes(),
+                    compareStreams(new ByteBufferInputStream(flacToRaw),
+                        new ByteBufferInputStream(new PcmAudioBufferStream(audioStream))));
+            }
+        }
+    }
+
+    public void testAsyncRelease() throws Exception {
+        OutputSurface outputSurface = new OutputSurface(1, 1);
+        MediaExtractor mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE_ID, "video/");
+        MediaFormat mediaFormat =
+                mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+        for (int i = 0; i < 100; ++i) {
+            final MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+
+            try {
+                codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+
+                codec.start();
+
+                final ConditionVariable cv = new ConditionVariable();
+                Thread[] threads = new Thread[10];
+                for (int j = 0; j < threads.length; ++j) {
+                    threads[j] = new Thread(new Runnable() {
+                        @Override
+                        public void run() {
+                            cv.block();
+                            codec.release();
+                        }
+                    });
+                }
+                for (Thread thread : threads) {
+                    thread.start();
+                }
+                // Wait a little bit so that threads may reach block() call.
+                Thread.sleep(50);
+                cv.open();
+                for (Thread thread : threads) {
+                    thread.join();
+                }
+            } finally {
+                codec.release();
+            }
+        }
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaController2Test.java b/tests/tests/media/src/android/media/cts/MediaController2Test.java
index 721ddf7..945c95a 100644
--- a/tests/tests/media/src/android/media/cts/MediaController2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaController2Test.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * 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.
@@ -16,1117 +16,336 @@
 
 package android.media.cts;
 
-import static junit.framework.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.assertEquals;
 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.Context;
-import android.content.Intent;
-import android.media.AudioManager;
 import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.Rating2;
-import android.media.SessionToken2;
-import android.media.VolumeProvider2;
-import android.media.cts.TestServiceRegistry.SessionServiceCallback;
-import android.media.cts.TestUtils.SyncHandler;
-import android.net.Uri;
+import android.media.Session2Command;
+import android.media.Session2CommandGroup;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Process;
-import android.os.ResultReceiver;
-import androidx.annotation.NonNull;
-import android.support.test.filters.FlakyTest;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.lang.reflect.Method;
-import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * Tests {@link MediaController2}.
+ * Tests {@link android.media.MediaController2}.
  */
-// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
-// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
-// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@FlakyTest
-@Ignore
-public class MediaController2Test extends MediaSession2TestBase {
-    private static final String TAG = "MediaController2Test";
+public class MediaController2Test {
+    private static final long WAIT_TIME_MS = 100L;
 
-    PendingIntent mIntent;
-    MediaSession2 mSession;
-    MediaController2 mController;
-    MockPlayer mPlayer;
-    MockPlaylistAgent mMockAgent;
+    static final Object sTestLock = new Object();
+
+    static final int ALLOWED_COMMAND_CODE = 100;
+    static final Session2CommandGroup SESSION_ALLOWED_COMMANDS = new Session2CommandGroup.Builder()
+            .addCommand(ALLOWED_COMMAND_CODE).build();
+    static final int SESSION_RESULT_CODE = 100;
+    static final String SESSION_RESULT_KEY = "test_result_key";
+    static final String SESSION_RESULT_VALUE = "test_result_value";
+    static final Session2Command.Result SESSION_COMMAND_RESULT;
+
+    static {
+        Bundle resultData = new Bundle();
+        resultData.putString(SESSION_RESULT_KEY, SESSION_RESULT_VALUE);
+        SESSION_COMMAND_RESULT = new Session2Command.Result(SESSION_RESULT_CODE, resultData);
+    }
+
+    static Handler sHandler;
+    static Executor sHandlerExecutor;
+
+    private Context mContext;
+    private MediaSession2 mSession;
+    private Session2Callback mSessionCallback;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
+            handlerThread.start();
+            sHandler = new Handler(handlerThread.getLooper());
+            sHandlerExecutor = (runnable) -> {
+                Handler handler;
+                synchronized (MediaSession2Test.class) {
+                    handler = sHandler;
+                }
+                if (handler != null) {
+                    handler.post(() -> {
+                        synchronized (sTestLock) {
+                            runnable.run();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+            sHandlerExecutor = null;
+        }
+    }
 
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
-        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
-        // Create this test specific MediaSession2 to use our own Handler.
-        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0);
-
-        mPlayer = new MockPlayer(1);
-        mMockAgent = new MockPlaylistAgent();
+        mContext = InstrumentationRegistry.getContext();
+        mSessionCallback = new Session2Callback();
         mSession = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(mMockAgent)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup2 onConnect(MediaSession2 session,
-                            ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onPlaylistMetadataChanged(MediaSession2 session,
-                            MediaPlaylistAgent playlistAgent,
-                            MediaMetadata2 metadata) {
-                        super.onPlaylistMetadataChanged(session, playlistAgent, metadata);
-                    }
-                })
-                .setSessionActivity(mIntent)
-                .setId(TAG).build();
-        mController = createController(mSession.getToken());
-        TestServiceRegistry.getInstance().setHandler(sHandler);
+                .setSessionCallback(sHandlerExecutor, mSessionCallback)
+                .build();
     }
 
     @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
+    public void cleanUp() {
         if (mSession != null) {
             mSession.close();
-        }
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    /**
-     * Test if the {@link MediaSession2TestBase.TestControllerCallback} wraps the callback proxy
-     * without missing any method.
-     */
-    @Test
-    public void testTestControllerCallback() {
-        Method[] methods = TestControllerCallback.class.getMethods();
-        assertNotNull(methods);
-        for (int i = 0; i < methods.length; i++) {
-            // For any methods in the controller callback, TestControllerCallback should have
-            // overriden the method and call matching API in the callback proxy.
-            assertNotEquals("TestControllerCallback should override " + methods[i]
-                            + " and call callback proxy",
-                    ControllerCallback.class, methods[i].getDeclaringClass());
-        }
-    }
-
-    @Test
-    public void testPlay() {
-        mController.play();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPlayCalled);
-    }
-
-    @Test
-    public void testPause() {
-        mController.pause();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPauseCalled);
-    }
-
-    @Ignore
-    @Test
-    public void testStop() {
-        mController.stop();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mStopCalled);
-    }
-
-    @Test
-    public void testPrepare() {
-        mController.prepare();
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mPrepareCalled);
-    }
-
-    @Test
-    public void testFastForward() {
-        // TODO(jaewan): Implement
-    }
-
-    @Test
-    public void testRewind() {
-        // TODO(jaewan): Implement
-    }
-
-    @Test
-    public void testSeekTo() {
-        final long seekPosition = 12125L;
-        mController.seekTo(seekPosition);
-        try {
-            assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        }
-        assertTrue(mPlayer.mSeekToCalled);
-        assertEquals(seekPosition, mPlayer.mSeekPosition);
-    }
-
-    @Test
-    public void testGettersAfterConnected() throws InterruptedException {
-        final int state = MediaPlayerBase.PLAYER_STATE_PLAYING;
-        final long position = 150000;
-        final long bufferedPosition = 900000;
-
-        mPlayer.mLastPlayerState = state;
-        mPlayer.mCurrentPosition = position;
-        mPlayer.mBufferedPosition = bufferedPosition;
-
-        MediaController2 controller = createController(mSession.getToken());
-        assertEquals(state, controller.getPlayerState());
-        assertEquals(bufferedPosition, controller.getBufferedPosition());
-        // TODO (jaewan): Enable this test when Session2/Controller2's get(set)PlaybackSpeed
-        //                is implemented. (b/74093080)
-        //assertEquals(speed, controller.getPlaybackSpeed());
-        //assertEquals(position + speed * elapsedTime, controller.getPosition(), delta);
-    }
-
-    @Test
-    public void testGetSessionActivity() {
-        PendingIntent sessionActivity = mController.getSessionActivity();
-        assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
-        assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
-    }
-
-    @Test
-    public void testSetPlaylist() throws InterruptedException {
-        final List<MediaItem2> list = TestUtils.createPlaylist(2);
-        mController.setPlaylist(list, null /* Metadata */);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mSetPlaylistCalled);
-        assertNull(mMockAgent.mMetadata);
-
-        assertNotNull(mMockAgent.mPlaylist);
-        assertEquals(list.size(), mMockAgent.mPlaylist.size());
-        for (int i = 0; i < list.size(); i++) {
-            // MediaController2.setPlaylist does not ensure the equality of the items.
-            assertEquals(list.get(i).getMediaId(), mMockAgent.mPlaylist.get(i).getMediaId());
-        }
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onPlaylistChanged(
-     * MediaController2, List, MediaMetadata2)}.
-     */
-    @Test
-    public void testGetPlaylist() throws InterruptedException {
-        final List<MediaItem2> testList = TestUtils.createPlaylist(2);
-        final AtomicReference<List<MediaItem2>> listFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistChanged(MediaController2 controller,
-                    List<MediaItem2> playlist, MediaMetadata2 metadata) {
-                assertNotNull(playlist);
-                assertEquals(testList.size(), playlist.size());
-                for (int i = 0; i < playlist.size(); i++) {
-                    assertEquals(testList.get(i).getMediaId(), playlist.get(i).getMediaId());
-                }
-                listFromCallback.set(playlist);
-                latch.countDown();
-            }
-        };
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return testList;
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testControllerCallback_onPlaylistChanged")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .setPlaylistAgent(agent)
-                .build()) {
-            MediaController2 controller = createController(
-                    session.getToken(), true, callback);
-            agent.notifyPlaylistChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(listFromCallback.get(), controller.getPlaylist());
-        }
-    }
-
-    @Test
-    public void testUpdatePlaylistMetadata() throws InterruptedException {
-        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
-        mController.updatePlaylistMetadata(testMetadata);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled);
-        assertNotNull(mMockAgent.mMetadata);
-        assertEquals(testMetadata.getMediaId(), mMockAgent.mMetadata.getMediaId());
-    }
-
-    @Test
-    public void testGetPlaylistMetadata() throws InterruptedException {
-        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
-        final AtomicReference<MediaMetadata2> metadataFromCallback = new AtomicReference<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(MediaController2 controller,
-                    MediaMetadata2 metadata) {
-                assertNotNull(testMetadata);
-                assertEquals(testMetadata.getMediaId(), metadata.getMediaId());
-                metadataFromCallback.set(metadata);
-                latch.countDown();
-            }
-        };
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public MediaMetadata2 getPlaylistMetadata() {
-                return testMetadata;
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testGetPlaylistMetadata")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                .setPlaylistAgent(agent)
-                .build()) {
-            MediaController2 controller = createController(session.getToken(), true, callback);
-            agent.notifyPlaylistMetadataChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(metadataFromCallback.get().getMediaId(),
-                    controller.getPlaylistMetadata().getMediaId());
-        }
-    }
-
-    /**
-     * Test whether {@link MediaSession2#setPlaylist(List, MediaMetadata2)} is notified
-     * through the
-     * {@link ControllerCallback#onPlaylistMetadataChanged(MediaController2, MediaMetadata2)}
-     * if the controller doesn't have {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST} but
-     * {@link SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST_METADATA}.
-     */
-    @Test
-    public void testControllerCallback_onPlaylistMetadataChanged() throws InterruptedException {
-        final MediaItem2 item = TestUtils.createMediaItemWithMetadata();
-        final List<MediaItem2> list = TestUtils.createPlaylist(2);
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaylistMetadataChanged(MediaController2 controller,
-                    MediaMetadata2 metadata) {
-                assertNotNull(metadata);
-                assertEquals(item.getMediaId(), metadata.getMediaId());
-                latch.countDown();
-            }
-        };
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup2 onConnect(MediaSession2 session,
-                    ControllerInfo controller) {
-                if (Process.myUid() == controller.getUid()) {
-                    SessionCommandGroup2 commands = new SessionCommandGroup2();
-                    commands.addCommand(new SessionCommand2(
-                              SessionCommand2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA));
-                    return commands;
-                }
-                return super.onConnect(session, controller);
-            }
-        };
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public MediaMetadata2 getPlaylistMetadata() {
-                return item.getMetadata();
-            }
-
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return list;
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testControllerCallback_onPlaylistMetadataChanged")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .setPlaylistAgent(agent)
-                .build()) {
-            MediaController2 controller = createController(session.getToken(), true, callback);
-            agent.notifyPlaylistMetadataChanged();
-            // It also calls onPlaylistMetadataChanged() if it doesn't have permission for getList()
-            agent.notifyPlaylistChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testAddPlaylistItem() throws InterruptedException {
-        final int testIndex = 12;
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mController.addPlaylistItem(testIndex, testMediaItem);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mMockAgent.mIndex);
-        // MediaController2.addPlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId());
-    }
-
-    @Test
-    public void testRemovePlaylistItem() throws InterruptedException {
-        mMockAgent.mPlaylist = TestUtils.createPlaylist(2);
-
-        // Recreate controller for sending removePlaylistItem.
-        // It's easier to ensure that MediaController2.getPlaylist() returns the playlist from the
-        // agent.
-        MediaController2 controller = createController(mSession.getToken());
-        MediaItem2 targetItem = controller.getPlaylist().get(0);
-        controller.removePlaylistItem(targetItem);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mRemovePlaylistItemCalled);
-        assertEquals(targetItem, mMockAgent.mItem);
-    }
-
-    @Test
-    public void testReplacePlaylistItem() throws InterruptedException {
-        final int testIndex = 12;
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mController.replacePlaylistItem(testIndex, testMediaItem);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mReplacePlaylistItemCalled);
-        // MediaController2.replacePlaylistItem does not ensure the equality of the items.
-        assertEquals(testMediaItem.getMediaId(), mMockAgent.mItem.getMediaId());
-    }
-
-    @Test
-    public void testSkipToPreviousItem() throws InterruptedException {
-        mController.skipToPreviousItem();
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mMockAgent.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void testSkipToNextItem() throws InterruptedException {
-        mController.skipToNextItem();
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mMockAgent.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void testSkipToPlaylistItem() throws InterruptedException {
-        MediaController2 controller = createController(mSession.getToken());
-        MediaItem2 targetItem = TestUtils.createMediaItemWithMetadata();
-        controller.skipToPlaylistItem(targetItem);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mSkipToPlaylistItemCalled);
-        assertEquals(targetItem, mMockAgent.mItem);
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onShuffleModeChanged(MediaController2, int)}.
-     */
-    @Test
-    public void testGetShuffleMode() throws InterruptedException {
-        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public int getShuffleMode() {
-                return testShuffleMode;
-            }
-        };
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) {
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-        };
-        mSession.updatePlayer(mPlayer, agent, null);
-        MediaController2 controller = createController(mSession.getToken(), true, callback);
-        agent.notifyShuffleModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testShuffleMode, controller.getShuffleMode());
-    }
-
-    @Test
-    public void testSetShuffleMode() throws InterruptedException {
-        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
-        mController.setShuffleMode(testShuffleMode);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mMockAgent.mShuffleMode);
-    }
-
-    /**
-     * This also tests {@link ControllerCallback#onRepeatModeChanged(MediaController2, int)}.
-     */
-    @Test
-    public void testGetRepeatMode() throws InterruptedException {
-        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public int getRepeatMode() {
-                return testRepeatMode;
-            }
-        };
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onRepeatModeChanged(MediaController2 controller, int repeatMode) {
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        };
-        mSession.updatePlayer(mPlayer, agent, null);
-        MediaController2 controller = createController(mSession.getToken(), true, callback);
-        agent.notifyRepeatModeChanged();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(testRepeatMode, controller.getRepeatMode());
-    }
-
-    @Test
-    public void testSetRepeatMode() throws InterruptedException {
-        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
-        mController.setRepeatMode(testRepeatMode);
-        assertTrue(mMockAgent.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertTrue(mMockAgent.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mMockAgent.mRepeatMode);
-    }
-
-    @Test
-    public void testSetVolumeTo() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
-        final MediaController2 controller = createController(mSession.getToken(), true, null);
-
-        final int targetVolume = 50;
-        controller.setVolumeTo(targetVolume, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mSetVolumeToCalled);
-        assertEquals(targetVolume, volumeProvider.mVolume);
-    }
-
-    @Test
-    public void testAdjustVolume() throws Exception {
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
-        TestVolumeProvider volumeProvider =
-                new TestVolumeProvider(volumeControlType, maxVolume, currentVolume);
-
-        mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
-        final MediaController2 controller = createController(mSession.getToken(), true, null);
-
-        final int direction = AudioManager.ADJUST_RAISE;
-        controller.adjustVolume(direction, 0 /* flags */);
-        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(volumeProvider.mAdjustVolumeCalled);
-        assertEquals(direction, volumeProvider.mDirection);
-    }
-
-    @Test
-    public void testGetPackageName() {
-        assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
-    }
-
-    @Test
-    public void testSendCustomCommand() throws InterruptedException {
-        // TODO(jaewan): Need to revisit with the permission.
-        final SessionCommand2 testCommand =
-                new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "testSendCustomAction");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onCustomCommand(MediaSession2 session, ControllerInfo controller,
-                    SessionCommand2 customCommand, Bundle args, ResultReceiver cb) {
-                super.onCustomCommand(session, controller, customCommand, args, cb);
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(testCommand, customCommand);
-                assertTrue(TestUtils.equals(testArgs, args));
-                assertNull(cb);
-                latch.countDown();
-            }
-        };
-        mSession.close();
-        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
-        final MediaController2 controller = createController(mSession.getToken());
-        controller.sendCustomCommand(testCommand, testArgs, null);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testControllerCallback_onConnected() throws InterruptedException {
-        // createController() uses controller callback to wait until the controller becomes
-        // available.
-        MediaController2 controller = createController(mSession.getToken());
-        assertNotNull(controller);
-    }
-
-    @Test
-    public void testControllerCallback_sessionRejects() throws InterruptedException {
-        final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup2 onConnect(MediaSession2 session,
-                    ControllerInfo controller) {
-                return null;
-            }
-        };
-        sHandler.postAndSync(() -> {
-            mSession.close();
-            mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-        });
-        MediaController2 controller =
-                createController(mSession.getToken(), false, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false);
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void testControllerCallback_releaseSession() throws InterruptedException {
-        sHandler.postAndSync(() -> {
-            mSession.close();
-        });
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void testControllerCallback_release() throws InterruptedException {
-        mController.close();
-        waitForDisconnect(mController, true);
-    }
-
-    @Test
-    public void testPlayFromSearch() throws InterruptedException {
-        final String request = "random query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller,
-                    String query, Bundle extras) {
-                super.onPlayFromSearch(session, controller, query, extras);
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, query);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromSearch").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.playFromSearch(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testPlayFromUri() throws InterruptedException {
-        final Uri request = Uri.parse("foo://boo");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPlayFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
-                    Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromUri").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.playFromUri(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testPlayFromMediaId() throws InterruptedException {
-        final String request = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
-                    String mediaId, Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, mediaId);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPlayFromMediaId").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.playFromMediaId(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testPrepareFromSearch() throws InterruptedException {
-        final String request = "random query";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
-                    String query, Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, query);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromSearch").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.prepareFromSearch(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testPrepareFromUri() throws InterruptedException {
-        final Uri request = Uri.parse("foo://boo");
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
-                    Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, uri);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromUri").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.prepareFromUri(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testPrepareFromMediaId() throws InterruptedException {
-        final String request = "media_id";
-        final Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
-                    String mediaId, Bundle extras) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, mediaId);
-                assertTrue(TestUtils.equals(bundle, extras));
-                latch.countDown();
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testPrepareFromMediaId").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.prepareFromMediaId(request, bundle);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testSetRating() throws InterruptedException {
-        final int ratingType = Rating2.RATING_5_STARS;
-        final float ratingValue = 3.5f;
-        final Rating2 rating = Rating2.newStarRating(ratingType, ratingValue);
-        final String mediaId = "media_id";
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback callback = new SessionCallback() {
-            @Override
-            public void onSetRating(MediaSession2 session, ControllerInfo controller,
-                    String mediaIdOut, Rating2 ratingOut) {
-                assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(mediaId, mediaIdOut);
-                assertEquals(rating, ratingOut);
-                latch.countDown();
-            }
-        };
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setSessionCallback(sHandlerExecutor, callback)
-                .setId("testSetRating").build()) {
-            MediaController2 controller = createController(session.getToken());
-            controller.setRating(mediaId, rating);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testIsConnected() throws InterruptedException {
-        assertTrue(mController.isConnected());
-        sHandler.postAndSync(()->{
-            mSession.close();
-        });
-        // postAndSync() to wait until the disconnection is propagated.
-        sHandler.postAndSync(()->{
-            assertFalse(mController.isConnected());
-        });
-    }
-
-    /**
-     * Test potential deadlock for calls between controller and session.
-     */
-    @Test
-    public void testDeadlock() throws InterruptedException {
-        sHandler.postAndSync(() -> {
-            mSession.close();
             mSession = null;
-        });
+        }
+    }
 
-        // Two more threads are needed not to block test thread nor test wide thread (sHandler).
-        final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
-        final HandlerThread testThread = new HandlerThread("testDeadlock_test");
-        sessionThread.start();
-        testThread.start();
-        final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
-        final Handler testHandler = new Handler(testThread.getLooper());
-        final CountDownLatch latch = new CountDownLatch(1);
-        try {
-            final MockPlayer player = new MockPlayer(0);
-            sessionHandler.postAndSync(() -> {
-                mSession = new MediaSession2.Builder(mContext)
-                        .setPlayer(mPlayer)
-                        .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                        .setId("testDeadlock").build();
-            });
-            final MediaController2 controller = createController(mSession.getToken());
-            testHandler.post(() -> {
-                final int state = MediaPlayerBase.PLAYER_STATE_ERROR;
-                for (int i = 0; i < 100; i++) {
-                    // triggers call from session to controller.
-                    player.notifyPlaybackState(state);
-                    // triggers call from controller to session.
-                    controller.play();
-
-                    // Repeat above
-                    player.notifyPlaybackState(state);
-                    controller.pause();
-                    player.notifyPlaybackState(state);
-                    controller.stop();
-                    player.notifyPlaybackState(state);
-                    controller.skipToNextItem();
-                    player.notifyPlaybackState(state);
-                    controller.skipToPreviousItem();
-                }
-                // This may hang if deadlock happens.
-                latch.countDown();
-            });
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    @Test
+    public void testGetConnectedToken() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller = new MediaController2(
+                mContext, mSession.getSessionToken(), sHandlerExecutor, controllerCallback)) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(mSession.getSessionToken(), controller.getConnectedSessionToken());
         } finally {
-            if (mSession != null) {
-                sessionHandler.postAndSync(() -> {
-                    // Clean up here because sessionHandler will be removed afterwards.
-                    mSession.close();
-                    mSession = null;
-                });
-            }
-            if (sessionThread != null) {
-                sessionThread.quitSafely();
-            }
-            if (testThread != null) {
-                testThread.quitSafely();
-            }
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+            assertNull(controllerCallback.mController.getConnectedSessionToken());
         }
     }
 
     @Test
-    public void testGetServiceToken() {
-        SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
-        assertNotNull(token);
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(MockMediaSessionService2.ID, token.getId());
-        assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+    public void testCallback_onConnected_onDisconnected() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller = new MediaController2(
+                mContext, mSession.getSessionToken(), sHandlerExecutor, controllerCallback)) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertTrue(controllerCallback.mAllowedCommands.hasCommand(ALLOWED_COMMAND_CODE));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
     }
 
     @Test
-    public void testConnectToService_sessionService() throws InterruptedException {
-        testConnectToService(MockMediaSessionService2.ID);
+    public void testCallback_onSessionCommand() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller = new MediaController2(
+                mContext, mSession.getSessionToken(), sHandlerExecutor, controllerCallback)) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            mSession.sendSessionCommand(mSessionCallback.mControllerInfo, command, commandArg);
+
+            assertTrue(controllerCallback.awaitOnSessionCommand(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(commandStr, controllerCallback.mCommand.getCustomCommand());
+            assertEquals(commandExtraValue,
+                    controllerCallback.mCommand.getExtras().getString(commandExtraKey));
+            assertEquals(commandArgValue, controllerCallback.mCommandArgs.getString(commandArgKey));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
     }
 
-    @Ignore
     @Test
-    public void testConnectToService_libraryService() throws InterruptedException {
-        testConnectToService(MockMediaLibraryService2.ID);
+    public void testCallback_onCommandResult() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller = new MediaController2(
+                mContext, mSession.getSessionToken(), sHandlerExecutor, controllerCallback)) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            controller.sendSessionCommand(command, commandArg);
+
+            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(SESSION_RESULT_CODE, controllerCallback.mCommandResult.getResultCode());
+            assertEquals(SESSION_RESULT_VALUE,
+                    controllerCallback.mCommandResult.getResultData()
+                            .getString(SESSION_RESULT_KEY));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
     }
 
-    public void testConnectToService(String id) throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
-                    @NonNull ControllerInfo controller) {
-                if (Process.myUid() == controller.getUid()) {
-                    if (mSession != null) {
-                        mSession.close();
-                    }
-                    mSession = session;
-                    mPlayer = (MockPlayer) session.getPlayer();
-                    assertEquals(mContext.getPackageName(), controller.getPackageName());
-                    assertFalse(controller.isTrusted());
-                    latch.countDown();
-                }
-                return super.onConnect(session, controller);
+    @Test
+    public void testCancelSessionCommand() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller = new MediaController2(
+                mContext, mSession.getSessionToken(), sHandlerExecutor, controllerCallback)) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command_";
+            String commandExtraKey = "test_extra_key_";
+            String commandExtraValue = "test_extra_value_";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key_";
+            String commandArgValue = "test_arg_value_";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            synchronized (sTestLock) {
+                Object token = controller.sendSessionCommand(command, commandArg);
+                controller.cancelSessionCommand(token);
             }
-        };
-        TestServiceRegistry.getInstance().setSessionCallback(sessionCallback);
-
-        mController = createController(TestUtils.getServiceToken(mContext, id));
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // Test command from controller to session service
-        mController.play();
-        assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-
-        // Test command from session service to controller
-        // TODO(jaewan): Add equivalent tests again
-        /*
-        final CountDownLatch latch = new CountDownLatch(1);
-        mController.registerPlayerEventCallback((state) -> {
-            assertNotNull(state);
-            assertEquals(PlaybackState.STATE_REWINDING, state.getState());
-            latch.countDown();
-        }, sHandler);
-        mPlayer.notifyPlaybackState(
-                TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        */
+            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(Session2Command.RESULT_INFO_SKIPPED,
+                    controllerCallback.mCommandResult.getResultCode());
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
     }
 
-    @Test
-    public void testControllerAfterSessionIsGone_session() throws InterruptedException {
-        testControllerAfterSessionIsGone(mSession.getToken().getId());
-    }
+    class Session2Callback extends MediaSession2.SessionCallback {
+        MediaSession2.ControllerInfo mControllerInfo;
 
-    // TODO(jaewan): Re-enable this test
-    @Ignore
-    @Test
-    public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
-        /*
-        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
-        testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
-        */
-    }
-
-    @Test
-    public void testClose_beforeConnected() throws InterruptedException {
-        MediaController2 controller =
-                createController(mSession.getToken(), false, null);
-        controller.close();
-    }
-
-    @Test
-    public void testClose_twice() {
-        mController.close();
-        mController.close();
-    }
-
-    @Test
-    public void testClose_session() throws InterruptedException {
-        final String id = mSession.getToken().getId();
-        mController.close();
-        // close is done immediately for session.
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsGone(id);
-    }
-
-    @Test
-    public void testClose_sessionService() throws InterruptedException {
-        testCloseFromService(MockMediaSessionService2.ID);
-    }
-
-    @Test
-    public void testClose_libraryService() throws InterruptedException {
-        testCloseFromService(MockMediaLibraryService2.ID);
-    }
-
-    private void testCloseFromService(String id) throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setSessionServiceCallback(new SessionServiceCallback() {
-            @Override
-            public void onDestroyed() {
-                latch.countDown();
-            }
-        });
-        mController = createController(TestUtils.getServiceToken(mContext, id));
-        mController.close();
-        // Wait until close triggers onDestroy() of the session service.
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertNull(TestServiceRegistry.getInstance().getServiceInstance());
-        testNoInteraction();
-
-        // Test whether the controller is notified about later close of the session or
-        // re-creation.
-        testControllerAfterSessionIsGone(id);
-    }
-
-    private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
-        sHandler.postAndSync(() -> {
-            // TODO(jaewan): Use Session.close later when we add the API.
-            mSession.close();
-        });
-        waitForDisconnect(mController, true);
-        testNoInteraction();
-
-        // Ensure that the controller cannot use newly create session with the same ID.
-        sHandler.postAndSync(() -> {
-            // Recreated session has different session stub, so previously created controller
-            // shouldn't be available.
-            mSession = new MediaSession2.Builder(mContext)
-                    .setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, new SessionCallback() {})
-                    .setId(id).build();
-        });
-        testNoInteraction();
-    }
-
-    private void testNoInteraction() throws InterruptedException {
-        // TODO: Uncomment
-        /*
-        final CountDownLatch latch = new CountDownLatch(1);
-        final PlayerEventCallback callback = new PlayerEventCallback() {
-            @Override
-            public void onPlaybackStateChanged(PlaybackState2 state) {
-                fail("Controller shouldn't be notified about change in session after the close.");
-                latch.countDown();
-            }
-        };
-        */
-
-        // TODO(jaewan): Add equivalent tests again
-        /*
-        mController.registerPlayerEventCallback(playbackListener, sHandler);
-        mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
-        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        mController.unregisterPlayerEventCallback(playbackListener);
-        */
-    }
-
-    // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
-    //               active/inactive and connection accept/refuse
-
-    class TestVolumeProvider extends VolumeProvider2 {
-        final CountDownLatch mLatch = new CountDownLatch(1);
-        boolean mSetVolumeToCalled;
-        boolean mAdjustVolumeCalled;
-        int mVolume;
-        int mDirection;
-
-        public TestVolumeProvider(int controlType, int maxVolume, int currentVolume) {
-            super(controlType, maxVolume, currentVolume);
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            mControllerInfo = controller;
+            return SESSION_ALLOWED_COMMANDS;
         }
 
         @Override
-        public void onSetVolumeTo(int volume) {
-            mSetVolumeToCalled = true;
-            mVolume = volume;
-            mLatch.countDown();
+        public Session2Command.Result onSessionCommand(MediaSession2 session,
+                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
+            return SESSION_COMMAND_RESULT;
+        }
+    }
+
+    class Controller2Callback extends MediaController2.ControllerCallback {
+        CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
+        CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
+        private CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
+        private CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
+
+        MediaController2 mController;
+        Session2Command mCommand;
+        Session2CommandGroup mAllowedCommands;
+        Bundle mCommandArgs;
+        Session2Command.Result mCommandResult;
+
+        public boolean await(long waitMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
         }
 
         @Override
-        public void onAdjustVolume(int direction) {
-            mAdjustVolumeCalled = true;
-            mDirection = direction;
-            mLatch.countDown();
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            mController = controller;
+            mAllowedCommands = allowedCommands;
+            mOnConnectedLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            mController = controller;
+            mOnDisconnectedLatch.countDown();
+        }
+
+        @Override
+        public Session2Command.Result onSessionCommand(MediaController2 controller,
+                Session2Command command, Bundle args) {
+            mController = controller;
+            mCommand = command;
+            mCommandArgs = args;
+            mOnSessionCommandLatch.countDown();
+            return SESSION_COMMAND_RESULT;
+        }
+
+        @Override
+        public void onCommandResult(MediaController2 controller,Object token,
+                Session2Command command, Session2Command.Result result) {
+            mController = controller;
+            mCommand = command;
+            mCommandResult = result;
+            mOnCommandResultLatch.countDown();
+        }
+
+        public boolean awaitOnConnected(long waitTimeMs) {
+            try {
+                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnDisconnected(long waitTimeMs) {
+            try {
+                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnSessionCommand(long waitTimeMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnCommandResult(long waitTimeMs) {
+            try {
+                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
         }
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index f4b2b39..55d5f59 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -16,6 +16,7 @@
 package android.media.cts;
 
 import static android.media.cts.Utils.compareRemoteUserInfo;
+import static android.media.session.PlaybackState.STATE_PLAYING;
 
 import android.content.Intent;
 import android.media.AudioManager;
@@ -24,6 +25,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.media.session.PlaybackState;
 import android.media.session.PlaybackState.CustomAction;
 import android.net.Uri;
 import android.os.Bundle;
@@ -65,11 +67,52 @@
         assertEquals(getContext().getPackageName(), mController.getPackageName());
     }
 
+    public void testGetPlaybackState() {
+        final int testState = STATE_PLAYING;
+        final long testPosition = 100000L;
+        final float testSpeed = 1.0f;
+        final long testActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP
+                | PlaybackState.ACTION_SEEK_TO;
+        final long testActiveQueueItemId = 3377;
+        final long testBufferedPosition = 100246L;
+        final String testErrorMsg = "ErrorMsg";
+
+        final Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        final double positionDelta = 500;
+
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(testState, testPosition, testSpeed)
+                .setActions(testActions)
+                .setActiveQueueItemId(testActiveQueueItemId)
+                .setBufferedPosition(testBufferedPosition)
+                .setErrorMessage(testErrorMsg)
+                .setExtras(extras)
+                .build();
+
+        mSession.setPlaybackState(state);
+
+        // Note: No need to wait since the AIDL call is not oneway.
+        PlaybackState stateOut = mController.getPlaybackState();
+        assertNotNull(stateOut);
+        assertEquals(testState, stateOut.getState());
+        assertEquals(testPosition, stateOut.getPosition(), positionDelta);
+        assertEquals(testSpeed, stateOut.getPlaybackSpeed(), 0.0f);
+        assertEquals(testActions, stateOut.getActions());
+        assertEquals(testActiveQueueItemId, stateOut.getActiveQueueItemId());
+        assertEquals(testBufferedPosition, stateOut.getBufferedPosition());
+        assertEquals(testErrorMsg, stateOut.getErrorMessage().toString());
+        assertNotNull(stateOut.getExtras());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+    }
+
     public void testGetRatingType() {
         assertEquals("Default rating type of a session must be Rating.RATING_NONE",
                 Rating.RATING_NONE, mController.getRatingType());
 
         mSession.setRatingType(Rating.RATING_5_STARS);
+        // Note: No need to wait since the AIDL call is not oneway.
         assertEquals(Rating.RATING_5_STARS, mController.getRatingType());
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index c504c4b..80581a1 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -20,6 +20,7 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecList;
 import android.media.MediaDrm;
+import android.media.MediaDrm.MediaDrmStateException;
 import android.media.MediaDrmException;
 import android.media.MediaFormat;
 import android.net.Uri;
@@ -101,12 +102,14 @@
             new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
 
     private byte[] mDrmInitData;
+    private byte[] mKeySetId;
     private byte[] mSessionId;
     private Looper mLooper;
     private MediaCodecClearKeyPlayer mMediaCodecPlayer;
     private MediaDrm mDrm = null;
     private final Object mLock = new Object();
     private SurfaceHolder mSurfaceHolder;
+    private boolean mLostStateReceived;
 
     @Override
     protected void setUp() throws Exception {
@@ -166,7 +169,7 @@
      *
      * @return JSON Web Key string.
      */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
+    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys, int keyType) {
         String jwkSet = "{\"keys\":[";
         for (int i = 0; i < keyIds.size(); ++i) {
             String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
@@ -175,7 +178,12 @@
             jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
                     "\",\"k\":\"" + key + "\"}";
         }
-        jwkSet += "]}";
+        jwkSet += "], \"type\":";
+        if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) {
+            jwkSet += "\"persistent-license\" }";
+        } else {
+            jwkSet += "\"temporary\" }";
+        }
         return jwkSet;
     }
 
@@ -184,11 +192,11 @@
      * set and send it to the CDM via provideKeyResponse().
      */
     private void getKeys(MediaDrm drm, String initDataType,
-            byte[] sessionId, byte[] drmInitData, byte[][] clearKeys) {
+            byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
         MediaDrm.KeyRequest drmRequest = null;;
         try {
             drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
-                    MediaDrm.KEY_TYPE_STREAMING, null);
+                    keyType, null);
         } catch (Exception e) {
             e.printStackTrace();
             Log.i(TAG, "Failed to get key request: " + e.toString());
@@ -204,26 +212,26 @@
             return;
         }
 
-        if (clearKeys.length != keyIds.size()) {
+        if (clearKeyIds.length != keyIds.size()) {
             Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
-                    keyIds.size() + ", keys=" + clearKeys.length);
+                    keyIds.size() + ", keys=" + clearKeyIds.length);
             return;
         }
 
         // Base64 encodes clearkeys. Keys are known to the application.
         Vector<String> keys = new Vector<String>();
-        for (int i = 0; i < clearKeys.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeys[i],
+        for (int i = 0; i < clearKeyIds.length; ++i) {
+            String clearKey = Base64.encodeToString(clearKeyIds[i],
                     Base64.NO_PADDING | Base64.NO_WRAP);
             keys.add(clearKey);
         }
 
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
+        String jwkSet = createJsonWebKeySet(keyIds, keys, keyType);
         byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
 
         try {
             try {
-                drm.provideKeyResponse(sessionId, jsonResponse);
+                mKeySetId = drm.provideKeyResponse(sessionId, jsonResponse);
             } catch (IllegalStateException e) {
                 Log.e(TAG, "Failed to provide key response: " + e.toString());
             }
@@ -233,7 +241,8 @@
         }
     }
 
-    private @NonNull MediaDrm startDrm(final byte[][] clearKeys, final String initDataType, final UUID drmSchemeUuid) {
+    private @NonNull MediaDrm startDrm(final byte[][] clearKeyIds, final String initDataType,
+                                       final UUID drmSchemeUuid, int keyType) {
         if (!MediaDrm.isCryptoSchemeSupported(drmSchemeUuid)) {
             throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
         }
@@ -266,15 +275,31 @@
                                     int extra, byte[] data) {
                                 if (event == MediaDrm.EVENT_KEY_REQUIRED) {
                                     Log.i(TAG, "MediaDrm event: Key required");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
+                                            keyType, clearKeyIds);
                                 } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
                                     Log.i(TAG, "MediaDrm event: Key expired");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
+                                            keyType, clearKeyIds);
                                 } else {
                                     Log.e(TAG, "Events not supported" + event);
                                 }
                             }
                         });
+                    mDrm.setOnSessionLostStateListener(new MediaDrm.OnSessionLostStateListener() {
+                            @Override
+                            public void onSessionLostState(MediaDrm md, byte[] sid) {
+                                if (md != mDrm) {
+                                    Log.e(TAG, "onSessionLostState callback: drm object mismatch");
+                                } else if (!Arrays.equals(mSessionId, sid)) {
+                                    Log.e(TAG, "onSessionLostState callback: sessionId mismatch: |" +
+                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+                                } else {
+                                    mLostStateReceived = true;
+                                }
+                            }
+                        }, null);
+
                     mLock.notify();
                 }
                 Looper.loop();  // Blocks forever until Looper.quit() is called.
@@ -350,32 +375,16 @@
         return true;
     }
 
-    /**
-     * Tests clear key system playback.
+    /*
+     * Verify if we can support playback resolution and has network connection.
+     * @return true if both conditions are true, else false
      */
-    private void testClearKeyPlayback(
-            UUID drmSchemeUuid,
-            String videoMime, String[] videoFeatures,
-            String initDataType, byte[][] clearKeys,
-            Uri audioUrl, boolean audioEncrypted,
-            Uri videoUrl, boolean videoEncrypted,
-            int videoWidth, int videoHeight, boolean scrambled) throws Exception {
-
-        if (isWatchDevice()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        mSessionId = null;
-        if (!scrambled) {
-            drm = startDrm(clearKeys, initDataType, drmSchemeUuid);
-            mSessionId = openSession(drm);
-        }
-
+    private boolean playbackPreCheck(String videoMime, String[] videoFeatures,
+            Uri videoUrl, int videoWidth, int videoHeight) {
         if (!isResolutionSupported(videoMime, videoFeatures, videoWidth, videoHeight)) {
             Log.i(TAG, "Device does not support " +
                     videoWidth + "x" + videoHeight + " resolution for " + videoMime);
-            return;
+            return false;
         }
 
         IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
@@ -397,6 +406,36 @@
             }
         }
         connectionStatus.testConnection(videoUrl);
+        return true;
+    }
+
+    /**
+     * Tests clear key system playback.
+     */
+    private void testClearKeyPlayback(
+            UUID drmSchemeUuid,
+            String videoMime, String[] videoFeatures,
+            String initDataType, byte[][] clearKeyIds,
+            Uri audioUrl, boolean audioEncrypted,
+            Uri videoUrl, boolean videoEncrypted,
+            int videoWidth, int videoHeight, boolean scrambled, int keyType) throws Exception {
+
+        if (isWatchDevice()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        if (!scrambled) {
+            drm = startDrm(clearKeyIds, initDataType, drmSchemeUuid, keyType);
+            mSessionId = openSession(drm);
+        }
+
+        if (false == playbackPreCheck(videoMime, videoFeatures, videoUrl,
+                videoWidth, videoHeight)) {
+            Log.e(TAG, "Failed playback precheck");
+            return;
+        }
 
         mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
                 getActivity().getSurfaceHolder(),
@@ -409,8 +448,21 @@
         mMediaCodecPlayer.prepare();
         if (!scrambled) {
             mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, clearKeys);
+            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, keyType, clearKeyIds);
         }
+
+        if (!scrambled && keyType == MediaDrm.KEY_TYPE_OFFLINE) {
+            closeSession(drm, mSessionId);
+            mSessionId = openSession(drm);
+            if (mKeySetId.length > 0) {
+                drm.restoreKeys(mSessionId, mKeySetId);
+            } else {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                throw new Error("Invalid keySetId size for offline license");
+            }
+        }
+
         // starts video playback
         mMediaCodecPlayer.startThread();
 
@@ -432,6 +484,73 @@
         }
     }
 
+    /**
+     * Tests KEY_TYPE_RELEASE for offline license.
+     */
+    public void testReleaseOfflineLicense() throws Exception {
+        if (isWatchDevice()) {
+            return;
+        }
+
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        mSessionId = null;
+        String initDataType = "cenc";
+
+        MediaDrm drm = startDrm(clearKeyIds, initDataType,
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
+        mSessionId = openSession(drm);
+
+        Uri videoUrl = CENC_VIDEO_URL;
+        if (false == playbackPreCheck(MIME_VIDEO_AVC,
+                new String[] { CodecCapabilities.FEATURE_SecurePlayback }, videoUrl,
+                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
+            Log.e(TAG, "Failed playback precheck");
+            return;
+        }
+
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getActivity().getSurfaceHolder(),
+                mSessionId, false /*scrambled */,
+                mContext.getResources());
+
+        Uri audioUrl = CENC_AUDIO_URL;
+        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, false);
+        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, true);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+
+        // Create and store the offline license
+        getKeys(mDrm, initDataType, mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
+                clearKeyIds);
+
+        // Verify the offline license is valid
+        closeSession(drm, mSessionId);
+        mSessionId = openSession(drm);
+        drm.restoreKeys(mSessionId, mKeySetId);
+        closeSession(drm, mSessionId);
+
+        // Release the offline license
+        getKeys(mDrm, initDataType, mKeySetId, mDrmInitData, MediaDrm.KEY_TYPE_RELEASE,
+                clearKeyIds);
+
+        // Verify restoreKeys will throw an exception if the offline license
+        // has already been released
+        mSessionId = openSession(drm);
+        try {
+            drm.restoreKeys(mSessionId, mKeySetId);
+        } catch (MediaDrmStateException e) {
+            // Expected exception caught, all is good
+            return;
+        } finally {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+
+        // Did not receive expected exception, throw an Error
+        throw new Error("Did not receive expected exception from restoreKeys");
+    }
+
     private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
         final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
         if (keyStatus.isEmpty()) {
@@ -458,7 +577,7 @@
         }
 
         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
-                CLEARKEY_SCHEME_UUID);
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
 
         mSessionId = openSession(drm);
 
@@ -481,7 +600,8 @@
         mMediaCodecPlayer.prepare();
 
         mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, "cenc", mSessionId, mDrmInitData, new byte[][] { CLEAR_KEY_CENC });
+        getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_STREAMING,
+                new byte[][] { CLEAR_KEY_CENC });
         boolean success = true;
         if (!queryKeyStatus(drm, mSessionId)) {
             success = false;
@@ -495,6 +615,91 @@
         }
     }
 
+    public void testOfflineKeyManagement() throws Exception {
+        if (isWatchDevice()) {
+            // skip this test on watch because it calls
+            // addTrack that requires codec
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
+
+        if (getClearkeyVersion(drm).matches("1.[01]")) {
+            Log.i(TAG, "Skipping testsOfflineKeyManagement: clearkey 1.2 required");
+            return;
+        }
+
+        mSessionId = openSession(drm);
+
+        // Test get offline keys
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getActivity().getSurfaceHolder(),
+                mSessionId, false,
+                mContext.getResources());
+        mMediaCodecPlayer.setAudioDataSource(CENC_AUDIO_URL, null, false);
+        mMediaCodecPlayer.setVideoDataSource(CENC_VIDEO_URL, null, true);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+
+        try {
+            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+
+            List<byte[]> keySetIds = drm.getOfflineLicenseKeySetIds();
+            int preCount = keySetIds.size();
+
+            getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
+                    new byte[][] { CLEAR_KEY_CENC });
+
+            if (drm.getOfflineLicenseState(mKeySetId) != MediaDrm.OFFLINE_LICENSE_STATE_USABLE) {
+                throw new Error("Offline license state is not usable");
+            }
+
+            keySetIds = drm.getOfflineLicenseKeySetIds();
+
+            if (keySetIds.size() != preCount + 1) {
+                throw new Error("KeySetIds size did not increment");
+            }
+
+            boolean found = false;
+            for (int i = 0; i < keySetIds.size(); i++) {
+                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new Error("New KeySetId is missing from KeySetIds");
+            }
+
+            drm.removeOfflineLicense(mKeySetId);
+
+            keySetIds = drm.getOfflineLicenseKeySetIds();
+            if (keySetIds.size() != preCount) {
+                throw new Error("KeySetIds size is incorrect");
+            }
+
+            found = false;
+            for (int i = 0; i < keySetIds.size(); i++) {
+                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (found) {
+                throw new Error("New KeySetId is still in from KeySetIds after removal");
+            }
+
+            // TODO: after RELEASE is implemented: add offline key, release it
+            // get offline key status, check state is inactive
+        } finally {
+            mMediaCodecPlayer.reset();
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+    }
+
     public void testClearKeyPlaybackCenc() throws Exception {
         testClearKeyPlayback(
             COMMON_PSSH_SCHEME_UUID,
@@ -503,7 +708,8 @@
             "cenc", new byte[][] { CLEAR_KEY_CENC },
             CENC_AUDIO_URL, false  /* audioEncrypted */,
             CENC_VIDEO_URL, true /* videoEncrypted */,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */);
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
     }
 
     public void testClearKeyPlaybackCenc2() throws Exception {
@@ -514,7 +720,20 @@
             "cenc", new byte[][] { CLEAR_KEY_CENC },
             CENC_AUDIO_URL, false /* audioEncrypted */ ,
             CENC_VIDEO_URL, true /* videoEncrypted */,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */);
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    public void testClearKeyPlaybackOfflineCenc() throws Exception {
+        testClearKeyPlayback(
+                CLEARKEY_SCHEME_UUID,
+                // using secure codec even though it is clear key DRM
+                MIME_VIDEO_AVC, new String[] { CodecCapabilities.FEATURE_SecurePlayback },
+                "cenc", new byte[][] { CLEAR_KEY_CENC },
+                CENC_AUDIO_URL, false /* audioEncrypted */ ,
+                CENC_VIDEO_URL, true /* videoEncrypted */,
+                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                MediaDrm.KEY_TYPE_OFFLINE);
     }
 
     public void testClearKeyPlaybackWebm() throws Exception {
@@ -524,7 +743,8 @@
             "webm", new byte[][] { CLEAR_KEY_WEBM },
             WEBM_URL, true /* audioEncrypted */,
             WEBM_URL, true /* videoEncrypted */,
-            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */);
+            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
     }
 
     public void testClearKeyPlaybackMpeg2ts() throws Exception {
@@ -534,7 +754,8 @@
             "mpeg2ts", null,
             MPEG2TS_SCRAMBLED_URL, false /* audioEncrypted */,
             MPEG2TS_SCRAMBLED_URL, false /* videoEncrypted */,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */);
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
     }
 
     public void testPlaybackMpeg2ts() throws Exception {
@@ -544,7 +765,8 @@
             "mpeg2ts", null,
             MPEG2TS_CLEAR_URL, false /* audioEncrypted */,
             MPEG2TS_CLEAR_URL, false /* videoEncrypted */,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */);
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
     }
 
     private String getStringProperty(final MediaDrm drm,  final String key) {
@@ -601,7 +823,7 @@
         }
 
         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
-                "cenc", CLEARKEY_SCHEME_UUID);
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
 
         try {
             // The following tests will not verify the value we are getting
@@ -646,7 +868,7 @@
         }
 
         MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
-                "cenc", CLEARKEY_SCHEME_UUID);
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
 
         try {
             if (cannotHandleSetPropertyString(drm)) {
@@ -726,7 +948,7 @@
         }
 
         MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
-                "cenc", CLEARKEY_SCHEME_UUID);
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
 
         try {
             if (getClearkeyVersion(drm).equals("1.0")) {
@@ -845,7 +1067,8 @@
             return;
         }
 
-        MediaDrm drm = startDrm(new byte[][] {CLEAR_KEY_CENC}, "cenc", CLEARKEY_SCHEME_UUID);
+        MediaDrm drm = startDrm(new byte[][] {CLEAR_KEY_CENC}, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
 
         byte[] sessionId = null;
         try {
@@ -874,7 +1097,8 @@
             mDrmInitData = mMediaCodecPlayer.getDrmInitData();
 
             for (int i = 0; i < NUMBER_OF_SECURE_STOPS; ++i) {
-                getKeys(drm, "cenc", mSessionId, mDrmInitData, new byte[][] {CLEAR_KEY_CENC});
+                getKeys(drm, "cenc", mSessionId, mDrmInitData,
+                        MediaDrm.KEY_TYPE_STREAMING, new byte[][] {CLEAR_KEY_CENC});
             }
             Log.d(TAG, "Test getSecureStops.");
             secureStops = drm.getSecureStops();
@@ -954,6 +1178,111 @@
         }
     }
 
+    /**
+     * Test that the framework handles a device returning
+     * ::android::hardware::drm@1.2::Status::ERROR_DRM_RESOURCE_CONTENTION.
+     * Expected behavior: throws MediaDrm.SessionException with
+     * errorCode ERROR_RESOURCE_CONTENTION
+     */
+    public void testResourceContentionError() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        boolean gotException = false;
+
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+            drm.setPropertyString("drmErrorTest", "resourceContention");
+            byte[] sessionId = drm.openSession();
+
+            try {
+                byte[] ignoredInitData = new byte[] { 1 };
+                drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
+            } catch (MediaDrm.SessionException e) {
+                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
+                    throw new Error("Incorrect error code, expected ERROR_RESOURCE_CONTENTION");
+                }
+                gotException = true;
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+        if (!gotException) {
+            throw new Error("Didn't receive expected MediaDrm.SessionException");
+        }
+    }
+
+    /**
+     * Test that the framework handles a device returning invoking
+     * the ::android::hardware::drm@1.2::sendSessionLostState callback
+     * Expected behavior: OnSessionLostState is called with
+     * the sessionId
+     */
+    public void testSessionLostStateError() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        boolean gotException = false;
+        mLostStateReceived = false;
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        mDrm.setPropertyString("drmErrorTest", "lostState");
+        mSessionId = openSession(drm);
+
+        // simulates session lost state here, detected by closeSession
+
+        try {
+            try {
+                closeSession(drm, mSessionId);
+            } catch (MediaDrmStateException e) {
+                gotException = true; // expected for lost state
+            }
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mLostStateReceived; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (!mLostStateReceived) {
+                throw new Error("Callback for OnSessionLostStateListener not received");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            stopDrm(drm);
+        }
+        if (!gotException) {
+            throw new Error("Didn't receive expected MediaDrmStateException");
+        }
+    }
+
+    public void testIsCryptoSchemeSupportedWithSecurityLevel() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        if (MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
+                                             MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL)) {
+            throw new Error("Clearkey claims to support SECURITY_LEVEL_HW_SECURE_ALL");
+        }
+        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
+                                              MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO)) {
+            throw new Error("Clearkey claims not to support SECURITY_LEVEL_SW_SECURE_CRYPTO");
+        }
+    }
+
     private String getClearkeyVersion(MediaDrm drm) {
         try {
             return drm.getPropertyString("version");
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
index 87ae2c0..2ac83cf 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
@@ -20,11 +20,11 @@
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 
 import android.media.MediaDrm;
-import android.media.MediaDrm.MediaDrmStateException;
 import android.os.PersistableBundle;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import com.google.common.io.BaseEncoding;
+import java.lang.IllegalArgumentException;
 import java.util.Base64;
 import java.util.HashSet;
 import java.util.StringJoiner;
@@ -144,7 +144,7 @@
 
         try {
           drm.getKeyRequest(sid, null, "", 2, null);
-        } catch (MediaDrmStateException e) {
+        } catch (IllegalArgumentException e) {
           // Exception expected.
         }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index 7dc9130..c5cfeef 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -16,21 +16,41 @@
 
 package android.media.cts;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.media.cts.R;
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.icu.util.ULocale;
+import android.media.AudioFormat;
 import android.media.AudioPresentation;
 import android.media.MediaDataSource;
 import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
+import java.io.Closeable;
+import java.io.InputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.Reader;
+import java.io.StreamTokenizer;
 
 public class MediaExtractorTest extends AndroidTestCase {
+    private static final String TAG = "MediaExtractorTest";
+
     protected Resources mResources;
     protected MediaExtractor mExtractor;
 
@@ -125,10 +145,348 @@
 
     }
 
+    static boolean audioPresentationSetMatchesReference(
+            Map<Integer, AudioPresentation> reference,
+            List<AudioPresentation> actual) {
+        if (reference.size() != actual.size()) {
+            Log.w(TAG, "AudioPresentations set size is invalid, expected: " +
+                    reference.size() + ", actual: " + actual.size());
+            return false;
+        }
+        for (AudioPresentation ap : actual) {
+            AudioPresentation refAp = reference.get(ap.getPresentationId());
+            if (refAp == null) {
+                Log.w(TAG, "AudioPresentation not found in the reference set, presentation id=" +
+                        ap.getPresentationId());
+                return false;
+            }
+            if (!refAp.equals(ap)) {
+                Log.w(TAG, "AudioPresentations are different, reference: " +
+                        refAp + ", actual: " + ap);
+                return false;
+            }
+        }
+        return true;
+    }
+
     public void testGetAudioPresentations() throws Exception {
-        TestMediaDataSource dataSource = setDataSource(R.raw.testvideo);
-        List<AudioPresentation> presentations = mExtractor.getAudioPresentations(0 /*trackIndex*/);
-        assertNotNull(presentations);
-        assertTrue(presentations.isEmpty());
+        final int resid = R.raw.MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps;
+        TestMediaDataSource dataSource = setDataSource(resid);
+        int ac4TrackIndex = -1;
+        for (int i = 0; i < mExtractor.getTrackCount(); i++) {
+            MediaFormat format = mExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (MediaFormat.MIMETYPE_AUDIO_AC4.equals(mime)) {
+                ac4TrackIndex = i;
+                break;
+            }
+        }
+        assertNotEquals(
+                "AC4 track was not found in MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps",
+                -1, ac4TrackIndex);
+
+        // The test file has two sets of audio presentations. The presentation set
+        // changes for every 100 audio presentation descriptors between two presentations.
+        // Instead of attempting to count the presentation descriptors, the test assumes
+        // a particular order of the presentations and advances to the next reference set
+        // once getAudioPresentations returns a set that doesn't match the current reference set.
+        // Thus the test can match the set 0 several times, then it encounters set 1,
+        // advances the reference set index, matches set 1 until it encounters set 2 etc.
+        // At the end it verifies that all the reference sets were met.
+        List<Map<Integer, AudioPresentation>> refPresentations = Arrays.asList(
+                new HashMap<Integer, AudioPresentation>() {{  // First set.
+                    put(10, new AudioPresentation.Builder(10)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(11, new AudioPresentation.Builder(11)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasAudioDescription(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(12, new AudioPresentation.Builder(12)
+                            .setLocale(ULocale.FRENCH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                }},
+                new HashMap<Integer, AudioPresentation>() {{  // Second set.
+                    put(10, new AudioPresentation.Builder(10)
+                            .setLocale(ULocale.GERMAN)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasAudioDescription(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(11, new AudioPresentation.Builder(11)
+                            .setLocale(new ULocale("es"))
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasSpokenSubtitles(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(12, new AudioPresentation.Builder(12)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                }},
+                null,
+                null
+        );
+        refPresentations.set(2, refPresentations.get(0));
+        refPresentations.set(3, refPresentations.get(1));
+        boolean[] presentationsMatched = new boolean[refPresentations.size()];
+        mExtractor.selectTrack(ac4TrackIndex);
+        for (int i = 0; i < refPresentations.size(); ) {
+            List<AudioPresentation> presentations = mExtractor.getAudioPresentations(ac4TrackIndex);
+            assertNotNull(presentations);
+            // Assumes all presentation sets have the same number of presentations.
+            assertEquals(refPresentations.get(i).size(), presentations.size());
+            if (!audioPresentationSetMatchesReference(refPresentations.get(i), presentations)) {
+                    // Time to advance to the next presentation set.
+                    i++;
+                    continue;
+            }
+            Log.d(TAG, "Matched presentation " + i);
+            presentationsMatched[i] = true;
+            // No need to wait for another switch after the last presentation has been matched.
+            if (i == presentationsMatched.length - 1 || !mExtractor.advance()) {
+                break;
+            }
+        }
+        for (int i = 0; i < presentationsMatched.length; i++) {
+            assertTrue("Presentation set " + i + " was not found in the stream",
+                    presentationsMatched[i]);
+        }
+    }
+
+    /*
+     * Makes sure if PTS(order) of a video file with BFrames matches the expected values in
+     * the corresponding text file with just PTS values.
+     */
+    public void testVideoPresentationTimeStampsMatch() throws Exception {
+        setDataSource(R.raw.binary_counter_320x240_30fps_600frames);
+        // Select the only video track present in the file.
+        final int trackCount = mExtractor.getTrackCount();
+        for (int i = 0; i < trackCount; i++) {
+            mExtractor.selectTrack(i);
+        }
+
+        Reader txtRdr = new BufferedReader(new InputStreamReader(mResources.openRawResource(
+                R.raw.timestamps_binary_counter_320x240_30fps_600frames)));
+        StreamTokenizer strTok = new StreamTokenizer(txtRdr);
+        strTok.parseNumbers();
+
+        boolean srcAdvance = false;
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+
+        strTok.nextToken();
+        do {
+            srcSampleTimeUs = mExtractor.getSampleTime();
+            testSampleTimeUs = (long) strTok.nval;
+
+            // Ignore round-off error if any.
+            if (Math.abs(srcSampleTimeUs - testSampleTimeUs) > 1) {
+                Log.d(TAG, "srcSampleTimeUs:" + srcSampleTimeUs + " testSampleTimeUs:" +
+                        testSampleTimeUs);
+                fail("video presentation timestamp not equal");
+            }
+
+            srcAdvance = mExtractor.advance();
+            // TODO: no need to reset strTok.nval to -1 once MediaExtractor.advance() bug -
+            //       b/121204004 is fixed
+            strTok.nval = -1;
+            strTok.nextToken();
+        } while (srcAdvance);
+    }
+
+    /* package */ static class ByteBufferDataSource extends MediaDataSource {
+        private final long mSize;
+        private TreeMap<Long, ByteBuffer> mMap = new TreeMap<Long, ByteBuffer>();
+
+        public ByteBufferDataSource(MediaCodecTest.ByteBufferStream bufferStream)
+                throws IOException {
+            long size = 0;
+            while (true) {
+                final ByteBuffer buffer = bufferStream.read();
+                if (buffer == null) break;
+                final int limit = buffer.limit();
+                if (limit == 0) continue;
+                size += limit;
+                mMap.put(size - 1, buffer); // key: last byte of validity for the buffer.
+            }
+            mSize = size;
+        }
+
+        @Override
+        public long getSize() {
+            return mSize;
+        }
+
+        @Override
+        public int readAt(long position, byte[] buffer, int offset, int size) {
+            Log.v(TAG, "reading at " + position + " offset " + offset + " size " + size);
+
+            // This chooses all buffers with key >= position (e.g. valid buffers)
+            final SortedMap<Long, ByteBuffer> map = mMap.tailMap(position);
+            int copied = 0;
+            for (Map.Entry<Long, ByteBuffer> e : map.entrySet()) {
+                // Get a read-only version of the byte buffer.
+                final ByteBuffer bb = e.getValue().asReadOnlyBuffer();
+                // Convert read position to an offset within that byte buffer, bboffs.
+                final long bboffs = position - e.getKey() + bb.limit() - 1;
+                if (bboffs >= bb.limit() || bboffs < 0) {
+                    break; // (negative position)?
+                }
+                bb.position((int)bboffs); // cast is safe as bb.limit is int.
+                final int tocopy = Math.min(size, bb.remaining());
+                if (tocopy == 0) {
+                    break; // (size == 0)?
+                }
+                bb.get(buffer, offset, tocopy);
+                copied += tocopy;
+                size -= tocopy;
+                offset += tocopy;
+                position += tocopy;
+                if (size == 0) {
+                    break; // finished copying.
+                }
+            }
+            if (copied == 0) {
+                copied = -1;  // signal end of file
+            }
+            return copied;
+        }
+
+        @Override
+        public void close() {
+            mMap = null;
+        }
+    }
+
+    /* package */ static class MediaExtractorStream
+                extends MediaCodecTest.ByteBufferStream implements Closeable {
+        public boolean mIsFloat;
+        public boolean mSawOutputEOS;
+        public MediaFormat mFormat;
+
+        private MediaExtractor mExtractor;
+
+        public MediaExtractorStream(
+                String mime,
+                MediaDataSource dataSource) throws Exception {
+            mExtractor = new MediaExtractor();
+            mExtractor.setDataSource(dataSource);
+            final int numTracks = mExtractor.getTrackCount();
+            // Single track?
+            // assertEquals("Number of tracks should be 1", 1, numTracks);
+            for (int i = 0; i < numTracks; ++i) {
+                final MediaFormat format = mExtractor.getTrackFormat(i);
+                final String actualMime = format.getString(MediaFormat.KEY_MIME);
+                if (mime.equals(actualMime)) {
+                    mExtractor.selectTrack(i);
+                    mFormat = format;
+                    break;
+                }
+            }
+            assertNotNull("MediaExtractor cannot find mime type " + mime, mFormat);
+            Integer actualEncoding = null;
+            try {
+                actualEncoding = mFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
+            } catch (Exception e) {
+                ; // trying to get a non-existent key throws exception
+            }
+            mIsFloat = actualEncoding != null && actualEncoding == AudioFormat.ENCODING_PCM_FLOAT;
+        }
+
+        public MediaExtractorStream(
+                String mime,
+                MediaCodecTest.ByteBufferStream inputStream) throws Exception {
+            this(mime, new ByteBufferDataSource(inputStream));
+        }
+
+        @Override
+        public ByteBuffer read() throws IOException {
+            if (mSawOutputEOS) {
+                return null;
+            }
+
+            // To preserve codec-like behavior, we create ByteBuffers
+            // equal to the media sample size.
+            final long size = mExtractor.getSampleSize();
+            if (size >= 0) {
+                final ByteBuffer inputBuffer = ByteBuffer.allocate((int)size);
+                final int red = mExtractor.readSampleData(inputBuffer, 0 /* offset */); // sic
+                if (red >= 0) {
+                    assertEquals("position must be zero", 0, inputBuffer.position());
+                    assertEquals("limit must be read bytes", red, inputBuffer.limit());
+                    mExtractor.advance();
+                    return inputBuffer;
+                }
+            }
+            mSawOutputEOS = true;
+            return null;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (mExtractor != null) {
+                mExtractor.release();
+                mExtractor = null;
+            }
+            mFormat = null;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mExtractor != null) {
+                Log.w(TAG, "MediaExtractorStream wasn't closed");
+                mExtractor.release();
+            }
+            mFormat = null;
+        }
+    }
+
+    @SmallTest
+    public void testFlacIdentity() throws Exception {
+        final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
+        final int CHANNEL_COUNT = 2;
+        final int SAMPLES = PCM_FRAMES * CHANNEL_COUNT;
+        final int[] SAMPLE_RATES = {44100, 192000}; // ensure 192kHz supported.
+
+        for (int sampleRate : SAMPLE_RATES) {
+            final MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNEL_COUNT);
+
+            Log.d(TAG, "Trying sample rate: " + sampleRate
+                    + " channel count: " + CHANNEL_COUNT);
+            format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
+
+            // TODO: Add float mode when MediaExtractor supports float configuration.
+            final MediaCodecTest.PcmAudioBufferStream audioStream =
+                    new MediaCodecTest.PcmAudioBufferStream(
+                            SAMPLES, sampleRate, 1000 /* frequency */, 100 /* sweep */,
+                          false /* useFloat */);
+
+            final MediaCodecTest.MediaCodecStream rawToFlac =
+                    new MediaCodecTest.MediaCodecStream(
+                            new MediaCodecTest.ByteBufferInputStream(audioStream),
+                            format, true /* encode */);
+            final MediaExtractorStream flacToRaw =
+                    new MediaExtractorStream("audio/raw", rawToFlac);
+
+            // Note: the existence of signed zero (as well as NAN) may make byte
+            // comparisons invalid for floating point output. In our case, since the
+            // floats come through integer to float conversion, it does not matter.
+            assertEquals("Audio data not identical after compression",
+                audioStream.sizeInBytes(),
+                MediaCodecTest.compareStreams(new MediaCodecTest.ByteBufferInputStream(flacToRaw),
+                    new MediaCodecTest.ByteBufferInputStream(
+                            new MediaCodecTest.PcmAudioBufferStream(audioStream))));
+        }
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaHeavyPresubmitTest.java b/tests/tests/media/src/android/media/cts/MediaHeavyPresubmitTest.java
new file mode 100644
index 0000000..7058344
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaHeavyPresubmitTest.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.media.cts;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for media heavy presubmit tests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MediaHeavyPresubmitTest {
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaItem2Test.java b/tests/tests/media/src/android/media/cts/MediaItem2Test.java
new file mode 100644
index 0000000..efb6f98
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaItem2Test.java
@@ -0,0 +1,117 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.media.MediaItem2;
+import android.media.MediaMetadata;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.MediaItem2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaItem2Test {
+
+    @Test
+    public void testBuilder() {
+        MediaMetadata meta = createMetadata("test", 1000);
+        long startPosition = 100;
+        long endPosition = 200;
+
+        MediaItem2.Builder builder = new MediaItem2.Builder()
+                .setMetadata(meta).setStartPosition(startPosition).setEndPosition(endPosition);
+        MediaItem2 item = builder.build();
+
+        assertEquals(meta, item.getMetadata());
+        assertEquals(startPosition, item.getStartPosition());
+        assertEquals(endPosition, item.getEndPosition());
+    }
+
+    @Test
+    public void testBuilder_illegal_end_position() {
+        MediaMetadata meta = createMetadata("test", 1000);
+        long startPosition = 100;
+        long endPosition = 2000;
+
+        MediaItem2.Builder builder = new MediaItem2.Builder()
+                .setMetadata(meta).setStartPosition(startPosition).setEndPosition(endPosition);
+        try {
+            MediaItem2 item = builder.build();
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testBuilder_illegal_start_end_position() {
+        MediaMetadata meta = createMetadata("test", 1000);
+        long startPosition = 200;
+        long endPosition = 100;
+
+        MediaItem2.Builder builder = new MediaItem2.Builder()
+                .setMetadata(meta).setStartPosition(startPosition).setEndPosition(endPosition);
+        try {
+            MediaItem2 item = builder.build();
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testSetMetadata() {
+        long startPosition = 100;
+        long endPosition = 200;
+
+        MediaItem2.Builder builder = new MediaItem2.Builder()
+                .setStartPosition(startPosition).setEndPosition(endPosition);
+        MediaItem2 item = builder.build();
+
+        // Set metadata when item's metadata is null
+        MediaMetadata meta = createMetadata("test", 1000);
+        item.setMetadata(meta);
+        assertEquals(meta, item.getMetadata());
+
+        // Set metadata with the same Media Id
+        MediaMetadata meta2 = createMetadata("test", 1000);
+        item.setMetadata(meta2);
+        assertEquals(meta2, item.getMetadata());
+
+        // Set metadata with different Media Id
+        MediaMetadata meta3 = createMetadata("test-other", 1000);
+        item.setMetadata(meta3);
+        // metadata shouldn't be changed
+        assertEquals(meta2, item.getMetadata());
+    }
+
+    private MediaMetadata createMetadata(String id, long duration) {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id);
+        builder.putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
+        return builder.build();
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadata2Test.java b/tests/tests/media/src/android/media/cts/MediaMetadata2Test.java
deleted file mode 100644
index 231cbff..0000000
--- a/tests/tests/media/src/android/media/cts/MediaMetadata2Test.java
+++ /dev/null
@@ -1,59 +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 static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import android.media.MediaMetadata2;
-import android.media.MediaMetadata2.Builder;
-import android.media.Rating2;
-import android.os.Bundle;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Ignore
-public class MediaMetadata2Test {
-    @Test
-    public void testBuilder() {
-        final Bundle extras = new Bundle();
-        extras.putString("MediaMetadata2Test", "testBuilder");
-        final String title = "title";
-        final long discNumber = 10;
-        final Rating2 rating = Rating2.newThumbRating(true);
-
-        Builder builder = new Builder();
-        builder.setExtras(extras);
-        builder.putString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, title);
-        builder.putLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER, discNumber);
-        builder.putRating(MediaMetadata2.METADATA_KEY_USER_RATING, rating);
-
-        MediaMetadata2 metadata = builder.build();
-        assertTrue(TestUtils.equals(extras, metadata.getExtras()));
-        assertEquals(title, metadata.getString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE));
-        assertEquals(discNumber, metadata.getLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER));
-        assertEquals(rating, metadata.getRating(MediaMetadata2.METADATA_KEY_USER_RATING));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 47640ed..91a536c 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -332,6 +332,30 @@
         testGetFrameAtTime(OPTION_CLOSEST, testCases);
     }
 
+    public void testGetFrameAtTimePreviousSyncEditList() {
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 60 }, { 2933334, 60 }, { 3133334, 90}};
+        testGetFrameAtTimeEditList(OPTION_PREVIOUS_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeNextSyncEditList() {
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 90 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 120}};
+        testGetFrameAtTimeEditList(OPTION_NEXT_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestSyncEditList() {
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 90}};
+        testGetFrameAtTimeEditList(OPTION_CLOSEST_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestEditList() {
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433335, 73 }, { 2533333, 76 }, { 2949334, 88 }, { 3117334, 94}};
+        testGetFrameAtTimeEditList(OPTION_CLOSEST, testCases);
+    }
+
     private void testGetFrameAtTime(int option, int[][] testCases) {
         testGetFrameAt(testCases, (r) -> {
             List<Bitmap> bitmaps = new ArrayList<>();
@@ -342,6 +366,16 @@
         });
     }
 
+    private void testGetFrameAtTimeEditList(int option, int[][] testCases) {
+        testGetFrameAtEditList(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                bitmaps.add(r.getFrameAtTime(testCases[i][0], option));
+            }
+            return bitmaps;
+        });
+    }
+
     public void testGetFrameAtIndex() {
         int[][] testCases = { { 60, 60 }, { 73, 73 }, { 76, 76 }, { 88, 88 }, { 94, 94} };
 
@@ -416,6 +450,32 @@
         retriever.release();
     }
 
+    private void testGetFrameAtEditList(int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
+        int resId = R.raw.binary_counter_320x240_30fps_600frames_editlist;
+        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();
+    }
+
     private void verifyVideoFrame(Bitmap bitmap, int[] testCase) {
         try {
             assertTrue("Failed to get bitmap for " + testCase[0], bitmap != null);
@@ -613,11 +673,6 @@
     }
 
     public void testGetImageAtIndex() throws Exception {
-        if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-            MediaUtils.skipTest("no video decoders for resource");
-            return;
-        }
-
         testGetImage(R.raw.heifwriter_input, 1920, 1080, 0 /*rotation*/,
                 4 /*imageCount*/, 3 /*primary*/, true /*useGrid*/, true /*checkColor*/);
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index 0b075e2..0d1796a 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -36,6 +36,9 @@
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+import java.util.stream.IntStream;
 
 @AppModeFull(reason = "No interaction with system server")
 public class MediaMuxerTest extends AndroidTestCase {
@@ -87,11 +90,32 @@
     }
 
     /**
-     * Test: make sure the muxer handles video, audio and metadata tracks correctly.
+     * Test: make sure the muxer handles video, audio and non standard compliant metadata tracks
+     * that generated before API29 correctly. This test will use extractor to extract the video
+     * track, audio and the non standard compliant metadata track from the source file, then
+     * remuxes them into a new file with standard compliant metadata track. Finally, it will check
+     * to make sure the new file's metadata track matches the source file's metadata track for the
+     * mime format and data payload.
      */
-    public void testVideoAudioMedatadata() throws Exception {
+    public void testVideoAudioMedatadataWithNonCompliantMetadataTrack() throws Exception {
         int source =
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro;
+                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant;
+        String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
+                .getAbsolutePath();
+        cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+    }
+
+    /**
+     * Test: make sure the muxer handles video, audio and standard compliant metadata tracks that
+     * generated from API29 correctly. This test will use extractor to extract the video track,
+     * audio and the standard compliant metadata track from the source file, then remuxes them
+     * into a new file with standard compliant metadata track. Finally, it will check to make sure
+     * the new file's metadata track matches the source file's metadata track for the mime format
+     * and data payload.
+     */
+     public void testVideoAudioMedatadataWithCompliantMetadataTrack() throws Exception {
+        int source =
+                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant;
         String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
                 .getAbsolutePath();
         cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
@@ -258,10 +282,202 @@
     }
 
     /**
+     * Test: makes sure if audio and video muxing using MPEG4Writer works well when there are frame
+     * drops as in b/63590381 and b/64949961 while B Frames encoding is enabled.
+     */
+    public void testSimulateAudioBVideoFramesDropIssues() throws Exception {
+        int sourceId = R.raw.video_h264_main_b_frames;
+        String outputFile = File.createTempFile(
+            "MediaMuxerTest_testSimulateAudioBVideoFramesDropIssues", ".mp4").getAbsolutePath();
+        try {
+            simulateVideoFramesDropIssuesAndMux(sourceId, outputFile, 2 /* track index */,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            verifyAFewSamplesTimestamp(sourceId, outputFile);
+            verifySamplesMatch(sourceId, outputFile, 1000000 /* sample at 1 sec */);
+            verifySamplesMatch(sourceId, outputFile, 7000000 /* sample at 7 sec */);
+        } finally {
+            new File(outputFile).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if video only muxing using MPEG4Writer works well when there are B Frames.
+     */
+    public void testAllTimestampsBVideoOnly() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_bframes_495kbps_30fps_editlist;
+        String outputFilePath = File.createTempFile("MediaMuxerTest_testAllTimestampsBVideoOnly",
+            ".mp4").getAbsolutePath();
+        try {
+            // No samples to drop in this case.
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, null);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure muxing works well when video with B Frames are muxed using MPEG4Writer
+     * and a few frames drop.
+     */
+    public void testTimestampsBVideoOnlyFramesDropOnce() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_bframes_495kbps_30fps_editlist;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropOnce", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames from sample index 56 to 76, I frame at 56.
+            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if video muxing while framedrops occurs twice using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsBVideoOnlyFramesDropTwice() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_bframes_495kbps_30fps_editlist;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropTwice", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames with sample index 57 to 67, P frame at 57.
+            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
+            // Drop frames with sample index 173 to 200, B frame at 173.
+            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing while framedrops once using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsAudioBVideoFramesDropOnce() throws Exception {
+        int sourceId = R.raw.video_h264_main_b_frames;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropOnce", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames from sample index 56 to 76, I frame at 56.
+            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing while framedrops twice using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsAudioBVideoFramesDropTwice() throws Exception {
+        int sourceId = R.raw.video_h264_main_b_frames;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropTwice", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames with sample index 57 to 67, P frame at 57.
+            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
+            // Drop frames with sample index 173 to 200, B frame at 173.
+            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video frames start later than audio.
+     */
+    public void testTimestampsAudioBVideoStartOffsetVideo() throws Exception {
+        int sourceId = R.raw.video_h264_main_b_frames;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoStartOffsetVideo", ".mp4").getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 400000us.
+            startOffsetUsVect.add(400000);
+            // Audio starts at 0us.
+            startOffsetUsVect.add(0);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames when audio
+     * samples start later than video.
+     */
+    public void testTimestampsAudioBVideoStartOffsetAudio() throws Exception {
+        int sourceId = R.raw.video_h264_main_b_frames;
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoStartOffsetAudio", ".mp4").getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 0us.
+            startOffsetUsVect.add(0);
+            // Audio starts at 400000us.
+            startOffsetUsVect.add(400000);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Clones a media file and then compares against the source file to make
+     * sure they match.
+     */
+    private void cloneAndVerify(int srcMedia, String outputMediaFile,
+            int expectedTrackCount, int degrees, int fmt) throws IOException {
+        try {
+            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
+                    degrees, fmt);
+            if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+                    fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+                verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
+                verifyLocationInFile(outputMediaFile);
+            }
+            // Check the sample on 1s and 0.5s.
+            verifySamplesMatch(srcMedia, outputMediaFile, 1000000);
+            verifySamplesMatch(srcMedia, outputMediaFile, 500000);
+        } finally {
+            new File(outputMediaFile).delete();
+        }
+    }
+
+    /**
      * Using the MediaMuxer to clone a media file.
      */
     private void cloneMediaUsingMuxer(int srcMedia, String dstMediaPath,
-            int expectedTrackCount, int degrees, int fmt) throws IOException {
+            int expectedTrackCount, int degrees, int fmt)
+            throws IOException {
         // Set up MediaExtractor to read from the source.
         AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
         MediaExtractor extractor = new MediaExtractor();
@@ -351,38 +567,17 @@
 
         muxer.stop();
         muxer.release();
+        extractor.release();
         srcFd.close();
         return;
     }
 
     /**
-     * Clones a media file and then compares against the source file to make
-     * sure they match.
-     */
-    private void cloneAndVerify(int srcMedia, String outputMediaFile,
-            int expectedTrackCount, int degrees, int fmt) throws IOException {
-        try {
-            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
-                    degrees, fmt);
-            if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
-                    fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
-                verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
-                verifyLocationInFile(outputMediaFile);
-            }
-            // Check the sample on 1s and 0.5s.
-            verifySamplesMatch(srcMedia, outputMediaFile, 1000000);
-            verifySamplesMatch(srcMedia, outputMediaFile, 500000);
-        } finally {
-            new File(outputMediaFile).delete();
-        }
-    }
-
-    /**
      * Compares some attributes using MediaMetadataRetriever to make sure the
      * cloned media file matches the source file.
      */
     private void verifyAttributesMatch(int srcMedia, String testMediaPath,
-            int degrees) {
+            int degrees) throws IOException {
         AssetFileDescriptor testFd = mResources.openRawResourceFd(srcMedia);
 
         MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
@@ -410,18 +605,51 @@
                 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
         String widthTest = retrieverTest.extractMetadata(
                 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-        assertEquals("Different height", widthSrc,
+        assertEquals("Different width", widthSrc,
                 widthTest);
 
         String durationSrc = retrieverSrc.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
         String durationTest = retrieverTest.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-        assertEquals("Different height", durationSrc,
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertEquals("Different duration", durationSrc,
                 durationTest);
 
         retrieverSrc.release();
         retrieverTest.release();
+        testFd.close();
+    }
+
+    private void verifyLocationInFile(String fileName) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fileName);
+        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
+        assertNotNull("No location information found in file " + fileName, location);
+
+
+        // parsing String location and recover the location information in floats
+        // Make sure the tolerance is very small - due to rounding errors.
+
+        // Get the position of the -/+ sign in location String, which indicates
+        // the beginning of the longitude.
+        int minusIndex = location.lastIndexOf('-');
+        int plusIndex = location.lastIndexOf('+');
+
+        assertTrue("+ or - is not found or found only at the beginning [" + location + "]",
+                (minusIndex > 0 || plusIndex > 0));
+        int index = Math.max(minusIndex, plusIndex);
+
+        float latitude = Float.parseFloat(location.substring(0, index - 1));
+        int lastIndex = location.lastIndexOf('/', index);
+        if (lastIndex == -1) {
+            lastIndex = location.length();
+        }
+        float longitude = Float.parseFloat(location.substring(index, lastIndex - 1));
+        assertTrue("Incorrect latitude: " + latitude + " [" + location + "]",
+                Math.abs(latitude - LATITUDE) <= TOLERANCE);
+        assertTrue("Incorrect longitude: " + longitude + " [" + location + "]",
+                Math.abs(longitude - LONGITUDE) <= TOLERANCE);
+        retriever.release();
     }
 
     /**
@@ -470,38 +698,463 @@
         if (!(byteBufSrc.equals(byteBufTest))) {
             fail("byteBuffer didn't match");
         }
+        extractorSrc.release();
+        extractorTest.release();
+        testFd.close();
     }
 
-    private void verifyLocationInFile(String fileName) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fileName);
-        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
-        assertNotNull("No location information found in file " + fileName, location);
+    /**
+     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
+     * some video frames as in the issues b/63590381 and b/64949961.
+     */
+    private void simulateVideoFramesDropIssuesAndMux(int srcMedia, String dstMediaPath,
+            int expectedTrackCount, int fmt) throws IOException {
+        // Set up MediaExtractor to read from the source.
+        AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+            srcFd.getLength());
 
+        int trackCount = extractor.getTrackCount();
+        assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
 
-        // parsing String location and recover the location information in floats
-        // Make sure the tolerance is very small - due to rounding errors.
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstMediaPath, fmt);
 
-        // Get the position of the -/+ sign in location String, which indicates
-        // the beginning of the longitude.
-        int minusIndex = location.lastIndexOf('-');
-        int plusIndex = location.lastIndexOf('+');
+        // Set up the tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
 
-        assertTrue("+ or - is not found or found only at the beginning [" + location + "]",
-                (minusIndex > 0 || plusIndex > 0));
-        int index = Math.max(minusIndex, plusIndex);
-
-        float latitude = Float.parseFloat(location.substring(0, index - 1));
-        int lastIndex = location.lastIndexOf('/', index);
-        if (lastIndex == -1) {
-            lastIndex = location.length();
+        for (int i = 0; i < trackCount; i++) {
+            extractor.selectTrack(i);
+            MediaFormat format = extractor.getTrackFormat(i);
+            int dstIndex = muxer.addTrack(format);
+            indexMap.put(i, dstIndex);
         }
-        float longitude = Float.parseFloat(location.substring(index, lastIndex - 1));
-        assertTrue("Incorrect latitude: " + latitude + " [" + location + "]",
-                Math.abs(latitude - LATITUDE) <= TOLERANCE);
-        assertTrue("Incorrect longitude: " + longitude + " [" + location + "]",
-                Math.abs(longitude - LONGITUDE) <= TOLERANCE);
-        retriever.release();
+
+        // Copy the samples from MediaExtractor to MediaMuxer.
+        boolean sawEOS = false;
+        int bufferSize = MAX_SAMPLE_SIZE;
+        int sampleCount = 0;
+        int offset = 0;
+        int videoSampleCount = 0;
+        // Counting frame index values starting from 1
+        final int muxAllTypeVideoFramesUntilIndex = 136; // I/P/B frames passed as it is until this
+        final int muxAllTypeVideoFramesFromIndex = 157; // I/P/B frames passed as it is from this
+        final int pFrameBeforeARandomBframeIndex = 137;
+        final int bFrameAfterPFrameIndex = pFrameBeforeARandomBframeIndex+1;
+        final int oneFrameDurationUs = 33333; // 30 fps input clip, one frame durationUs = 33333.
+
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        muxer.start();
+        while (!sawEOS) {
+            bufferInfo.offset = 0;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+            if (bufferInfo.size < 0) {
+                if (VERBOSE) {
+                    Log.d(TAG, "saw input EOS.");
+                }
+                sawEOS = true;
+                bufferInfo.size = 0;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                bufferInfo.flags = extractor.getSampleFlags();
+                int trackIndex = extractor.getSampleTrackIndex();
+                // Video track at index 0, skip some video frames while muxing.
+                if (trackIndex == 0) {
+                    ++videoSampleCount;
+                    if (VERBOSE) {
+                        Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+                    }
+                    if (videoSampleCount <= muxAllTypeVideoFramesUntilIndex ||
+                            videoSampleCount == bFrameAfterPFrameIndex ||
+                            videoSampleCount >= muxAllTypeVideoFramesFromIndex) {
+                        // Write frame as it is.
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    } else if (videoSampleCount == pFrameBeforeARandomBframeIndex) {
+                        // Adjust time stamp for this P frame to a few frames later, say 15
+                        bufferInfo.presentationTimeUs += oneFrameDurationUs * 15;
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    }
+                    else {
+                        // Skip frames after bFrameAfterPFrameIndex
+                        // and before muxAllTypeVideoFramesFromIndex, 139 to 156.
+                        if (VERBOSE) {
+                            Log.i(TAG, "skipped this frame");
+                        }
+                    }
+                } else {
+                    // write audio data as it is continuously
+                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                }
+                extractor.advance();
+                sampleCount++;
+                if (VERBOSE) {
+                    Log.d(TAG, "Frame (" + sampleCount + ") " +
+                            "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+                            " Flags:" + bufferInfo.flags +
+                            " TrackIndex:" + trackIndex +
+                            " Size(bytes) " + bufferInfo.size );
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        extractor.release();
+        srcFd.close();
+
+        return;
+    }
+
+    /* Uses two MediaExtractor's and checks whether timestamps of first few and another few
+     *  from last sync frame matches
+     */
+    private void verifyAFewSamplesTimestamp(int srcMediaId, String testMediaPath)
+            throws IOException {
+        final int numFramesTSCheck = 20; // Num frames to be checked for its timestamps
+
+        AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMediaId);
+        MediaExtractor extractorSrc = new MediaExtractor();
+        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
+            srcFd.getStartOffset(), srcFd.getLength());
+        MediaExtractor extractorTest = new MediaExtractor();
+        extractorTest.setDataSource(testMediaPath);
+
+        int trackCount = extractorSrc.getTrackCount();
+        for (int i = 0; i < trackCount; i++) {
+            extractorSrc.selectTrack(i);
+            extractorTest.selectTrack(i);
+        }
+        // Check time stamps for numFramesTSCheck frames from start(0).
+        checkNumFramesTimestamp(0, numFramesTSCheck, extractorSrc, extractorTest);
+        // Check time stamps for numFramesTSCheck frames from 6000000 -
+        // sync frame after framedrops at index 172 of video track.
+        checkNumFramesTimestamp(6000000, numFramesTSCheck, extractorSrc, extractorTest);
+
+        extractorSrc.release();
+        extractorTest.release();
+        srcFd.close();
+    }
+
+    private void checkNumFramesTimestamp(long seekTimeUs, int numFrames,
+        MediaExtractor extractorSrc, MediaExtractor extractorTest) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        extractorSrc.seekTo(seekTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        extractorTest.seekTo(seekTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        while (numFrames-- > 0 ) {
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if(srcSampleTimeUs == -1 || testSampleTimeUs == -1){
+                fail("either of tracks reached end of stream");
+            }
+            if (srcSampleTimeUs != testSampleTimeUs ) {
+                if (VERBOSE) {
+                    Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                    Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                }
+                fail("timestamps didn't match");
+            }
+            extractorSrc.advance();
+            extractorTest.advance();
+        }
+    }
+
+    /**
+     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
+     * 0 or more video frames and desired start offsets for each track.
+     * startOffsetUsVect : order of tracks is the same as in the input file
+     */
+    private void cloneMediaWithSamplesDropAndStartOffsets(int srcMedia, String dstMediaPath,
+            int fmt, HashSet<Integer> samplesDropSet, Vector<Integer> startOffsetUsVect)
+            throws IOException {
+        // Set up MediaExtractor to read from the source.
+        AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+            srcFd.getLength());
+
+        int trackCount = extractor.getTrackCount();
+
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstMediaPath, fmt);
+
+        // Set up the tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
+
+        int videoTrackIndex = 100;
+        int videoStartOffsetUs = 0;
+        int audioTrackIndex = 100;
+        int audioStartOffsetUs = 0;
+        for (int i = 0; i < trackCount; i++) {
+            extractor.selectTrack(i);
+            MediaFormat format = extractor.getTrackFormat(i);
+            int dstIndex = muxer.addTrack(format);
+            indexMap.put(i, dstIndex);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                videoTrackIndex = i;
+                // Make sure there's an entry for video track.
+                if (startOffsetUsVect != null && (videoTrackIndex < startOffsetUsVect.size())) {
+                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
+                }
+            }
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                audioTrackIndex = i;
+                // Make sure there's an entry for audio track.
+                if (startOffsetUsVect != null && (audioTrackIndex < startOffsetUsVect.size())) {
+                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
+                }
+            }
+        }
+
+        // Copy the samples from MediaExtractor to MediaMuxer.
+        boolean sawEOS = false;
+        int bufferSize = MAX_SAMPLE_SIZE;
+        int sampleCount = 0;
+        int offset = 0;
+        int videoSampleCount = 0;
+
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        muxer.start();
+        while (!sawEOS) {
+            bufferInfo.offset = 0;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+            if (bufferInfo.size < 0) {
+                if (VERBOSE) {
+                    Log.d(TAG, "saw input EOS.");
+                }
+                sawEOS = true;
+                bufferInfo.size = 0;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                bufferInfo.flags = extractor.getSampleFlags();
+                int trackIndex = extractor.getSampleTrackIndex();
+                if (trackIndex == videoTrackIndex) {
+                    ++videoSampleCount;
+                    if (VERBOSE) {
+                        Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+                    }
+                    if (samplesDropSet == null || (!samplesDropSet.contains(videoSampleCount))) {
+                        // Write video frame with start offset adjustment.
+                        bufferInfo.presentationTimeUs += videoStartOffsetUs;
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    }
+                    else {
+                        if (VERBOSE) {
+                            Log.i(TAG, "skipped this frame");
+                        }
+                    }
+                } else {
+                    // write audio sample with start offset adjustment.
+                    bufferInfo.presentationTimeUs += audioStartOffsetUs;
+                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                }
+                extractor.advance();
+                sampleCount++;
+                if (VERBOSE) {
+                    Log.i(TAG, "Sample (" + sampleCount + ")" +
+                            " TrackIndex:" + trackIndex +
+                            " PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+                            " Flags:" + bufferInfo.flags +
+                            " Size(bytes)" + bufferInfo.size );
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        extractor.release();
+        srcFd.close();
+
+        return;
+    }
+
+    /*
+     * 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 {
+        AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMediaId);
+        MediaExtractor extractorSrc = new MediaExtractor();
+        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
+            srcFd.getStartOffset(), srcFd.getLength());
+        MediaExtractor extractorTest = new MediaExtractor();
+        extractorTest.setDataSource(testMediaPath);
+
+        int videoTrackIndex = -1;
+        int videoStartOffsetUs = 0;
+        int trackCount = extractorSrc.getTrackCount();
+
+        // Select video track.
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractorSrc.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                videoTrackIndex = i;
+                if (startOffsetUsVect != null && videoTrackIndex < startOffsetUsVect.size()) {
+                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
+                }
+                extractorSrc.selectTrack(videoTrackIndex);
+                extractorTest.selectTrack(videoTrackIndex);
+            }
+        }
+        if (videoTrackIndex != -1) {
+            checkVideoSamplesTimeStamps(extractorSrc, extractorTest, samplesDropSet,
+                videoStartOffsetUs);
+        }
+
+        extractorSrc.unselectTrack(videoTrackIndex);
+        extractorTest.unselectTrack(videoTrackIndex);
+
+        int audioTrackIndex = -1;
+        int audioSampleCount = 0;
+        int audioStartOffsetUs = 0;
+        //select audio track
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractorSrc.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                audioTrackIndex = i;
+                if (startOffsetUsVect != null && audioTrackIndex < startOffsetUsVect.size()) {
+                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
+                }
+                extractorSrc.selectTrack(audioTrackIndex);
+                extractorTest.selectTrack(audioTrackIndex);
+            }
+        }
+
+        if (audioTrackIndex != -1) {
+           // No audio track
+            checkAudioSamplesTimestamps(extractorSrc, extractorTest, audioStartOffsetUs);
+        }
+
+        extractorSrc.release();
+        extractorTest.release();
+        srcFd.close();
+    }
+
+    // Check timestamps of all video samples.
+    private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc,
+                MediaExtractor extractorTest, HashSet<Integer> samplesDropSet,
+                int videoStartOffsetUs) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        boolean srcAdvance = false;
+        boolean testAdvance = false;
+        int videoSampleCount = 0;
+
+        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+
+        do {
+            ++videoSampleCount;
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if (VERBOSE) {
+                Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+                Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+            }
+            if (samplesDropSet == null || !samplesDropSet.contains(videoSampleCount)) {
+                if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
+                    // TODO: Uncomment once MediaExtractor.advance() bug - b/121204004 is fixed.
+                    // or if that was not bug, then find another approach.
+                    // if (VERBOSE) {
+                    //     Log.d(TAG, "Fail:either of tracks reached end of stream");
+                    //     Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+                    //     Log.d(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
+                    // }
+                    // fail("either of tracks 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 (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);
+                    }
+                    fail("video timestamps didn't match");
+                }
+                testAdvance = extractorTest.advance();
+            }
+            srcAdvance = extractorSrc.advance();
+        } while(srcAdvance && testAdvance);
+        if (srcAdvance != testAdvance) {
+            if (VERBOSE) {
+                Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+            }
+            fail("either video track has not reached its last sample");
+        }
+    }
+
+    private void checkAudioSamplesTimestamps(MediaExtractor extractorSrc,
+                MediaExtractor extractorTest, int audioStartOffsetUs) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        boolean srcAdvance = false;
+        boolean testAdvance = false;
+        int audioSampleCount = 0;
+
+        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+
+        // Check timestamps of all audio samples.
+        do {
+            ++audioSampleCount;
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if(VERBOSE) {
+                Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+            }
+
+            if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
+                // TODO: Uncomment once MediaExtractor.advance() bug - b/121204004 is fixed.
+                // or if that was not bug, then find another approach.
+                // if (VERBOSE) {
+                //     Log.d(TAG, "Fail:either of tracks reached end of stream");
+                //     Log.d(TAG, "audioSampleCount:" + audioSampleCount);
+                //     Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                // }
+                // fail("either of tracks 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
+            // 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.
+            else if ((audioSampleCount > 1 &&
+                (srcSampleTimeUs + audioStartOffsetUs) != testSampleTimeUs) ||
+                (audioSampleCount == 1 && srcSampleTimeUs != testSampleTimeUs)) {
+                    if (VERBOSE) {
+                        Log.d(TAG, "Fail:audio timestamps didn't match");
+                        Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                        Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                        Log.d(TAG, "audioSampleCount:" + audioSampleCount);
+                    }
+                    fail("audio timestamps didn't match");
+                }
+            testAdvance = extractorTest.advance();
+            srcAdvance = extractorSrc.advance();
+        } while(srcAdvance && testAdvance);
+        if (srcAdvance != testAdvance) {
+            if (VERBOSE) {
+                Log.d(TAG, "audioSampleCount:" + audioSampleCount);
+            }
+            fail("either audio track has not reached its last sample");
+        }
     }
 }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTest.java b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTest.java
index a4c5c4c..de1b747 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTest.java
@@ -22,15 +22,8 @@
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
-import android.support.test.rule.GrantPermissionRule;
 import android.support.test.runner.AndroidJUnit4;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import java.io.File;
 
 /**
@@ -41,26 +34,19 @@
  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
 @AppModeFull(reason = "Instant apps cannot hold READ/WRITE_EXTERNAL_STORAGE")
 public class MediaPlayer2DrmTest extends MediaPlayer2DrmTestBase {
 
     private static final String LOG_TAG = "MediaPlayer2DrmTest";
 
-    @Rule
-    public GrantPermissionRule mRuntimePermissionRule =
-            GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-
-    @Before
     @Override
-    public void setUp() throws Throwable {
+    protected void setUp() throws Exception {
         super.setUp();
     }
 
-    @After
     @Override
-    public void tearDown() throws Throwable {
+    protected void tearDown() throws Exception {
         super.tearDown();
     }
 
@@ -110,7 +96,6 @@
 
     // Tests
 
-    @Test
     @LargeTest
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V0_SYNC() throws Exception {
         download(CENC_AUDIO_URL,
@@ -119,7 +104,6 @@
                 ModularDrmTestType.V0_SYNC_TEST);
     }
 
-    @Test
     @LargeTest
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V1_ASYNC() throws Exception {
         download(CENC_AUDIO_URL,
@@ -128,7 +112,6 @@
                 ModularDrmTestType.V1_ASYNC_TEST);
     }
 
-    @Test
     @LargeTest
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V2_SYNC_CONFIG() throws Exception {
         download(CENC_AUDIO_URL,
@@ -137,7 +120,6 @@
                 ModularDrmTestType.V2_SYNC_CONFIG_TEST);
     }
 
-    @Test
     @LargeTest
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V3_ASYNC_DRMPREPARED() throws Exception {
         download(CENC_AUDIO_URL,
@@ -146,6 +128,22 @@
                 ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
     }
 
+    @LargeTest
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V4_OFFLINE_KEY() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V4_SYNC_OFFLINE_KEY);
+    }
+
+    @LargeTest
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V5_ASYNC_CALLBACKS() throws Exception {
+        download(CENC_AUDIO_URL,
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V5_ASYNC_CALLBACKS_TEST);
+    }
+
     // helpers
 
     private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
index 314c051..66b38f2 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2DrmTestBase.java
@@ -15,51 +15,39 @@
  */
 package android.media.cts;
 
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 import android.app.DownloadManager;
 import android.app.DownloadManager.Request;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.media.DataSourceDesc;
+import android.media.UriDataSourceDesc;
 import android.media.MediaDrm;
-import android.media.ResourceBusyException;
-import android.media.UnsupportedSchemeException;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayer2.DrmInfo;
+import android.media.MediaPlayer2.DrmPreparationInfo;
+import android.media.MediaDrm.KeyRequest;
 import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
-import android.os.PowerManager;
 import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
+import android.test.ActivityInstrumentationTestCase2;
 import android.util.Base64;
 import android.util.Log;
+import android.util.Size;
 import android.view.SurfaceHolder;
-import android.view.WindowManager;
-
-import androidx.annotation.CallSuper;
-import androidx.media.DataSourceDesc;
-import androidx.media.MediaPlayer2;
-import androidx.media.MediaPlayer2.DrmInfo;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.UUID;
@@ -67,17 +55,21 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
 
 /**
  * Base class for DRM tests which use MediaPlayer2 to play audio or video.
  */
-public class MediaPlayer2DrmTestBase {
+public class MediaPlayer2DrmTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
+
     protected static final int STREAM_RETRIES = 3;
 
     protected Monitor mSetDataSourceCallCompleted = new Monitor();
     protected Monitor mOnPreparedCalled = new Monitor();
     protected Monitor mOnVideoSizeChangedCalled = new Monitor();
     protected Monitor mOnPlaybackCompleted = new Monitor();
+    protected Monitor mOnPlaylistCompleted = new Monitor();
     protected Monitor mOnDrmInfoCalled = new Monitor();
     protected Monitor mOnDrmPreparedCalled = new Monitor();
     protected int mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
@@ -87,56 +79,71 @@
 
     protected MediaPlayer2 mPlayer = null;
     protected MediaStubActivity mActivity;
-    protected Instrumentation mInstrumentation;
 
     protected ExecutorService mExecutor;
-    protected MediaPlayer2.EventCallback mECb = null;
+    protected MediaPlayer2.EventCallback mECb = new MediaPlayer2.EventCallback() {
+        @Override
+        public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, Size size) {
+            Log.v(TAG, "VideoSizeChanged" + " w:" + size.getWidth()
+                    + " h:" + size.getHeight());
+            mOnVideoSizeChangedCalled.signal();
+        }
 
-    @Rule
-    public ActivityTestRule<MediaStubActivity> mActivityRule =
-            new ActivityTestRule<>(MediaStubActivity.class);
-    public PowerManager.WakeLock mScreenLock;
-    private KeyguardManager mKeyguardManager;
+        @Override
+        public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+            fail("Media player had error " + what + " playing video");
+        }
 
-    @Before
-    @CallSuper
-    public void setUp() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mKeyguardManager = (KeyguardManager)
-                mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-        mActivity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Keep screen on while testing.
-                mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                mActivity.setTurnScreenOn(true);
-                mActivity.setShowWhenLocked(true);
-                mKeyguardManager.requestDismissKeyguard(mActivity, null);
+        @Override
+        public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+            if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                mOnPreparedCalled.signal();
+            } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                Log.v(TAG, "playLoadedVideo: MEDIA_INFO_DATA_SOURCE_END");
+                mOnPlaybackCompleted.signal();
+            } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
+                Log.v(TAG, "playLoadedVideo: MEDIA_INFO_DATA_SOURCE_LIST_END");
+                mOnPlaylistCompleted.signal();
             }
-        });
-        mInstrumentation.waitForIdleSync();
+        }
 
+        @Override
+        public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
+                int what, int status) {
+            if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
+                mCallStatus = status;
+                mSetDataSourceCallCompleted.signal();
+            }
+        }
+    };
+
+    public MediaPlayer2DrmTestBase() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        mContext = getInstrumentation().getTargetContext();
         try {
-            mActivityRule.runOnUiThread(new Runnable() {
+            runTestOnUiThread(new Runnable() {
                 public void run() {
-                    mPlayer = MediaPlayer2.create();
+                    mPlayer = new MediaPlayer2(mContext);
                 }
             });
         } catch (Throwable e) {
             e.printStackTrace();
             fail();
         }
-
-        mContext = mInstrumentation.getTargetContext();
         mResources = mContext.getResources();
 
-        mExecutor = Executors.newFixedThreadPool(1);
+        mExecutor = Executors.newFixedThreadPool(2);
     }
 
-    @After
-    @CallSuper
-    public void tearDown() throws Throwable {
+    @Override
+    protected void tearDown() throws Exception {
         if (mPlayer != null) {
             mPlayer.close();
             mPlayer = null;
@@ -184,6 +191,7 @@
         V2_SYNC_CONFIG_TEST,
         V3_ASYNC_DRMPREPARED_TEST,
         V4_SYNC_OFFLINE_KEY,
+        V5_ASYNC_CALLBACKS_TEST,
     }
 
     // TODO: After living on these tests for a while, we can consider grouping them based on
@@ -255,12 +263,14 @@
             case V1_ASYNC_TEST:
             case V2_SYNC_CONFIG_TEST:
             case V3_ASYNC_DRMPREPARED_TEST:
+            case V5_ASYNC_CALLBACKS_TEST:
                 playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
                 break;
 
             case V4_SYNC_OFFLINE_KEY:
                 playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
                 break;
+
         }
     }
 
@@ -272,41 +282,11 @@
         mAudioOnly = (width == 0);
 
         mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        mECb = new MediaPlayer2.EventCallback() {
-                @Override
-                public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) {
-                    Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
-                    mOnVideoSizeChangedCalled.signal();
-                }
 
-                @Override
-                public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                    fail("Media player had error " + what + " playing video");
-                }
-
-                @Override
-                public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                    if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                        mOnPreparedCalled.signal();
-                    } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
-                        Log.v(TAG, "playLoadedVideo: onInfo_PlaybackComplete");
-                        mOnPlaybackCompleted.signal();
-                    }
-                }
-
-                @Override
-                public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
-                        int what, int status) {
-                    if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
-                        mCallStatus = status;
-                        mSetDataSourceCallCompleted.signal();
-                    }
-                }
-            };
-
-        mPlayer.setEventCallback(mExecutor, mECb);
+        mPlayer.registerEventCallback(mExecutor, mECb);
         Log.v(TAG, "playLoadedVideo: setDataSource()");
-        mPlayer.setDataSource(new DataSourceDesc.Builder().setDataSource(mContext, file).build());
+        DataSourceDesc dsd = new UriDataSourceDesc.Builder().setDataSource(mContext, file).build();
+        mPlayer.setDataSource(dsd);
         mSetDataSourceCallCompleted.waitForSignal();
         if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
             throw new PrepareFailedException();
@@ -319,20 +299,27 @@
         try {
             switch (testType) {
                 case V0_SYNC_TEST:
-                    preparePlayerAndDrm_V0_syncDrmSetup();
+                    preparePlayerAndDrm_V0_syncDrmSetup(dsd);
                     break;
 
                 case V1_ASYNC_TEST:
-                    preparePlayerAndDrm_V1_asyncDrmSetup();
+                    preparePlayerAndDrm_V1_asyncDrmSetup(dsd);
                     break;
 
                 case V2_SYNC_CONFIG_TEST:
-                    preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
+                    preparePlayerAndDrm_V2_syncDrmSetupPlusConfig(dsd);
                     break;
 
                 case V3_ASYNC_DRMPREPARED_TEST:
-                    preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
+                    preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener(dsd);
                     break;
+
+                case V5_ASYNC_CALLBACKS_TEST:
+                    preparePlayerAndDrm_V5_asyncDrmSetupCallbacks(dsd);
+                    break;
+
+                default:
+                    throw new IllegalArgumentException("invalid test type: " + testType);
             }
 
         } catch (IOException e) {
@@ -358,14 +345,15 @@
 
         try {
             Log.v(TAG, "playLoadedVideo: releaseDrm");
-            mPlayer.releaseDrm();
+            mPlayer.reset();
+            mPlayer.releaseDrm(dsd);
         } catch (Exception e) {
             e.printStackTrace();
             throw new PrepareFailedException();
         }
     }
 
-    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
+    private void preparePlayerAndDrm_V0_syncDrmSetup(DataSourceDesc dsd) throws Exception {
         Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
         mPlayer.prepare();
         mOnPreparedCalled.waitForSignal();
@@ -373,26 +361,33 @@
             throw new IOException();
         }
 
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
+        DrmInfo drmInfo = mPlayer.getDrmInfo(dsd);
         if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
+            setupDrm(dsd, drmInfo, true /* prepareDrm */,
+                    true /* synchronousNetworking */, MediaDrm.KEY_TYPE_STREAMING);
             Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
         }
     }
 
-    private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
+    private void preparePlayerAndDrm_V1_asyncDrmSetup(DataSourceDesc dsd) throws InterruptedException {
         final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
 
         mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
             @Override
-            public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
+            public DrmPreparationInfo onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd2,
+                    DrmInfo drmInfo) {
                 Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
+                if (dsd != dsd2) {
+                    Log.e(TAG, "preparePlayerAndDrm_V1: onDrmInfo dsd mismatch");
+                    asyncSetupDrmError.set(true);
+                    mOnDrmInfoCalled.signal();
+                    return null;
+                }
 
                 // in the callback (async mode) so handling exceptions here
                 try {
-                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
+                    setupDrm(dsd2, drmInfo, true /* prepareDrm */,
+                            true /* synchronousNetworking */, MediaDrm.KEY_TYPE_STREAMING);
                 } catch (Exception e) {
                     Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
                     asyncSetupDrmError.set(true);
@@ -400,6 +395,7 @@
 
                 mOnDrmInfoCalled.signal();
                 Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
+                return null;
             }
         });
 
@@ -417,23 +413,29 @@
         }
     }
 
-    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
-        mPlayer.setOnDrmConfigHelper(new MediaPlayer2.OnDrmConfigHelper() {
+    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig(DataSourceDesc dsd)
+            throws Exception {
+        final AtomicBoolean drmConfigError = new AtomicBoolean(false);
+        mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
             @Override
-            public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd) {
+            public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd2, MediaDrm drmObj) {
+                if (dsd != dsd2) {
+                    Log.e(TAG, "preparePlayerAndDrm_V2: onDrmConfig dsd mismatch");
+                    drmConfigError.set(true);
+                    return;
+                }
+
                 String widevineSecurityLevel3 = "L3";
                 String securityLevelProperty = "securityLevel";
 
                 try {
-                    String level = mp.getDrmPropertyString(securityLevelProperty);
+                    String level = drmObj.getPropertyString(securityLevelProperty);
                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
                             + securityLevelProperty + " -> " + level);
-                    mp.setDrmPropertyString(securityLevelProperty, widevineSecurityLevel3);
-                    level = mp.getDrmPropertyString(securityLevelProperty);
+                    drmObj.setPropertyString(securityLevelProperty, widevineSecurityLevel3);
+                    level = drmObj.getPropertyString(securityLevelProperty);
                     Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: "
                             + securityLevelProperty + " -> " + level);
-                } catch (MediaPlayer2.NoDrmSchemeException e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
                 } catch (Exception e) {
                     Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
                 }
@@ -448,32 +450,38 @@
             throw new IOException();
         }
 
-        DrmInfo drmInfo = mPlayer.getDrmInfo();
+        if (drmConfigError.get()) {
+            fail("preparePlayerAndDrm_V2: onDrmConfig");
+        }
+
+        DrmInfo drmInfo = mPlayer.getDrmInfo(dsd);
         if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
+            setupDrm(dsd, drmInfo, true /* prepareDrm */,
+                    true /* synchronousNetworking */, MediaDrm.KEY_TYPE_STREAMING);
             Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
         }
     }
 
-    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
+    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener(DataSourceDesc dsd)
             throws InterruptedException {
         final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
 
         mPlayer.setDrmEventCallback(mExecutor, new MediaPlayer2.DrmEventCallback() {
             @Override
-            public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
+            public DrmPreparationInfo onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd2,
+                    DrmInfo drmInfo) {
                 Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
 
                 // DRM preperation
                 List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.isEmpty()) {
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
+                if (dsd != dsd2 || supportedSchemes.isEmpty()) {
+                    String msg = dsd != dsd2 ? "dsd mismatch" : "No supportedSchemes";
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo " + msg);
                     asyncSetupDrmError.set(true);
                     mOnDrmInfoCalled.signal();
                     // we won't call prepareDrm anymore but need to get passed the wait
                     mOnDrmPreparedCalled.signal();
-                    return;
+                    return null;
                 }
 
                 // setting up with the first supported UUID
@@ -481,37 +489,37 @@
                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
                 Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
 
-                try {
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
-                    mp.prepareDrm(drmScheme);
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
+                mp.prepareDrm(dsd2, drmScheme);
+
+                mOnDrmInfoCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
+                return null;
+            }
+
+            @Override
+            public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd2, int status,
+                    byte[] keySetId) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
+
+                if (dsd != dsd2) {
                     asyncSetupDrmError.set(true);
-                    mOnDrmInfoCalled.signal();
-                    // need to get passed the wait
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmPrepared dsd mismatch");
                     mOnDrmPreparedCalled.signal();
                     return;
                 }
 
-                mOnDrmInfoCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
-            }
+                if (status != MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS) {
+                    asyncSetupDrmError.set(true);
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmPrepared did not succeed");
+                }
 
-            @Override
-            public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd, int status) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
-
-                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
-                           status == MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS);
-
-                DrmInfo drmInfo = mPlayer.getDrmInfo();
+                DrmInfo drmInfo = mPlayer.getDrmInfo(dsd2);
 
                 // in the callback (async mode) so handling exceptions here
                 try {
-                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
+                    setupDrm(dsd2, drmInfo, false /* prepareDrm */,
+                            true /* synchronousNetworking */, MediaDrm.KEY_TYPE_STREAMING);
                 } catch (Exception e) {
                     Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
                     asyncSetupDrmError.set(true);
@@ -537,100 +545,203 @@
         }
     }
 
+    private final class CtsDrmEventCallback extends MediaPlayer2.DrmEventCallback {
+        final String DRM_EVENT_CB_TAG = CtsDrmEventCallback.class.getSimpleName();
+        final AtomicBoolean mDrmCallbackError;
+        final List<DataSourceDesc> mDsds;
+        final int mKeyType;
+        byte[] mOfflineKeySetId;
+
+        CtsDrmEventCallback(AtomicBoolean drmCallbackError, List<DataSourceDesc> dsds,
+                int keyType) {
+            mDrmCallbackError = drmCallbackError;
+            mDsds = new ArrayList<DataSourceDesc>(dsds);
+            mKeyType = keyType;
+        }
+
+        @Override
+        public DrmPreparationInfo onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd,
+                DrmInfo drmInfo) {
+            Log.v(DRM_EVENT_CB_TAG, "onDrmInfo" + drmInfo);
+
+            // DRM preparation
+            List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
+            final DataSourceDesc curDsd = mDsds.get(0);
+            if (curDsd != dsd || supportedSchemes.isEmpty()) {
+                String msg = curDsd != dsd ? "dsd mismatch" : "No supportedSchemes";
+                Log.e(DRM_EVENT_CB_TAG, "onDrmInfo " + msg);
+                mDrmCallbackError.set(true);
+                return null;
+            }
+
+            // setting up with the first supported UUID
+            // instead of supportedSchemes[0] in GTS
+            DrmPreparationInfo.Builder drmBuilder = new DrmPreparationInfo.Builder();
+            UUID drmScheme = CLEARKEY_SCHEME_UUID;
+            drmBuilder.setUuid(drmScheme);
+            if (mOfflineKeySetId != null && mOfflineKeySetId.length > 0) {
+                drmBuilder.setKeySetId(mOfflineKeySetId);
+                return drmBuilder.build();
+            }
+
+            drmBuilder.setMimeType("cenc");
+            drmBuilder.setKeyType(mKeyType);
+
+            byte[] psshData = drmInfo.getPssh().get(drmScheme);
+            byte[] initData;
+            // diverging from GTS
+            if (psshData == null) {
+                initData = mClearKeyPssh;
+                Log.d(DRM_EVENT_CB_TAG,
+                        "CLEARKEY scheme not found in PSSH. Using default data.");
+            } else {
+                // Can skip conversion if ClearKey adds support for BMFF initData b/64863112
+                initData = makeCencPSSH(drmScheme, psshData);
+            }
+            drmBuilder.setInitData(initData);
+            Log.d(DRM_EVENT_CB_TAG,
+                    "initData[" + drmScheme + "]: " + Arrays.toString(initData));
+
+            Log.v(DRM_EVENT_CB_TAG, "onDrmInfo done!");
+            return drmBuilder.build();
+        }
+
+        @Override
+        public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd2, MediaDrm drmObj) {
+
+            final DataSourceDesc curDsd = mDsds.get(0);
+            if (curDsd != dsd2) {
+                Log.e(DRM_EVENT_CB_TAG, "onDrmConfig dsd mismatch");
+                mDrmCallbackError.set(true);
+                return;
+            }
+
+            try {
+                String securityLevelProperty = "securityLevel";
+                String widevineSecurityLevel3 = "L3";
+                String level = drmObj.getPropertyString(securityLevelProperty);
+                Log.v(DRM_EVENT_CB_TAG, "getDrmPropertyString: "
+                        + securityLevelProperty + " -> " + level);
+                drmObj.setPropertyString(securityLevelProperty, widevineSecurityLevel3);
+                level = drmObj.getPropertyString(securityLevelProperty);
+                Log.v(DRM_EVENT_CB_TAG, "getDrmPropertyString: "
+                        + securityLevelProperty + " -> " + level);
+            } catch (Exception e) {
+                Log.v(DRM_EVENT_CB_TAG, "onDrmConfig EXCEPTION " + e);
+            }
+
+        }
+
+        @Override
+        public byte[] onDrmKeyRequest(MediaPlayer2 mp, DataSourceDesc dsd, KeyRequest req) {
+            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
+            byte[] response = createKeysResponse(req, clearKeys, mKeyType);
+            return response;
+        }
+
+        @Override
+        public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd, int status,
+                byte[] keySetId) {
+
+            Log.v(DRM_EVENT_CB_TAG, "onDrmPrepared status: " + status);
+            String errMsg = null;
+            final DataSourceDesc curDsd = mDsds.remove(0);
+            if (curDsd != dsd) {
+                errMsg = "dsd mismatch";
+            }
+
+            if (status != MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS) {
+                errMsg = "drm prepare failed";
+            }
+
+            final boolean hasKeySetId = keySetId != null && keySetId.length > 0;
+            final boolean isOffline = mKeyType == MediaDrm.KEY_TYPE_OFFLINE;
+            mOfflineKeySetId = keySetId;
+            if (hasKeySetId && !isOffline) {
+                errMsg = "unexpected keySetId";
+            }
+            if (!hasKeySetId && isOffline) {
+                errMsg = "expecting keySetId";
+            }
+
+            if (errMsg != null) {
+                mDrmCallbackError.set(true);
+                Log.e(DRM_EVENT_CB_TAG, "onDrmPrepared " + errMsg);
+            }
+
+        }
+    }
+
+    private void preparePlayerAndDrm_V5_asyncDrmSetupCallbacks(DataSourceDesc dsd)
+            throws InterruptedException {
+
+        final AtomicBoolean drmCallbackError = new AtomicBoolean(false);
+        List<DataSourceDesc> dsds = Collections.singletonList(dsd);
+        mPlayer.setDrmEventCallback(mExecutor,
+                new CtsDrmEventCallback(drmCallbackError, dsds, MediaDrm.KEY_TYPE_STREAMING));
+        Log.v(TAG, "preparePlayerAndDrm_V5: calling prepare()");
+        mPlayer.prepare();
+
+        // Waiting till the player is prepared
+        mOnPreparedCalled.waitForSignal();
+
+        // handle error (async) in main thread rather than callbacks
+        if (drmCallbackError.get()) {
+            fail("preparePlayerAndDrm_V5: setupDrm");
+        }
+
+    }
+
     private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
             final Integer height, int playTime) throws Exception {
         final float volume = 0.5f;
 
-        mAudioOnly = (width == 0);
+        mAudioOnly = (width == 0) || (height == 0);
+        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
 
         SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
-        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setSurface " + surfaceHolder);
+        Log.v(TAG, "V4_offlineKey: setSurface " + surfaceHolder);
         mPlayer.setSurface(surfaceHolder.getSurface());
         surfaceHolder.setKeepScreenOn(true);
 
-        mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        DrmInfo drmInfo = null;
+        final AtomicBoolean drmCallbackError = new AtomicBoolean(false);
+        UriDataSourceDesc.Builder dsdBuilder = new UriDataSourceDesc.Builder();
+        DataSourceDesc dsd = dsdBuilder.setDataSource(mContext, file).build();
+        DataSourceDesc dsd2 = dsdBuilder.build();
+        List<DataSourceDesc> dsds = Arrays.asList(dsd, dsd2);
 
-        for (int round = 0; round < 2; round++) {
-            boolean keyRequestRound = (round == 0);
-            boolean restoreRound = (round == 1);
-            Log.v(TAG, "playLoadedVideo: round " + round);
+        Log.v(TAG, "V4_offlineKey: set(Next)DataSource()");
+        mPlayer.registerEventCallback(mExecutor, mECb);
+        mPlayer.setDataSource(dsd);
+        mPlayer.setNextDataSource(dsd2);
+        mPlayer.setDrmEventCallback(mExecutor,
+                new CtsDrmEventCallback(drmCallbackError, dsds, MediaDrm.KEY_TYPE_OFFLINE));
 
-            try {
-                mPlayer.setEventCallback(mExecutor, mECb);
+        Log.v(TAG, "V4_offlineKey: prepare()");
+        mPlayer.prepare();
+        mOnPreparedCalled.waitForSignal();
 
-                Log.v(TAG, "playLoadedVideo: setDataSource()");
-                mPlayer.setDataSource(
-                        new DataSourceDesc.Builder().setDataSource(mContext, file).build());
+        Log.v(TAG, "V4_offlineKey: play()");
+        mPlayer.play();
+        if (!mAudioOnly) {
+            mOnVideoSizeChangedCalled.waitForSignal();
+        }
+        mPlayer.setPlayerVolume(volume);
 
-                Log.v(TAG, "playLoadedVideo: prepare()");
-                mPlayer.prepare();
-                mOnPreparedCalled.waitForSignal();
+        // wait for completion
+        if (playTime == 0) {
+            Log.v(TAG, "V4_offlineKey: waiting for playback completion");
+            mOnPlaybackCompleted.waitForCountedSignals(dsds.size());
+            mOnPlaylistCompleted.waitForSignal();
+        } else {
+            Log.v(TAG, "V4_offlineKey: waiting while playing for " + playTime);
+            mOnPlaybackCompleted.waitForSignal(playTime);
+            mPlayer.skipToNext();
+            mOnPlaylistCompleted.waitForSignal(playTime);
+        }
 
-                // but preparing the DRM every time with proper key request type
-                drmInfo = mPlayer.getDrmInfo();
-                if (drmInfo != null) {
-                    if (keyRequestRound) {
-                        // asking for offline keys
-                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                                 MediaDrm.KEY_TYPE_OFFLINE);
-                    } else if (restoreRound) {
-                        setupDrmRestore(drmInfo, true /* prepareDrm */);
-                    } else {
-                        fail("preparePlayer: unexpected round " + round);
-                    }
-                    Log.v(TAG, "preparePlayer: setupDrm done!");
-                }
-
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            Log.v(TAG, "playLoadedVideo: play()");
-            mPlayer.play();
-            if (!mAudioOnly) {
-                mOnVideoSizeChangedCalled.waitForSignal();
-            }
-            mPlayer.setPlayerVolume(volume);
-
-            // waiting to complete
-            if (playTime == 0) {
-                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-                mOnPlaybackCompleted.waitForSignal();
-            } else {
-                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-                mOnPlaybackCompleted.waitForSignal(playTime);
-            }
-
-            try {
-                if (drmInfo != null) {
-                    if (restoreRound) {
-                        // releasing the offline key
-                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
-                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
-                        Log.v(TAG, "playLoadedVideo: released offline keys");
-                    }
-
-                    Log.v(TAG, "playLoadedVideo: releaseDrm");
-                    mPlayer.releaseDrm();
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            if (keyRequestRound) {
-                mOnPreparedCalled.reset();
-                mOnVideoSizeChangedCalled.reset();
-                mOnPlaybackCompleted.reset();
-                final int sleepBetweenRounds = 1000;
-                Thread.sleep(sleepBetweenRounds);
-
-                Log.v(TAG, "playLoadedVideo: reset");
-                mPlayer.reset();
-            }
-        }  // for
+        mPlayer.close();
+        // TODO: release the offline key
     }
 
     // Converts a BMFF PSSH initData to a raw cenc initData
@@ -667,14 +778,15 @@
      * @param synchronousNetworking whether the network operation of key request/response will
      *        be performed synchronously
      */
-    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
-            int keyType) throws Exception {
+    private void setupDrm(DataSourceDesc dsd, DrmInfo drmInfo, boolean prepareDrm,
+            boolean synchronousNetworking, int keyType) throws Exception {
         Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm
                 + " synchronousNetworking: " + synchronousNetworking);
         try {
             byte[] initData = null;
             String mime = null;
             String keyTypeStr = "Unexpected";
+            final AtomicBoolean prepareDrmFailed = new AtomicBoolean(false);
 
             switch (keyType) {
                 case MediaDrm.KEY_TYPE_STREAMING:
@@ -690,7 +802,24 @@
                     Log.d(TAG, "setupDrm: selected " + drmScheme);
 
                     if (prepareDrm) {
-                        mPlayer.prepareDrm(drmScheme);
+                        final Monitor drmPrepared = new Monitor();
+                        mPlayer.setDrmEventCallback(
+                                mExecutor, new MediaPlayer2.DrmEventCallback() {
+                            @Override
+                            public void onDrmPrepared(MediaPlayer2 mp, DataSourceDesc dsd2,
+                                    int status, byte[] keySetId) {
+                                if (status != MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS
+                                        || dsd != dsd2) {
+                                    prepareDrmFailed.set(true);
+                                }
+                                drmPrepared.signal();
+                            }
+                        });
+                        mPlayer.prepareDrm(dsd, drmScheme);
+                        drmPrepared.waitForSignal();
+                        if (prepareDrmFailed.get()) {
+                            fail("setupDrm: prepareDrm failed");
+                        }
                     }
 
                     byte[] psshData = drmInfo.getPssh().get(drmScheme);
@@ -725,6 +854,7 @@
             }
 
             final MediaDrm.KeyRequest request = mPlayer.getDrmKeyRequest(
+                    dsd,
                     (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
                     initData,
                     mime,
@@ -737,10 +867,11 @@
 
             // diverging from GTS
             byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
-            byte[] response = createKeysResponse(request, clearKeys);
+            byte[] response = createKeysResponse(request, clearKeys, keyType);
 
             // null is returned when the response is for a streaming or release request.
             byte[] keySetId = mPlayer.provideDrmKeyResponse(
+                    dsd,
                     (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
                     response);
             Log.d(TAG, "setupDrm: provideDrmKeyResponse -> " + Arrays.toString(keySetId));
@@ -751,22 +882,6 @@
             Log.d(TAG, "setupDrm: NoDrmSchemeException");
             e.printStackTrace();
             throw e;
-        } catch (MediaPlayer2.ProvisioningNetworkErrorException e) {
-            Log.d(TAG, "setupDrm: ProvisioningNetworkErrorException");
-            e.printStackTrace();
-            throw e;
-        } catch (MediaPlayer2.ProvisioningServerErrorException e) {
-            Log.d(TAG, "setupDrm: ProvisioningServerErrorException");
-            e.printStackTrace();
-            throw e;
-        } catch (UnsupportedSchemeException e) {
-            Log.d(TAG, "setupDrm: UnsupportedSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (ResourceBusyException e) {
-            Log.d(TAG, "setupDrm: ResourceBusyException");
-            e.printStackTrace();
-            throw e;
         } catch (Exception e) {
             Log.d(TAG, "setupDrm: Exception " + e);
             e.printStackTrace();
@@ -774,10 +889,13 @@
         }
     } // setupDrm
 
-    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
+    private void setupDrmRestore(DataSourceDesc dsd, DrmInfo drmInfo, boolean prepareDrm)
+            throws Exception {
         Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
         try {
             if (prepareDrm) {
+                final AtomicBoolean prepareDrmFailed = new AtomicBoolean(false);
+
                 // DRM preparation
                 List<UUID> supportedSchemes = drmInfo.getSupportedSchemes();
                 if (supportedSchemes.isEmpty()) {
@@ -788,14 +906,31 @@
                 UUID drmScheme = CLEARKEY_SCHEME_UUID;
                 Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
 
-                mPlayer.prepareDrm(drmScheme);
+                final Monitor drmPrepared = new Monitor();
+                mPlayer.setDrmEventCallback(
+                        mExecutor, new MediaPlayer2.DrmEventCallback() {
+                    @Override
+                    public void onDrmPrepared(
+                            MediaPlayer2 mp, DataSourceDesc dsd2, int status, byte[] keySetId) {
+                        if (status != MediaPlayer2.PREPARE_DRM_STATUS_SUCCESS
+                                || dsd != dsd2) {
+                            prepareDrmFailed.set(true);
+                        }
+                        drmPrepared.signal();
+                    }
+                });
+                mPlayer.prepareDrm(dsd, drmScheme);
+                drmPrepared.waitForSignal();
+                if (prepareDrmFailed.get()) {
+                    fail("setupDrmRestore: prepareDrm failed");
+                }
             }
 
             if (mKeySetId == null) {
                 fail("setupDrmRestore: Offline key has not been setup.");
             }
 
-            mPlayer.restoreDrmKeys(mKeySetId);
+            mPlayer.restoreDrmKeys(dsd, mKeySetId);
 
         } catch (MediaPlayer2.NoDrmSchemeException e) {
             Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
@@ -863,22 +998,29 @@
      *
      * @return JSON Web Key string.
      */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
+    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys, int keyType) {
         String jwkSet = "{\"keys\":[";
         for (int i = 0; i < keyIds.size(); ++i) {
             String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
             String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
 
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id + "\",\"k\":\"" + key + "\"}";
+            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
+                    "\",\"k\":\"" + key + "\"}";
         }
-        jwkSet += "]}";
+        jwkSet += "], \"type\":";
+        if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) {
+            jwkSet += "\"persistent-license\" }";
+        } else {
+            jwkSet += "\"temporary\" }";
+        }
         return jwkSet;
     }
 
     /**
      * Retrieves clear key ids from KeyRequest and creates the response in place.
      */
-    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
+    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys,
+            int keyType) {
 
         Vector<String> keyIds = new Vector<String>();
         if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
@@ -887,20 +1029,20 @@
         }
 
         if (clearKeys.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids="
-                    + keyIds.size() + ", keys=" + clearKeys.length);
+            Log.e(TAG, "Mismatch number of key ids and keys: ids=" + keyIds.size() + ", keys="
+                    + clearKeys.length);
             return null;
         }
 
         // Base64 encodes clearkeys. Keys are known to the application.
         Vector<String> keys = new Vector<String>();
         for (int i = 0; i < clearKeys.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeys[i],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
+            String clearKey =
+                    Base64.encodeToString(clearKeys[i], Base64.NO_PADDING | Base64.NO_WRAP);
             keys.add(clearKey);
         }
 
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
+        String jwkSet = createJsonWebKeySet(keyIds, keys, keyType);
         byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
 
         return jsonResponse;
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java b/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java
index 2538cea..da4d28c 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2Test.java
@@ -17,6 +17,7 @@
 
 import android.media.cts.R;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
@@ -24,9 +25,12 @@
 import android.hardware.Camera;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
+import android.media.CallbackDataSourceDesc;
 import android.media.DataSourceDesc;
+import android.media.FileDataSourceDesc;
+import android.media.UriDataSourceDesc;
 import android.media.MediaCodec;
-import android.media.Media2DataSource;
+import android.media.DataSourceCallback;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
@@ -47,20 +51,31 @@
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.platform.test.annotations.RequiresDevice;
 import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.webkit.cts.CtsTestServer;
 
 import com.android.compatibility.common.util.MediaUtils;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.UUID;
 import java.util.Vector;
@@ -72,6 +87,8 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import junit.framework.AssertionFailedError;
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
 
 /**
  * Tests for the MediaPlayer2 API and local video/audio playback.
@@ -84,9 +101,6 @@
 @RequiresDevice
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class MediaPlayer2Test extends MediaPlayer2TestBase {
-    // TODO: remove this flag to enable tests.
-    private static final boolean IGNORE_TESTS = true;
-
     private String RECORDED_FILE;
     private static final String LOG_TAG = "MediaPlayer2Test";
 
@@ -125,18 +139,17 @@
 
     // Bug 13652927
     public void testVorbisCrash() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         MediaPlayer2 mp = mPlayer;
         MediaPlayer2 mp2 = mPlayer2;
         AssetFileDescriptor afd2 = mResources.openRawResourceFd(R.raw.testmp3_2);
-        mp2.setDataSource(new DataSourceDesc.Builder()
-                .setDataSource(afd2.getFileDescriptor(), afd2.getStartOffset(), afd2.getLength())
+        mp2.setDataSource(new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd2.getFileDescriptor()),
+                    afd2.getStartOffset(), afd2.getLength())
                 .build());
+        afd2.close();
         Monitor onPrepareCalled = new Monitor();
         Monitor onErrorCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -149,11 +162,10 @@
                 onErrorCalled.signal();
             }
         };
-        mp2.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mp2.registerEventCallback(mExecutor, ecb);
         mp2.prepare();
         onPrepareCalled.waitForSignal();
-        afd2.close();
-        mp2.clearMediaPlayer2EventCallback();
+        mp2.unregisterEventCallback(ecb);
 
         mp2.loopCurrent(true);
         mp2.play();
@@ -161,31 +173,28 @@
         for (int i = 0; i < 20; i++) {
             try {
                 AssetFileDescriptor afd = mResources.openRawResourceFd(R.raw.bug13652927);
-                mp.setDataSource(new DataSourceDesc.Builder()
-                        .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                            afd.getLength())
+                mp.setDataSource(new FileDataSourceDesc.Builder()
+                        .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                            afd.getStartOffset(), afd.getLength())
                         .build());
-                mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+                afd.close();
+                mp.registerEventCallback(mExecutor, ecb);
                 onPrepareCalled.reset();
                 mp.prepare();
                 onErrorCalled.waitForSignal();
-                afd.close();
             } catch (Exception e) {
                 // expected to fail
                 Log.i("@@@", "failed: " + e);
             }
             Thread.sleep(500);
-            assertTrue("media player died", mp2.isPlaying());
+            assertTrue("media player died", mp2.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             mp.reset();
         }
     }
 
     public void testPlayNullSourcePath() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         Monitor onSetDataSourceCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
@@ -199,14 +208,11 @@
         }
 
         onSetDataSourceCalled.reset();
-        mPlayer.setDataSource((DataSourceDesc)null);
+        mPlayer.setDataSource((DataSourceDesc) null);
         onSetDataSourceCalled.waitForSignal();
     }
 
     public void testPlayAudioFromDataURI() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int mp3Duration = 34909;
         final int tolerance = 70;
         final int seekDuration = 100;
@@ -228,7 +234,7 @@
         Monitor onPlayCalled = new Monitor();
         Monitor onSeekToCalled = new Monitor();
         Monitor onLoopCurrentCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -247,20 +253,24 @@
                 }
             }
         };
-        mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mp.registerEventCallback(mExecutor, ecb);
 
         try {
             AudioAttributes attributes = new AudioAttributes.Builder()
                     .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                     .build();
             mp.setAudioAttributes(attributes);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "MediaPlayer2Test");
+            mp.setWakeLock(wakeLock);
 
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             assertFalse(mp.isLooping());
             onLoopCurrentCalled.reset();
@@ -268,7 +278,7 @@
             onLoopCurrentCalled.waitForSignal();
             assertTrue(mp.isLooping());
 
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
+            assertEquals(mp3Duration, mp.getDuration(mp.getCurrentDataSource()), tolerance);
             long pos = mp.getCurrentPosition();
             assertTrue(pos >= 0);
             assertTrue(pos < mp3Duration - seekDuration);
@@ -281,30 +291,30 @@
             // test pause and restart
             mp.pause();
             Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             // test stop and restart
             mp.reset();
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
-            mp.setDataSource(new DataSourceDesc.Builder()
+            mp.registerEventCallback(mExecutor, ecb);
+            mp.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
             onPrepareCalled.reset();
             mp.prepare();
             onPrepareCalled.waitForSignal();
 
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             // waiting to complete
-            while(mp.isPlaying()) {
+            while(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
                 Thread.sleep(SLEEP_TIME);
             }
         } finally {
@@ -313,9 +323,6 @@
     }
 
     public void testPlayAudio() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int resid = R.raw.testmp3_2;
         final int mp3Duration = 34909;
         final int tolerance = 70;
@@ -327,7 +334,7 @@
         Monitor onPlayCalled = new Monitor();
         Monitor onSeekToCalled = new Monitor();
         Monitor onLoopCurrentCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -346,20 +353,24 @@
                 }
             }
         };
-        mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mp.registerEventCallback(mExecutor, ecb);
 
         try {
             AudioAttributes attributes = new AudioAttributes.Builder()
                     .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                     .build();
             mp.setAudioAttributes(attributes);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "MediaPlayer2Test");
+            mp.setWakeLock(wakeLock);
 
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             assertFalse(mp.isLooping());
             onLoopCurrentCalled.reset();
@@ -367,7 +378,7 @@
             onLoopCurrentCalled.waitForSignal();
             assertTrue(mp.isLooping());
 
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
+            assertEquals(mp3Duration, mp.getDuration(mp.getCurrentDataSource()), tolerance);
             long pos = mp.getCurrentPosition();
             assertTrue(pos >= 0);
             assertTrue(pos < mp3Duration - seekDuration);
@@ -380,33 +391,34 @@
             // test pause and restart
             mp.pause();
             Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             // test stop and restart
             mp.reset();
             AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
-            mp.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
+            mp.setDataSource(new FileDataSourceDesc.Builder()
+                    .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                        afd.getStartOffset(), afd.getLength())
                     .build());
+            afd.close();
 
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mp.registerEventCallback(mExecutor, ecb);
             onPrepareCalled.reset();
             mp.prepare();
             onPrepareCalled.waitForSignal();
-            afd.close();
 
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             // waiting to complete
-            while(mp.isPlaying()) {
+            while(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
                 Thread.sleep(SLEEP_TIME);
             }
         } finally {
@@ -415,9 +427,6 @@
     }
 
     public void testConcurentPlayAudio() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int resid = R.raw.test1m1s; // MP3 longer than 1m are usualy offloaded
         final int tolerance = 70;
 
@@ -428,8 +437,8 @@
             for (MediaPlayer2 mp : mps) {
                 Monitor onPlayCalled = new Monitor();
                 Monitor onLoopCurrentCalled = new Monitor();
-                MediaPlayer2.MediaPlayer2EventCallback ecb =
-                    new MediaPlayer2.MediaPlayer2EventCallback() {
+                MediaPlayer2.EventCallback ecb =
+                    new MediaPlayer2.EventCallback() {
                         @Override
                         public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
                                 int what, int status) {
@@ -440,19 +449,24 @@
                             }
                         }
                     };
-                mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+                mp.registerEventCallback(mExecutor, ecb);
 
                 AudioAttributes attributes = new AudioAttributes.Builder()
                         .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                         .build();
                 mp.setAudioAttributes(attributes);
-                mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+                PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+                PowerManager.WakeLock wakeLock =
+                        pm.newWakeLock(
+                                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                                "MediaPlayer2Test");
+                mp.setWakeLock(wakeLock);
 
-                assertFalse(mp.isPlaying());
+                assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
                 onPlayCalled.reset();
                 mp.play();
                 onPlayCalled.waitForSignal();
-                assertTrue(mp.isPlaying());
+                assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
                 assertFalse(mp.isLooping());
                 onLoopCurrentCalled.reset();
@@ -477,9 +491,6 @@
     }
 
     public void testPlayAudioLooping() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int resid = R.raw.testmp3;
 
         MediaPlayer2 mp = createMediaPlayer2(mContext, resid);
@@ -488,17 +499,27 @@
                     .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                     .build();
             mp.setAudioAttributes(attributes);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "MediaPlayer2Test");
+            mp.setWakeLock(wakeLock);
+
             mp.loopCurrent(true);
             Monitor onCompletionCalled = new Monitor();
+            Monitor onRepeatCalled = new Monitor();
             Monitor onPlayCalled = new Monitor();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd,
                             int what, int extra) {
-                        Log.i("@@@", "got oncompletion");
-                        onCompletionCalled.signal();
+                        if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT) {
+                            onRepeatCalled.signal();
+                        } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                            Log.i("@@@", "got oncompletion");
+                            onCompletionCalled.signal();
+                        }
                     }
 
                     @Override
@@ -509,23 +530,24 @@
                         }
                     }
                 };
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mp.registerEventCallback(mExecutor, ecb);
 
-            assertFalse(mp.isPlaying());
+            assertFalse(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             onPlayCalled.reset();
             mp.play();
             onPlayCalled.waitForSignal();
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
-            long duration = mp.getDuration();
+            long duration = mp.getDuration(mp.getCurrentDataSource());
             Thread.sleep(duration * 4); // allow for several loops
-            assertTrue(mp.isPlaying());
+            assertTrue(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             assertEquals("wrong number of completion signals", 0,
                     onCompletionCalled.getNumSignal());
+            assertTrue(onRepeatCalled.getNumSignal() > 0);
             mp.loopCurrent(false);
 
             // wait for playback to finish
-            while(mp.isPlaying()) {
+            while(mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
                 Thread.sleep(SLEEP_TIME);
             }
             assertEquals("wrong number of completion signals", 1,
@@ -536,9 +558,6 @@
     }
 
     public void testPlayMidi() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int resid = R.raw.midi8sec;
         final int midiDuration = 8000;
         final int tolerance = 70;
@@ -549,8 +568,8 @@
         Monitor onPrepareCalled = new Monitor();
         Monitor onSeekToCalled = new Monitor();
         Monitor onLoopCurrentCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb =
-            new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb =
+            new MediaPlayer2.EventCallback() {
                 @Override
                 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                     if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -568,14 +587,18 @@
                     }
                 }
             };
-        mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mp.registerEventCallback(mExecutor, ecb);
 
         try {
             AudioAttributes attributes = new AudioAttributes.Builder()
                     .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                     .build();
             mp.setAudioAttributes(attributes);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "MediaPlayer2Test");
+            mp.setWakeLock(wakeLock);
 
             mp.play();
 
@@ -585,7 +608,7 @@
             onLoopCurrentCalled.waitForSignal();
             assertTrue(mp.isLooping());
 
-            assertEquals(midiDuration, mp.getDuration(), tolerance);
+            assertEquals(midiDuration, mp.getDuration(mp.getCurrentDataSource()), tolerance);
             long pos = mp.getCurrentPosition();
             assertTrue(pos >= 0);
             assertTrue(pos < midiDuration - seekDuration);
@@ -598,15 +621,16 @@
             // test stop and restart
             mp.reset();
             AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
-            mp.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
+            mp.setDataSource(new FileDataSourceDesc.Builder()
+                    .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                        afd.getStartOffset(), afd.getLength())
                     .build());
+            afd.close();
 
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mp.registerEventCallback(mExecutor, ecb);
             onPrepareCalled.reset();
             mp.prepare();
             onPrepareCalled.waitForSignal();
-            afd.close();
 
             mp.play();
 
@@ -681,9 +705,6 @@
     }
 
     public void testPlayAudioTwice() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int resid = R.raw.camera_click;
 
         MediaPlayer2 mp = createMediaPlayer2(mContext, resid);
@@ -692,7 +713,11 @@
                     .setInternalLegacyStreamType(AudioManager.STREAM_MUSIC)
                     .build();
             mp.setAudioAttributes(attributes);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "MediaPlayer2Test");
+            mp.setWakeLock(wakeLock);
 
             OutputListener listener = new OutputListener(mp.getAudioSessionId());
 
@@ -701,7 +726,8 @@
 
             mp.play();
             Thread.sleep(SLEEP_TIME);
-            assertFalse("player was still playing after " + SLEEP_TIME + " ms", mp.isPlaying());
+            assertFalse("player was still playing after " + SLEEP_TIME + " ms",
+                    mp.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             assertTrue("nothing heard while test ran", listener.heardSound());
             listener.reset();
             mp.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
@@ -715,28 +741,48 @@
     }
 
     public void testPlayVideo() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(R.raw.testvideo, 352, 288);
     }
 
+    public void testPlayVideoWithCookies() throws Exception {
+        String cookieName = "foo";
+        String cookieValue = "bar";
+        HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
+        cookie.setDomain(".local");
+
+        CtsTestServer foo = new CtsTestServer(mContext);
+        String video = "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4";
+        Uri uri = Uri.parse(foo.getAssetUrl(video));
+        List<HttpCookie> cookies = Collections.singletonList(cookie);
+        Map<String, String> headers = Collections.<String, String>emptyMap();
+        playVideoWithRetries(uri, headers, cookies, 270, 480, 0);
+
+        HttpRequest req = foo.getLastRequest(video);
+        for (Header h : req.getAllHeaders()) {
+            String v = h.getValue();
+            String regex = ".*" + cookie.getName() + ".*" + cookie.getValue() + ".*";
+            Log.v(LOG_TAG, String.format("testCookies: header %s regex %s", h, regex));
+            if (v.matches(regex)) {
+                return;
+            }
+        }
+
+        fail("missing cookie: " + cookie);
+    }
+
     /**
      * Test for reseting a surface during video playback
      * After reseting, the video should continue playing
      * from the time setDisplay() was called
      */
     public void testVideoSurfaceResetting() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int tolerance = 150;
         final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
         final int seekPos = 4760;  // This is the I-frame position
 
         final CountDownLatch seekDone = new CountDownLatch(1);
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
@@ -765,7 +811,7 @@
          */
         // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
         // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         Thread.sleep(SLEEP_TIME);
 
@@ -780,7 +826,7 @@
         posAfter = mPlayer.getCurrentPosition();
         // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
         // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         Thread.sleep(SLEEP_TIME);
 
@@ -790,36 +836,24 @@
 
         // TODO: uncomment out line below when MediaPlayer2 can seek to requested position.
         // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         Thread.sleep(SLEEP_TIME);
     }
 
     public void testRecordedVideoPlayback0() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         testRecordedVideoPlaybackWithAngle(0);
     }
 
     public void testRecordedVideoPlayback90() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         testRecordedVideoPlaybackWithAngle(90);
     }
 
     public void testRecordedVideoPlayback180() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         testRecordedVideoPlaybackWithAngle(180);
     }
 
     public void testRecordedVideoPlayback270() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         testRecordedVideoPlaybackWithAngle(270);
     }
 
@@ -917,39 +951,74 @@
     }
 
     public void testPlaylist() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         if (!checkLoadResource(
                 R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
             return; // skip
         }
-        DataSourceDesc dsd1 = createDataSourceDesc(
-                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz);
-        DataSourceDesc dsd2 = createDataSourceDesc(
-                R.raw.testvideo);
+
+        int resid = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz;
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;  // skip
+        }
+
+        AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid);
+        DataSourceDesc dsd2 = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd2.getFileDescriptor()),
+                        afd2.getStartOffset(), afd2.getLength())
+                .build();
+        afd2.close();
+
+        resid = R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz;
+        AssetFileDescriptor afd3 = mResources.openRawResourceFd(resid);
+        DataSourceDesc dsd3 = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd3.getFileDescriptor()),
+                        afd3.getStartOffset(), afd3.getLength())
+                .build();
+        afd3.close();
+
         ArrayList<DataSourceDesc> nextDSDs = new ArrayList<DataSourceDesc>(2);
         nextDSDs.add(dsd2);
-        nextDSDs.add(dsd1);
+        nextDSDs.add(dsd3);
 
         mPlayer.setNextDataSources(nextDSDs);
 
-        Monitor onCompletion1Called = new Monitor();
+        Monitor onStartCalled = new Monitor();
+        Monitor onStart2Called = new Monitor();
+        Monitor onStart3Called = new Monitor();
         Monitor onCompletion2Called = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        Monitor onCompletion3Called = new Monitor();
+        Monitor onListCompletionCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
                     Log.i(LOG_TAG, "testPlaylist: prepared dsd MediaId=" + dsd.getMediaId());
                     mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
-                    if (dsd == dsd1) {
-                        onCompletion1Called.signal();
-                    } else if (dsd == dsd2) {
-                        onCompletion2Called.signal();
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_START dsd2");
+                        onStart2Called.signal();
+                    } else if (dsd == dsd3) {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_START dsd3");
+                        onStart3Called.signal();
                     } else {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_START other");
+                        onStartCalled.signal();
+                    }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_END dsd2");
+                        onCompletion2Called.signal();
+                    } else if (dsd == dsd3) {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_END dsd3");
+                        onCompletion3Called.signal();
+                    } else {
+                        Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_END other");
                         mOnCompletionCalled.signal();
                     }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
+                    Log.i(LOG_TAG, "testPlaylist: MEDIA_INFO_DATA_SOURCE_LIST_END");
+                    onListCompletionCalled.signal();
                 }
             }
         };
@@ -958,7 +1027,195 @@
         }
 
         mOnCompletionCalled.reset();
-        onCompletion1Called.reset();
+        onCompletion2Called.reset();
+        onCompletion3Called.reset();
+
+        mPlayer.setDisplay(mActivity.getSurfaceHolder());
+
+        mPlayer.prepare();
+
+        mPlayer.play();
+
+        onStartCalled.waitForSignal();
+        onStart2Called.waitForSignal();
+        onStart3Called.waitForSignal();
+        mOnCompletionCalled.waitForSignal();
+        onCompletion2Called.waitForSignal();
+        onCompletion3Called.waitForSignal();
+        onListCompletionCalled.waitForSignal();
+
+        mPlayer.reset();
+    }
+
+    public void testSkipToNext() throws Exception {
+        if (!checkLoadResource(
+                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
+            return; // skip
+        }
+
+        int resid = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz;
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;  // skip
+        }
+
+        AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid);
+        DataSourceDesc dsd2 = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd2.getFileDescriptor()),
+                        afd2.getStartOffset(), afd2.getLength())
+                .build();
+        afd2.close();
+
+        resid = R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz;
+        AssetFileDescriptor afd3 = mResources.openRawResourceFd(resid);
+        DataSourceDesc dsd3 = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd3.getFileDescriptor()),
+                        afd3.getStartOffset(), afd3.getLength())
+                .build();
+        afd3.close();
+
+        ArrayList<DataSourceDesc> nextDSDs = new ArrayList<DataSourceDesc>(2);
+        nextDSDs.add(dsd2);
+        nextDSDs.add(dsd3);
+
+        mPlayer.setNextDataSources(nextDSDs);
+
+        Monitor onStartCalled = new Monitor();
+        Monitor onStart2Called = new Monitor();
+        Monitor onStart3Called = new Monitor();
+        Monitor onCompletion2Called = new Monitor();
+        Monitor onCompletion3Called = new Monitor();
+        Monitor onListCompletionCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                    Log.i(LOG_TAG, "testSkipToNext: prepared dsd MediaId=" + dsd.getMediaId());
+                    mOnPrepareCalled.signal();
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_START dsd2");
+                        onStart2Called.signal();
+                    } else if (dsd == dsd3) {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_START dsd3");
+                        onStart3Called.signal();
+                    } else {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_START other");
+                        onStartCalled.signal();
+                    }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_END dsd2");
+                        onCompletion2Called.signal();
+                    } else if (dsd == dsd3) {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_END dsd3");
+                        onCompletion3Called.signal();
+                    } else {
+                        Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_END other");
+                        mOnCompletionCalled.signal();
+                    }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
+                    Log.i(LOG_TAG, "testSkipToNext: MEDIA_INFO_DATA_SOURCE_LIST_END");
+                    onListCompletionCalled.signal();
+                }
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mOnCompletionCalled.reset();
+        onCompletion2Called.reset();
+        onCompletion3Called.reset();
+
+        mPlayer.setDisplay(mActivity.getSurfaceHolder());
+
+        mPlayer.prepare();
+
+        mPlayer.play();
+        Thread.sleep(1000);
+        onStartCalled.waitForSignal();
+
+        mPlayer.skipToNext();
+        mOnCompletionCalled.waitForSignal(3000);
+        assertTrue("current data source is not dsd2",
+                mPlayer.getCurrentDataSource() == dsd2);
+        Thread.sleep(1000);
+        onStart2Called.waitForSignal();
+
+        mPlayer.skipToNext();
+        onCompletion2Called.waitForSignal(3000);
+        assertTrue("current data source is not dsd2",
+                mPlayer.getCurrentDataSource() == dsd3);
+        onStart3Called.waitForSignal();
+
+        onCompletion3Called.waitForSignal();
+        onListCompletionCalled.waitForSignal();
+
+        mPlayer.reset();
+    }
+
+    public void testClearNextDataSources() throws Exception {
+        if (!checkLoadResource(
+                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
+            return; // skip
+        }
+
+        int resid = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz;
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;  // skip
+        }
+
+        AssetFileDescriptor afd2 = mResources.openRawResourceFd(resid);
+        DataSourceDesc dsd2 = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd2.getFileDescriptor()),
+                        afd2.getStartOffset(), afd2.getLength())
+                .build();
+        afd2.close();
+
+        mPlayer.setNextDataSource(dsd2);
+
+        Monitor onStartCalled = new Monitor();
+        Monitor onStart2Called = new Monitor();
+        Monitor onCompletion2Called = new Monitor();
+        Monitor onListCompletionCalled = new Monitor();
+        Monitor onClearNextDataSourcesCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testClearNextDataSources: DATA_SOURCE_START dsd2");
+                        onStart2Called.signal();
+                    } else {
+                        Log.i(LOG_TAG, "testClearNextDataSources: DATA_SOURCE_START other");
+                        onStartCalled.signal();
+                    }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                    if (dsd == dsd2) {
+                        Log.i(LOG_TAG, "testClearNextDataSources: DATA_SOURCE_END dsd2");
+                        onCompletion2Called.signal();
+                    } else {
+                        Log.i(LOG_TAG, "testClearNextDataSources: DATA_SOURCE_END other");
+                        mOnCompletionCalled.signal();
+                    }
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
+                    Log.i(LOG_TAG, "testClearNextDataSources: DATA_SOURCE_LIST_END");
+                    onListCompletionCalled.signal();
+                }
+            }
+
+            @Override
+            public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                if (what == MediaPlayer2.CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES) {
+                    onClearNextDataSourcesCalled.signal();
+                }
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mOnCompletionCalled.reset();
         onCompletion2Called.reset();
 
         mPlayer.setDisplay(mActivity.getSurfaceHolder());
@@ -967,31 +1224,37 @@
 
         mPlayer.play();
 
+        Thread.sleep(1000);  // sleep 1 second
+        mPlayer.clearNextDataSources();
+
+        onStartCalled.waitForSignal();
         mOnCompletionCalled.waitForSignal();
-        onCompletion2Called.waitForSignal();
-        onCompletion1Called.waitForSignal();
+        onListCompletionCalled.waitForSignal();
+        assertEquals("clearNextDataSources is confirmed none or more than once",
+                1, onClearNextDataSourcesCalled.getNumSignal());
+        assertEquals("next dsd was mistakenly started", 0, onStart2Called.getNumSignal());
+        assertEquals("next dsd was mistakenly played", 0, onCompletion2Called.getNumSignal());
 
         mPlayer.reset();
     }
 
     // setPlaybackParams() with non-zero speed should NOT start playback.
-    // TODO: enable this test when MediaPlayer2.setPlaybackParams() is fixed
-    /*
-    public void testSetPlaybackParamsPositiveSpeed() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
+    // zero speed is invalid.
+    public void testSetPlaybackParamsSpeeds() throws Exception {
         if (!checkLoadResource(
                 R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
             return; // skip
         }
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        Monitor onSetPlaybackParamsCompleteCalled = new Monitor();
+        AtomicInteger setPlaybackParamsStatus =
+                new AtomicInteger(MediaPlayer2.CALL_STATUS_NO_ERROR);
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @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) {
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                     mOnCompletionCalled.signal();
                 }
             }
@@ -1000,6 +1263,9 @@
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
                     mOnSeekCompleteCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS) {
+                    setPlaybackParamsStatus.set(status);
+                    onSetPlaybackParamsCompleteCalled.signal();
                 }
             }
         };
@@ -1010,60 +1276,71 @@
         mOnCompletionCalled.reset();
         mPlayer.setDisplay(mActivity.getSurfaceHolder());
 
-        mOnPrepareCalled.reset();
         mPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
 
         mOnSeekCompleteCalled.reset();
         mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
         mOnSeekCompleteCalled.waitForSignal();
 
-        final float playbackRate = 1.0f;
+        float playbackRate = 0.f;
+        onSetPlaybackParamsCompleteCalled.reset();
+        mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+        onSetPlaybackParamsCompleteCalled.waitForSignal();
+        assertTrue("speed of zero is invalid",
+                setPlaybackParamsStatus.intValue() == MediaPlayer2.CALL_STATUS_BAD_VALUE);
+
+        playbackRate = 1.0f;
 
         int playTime = 2000;  // The testing clip is about 10 second long.
+        onSetPlaybackParamsCompleteCalled.reset();
         mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer2 should be playing", mPlayer.isPlaying());
         Thread.sleep(playTime);
-        assertTrue("MediaPlayer2 should still be playing",
-                mPlayer.getCurrentPosition() > 0);
+        onSetPlaybackParamsCompleteCalled.waitForSignal();
+        assertTrue("MediaPlayer2 should not be playing",
+                mPlayer.getState() != MediaPlayer2.PLAYER_STATE_PLAYING);
 
-        long duration = mPlayer.getDuration();
+        long duration = mPlayer.getDuration(mPlayer.getCurrentDataSource());
         mOnSeekCompleteCalled.reset();
         mPlayer.seekTo(duration - 1000, MediaPlayer2.SEEK_PREVIOUS_SYNC);
         mOnSeekCompleteCalled.waitForSignal();
 
-        mOnCompletionCalled.waitForSignal();
-        assertFalse("MediaPlayer2 should not be playing", mPlayer.isPlaying());
-        long eosPosition = mPlayer.getCurrentPosition();
+        mPlayer.play();
 
+        mOnCompletionCalled.waitForSignal();
+        assertFalse("MediaPlayer2 should not be playing",
+                mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
+
+        onSetPlaybackParamsCompleteCalled.reset();
         mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer2 should be playing after EOS", mPlayer.isPlaying());
-        Thread.sleep(playTime);
-        long position = mPlayer.getCurrentPosition();
-        assertTrue("MediaPlayer2 should still be playing after EOS",
-                position > 0 && position < eosPosition);
+        onSetPlaybackParamsCompleteCalled.waitForSignal();
+        assertFalse("MediaPlayer2 should not be playing after EOS",
+                mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         mPlayer.reset();
     }
-    */
 
     public void testPlaybackRate() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int toleranceMs = 1000;
         if (!checkLoadResource(
                 R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
             return; // skip
         }
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        Monitor onSetPlaybackParamsCompleteCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
                     mOnPrepareCalled.signal();
                 }
             }
+
+            @Override
+            public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                if (what == MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS) {
+                    onSetPlaybackParamsCompleteCalled.signal();
+                }
+            }
         };
         synchronized (mEventCbLock) {
             mEventCallbacks.add(ecb);
@@ -1081,16 +1358,18 @@
         float[] rates = { 0.25f, 0.5f, 1.0f, 2.0f };
         for (float playbackRate : rates) {
             mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-            Thread.sleep(1000);
             int playTime = 4000;  // The testing clip is about 10 second long.
+            onSetPlaybackParamsCompleteCalled.reset();
             mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
             mPlayer.play();
             Thread.sleep(playTime);
+            onSetPlaybackParamsCompleteCalled.waitForSignal();
             PlaybackParams pbp = mPlayer.getPlaybackParams();
             assertEquals(
                     playbackRate, pbp.getSpeed(),
                     FLOAT_TOLERANCE + playbackRate * sync.getTolerance());
-            assertTrue("MediaPlayer2 should still be playing", mPlayer.isPlaying());
+            assertTrue("MediaPlayer2 should still be playing",
+                    mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             long playedMediaDurationMs = mPlayer.getCurrentPosition();
             int diff = Math.abs((int)(playedMediaDurationMs / playbackRate) - playTime);
@@ -1100,23 +1379,19 @@
             }
             mPlayer.pause();
             pbp = mPlayer.getPlaybackParams();
-            // TODO: pause() should NOT change PlaybackParams.
-            // assertEquals(0.f, pbp.getSpeed(), FLOAT_TOLERANCE);
+            assertEquals(playbackRate, pbp.getSpeed(), FLOAT_TOLERANCE);
         }
         mPlayer.reset();
     }
 
     public void testSeekModes() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         // This clip has 2 I frames at 66687us and 4299687us.
         if (!checkLoadResource(
                 R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz)) {
             return; // skip
         }
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -1201,9 +1476,6 @@
     }
 
     public void testGetTimestamp() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int toleranceUs = 100000;
         final float playbackRate = 1.0f;
         if (!checkLoadResource(
@@ -1212,7 +1484,8 @@
         }
 
         Monitor onPauseCalled = new Monitor();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        Monitor onSetPlaybackParamsCompleteCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -1224,6 +1497,8 @@
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_PAUSE) {
                     onPauseCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_SET_PLAYBACK_PARAMS) {
+                    onSetPlaybackParamsCompleteCalled.signal();
                 }
             }
         };
@@ -1238,8 +1513,10 @@
         mOnPrepareCalled.waitForSignal();
 
         mPlayer.play();
+        onSetPlaybackParamsCompleteCalled.reset();
         mPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
         Thread.sleep(SLEEP_TIME);  // let player get into stable state.
+        onSetPlaybackParamsCompleteCalled.waitForSignal();
         long nt1 = System.nanoTime();
         MediaTimestamp ts1 = mPlayer.getTimestamp();
         long nt2 = System.nanoTime();
@@ -1247,7 +1524,7 @@
         assertEquals("MediaPlayer2 had error in clockRate " + ts1.getMediaClockRate(),
                 playbackRate, ts1.getMediaClockRate(), 0.001f);
         assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
-                nt1 <= ts1.getAnchorSytemNanoTime() && ts1.getAnchorSytemNanoTime() <= nt2);
+                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
 
         onPauseCalled.reset();
         mPlayer.pause();
@@ -1277,71 +1554,47 @@
 
     public void testLocalVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz, 1280, 720);
     }
     public void testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented,
                 480, 360);
@@ -1350,232 +1603,158 @@
 
     public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz, 480, 360);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         playVideoTest(
                 R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     private void readSubtitleTracks() throws Exception {
         mSubtitleTrackIndex.clear();
-        List<MediaPlayer2.TrackInfo> trackInfos = mPlayer.getTrackInfo();
+        List<MediaPlayer2.TrackInfo> trackInfos =
+                mPlayer.getTrackInfo(mPlayer.getCurrentDataSource());
         if (trackInfos == null || trackInfos.size() == 0) {
             return;
         }
@@ -1594,14 +1773,14 @@
 
     private void selectSubtitleTrack(int index) throws Exception {
         int trackIndex = mSubtitleTrackIndex.get(index);
-        mPlayer.selectTrack(trackIndex);
+        mPlayer.selectTrack(mPlayer.getCurrentDataSource(), trackIndex);
         mSelectedSubtitleIndex = index;
     }
 
     private void deselectSubtitleTrack(int index) throws Exception {
         int trackIndex = mSubtitleTrackIndex.get(index);
         mOnDeselectTrackCalled.reset();
-        mPlayer.deselectTrack(trackIndex);
+        mPlayer.deselectTrack(mPlayer.getCurrentDataSource(), trackIndex);
         mOnDeselectTrackCalled.waitForSignal();
         if (mSelectedSubtitleIndex == index) {
             mSelectedSubtitleIndex = -1;
@@ -1609,16 +1788,13 @@
     }
 
     public void testDeselectTrackForSubtitleTracks() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
             return; // skip;
         }
 
         getInstrumentation().waitForIdleSync();
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -1639,23 +1815,25 @@
                     mOnDeselectTrackCalled.signal();
                 }
             }
+
+            @Override
+            public void onSubtitleData(MediaPlayer2 mp, DataSourceDesc dsd, SubtitleData data) {
+                if (data != null && data.getData() != null) {
+                    mOnSubtitleDataCalled.signal();
+                }
+            }
         };
         synchronized (mEventCbLock) {
             mEventCallbacks.add(ecb);
         }
 
-        mPlayer.setOnSubtitleDataListener(new MediaPlayer2.OnSubtitleDataListener() {
-            @Override
-            public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
-                if (data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-        });
-
         mPlayer.setDisplay(getActivity().getSurfaceHolder());
         mPlayer.setScreenOnWhilePlaying(true);
-        mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                        "MediaPlayer2Test");
+        mPlayer.setWakeLock(wakeLock);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -1664,7 +1842,7 @@
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // Closed caption tracks are in-band.
         // So, those tracks will be found after processing a number of frames.
@@ -1697,23 +1875,11 @@
     }
 
     public void testChangeSubtitleTrack() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
             return; // skip;
         }
 
-        mPlayer.setOnSubtitleDataListener(new MediaPlayer2.OnSubtitleDataListener() {
-            @Override
-            public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
-                if (data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-        });
-
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -1729,6 +1895,13 @@
                     mOnPlayCalled.signal();
                 }
             }
+
+            @Override
+            public void onSubtitleData(MediaPlayer2 mp, DataSourceDesc dsd, SubtitleData data) {
+                if (data != null && data.getData() != null) {
+                    mOnSubtitleDataCalled.signal();
+                }
+            }
         };
         synchronized (mEventCbLock) {
             mEventCallbacks.add(ecb);
@@ -1736,7 +1909,11 @@
 
         mPlayer.setDisplay(getActivity().getSurfaceHolder());
         mPlayer.setScreenOnWhilePlaying(true);
-        mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                        "MediaPlayer2Test");
+        mPlayer.setWakeLock(wakeLock);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -1745,7 +1922,7 @@
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // Closed caption tracks are in-band.
         // So, those tracks will be found after processing a number of frames.
@@ -1768,16 +1945,13 @@
     }
 
     public void testGetTrackInfoForVideoWithSubtitleTracks() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         if (!checkLoadResource(R.raw.testvideo_with_2_subtitle_tracks)) {
             return; // skip;
         }
 
         getInstrumentation().waitForIdleSync();
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -1800,7 +1974,11 @@
 
         mPlayer.setDisplay(getActivity().getSurfaceHolder());
         mPlayer.setScreenOnWhilePlaying(true);
-        mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                        "MediaPlayer2Test");
+        mPlayer.setWakeLock(wakeLock);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -1809,7 +1987,7 @@
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // The media metadata will be changed while playing since closed caption tracks are in-band
         // and those tracks will be found after processing a number of frames. These tracks will be
@@ -1830,9 +2008,6 @@
      *  The ones being used here are 10 seconds long.
      */
     public void testResumeAtEnd() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         int testsRun =
             testResumeAtEnd(R.raw.loudsoftmp3) +
             testResumeAtEnd(R.raw.loudsoftwav) +
@@ -1854,18 +2029,18 @@
             return 0; // skip
         }
         mOnCompletionCalled.reset();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @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) {
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                     mOnCompletionCalled.signal();
                     mPlayer.play();
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -1876,16 +2051,15 @@
         mPlayer.play();
         // sleep long enough that we restart playback at least once, but no more
         Thread.sleep(10000);
-        assertTrue("MediaPlayer2 should still be playing", mPlayer.isPlaying());
+        assertTrue("MediaPlayer2 should still be playing",
+                mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
+        mPlayer.unregisterEventCallback(ecb);
         mPlayer.reset();
         assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
         return 1;
     }
 
     public void testPositionAtEnd() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         int testsRun =
             testPositionAtEnd(R.raw.test1m1shighstereo) +
             testPositionAtEnd(R.raw.loudsoftmp3) +
@@ -1912,12 +2086,12 @@
         mPlayer.setAudioAttributes(attributes);
 
         mOnCompletionCalled.reset();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @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) {
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                     mOnCompletionCalled.signal();
                 }
             }
@@ -1929,19 +2103,19 @@
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
         mOnPrepareCalled.waitForSignal();
 
-        long duration = mPlayer.getDuration();
+        long duration = mPlayer.getDuration(mPlayer.getCurrentDataSource());
         assertTrue("resource too short", duration > 6000);
         mPlayer.seekTo(duration - 5000, MediaPlayer2.SEEK_PREVIOUS_SYNC);
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        while (mPlayer.isPlaying()) {
+        while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
             Log.i("@@@@", "position: " + mPlayer.getCurrentPosition());
             Thread.sleep(500);
         }
@@ -1952,9 +2126,6 @@
     }
 
     public void testCallback() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
         final int mp4Duration = 8484;
 
         if (!checkLoadResource(R.raw.testvideo)) {
@@ -1965,10 +2136,9 @@
         mPlayer.setScreenOnWhilePlaying(true);
 
         mOnCompletionCalled.reset();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
-            public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd,
-                    int width, int height) {
+            public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, Size size) {
                 mOnVideoSizeChangedCalled.signal();
             }
 
@@ -1983,7 +2153,7 @@
 
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
                     mOnPrepareCalled.signal();
-                } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                     mOnCompletionCalled.signal();
                 }
             }
@@ -2014,19 +2184,16 @@
         assertFalse(mOnCompletionCalled.isSignalled());
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        while(mPlayer.isPlaying()) {
+        while(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
             Thread.sleep(SLEEP_TIME);
         }
-        assertFalse(mPlayer.isPlaying());
+        assertFalse(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
         mOnCompletionCalled.waitForSignal();
         assertFalse(mOnErrorCalled.isSignalled());
         mPlayer.reset();
     }
 
     public void testRecordAndPlay() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
         if (!hasMicrophone()) {
             MediaUtils.skipTest(LOG_TAG, "no microphone");
             return;
@@ -2042,9 +2209,9 @@
             recordMedia(outputFileLocation);
 
             Uri uri = Uri.parse(outputFileLocation);
-            MediaPlayer2 mp = MediaPlayer2.create();
+            MediaPlayer2 mp = new MediaPlayer2(mContext);
             try {
-                mp.setDataSource(new DataSourceDesc.Builder()
+                mp.setDataSource(new UriDataSourceDesc.Builder()
                         .setDataSource(mContext, uri)
                         .build());
                 mp.prepare();
@@ -2104,11 +2271,8 @@
                 PackageManager.FEATURE_MICROPHONE);
     }
 
-    // Smoke test playback from a Media2DataSource.
-    public void testPlaybackFromAMedia2DataSource() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
+    // Smoke test playback from a DataSourceCallback.
+    public void testPlaybackFromADataSourceCallback() throws Exception {
         final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
         final int duration = 10000;
 
@@ -2116,22 +2280,22 @@
             return;
         }
 
-        TestMedia2DataSource dataSource =
-                TestMedia2DataSource.fromAssetFd(mResources.openRawResourceFd(resid));
+        TestDataSourceCallback dataSource =
+                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
         // Test returning -1 from getSize() to indicate unknown size.
         dataSource.returnFromGetSize(-1);
-        mPlayer.setDataSource(new DataSourceDesc.Builder()
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
                 .setDataSource(dataSource)
                 .build());
         playLoadedVideo(null, null, -1);
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // Test pause and restart.
         mPlayer.pause();
         Thread.sleep(SLEEP_TIME);
-        assertFalse(mPlayer.isPlaying());
+        assertFalse(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -2146,20 +2310,20 @@
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // Test reset.
         mPlayer.reset();
-        mPlayer.setDataSource(new DataSourceDesc.Builder()
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
                 .setDataSource(dataSource)
                 .build());
 
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mOnPrepareCalled.reset();
         mPlayer.prepare();
@@ -2168,21 +2332,18 @@
         mOnPlayCalled.reset();
         mPlayer.play();
         mOnPlayCalled.waitForSignal();
-        assertTrue(mPlayer.isPlaying());
+        assertTrue(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
         // Test seek. Note: the seek position is cached and returned as the
         // current position so there's no point in comparing them.
         mPlayer.seekTo(duration - SLEEP_TIME, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-        while (mPlayer.isPlaying()) {
+        while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
             Thread.sleep(SLEEP_TIME);
         }
     }
 
-    public void testNullMedia2DataSourceIsRejected() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+    public void testNullDataSourceCallbackIsRejected() throws Exception {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
@@ -2191,19 +2352,16 @@
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
         mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-        mPlayer.setDataSource((DataSourceDesc)null);
+        mPlayer.setDataSource((DataSourceDesc) null);
         mOnPlayCalled.waitForSignal();
         assertTrue(mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR);
     }
 
-    public void testMedia2DataSourceIsClosedOnReset() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+    public void testDataSourceCallbackIsClosedOnReset() throws Exception {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
@@ -2212,10 +2370,10 @@
                 }
             }
         };
-        mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+        mPlayer.registerEventCallback(mExecutor, ecb);
 
-        TestMedia2DataSource dataSource = new TestMedia2DataSource(new byte[0]);
-        mPlayer.setDataSource(new DataSourceDesc.Builder()
+        TestDataSourceCallback dataSource = new TestDataSourceCallback(new byte[0]);
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
                 .setDataSource(dataSource)
                 .build());
         mOnPlayCalled.waitForSignal();
@@ -2223,23 +2381,20 @@
         assertTrue(dataSource.isClosed());
     }
 
-    public void testPlaybackFailsIfMedia2DataSourceThrows() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
+    public void testPlaybackFailsIfDataSourceCallbackThrows() throws Exception {
         final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
         if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
             return;
         }
 
         setOnErrorListener();
-        TestMedia2DataSource dataSource =
-                TestMedia2DataSource.fromAssetFd(mResources.openRawResourceFd(resid));
-        mPlayer.setDataSource(new DataSourceDesc.Builder()
+        TestDataSourceCallback dataSource =
+                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
                 .setDataSource(dataSource)
                 .build());
 
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -2260,23 +2415,20 @@
         assertTrue(mOnErrorCalled.waitForSignal());
     }
 
-    public void testPlaybackFailsIfMedia2DataSourceReturnsAnError() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
+    public void testPlaybackFailsIfDataSourceCallbackReturnsAnError() throws Exception {
         final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
         if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
             return;
         }
 
-        TestMedia2DataSource dataSource =
-                TestMedia2DataSource.fromAssetFd(mResources.openRawResourceFd(resid));
-        mPlayer.setDataSource(new DataSourceDesc.Builder()
+        TestDataSourceCallback dataSource =
+                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
                 .setDataSource(dataSource)
                 .build());
 
         setOnErrorListener();
-        MediaPlayer2.MediaPlayer2EventCallback ecb = new MediaPlayer2.MediaPlayer2EventCallback() {
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -2296,4 +2448,458 @@
         mPlayer.play();
         assertTrue(mOnErrorCalled.waitForSignal());
     }
+
+    public void testClose() throws Exception {
+        assertTrue(loadResource(R.raw.testmp3_2));
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                .build();
+        mPlayer.setAudioAttributes(attributes);
+        mPlayer.prepare();
+        mPlayer.play();
+        mPlayer.close();
+        mExecutor.shutdown();
+
+        // Tests whether the notification from the player after the close() doesn't crash.
+        Thread.sleep(SLEEP_TIME);
+    }
+
+    public void testPause() throws Exception {
+        if (!checkLoadResource(
+                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz)) {
+            return; // skip
+        }
+
+        Monitor onStartCalled = new Monitor();
+        Monitor mOnVideoSizeChangedCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, Size size) {
+                if (size.getWidth() > 0 && size.getHeight() > 0) {
+                    mOnVideoSizeChangedCalled.signal();
+                }
+            }
+
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_START) {
+                        Log.i(LOG_TAG, "testPause: MEDIA_INFO_DATA_SOURCE_START");
+                        onStartCalled.signal();
+                }
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mPlayer.setDisplay(mActivity.getSurfaceHolder());
+
+        mPlayer.prepare();
+
+        mPlayer.pause();
+
+        onStartCalled.waitForSignal();
+        mOnVideoSizeChangedCalled.waitForSignal();
+        Thread.sleep(1000);
+
+        assertFalse("MediaPlayer2 should not be playing",
+                mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
+        assertTrue("MediaPlayer2 should be paused",
+                mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PAUSED);
+        mPlayer.reset();
+    }
+
+    public void testConsecutiveSeeks() throws Exception {
+        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+        final TestDataSourceCallback source =
+                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
+        final Monitor readAllowed = new Monitor();
+        DataSourceCallback dataSource = new DataSourceCallback() {
+            @Override
+            public int readAt(long position, byte[] buffer, int offset, int size)
+                    throws IOException {
+                try {
+                    readAllowed.waitForSignal();
+                } catch (InterruptedException e) {
+                    fail();
+                }
+                return source.readAt(position, buffer, offset, size);
+            }
+
+            @Override
+            public long getSize() throws IOException {
+                return source.getSize();
+            }
+
+            @Override
+            public void close() throws IOException {
+                source.close();
+            }
+        };
+        final Monitor labelReached = new Monitor();
+        final Monitor onSeekToCompleted = new Monitor();
+        final ArrayList<Pair<Integer, Integer>> commandsCompleted = new ArrayList<>();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onCallCompleted(
+                    MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                commandsCompleted.add(new Pair<>(what, status));
+                if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
+                    onSeekToCompleted.signal();
+                }
+            }
+
+            @Override
+            public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                mOnErrorCalled.signal();
+            }
+
+            @Override
+            public void onCommandLabelReached(MediaPlayer2 mp, @NonNull Object label) {
+                labelReached.signal();
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mOnErrorCalled.reset();
+
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
+                .setDataSource(dataSource)
+                .build());
+
+        // prepare() will be pending until readAllowed is signaled.
+        mPlayer.prepare();
+
+        mPlayer.seekTo(3000);
+        mPlayer.seekTo(2000);
+        mPlayer.seekTo(1000);
+        mPlayer.notifyWhenCommandLabelReached(new Object());
+
+        readAllowed.signal();
+        labelReached.waitForSignal();
+
+        assertFalse(mOnErrorCalled.isSignalled());
+        assertEquals(5, commandsCompleted.size());
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE,
+                        MediaPlayer2.CALL_STATUS_NO_ERROR),
+                commandsCompleted.get(0));
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_PREPARE,
+                        MediaPlayer2.CALL_STATUS_NO_ERROR),
+                commandsCompleted.get(1));
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
+                        MediaPlayer2.CALL_STATUS_SKIPPED),
+                commandsCompleted.get(2));
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
+                        MediaPlayer2.CALL_STATUS_SKIPPED),
+                commandsCompleted.get(3));
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
+                        MediaPlayer2.CALL_STATUS_NO_ERROR),
+                commandsCompleted.get(4));
+
+        commandsCompleted.clear();
+        onSeekToCompleted.reset();
+        mPlayer.seekTo(3000);
+        onSeekToCompleted.waitForSignal();
+        onSeekToCompleted.reset();
+        mPlayer.seekTo(3000);
+        onSeekToCompleted.waitForSignal();
+
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
+                        MediaPlayer2.CALL_STATUS_NO_ERROR),
+                commandsCompleted.get(0));
+        assertEquals(
+                new Pair<>(MediaPlayer2.CALL_COMPLETED_SEEK_TO,
+                        MediaPlayer2.CALL_STATUS_SKIPPED),
+                commandsCompleted.get(1));
+
+        mPlayer.reset();
+    }
+
+    public void testStartEndPositions() throws Exception {
+        long startPosMs = 3000;
+        long endPosMs = 7000;
+        AssetFileDescriptor afd = mResources.openRawResourceFd(
+                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz);
+        mPlayer.setDataSource(new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                    afd.getStartOffset(), afd.getLength())
+                .setStartPosition(startPosMs)
+                .setEndPosition(endPosMs)
+                .build());
+        afd.close();
+        mPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mPlayer.setScreenOnWhilePlaying(true);
+
+        final Monitor videoRenderingStarted = new Monitor();
+        final Monitor repeatCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START) {
+                    videoRenderingStarted.signal();
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
+                    mOnCompletionCalled.signal();
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_REPEAT) {
+                    repeatCalled.signal();
+                }
+            }
+
+            @Override
+            public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                mOnErrorCalled.signal();
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mOnErrorCalled.reset();
+
+        // prepare() will be pending until readAllowed is signaled.
+        mPlayer.prepare();
+        mPlayer.play();
+
+        videoRenderingStarted.waitForSignal();
+        long startRenderingTime = mPlayer.getCurrentPosition();
+        Log.i(LOG_TAG, "testStartEndPositions: start rendering time " + startRenderingTime
+                + " vs requested " + startPosMs);
+        assertEquals(startPosMs, startRenderingTime, 250);  // 250 ms tolerance
+
+        mOnCompletionCalled.waitForSignal();
+        long endRenderingTime = mPlayer.getCurrentPosition();
+        Log.i(LOG_TAG, "testStartEndPositions: end rendering time " + endRenderingTime
+                + " vs requested " + endPosMs);
+        assertEquals(endPosMs, endRenderingTime, 250);  // 250 ms tolerance
+
+        assertFalse(mOnErrorCalled.isSignalled());
+
+        videoRenderingStarted.reset();
+        mOnCompletionCalled.reset();
+        mPlayer.play();
+
+        videoRenderingStarted.waitForSignal();
+        videoRenderingStarted.reset();
+        startRenderingTime = mPlayer.getCurrentPosition();
+        Log.i(LOG_TAG, "testStartEndPositions: replay start rendering time " + startRenderingTime
+                + " vs requested " + startPosMs);
+        assertEquals(startPosMs, startRenderingTime, 250);  // 250 ms tolerance
+
+        repeatCalled.reset();
+        mPlayer.loopCurrent(true);
+        repeatCalled.waitForSignal();
+        videoRenderingStarted.waitForSignal();
+        startRenderingTime = mPlayer.getCurrentPosition();
+        Log.i(LOG_TAG, "testStartEndPositions: looping start rendering time " + startRenderingTime
+                + " vs requested " + startPosMs);
+        assertEquals(startPosMs, startRenderingTime, 250);  // 250 ms tolerance
+
+        Thread.sleep(1000);
+        mPlayer.reset();
+    }
+
+    public void testCancelPendingCommands() throws Exception {
+        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+
+        final Monitor readRequested = new Monitor();
+        final Monitor readAllowed = new Monitor();
+        DataSourceCallback dataSource = new DataSourceCallback() {
+            TestDataSourceCallback mTestSource =
+                TestDataSourceCallback.fromAssetFd(mResources.openRawResourceFd(resid));
+            @Override
+            public int readAt(long position, byte[] buffer, int offset, int size)
+                    throws IOException {
+                try {
+                    readRequested.signal();
+                    readAllowed.waitForSignal();
+                } catch (InterruptedException e) {
+                    fail();
+                }
+                return mTestSource.readAt(position, buffer, offset, size);
+            }
+
+            @Override
+            public long getSize() throws IOException {
+                return mTestSource.getSize();
+            }
+
+            @Override
+            public void close() throws IOException {
+                mTestSource.close();
+            }
+        };
+
+        final ArrayList<Integer> commandsCompleted = new ArrayList<>();
+        setOnErrorListener();
+        final Monitor labelReached = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                    mOnPrepareCalled.signal();
+                }
+            }
+
+            @Override
+            public void onCallCompleted(
+                    MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                commandsCompleted.add(what);
+            }
+
+            @Override
+            public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                mOnErrorCalled.signal();
+            }
+
+            @Override
+            public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
+                labelReached.signal();
+            }
+        };
+        synchronized (mEventCbLock) {
+            mEventCallbacks.add(ecb);
+        }
+
+        mOnPrepareCalled.reset();
+        mOnErrorCalled.reset();
+
+        mPlayer.setDataSource(new CallbackDataSourceDesc.Builder()
+                .setDataSource(dataSource)
+                .build());
+
+
+        // prepare() will be pending until readAllowed is signaled.
+        mPlayer.prepare();
+
+        Object playToken = mPlayer.play();
+        Object seekToken = mPlayer.seekTo(1000);
+        mPlayer.pause();
+
+        readRequested.waitForSignal();
+
+        // Cancel the pending commands while preparation is on hold.
+        boolean canceled = mPlayer.cancelCommand(playToken);
+        assertTrue("play command should be in pending queue", canceled);
+        canceled = mPlayer.cancelCommand(seekToken);
+        assertTrue("seekTo command should be in pending queue", canceled);
+
+        Object dummy = new Object();
+        canceled = mPlayer.cancelCommand(dummy);
+        assertFalse("dummy command should not be in pending queue", canceled);
+
+        // Make the on-going prepare operation fail and check the results.
+        readAllowed.signal();
+        mPlayer.notifyWhenCommandLabelReached(new Object());
+        labelReached.waitForSignal();
+
+        assertEquals(3, commandsCompleted.size());
+        assertEquals(MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE, (int) commandsCompleted.get(0));
+        assertEquals(MediaPlayer2.CALL_COMPLETED_PREPARE, (int) commandsCompleted.get(1));
+        assertEquals(MediaPlayer2.CALL_COMPLETED_PAUSE, (int) commandsCompleted.get(2));
+        assertEquals(0, mOnErrorCalled.getNumSignal());
+    }
+
+    public void testHandleFileDataSourceDesc() throws Exception {
+        final int resid1 = R.raw.testmp3;
+        final int resid2 = R.raw.testmp3_4;
+
+        MediaPlayer2 mp = new MediaPlayer2(mContext);
+
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                        "MediaPlayer2Test");
+        mp.setWakeLock(wakeLock);
+
+        Monitor onPrepareCalled = new Monitor();
+        Monitor onSetDataSourceCalled = new Monitor();
+        Monitor onDataSourceListEndCalled = new Monitor();
+        Monitor onSetNextDataSourceCalled = new Monitor();
+        Monitor onClearNextDataSourcesCalled = new Monitor();
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                    onPrepareCalled.signal();
+                } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_LIST_END) {
+                    onDataSourceListEndCalled.signal();
+                }
+            }
+
+            @Override
+            public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                if (what == MediaPlayer2.CALL_COMPLETED_SET_DATA_SOURCE) {
+                    onSetDataSourceCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES) {
+                    onClearNextDataSourcesCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_SET_NEXT_DATA_SOURCE) {
+                    onSetNextDataSourceCalled.signal();
+                }
+            }
+        };
+        mp.registerEventCallback(mExecutor, ecb);
+
+        FileDataSourceDesc fdsd1 = openFile(resid1);
+        FileDataSourceDesc fdsd2 = openFile(resid2);
+
+        mp.setDataSource(fdsd1);
+        mp.setNextDataSource(fdsd2);
+        mp.prepare();
+        onPrepareCalled.waitForSignal();
+        mp.reset();
+        assertTrue(isFileDataSourceDescClosed(fdsd1));
+        assertTrue(isFileDataSourceDescClosed(fdsd2));
+
+        fdsd1 = openFile(resid1);
+        fdsd2 = openFile(resid2);
+        mp.setDataSource(fdsd1);
+        mp.setNextDataSource(fdsd2);
+        onClearNextDataSourcesCalled.reset();
+        mp.clearNextDataSources();
+        onClearNextDataSourcesCalled.waitForSignal();
+        assertTrue(isFileDataSourceDescClosed(fdsd2));
+
+        fdsd2 = openFile(resid2);
+        onSetNextDataSourceCalled.reset();
+        mp.setNextDataSource(fdsd2);
+        onSetNextDataSourceCalled.waitForSignal();
+        onSetNextDataSourceCalled.reset();
+        mp.setNextDataSource(fdsd2);
+        onSetNextDataSourceCalled.waitForSignal();
+        assertFalse(isFileDataSourceDescClosed(fdsd2));
+
+        onDataSourceListEndCalled.reset();
+        mp.prepare();
+        mp.play();
+        onDataSourceListEndCalled.waitForSignal();
+        assertTrue(isFileDataSourceDescClosed(fdsd1));
+
+        mp.close();
+    }
+
+    private FileDataSourceDesc openFile(int resid) throws Exception {
+        AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
+        FileDataSourceDesc fdsd = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                    afd.getStartOffset(), afd.getLength())
+                .build();
+        afd.close();
+        return fdsd;
+    }
+
+    private boolean isFileDataSourceDescClosed(FileDataSourceDesc fdsd) {
+        try {
+            fdsd.getParcelFileDescriptor().getFd();
+        } catch (IllegalStateException e) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
index 2975d47..07ea32f 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayer2TestBase.java
@@ -22,15 +22,20 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.DataSourceDesc;
+import android.media.FileDataSourceDesc;
+import android.media.UriDataSourceDesc;
 import android.media.MediaPlayer2;
 import android.media.MediaTimestamp;
+import android.media.SubtitleData;
 import android.media.TimedMetaData;
 import android.media.TimedText;
 import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.SurfaceHolder;
+import android.util.Size;
 
 import com.android.compatibility.common.util.MediaUtils;
 
@@ -53,7 +58,6 @@
     protected static final int SLEEP_TIME = 1000;
     protected static final int LONG_SLEEP_TIME = 6000;
     protected static final int STREAM_RETRIES = 20;
-    protected static boolean sUseScaleToFitMode = false;
 
     protected Monitor mOnVideoSizeChangedCalled = new Monitor();
     protected Monitor mOnVideoRenderingStartCalled = new Monitor();
@@ -77,11 +81,11 @@
     protected MediaStubActivity mActivity;
 
     protected final Object mEventCbLock = new Object();
-    protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks =
-            new ArrayList<MediaPlayer2.MediaPlayer2EventCallback>();
+    protected List<MediaPlayer2.EventCallback> mEventCallbacks =
+            new ArrayList<MediaPlayer2.EventCallback>();
     protected final Object mEventCbLock2 = new Object();
-    protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks2 =
-            new ArrayList<MediaPlayer2.MediaPlayer2EventCallback>();
+    protected List<MediaPlayer2.EventCallback> mEventCallbacks2 =
+            new ArrayList<MediaPlayer2.EventCallback>();
 
     // convenience functions to create MediaPlayer2
     protected static MediaPlayer2 createMediaPlayer2(Context context, Uri uri) {
@@ -98,12 +102,12 @@
     protected static MediaPlayer2 createMediaPlayer2(Context context, Uri uri, SurfaceHolder holder,
             AudioAttributes audioAttributes, int audioSessionId) {
         try {
-            MediaPlayer2 mp = MediaPlayer2.create();
+            MediaPlayer2 mp = new MediaPlayer2(context);
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                     new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
             mp.setAudioSessionId(audioSessionId);
-            mp.setDataSource(new DataSourceDesc.Builder()
+            mp.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(context, uri)
                     .build());
             if (holder != null) {
@@ -111,8 +115,8 @@
             }
             Monitor onPrepareCalled = new Monitor();
             ExecutorService executor = Executors.newFixedThreadPool(1);
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -120,10 +124,10 @@
                         }
                     }
                 };
-            mp.setMediaPlayer2EventCallback(executor, ecb);
+            mp.registerEventCallback(executor, ecb);
             mp.prepare();
             onPrepareCalled.waitForSignal();
-            mp.clearMediaPlayer2EventCallback();
+            mp.unregisterEventCallback(ecb);
             executor.shutdown();
             return mp;
         } catch (IllegalArgumentException ex) {
@@ -153,21 +157,23 @@
                 return null;
             }
 
-            MediaPlayer2 mp = MediaPlayer2.create();
+            MediaPlayer2 mp = new MediaPlayer2(context);
 
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                     new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
             mp.setAudioSessionId(audioSessionId);
 
-            mp.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
+            mp.setDataSource(new FileDataSourceDesc.Builder()
+                    .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                        afd.getStartOffset(), afd.getLength())
                     .build());
+            afd.close();
 
             Monitor onPrepareCalled = new Monitor();
             ExecutorService executor = Executors.newFixedThreadPool(1);
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -175,11 +181,10 @@
                         }
                     }
                 };
-            mp.setMediaPlayer2EventCallback(executor, ecb);
+            mp.registerEventCallback(executor, ecb);
             mp.prepare();
             onPrepareCalled.waitForSignal();
-            mp.clearMediaPlayer2EventCallback();
-            afd.close();
+            mp.unregisterEventCallback(ecb);
             executor.shutdown();
             return mp;
         } catch (IOException ex) {
@@ -207,18 +212,18 @@
         super.setUp();
         mActivity = getActivity();
         getInstrumentation().waitForIdleSync();
+        mContext = getInstrumentation().getTargetContext();
         try {
             runTestOnUiThread(new Runnable() {
                 public void run() {
-                    mPlayer = MediaPlayer2.create();
-                    mPlayer2 = MediaPlayer2.create();
+                    mPlayer = new MediaPlayer2(mContext);
+                    mPlayer2 = new MediaPlayer2(mContext);
                 }
             });
         } catch (Throwable e) {
             e.printStackTrace();
             fail();
         }
-        mContext = getInstrumentation().getTargetContext();
         mResources = mContext.getResources();
         mExecutor = Executors.newFixedThreadPool(1);
 
@@ -242,13 +247,13 @@
     }
 
     protected void setUpMP2ECb(MediaPlayer2 mp, Object cbLock,
-            List<MediaPlayer2.MediaPlayer2EventCallback> ecbs) {
-        mp.setMediaPlayer2EventCallback(mExecutor, new MediaPlayer2.MediaPlayer2EventCallback() {
+            List<MediaPlayer2.EventCallback> ecbs) {
+        mp.registerEventCallback(mExecutor, new MediaPlayer2.EventCallback() {
             @Override
-            public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) {
+            public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, Size size) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
-                        ecb.onVideoSizeChanged(mp, dsd, w, h);
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
+                        ecb.onVideoSizeChanged(mp, dsd, size);
                     }
                 }
             }
@@ -256,7 +261,7 @@
             @Override
             public void onTimedText(MediaPlayer2 mp, DataSourceDesc dsd, TimedText text) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onTimedText(mp, dsd, text);
                     }
                 }
@@ -266,7 +271,7 @@
             public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd,
                     TimedMetaData data) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onTimedMetaDataAvailable(mp, dsd, data);
                     }
                 }
@@ -275,7 +280,7 @@
             @Override
             public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onError(mp, dsd, what, extra);
                     }
                 }
@@ -284,7 +289,7 @@
             @Override
             public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onInfo(mp, dsd, what, extra);
                     }
                 }
@@ -293,18 +298,18 @@
             @Override
             public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onCallCompleted(mp, dsd, what, status);
                     }
                 }
             }
 
             @Override
-            public void onMediaTimeChanged(MediaPlayer2 mp, DataSourceDesc dsd,
+            public void onMediaTimeDiscontinuity(MediaPlayer2 mp, DataSourceDesc dsd,
                     MediaTimestamp timestamp) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
-                        ecb.onMediaTimeChanged(mp, dsd, timestamp);
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
+                        ecb.onMediaTimeDiscontinuity(mp, dsd, timestamp);
                     }
                 }
             }
@@ -312,11 +317,20 @@
             @Override
             public void onCommandLabelReached(MediaPlayer2 mp, Object label) {
                 synchronized (cbLock) {
-                    for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
                         ecb.onCommandLabelReached(mp, label);
                     }
                 }
             }
+
+            @Override
+            public void onSubtitleData(MediaPlayer2 mp, DataSourceDesc dsd, SubtitleData data) {
+                synchronized (cbLock) {
+                    for (MediaPlayer2.EventCallback ecb : ecbs) {
+                        ecb.onSubtitleData(mp, dsd, data);
+                    }
+                }
+            }
         });
     }
 
@@ -328,22 +342,13 @@
 
         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
         try {
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
+            mPlayer.setDataSource(new FileDataSourceDesc.Builder()
+                    .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                        afd.getStartOffset(), afd.getLength())
                     .build());
-
-            // Although it is only meant for video playback, it should not
-            // cause issues for audio-only playback.
-            int videoScalingMode = sUseScaleToFitMode?
-                                    MediaPlayer2.VIDEO_SCALING_MODE_SCALE_TO_FIT
-                                  : MediaPlayer2.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING;
-
-            mPlayer.setVideoScalingMode(videoScalingMode);
+            afd.close();
         } finally {
-            // TODO: close afd only after setDataSource is confirmed.
-            // afd.close();
         }
-        sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
         return true;
     }
 
@@ -353,25 +358,18 @@
         }
 
         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
-        return new DataSourceDesc.Builder()
-                .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength())
+        FileDataSourceDesc fdsd = new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                        afd.getStartOffset(), afd.getLength())
                 .build();
+        afd.close();
+        return fdsd;
     }
 
     protected boolean checkLoadResource(int resid) throws Exception {
         return MediaUtils.check(loadResource(resid), "no decoder found");
     }
 
-    protected void loadSubtitleSource(int resid) throws Exception {
-        AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
-        try {
-            mPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                      afd.getLength(), MediaPlayer2.MEDIA_MIMETYPE_TEXT_SUBRIP);
-        } finally {
-            afd.close();
-        }
-    }
-
     protected void playLiveVideoTest(String path, int playTime) throws Exception {
         playVideoWithRetries(path, null, null, playTime);
     }
@@ -390,7 +388,7 @@
         final Uri uri = Uri.parse(path);
         for (int i = 0; i < STREAM_RETRIES; i++) {
           try {
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
+            mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
             playLoadedVideo(width, height, playTime);
@@ -424,7 +422,7 @@
         boolean playedSuccessfully = false;
         for (int i = 0; i < STREAM_RETRIES; i++) {
             try {
-                mPlayer.setDataSource(new DataSourceDesc.Builder()
+                mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                         .setDataSource(getInstrumentation().getTargetContext(),
                             uri, headers, cookies)
                         .build());
@@ -462,20 +460,20 @@
         mPlayer.setScreenOnWhilePlaying(true);
 
         synchronized (mEventCbLock) {
-            mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() {
+            mEventCallbacks.add(new MediaPlayer2.EventCallback() {
                 @Override
-                public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) {
-                    if (w == 0 && h == 0) {
+                public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, Size size) {
+                    if (size.getWidth() == 0 && size.getHeight() == 0) {
                         // A size of 0x0 can be sent initially one time when using NuPlayer.
                         assertFalse(mOnVideoSizeChangedCalled.isSignalled());
                         return;
                     }
                     mOnVideoSizeChangedCalled.signal();
                     if (width != null) {
-                        assertEquals(width.intValue(), w);
+                        assertEquals(width.intValue(), size.getWidth());
                     }
                     if (height != null) {
-                        assertEquals(height.intValue(), h);
+                        assertEquals(height.intValue(), size.getHeight());
                     }
                 }
 
@@ -524,7 +522,7 @@
         if (playTime == -1) {
             return;
         } else if (playTime == 0) {
-            while (mPlayer.isPlaying()) {
+            while (mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING) {
                 Thread.sleep(SLEEP_TIME);
             }
         } else {
@@ -568,7 +566,7 @@
             }
         }
 
-        mPlayer.stop();
+        mPlayer.pause();
     }
 
     private static class PrepareFailedException extends Exception {}
@@ -585,7 +583,7 @@
 
     protected void setOnErrorListener() {
         synchronized (mEventCbLock) {
-            mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() {
+            mEventCallbacks.add(new MediaPlayer2.EventCallback() {
                 @Override
                 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                     mOnErrorCalled.signal();
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
index c88f34d..7d2030e 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
@@ -45,8 +45,6 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
-import android.support.test.filters.SmallTest;
-import android.platform.test.annotations.RequiresDevice;
 import android.util.Log;
 
 import com.android.compatibility.common.util.MediaUtils;
@@ -70,8 +68,6 @@
  * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
  */
-@SmallTest
-@RequiresDevice
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class MediaPlayerDrmTest extends MediaPlayerDrmTestBase {
 
@@ -133,8 +129,6 @@
 
     // Tests
 
-    @SmallTest
-    @RequiresDevice
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V0_SYNC() throws Exception {
         download(CENC_AUDIO_URL,
                 CENC_AUDIO_URL_DOWNLOADED,
@@ -142,8 +136,6 @@
                 ModularDrmTestType.V0_SYNC_TEST);
     }
 
-    @SmallTest
-    @RequiresDevice
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V1_ASYNC() throws Exception {
         download(CENC_AUDIO_URL,
                 CENC_AUDIO_URL_DOWNLOADED,
@@ -151,8 +143,6 @@
                 ModularDrmTestType.V1_ASYNC_TEST);
     }
 
-    @SmallTest
-    @RequiresDevice
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V2_SYNC_CONFIG() throws Exception {
         download(CENC_AUDIO_URL,
                 CENC_AUDIO_URL_DOWNLOADED,
@@ -160,8 +150,6 @@
                 ModularDrmTestType.V2_SYNC_CONFIG_TEST);
     }
 
-    @SmallTest
-    @RequiresDevice
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V3_ASYNC_DRMPREPARED() throws Exception {
         download(CENC_AUDIO_URL,
                 CENC_AUDIO_URL_DOWNLOADED,
@@ -169,8 +157,6 @@
                 ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
     }
 
-    @SmallTest
-    @RequiresDevice
     public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V5_ASYNC_WITH_HANDLER() throws Exception {
         download(CENC_AUDIO_URL,
                 CENC_AUDIO_URL_DOWNLOADED,
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 91bc3ee..74d406b 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -264,11 +264,23 @@
         }
     }
 
-    public void testPlayAudio() throws Exception {
-        final int resid = R.raw.testmp3_2;
-        final int mp3Duration = 34909;
-        final int tolerance = 70;
-        final int seekDuration = 100;
+    public void testPlayAudioMp3() throws Exception {
+        testPlayAudio(R.raw.testmp3_2,
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    public void testPlayAudioOpus() throws Exception {
+        testPlayAudio(R.raw.testopus,
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    public void testPlayAudioAmr() throws Exception {
+        testPlayAudio(R.raw.testamr,
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    public void testPlayAudio(int resid,
+            int mp3Duration, int tolerance, int seekDuration) throws Exception {
 
         MediaPlayer mp = MediaPlayer.create(mContext, resid);
         try {
@@ -1333,7 +1345,7 @@
         assertEquals("MediaPlayer had error in clockRate " + ts1.getMediaClockRate(),
                 playbackRate, ts1.getMediaClockRate(), 0.001f);
         assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
-                nt1 <= ts1.getAnchorSytemNanoTime() && ts1.getAnchorSytemNanoTime() <= nt2);
+                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
 
         mMediaPlayer.pause();
         ts1 = mMediaPlayer.getTimestamp();
@@ -1500,7 +1512,7 @@
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
@@ -1512,7 +1524,7 @@
     public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
@@ -1536,7 +1548,7 @@
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
@@ -1548,7 +1560,7 @@
     public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
@@ -1572,7 +1584,7 @@
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
@@ -1584,7 +1596,7 @@
     public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
@@ -1608,7 +1620,7 @@
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
             throws Exception {
         playVideoTest(
-                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
+                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_22050hz, 176, 144);
     }
 
     public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
diff --git a/tests/tests/media/src/android/media/cts/MediaRandomTest.java b/tests/tests/media/src/android/media/cts/MediaRandomTest.java
index e016730..3db1d13 100644
--- a/tests/tests/media/src/android/media/cts/MediaRandomTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRandomTest.java
@@ -15,18 +15,15 @@
  */
 package android.media.cts;
 
-
-import android.media.cts.R;
-
-import android.media.MediaRecorder;
-import android.media.MediaPlayer;
-import android.platform.test.annotations.AppModeFull;
-import android.view.SurfaceHolder;
-import android.test.ActivityInstrumentationTestCase2;
-import android.os.Environment;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.media.MediaRecorder;
+import android.media.MediaPlayer;
+import android.os.Environment;
+import android.platform.test.annotations.AppModeFull;
+import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
+import android.view.SurfaceHolder;
 
 import java.util.Random;
 
@@ -41,6 +38,7 @@
  * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
  */
+@MediaHeavyPresubmitTest
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class MediaRandomTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
     private static final String TAG = "MediaRandomTest";
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index f1ad46c..042881c 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -675,6 +675,7 @@
         assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max);
         assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max);
         assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_PERFORMANCE <= max);
     }
 
     public void testRecorderAudio() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java b/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
index 52da641..3eb1ab4 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
@@ -18,8 +18,6 @@
 
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.Uri;
-import android.os.Bundle;
 import android.os.Environment;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
@@ -27,8 +25,6 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class MediaScannerNotificationTest extends AndroidTestCase {
@@ -50,13 +46,7 @@
         String [] temps = new String[] { "avi", "gif", "jpg", "dat", "mp3", "mp4", "txt" };
         String tmpPath = createTempFiles(temps);
 
-        Bundle args = new Bundle();
-        args.putString("volume", "external");
-        Intent i = new Intent("android.media.IMediaScannerService").putExtras(args);
-        i.setClassName("com.android.providers.media",
-                "com.android.providers.media.MediaScannerService");
-        mContext.startService(i);
-
+        MediaScannerTest.startMediaScan();
         startedReceiver.waitForBroadcast();
         finishedReceiver.waitForBroadcast();
 
@@ -71,7 +61,7 @@
         }
         startedReceiver.reset();
         finishedReceiver.reset();
-        mContext.startService(i);
+        MediaScannerTest.startMediaScan();
         startedReceiver.waitForBroadcast();
         finishedReceiver.waitForBroadcast();
 
@@ -82,7 +72,7 @@
         // scan one more time just to clean everything up nicely
         startedReceiver.reset();
         finishedReceiver.reset();
-        mContext.startService(i);
+        MediaScannerTest.startMediaScan();
         startedReceiver.waitForBroadcast();
         finishedReceiver.waitForBroadcast();
 
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 16034e4..1b0d6fc 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -16,12 +16,9 @@
 
 package android.media.cts;
 
-import android.media.cts.R;
-
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -30,17 +27,16 @@
 import android.media.MediaMetadataRetriever;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.mtp.MtpConstants;
 import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.filters.SmallTest;
-import android.platform.test.annotations.RequiresDevice;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
 import android.provider.MediaStore;
+import android.support.test.InstrumentationRegistry;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.compatibility.common.util.FileCopyHelper;
@@ -154,30 +150,6 @@
         c.moveToFirst();
         assertEquals(localizedTitle, c.getString(0));
 
-        // Update localizable audio file to have unlocalizable title
-        final ContentValues values = new ContentValues();
-        final String newTitle = "New Title";
-        values.put("title", newTitle);
-        res.update(media2Uri, values, null, null);
-
-        // Ensure title comes back correctly
-        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(newTitle, c.getString(0));
-
-        // Update audio file to have localizable title once again
-        final String newLocalizableTitle =
-            "android.resource://android.media.cts/string/test_localizable_title";
-        values.put("title", newLocalizableTitle);
-        res.update(media2Uri, values, null, null);
-
-        // Ensure title comes back localized
-        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(localizedTitle, c.getString(0));
-
         mMediaScannerConnection.disconnect();
         c.close();
     }
@@ -210,12 +182,7 @@
         // add nomedia file and insert into database, file should no longer be in audio view
         File nomedia = new File(mMediaFile.getParent() + "/.nomedia");
         nomedia.createNewFile();
-        ContentValues values = new ContentValues();
-        values.put(MediaStore.MediaColumns.DATA, nomedia.getAbsolutePath());
-        values.put(MediaStore.Files.FileColumns.FORMAT, MtpConstants.FORMAT_UNDEFINED);
-        Uri nomediauri = res.insert(MediaStore.Files.getContentUri("external"), values);
-        // clean up nomedia file
-        nomedia.delete();
+        startMediaScanAndWait();
 
         // entry should not be in audio view anymore
         c = res.query(audiouri, null, null, null, null);
@@ -223,6 +190,7 @@
         c.close();
 
         // with nomedia file removed, do media scan and check that entry is in audio table again
+        nomedia.delete();
         startMediaScanAndWait();
 
         // Give the 2nd stage scan that makes the unhidden files visible again
@@ -403,6 +371,10 @@
     }
 
     private void canonicalizeTest(int resId) throws Exception {
+        // erase all audio files that might confuse us below
+        mContext.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                null, null);
+
         // write file and scan to insert into database
         String fileDir = Environment.getExternalStorageDirectory() + "/"
                 + getClass().getCanonicalName() + "/canonicaltest-" + System.currentTimeMillis();
@@ -643,6 +615,14 @@
         }
     }
 
+    static void startMediaScan() {
+        // Ugh, the best proxy we have is pretending that it was just mounted
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_VOLUME "
+                        + "--receiver-include-background -d "
+                        + Uri.fromFile(Environment.getExternalStorageDirectory()));
+    }
+
     private void startMediaScanAndWait() throws InterruptedException {
         ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver(
                 Intent.ACTION_MEDIA_SCANNER_FINISHED);
@@ -650,12 +630,7 @@
         finishedIntentFilter.addDataScheme("file");
         mContext.registerReceiver(finishedReceiver, finishedIntentFilter);
 
-        Bundle args = new Bundle();
-        args.putString("volume", "external");
-        Intent i = new Intent("android.media.IMediaScannerService").putExtras(args);
-        i.setClassName("com.android.providers.media",
-                "com.android.providers.media.MediaScannerService");
-        mContext.startService(i);
+        startMediaScan();
 
         finishedReceiver.waitForBroadcast();
         mContext.unregisterReceiver(finishedReceiver);
@@ -714,6 +689,7 @@
         }
 
         public void onScanCompleted(String path, Uri uri) {
+            Log.v("MediaScannerTest", "onScanCompleted for " + path + " to " + uri);
             mediaPath = path;
             if (uri != null) {
                 mediaUri = uri;
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java b/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java
new file mode 100644
index 0000000..d8a34a0
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.media.cts;
+
+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 android.content.ComponentName;
+import android.content.Context;
+import android.media.MediaController2;
+import android.media.MediaSession2;
+import android.media.MediaSession2Service;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.CountDownTimer;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaSession2Service}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSession2ServiceTest {
+    private static final long TIMEOUT_MS = 3000L;
+
+    private static HandlerExecutor sHandlerExecutor;
+    private final List<MediaController2> mControllers = new ArrayList<>();
+    private Context mContext;
+    private Session2Token mToken;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2ServiceTest.class) {
+            if (sHandlerExecutor != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSession2ServiceTest");
+            handlerThread.start();
+            sHandlerExecutor = new HandlerExecutor(handlerThread.getLooper());
+            StubMediaSession2Service.setHandlerExecutor(sHandlerExecutor);
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandlerExecutor == null) {
+                return;
+            }
+            StubMediaSession2Service.setHandlerExecutor(null);
+            sHandlerExecutor.getLooper().quitSafely();
+            sHandlerExecutor = null;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mToken = new Session2Token(mContext,
+                new ComponentName(mContext, StubMediaSession2Service.class));
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        for (MediaController2 controller : mControllers) {
+            controller.close();
+        }
+        mControllers.clear();
+
+        StubMediaSession2Service.setTestInjector(null);
+    }
+
+    /**
+     * Tests whether {@link MediaSession2Service#onGetPrimarySession()} is called whenever a
+     * controller is created with the service's token.
+     */
+    @Test
+    public void testOnGetPrimarySession() throws InterruptedException {
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+        assertNotEquals(mToken, controller1.getConnectedSessionToken());
+        assertEquals(Session2Token.TYPE_SESSION,
+                controller1.getConnectedSessionToken().getType());
+        assertEquals(controller1.getConnectedSessionToken(),
+                controller2.getConnectedSessionToken());
+    }
+
+    /**
+     * Tests whether {@link MediaSession2Service#onGetPrimarySession()} is called whenever a
+     * controller is created with the service's token.
+     */
+    @Test
+    public void testOnGetPrimarySession_multipleSessions() throws InterruptedException {
+        final List<Session2Token> tokens = new ArrayList<>();
+        StubMediaSession2Service.setTestInjector(
+                new StubMediaSession2Service.TestInjector() {
+                    @Override
+                    MediaSession2 onGetPrimarySession() {
+                        MediaSession2 session = new MediaSession2.Builder(mContext)
+                                .setId("testOnGetPrimarySession_multipleSession"
+                                        + System.currentTimeMillis())
+                                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                                .build();
+                        tokens.add(session.getSessionToken());
+                        return session;
+                    }
+                });
+
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+
+        assertNotEquals(mToken, controller1.getConnectedSessionToken());
+        assertNotEquals(mToken, controller2.getConnectedSessionToken());
+
+        assertNotEquals(controller1.getConnectedSessionToken(),
+                controller2.getConnectedSessionToken());
+        assertEquals(2, tokens.size());
+        assertEquals(tokens.get(0), controller1.getConnectedSessionToken());
+        assertEquals(tokens.get(1), controller2.getConnectedSessionToken());
+    }
+
+    @Test
+    public void testAllControllersDisconnected_oneSession() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        StubMediaSession2Service.setTestInjector(
+                new StubMediaSession2Service.TestInjector() {
+                    @Override
+                    void onServiceDestroyed() {
+                        latch.countDown();
+                    }
+                });
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+        controller1.close();
+        controller2.close();
+
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testAllControllersDisconnected_multipleSession() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetPrimarySession() {
+                return new MediaSession2.Builder(mContext)
+                        .setId("testAllControllersDisconnected_multipleSession"
+                                + System.currentTimeMillis())
+                        .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                        .build();
+            }
+
+            @Override
+            void onServiceDestroyed() {
+                latch.countDown();
+            }
+        });
+
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+        controller1.close();
+        controller2.close();
+
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetSessions() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("testGetSessions")
+                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                .build()) {
+            service.addSession(session);
+            List<MediaSession2> sessions = service.getSessions();
+            assertTrue(sessions.contains(session));
+            assertEquals(2, sessions.size());
+
+            service.removeSession(session);
+            sessions = service.getSessions();
+            assertFalse(sessions.contains(session));
+        }
+    }
+
+    @Test
+    public void testAddSessions_removedWhenClose() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("testAddSessions_removedWhenClose")
+                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                .build()) {
+            service.addSession(session);
+            List<MediaSession2> sessions = service.getSessions();
+            assertTrue(sessions.contains(session));
+            assertEquals(2, sessions.size());
+
+            session.close();
+            sessions = service.getSessions();
+            assertFalse(sessions.contains(session));
+        }
+    }
+
+    @Test
+    public void testOnUpdateNotification() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        MediaSession2 primarySession = service.getSessions().get(0);
+        CountDownLatch latch = new CountDownLatch(2);
+
+        StubMediaSession2Service.setTestInjector(
+                new StubMediaSession2Service.TestInjector() {
+                    @Override
+                    MediaSession2Service.MediaNotification onUpdateNotification(
+                            MediaSession2 session) {
+                        assertEquals(primarySession, session);
+                        switch ((int) latch.getCount()) {
+                            case 2:
+
+                                break;
+                            case 1:
+                        }
+                        latch.countDown();
+                        return super.onUpdateNotification(session);
+                    }
+                });
+
+        primarySession.setPlaybackActive(true);
+        primarySession.setPlaybackActive(false);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private MediaController2 createConnectedController(Session2Token token)
+            throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        MediaController2 controller = new MediaController2(mContext, token, sHandlerExecutor,
+                new MediaController2.ControllerCallback() {
+                    @Override
+                    public void onConnected(MediaController2 controller,
+                            Session2CommandGroup allowedCommands) {
+                        latch.countDown();
+                        super.onConnected(controller, allowedCommands);
+                    }
+                });
+        mControllers.add(controller);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return controller;
+    }
+
+    private static class SessionCallback extends MediaSession2.SessionCallback {
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            if (controller.getUid() == Process.myUid()) {
+                return new Session2CommandGroup.Builder().build();
+            }
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
index 0f2c56b..9738f65 100644
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * 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.
@@ -16,110 +16,102 @@
 
 package android.media.cts;
 
-import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
-
 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;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
 import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
-import android.media.MediaSession2.Builder;
-import android.media.SessionCommand2;
-import android.media.MediaSession2.CommandButton;
-import android.media.SessionCommandGroup2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.VolumeProvider2;
+import android.media.Session2Command;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Process;
-import android.os.ResultReceiver;
-import android.platform.test.annotations.AppModeFull;
-import androidx.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import junit.framework.Assert;
-
-import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
- * Tests {@link MediaSession2}.
+ * Tests {@link android.media.MediaSession2}.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@Ignore
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSession2Test extends MediaSession2TestBase {
-    private static final String TAG = "MediaSession2Test";
+public class MediaSession2Test {
+    private static final long WAIT_TIME_MS = 300L;
 
-    private MediaSession2 mSession;
-    private MockPlayer mPlayer;
-    private MockPlaylistAgent mMockAgent;
+    static Handler sHandler;
+    static Executor sHandlerExecutor;
+    final static Object sTestLock = new Object();
+
+    private Context mContext;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
+            handlerThread.start();
+            sHandler = new Handler(handlerThread.getLooper());
+            sHandlerExecutor = (runnable) -> {
+                Handler handler;
+                synchronized (MediaSession2Test.class) {
+                    handler = sHandler;
+                }
+                if (handler != null) {
+                    handler.post(() -> {
+                        synchronized (sTestLock) {
+                            runnable.run();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+            sHandlerExecutor = null;
+        }
+    }
 
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
-        mPlayer = new MockPlayer(0);
-        mMockAgent = new MockPlaylistAgent();
-        mSession = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(mMockAgent)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup2 onConnect(MediaSession2 session,
-                            ControllerInfo controller) {
-                        if (Process.myUid() == controller.getUid()) {
-                            return super.onConnect(session, controller);
-                        }
-                        return null;
-                    }
-                }).build();
+        mContext = InstrumentationRegistry.getContext();
     }
 
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        mSession.close();
-    }
-
-    @Ignore
     @Test
-    public void testBuilder() {
+    public void testBuilder_setIllegalArguments() {
+        MediaSession2.Builder builder;
         try {
-            MediaSession2.Builder builder = new Builder(mContext);
-            fail("null player shouldn't be allowed");
+            builder = new MediaSession2.Builder(null);
+            fail("null context shouldn't be allowed");
         } catch (IllegalArgumentException e) {
             // expected. pass-through
         }
-        MediaSession2.Builder builder = new Builder(mContext).setPlayer(mPlayer);
         try {
+            builder = new MediaSession2.Builder(mContext);
             builder.setId(null);
             fail("null id shouldn't be allowed");
         } catch (IllegalArgumentException e) {
@@ -128,749 +120,376 @@
     }
 
     @Test
-    public void testPlayerStateChange() throws Exception {
-        final int targetState = MediaPlayerBase.PLAYER_STATE_PLAYING;
-        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
-        sHandler.postAndSync(() -> {
-            mSession.close();
-            mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                        @Override
-                        public void onPlayerStateChanged(MediaSession2 session,
-                                MediaPlayerBase player, int state) {
-                            assertEquals(targetState, state);
-                            latchForSessionCallback.countDown();
-                        }
-                    }).build();
-        });
-
-        final CountDownLatch latchForControllerCallback = new CountDownLatch(1);
-        final MediaController2 controller =
-                createController(mSession.getToken(), true, new ControllerCallback() {
-                    @Override
-                    public void onPlayerStateChanged(MediaController2 controllerOut, int state) {
-                        assertEquals(targetState, state);
-                        latchForControllerCallback.countDown();
-                    }
-                });
-
-        mPlayer.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PLAYING);
-        assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(latchForControllerCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertEquals(targetState, controller.getPlayerState());
-    }
-
-    @Test
-    public void testCurrentDataSourceChanged() throws Exception {
-        final int listSize = 5;
-        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return list;
-            }
-        };
-
-        MediaItem2 currentItem = list.get(3);
-
-        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testCurrentDataSourceChanged")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public void onCurrentMediaItemChanged(MediaSession2 session,
-                            MediaPlayerBase player, MediaItem2 itemOut) {
-                        assertSame(currentItem, itemOut);
-                        latchForSessionCallback.countDown();
-                    }
-                }).build()) {
-
-            mPlayer.notifyCurrentDataSourceChanged(currentItem.getDataSourceDesc());
-            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            // TODO (jaewan): Test that controllers are also notified. (b/74505936)
+    public void testBuilder_createSessionWithoutId() {
+        try (MediaSession2 session = new MediaSession2.Builder(mContext).build()) {
+            assertEquals("", session.getSessionId());
         }
     }
 
     @Test
-    public void testMediaPrepared() throws Exception {
-        final int listSize = 5;
-        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return list;
-            }
-        };
-
-        MediaItem2 currentItem = list.get(3);
-
-        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testMediaPrepared")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public void onMediaPrepared(MediaSession2 session, MediaPlayerBase player,
-                            MediaItem2 itemOut) {
-                        assertSame(currentItem, itemOut);
-                        latchForSessionCallback.countDown();
-                    }
-                }).build()) {
-
-            mPlayer.notifyMediaPrepared(currentItem.getDataSourceDesc());
-            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            // TODO (jaewan): Test that controllers are also notified. (b/74505936)
+    public void testBuilder_createSessionWithDupId() {
+        final String dupSessionId = "TEST_SESSION_DUP_ID";
+        MediaSession2.Builder builder = new MediaSession2.Builder(mContext).setId(dupSessionId);
+        try (
+            MediaSession2 session1 = builder.build();
+            MediaSession2 session2 = builder.build()
+        ) {
+            fail("Duplicated id shouldn't be allowed");
+        } catch (IllegalStateException e) {
+            // expected. pass-through
         }
     }
 
     @Test
-    public void testBufferingStateChanged() throws Exception {
-        final int listSize = 5;
-        final List<MediaItem2> list = TestUtils.createPlaylist(listSize);
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return list;
-            }
-        };
-
-        MediaItem2 currentItem = list.get(3);
-        final int buffState = MediaPlayerBase.BUFFERING_STATE_BUFFERING_COMPLETE;
-
-        final CountDownLatch latchForSessionCallback = new CountDownLatch(1);
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testBufferingStateChanged")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public void onBufferingStateChanged(MediaSession2 session,
-                            MediaPlayerBase player, MediaItem2 itemOut, int stateOut) {
-                        assertSame(currentItem, itemOut);
-                        assertEquals(buffState, stateOut);
-                        latchForSessionCallback.countDown();
-                    }
-                }).build()) {
-
-            mPlayer.notifyBufferingStateChanged(currentItem.getDataSourceDesc(), buffState);
-            assertTrue(latchForSessionCallback.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            // TODO (jaewan): Test that controllers are also notified. (b/74505936)
+    public void testSession2Token() {
+        try (MediaSession2 session = new MediaSession2.Builder(mContext).build()) {
+            Session2Token token = session.getSessionToken();
+            assertEquals(Process.myUid(), token.getUid());
+            assertEquals(mContext.getPackageName(), token.getPackageName());
+            assertNull(token.getServiceName());
+            assertEquals(Session2Token.TYPE_SESSION, token.getType());
         }
     }
 
     @Test
-    public void testUpdatePlayer() throws Exception {
-        final int targetState = MediaPlayerBase.PLAYER_STATE_PLAYING;
-        final CountDownLatch latch = new CountDownLatch(1);
-        sHandler.postAndSync(() -> {
-            mSession.close();
-            mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                        @Override
-                        public void onPlayerStateChanged(MediaSession2 session,
-                                MediaPlayerBase player, int state) {
-                            assertEquals(targetState, state);
-                            latch.countDown();
-                        }
-                    }).build();
-        });
-
-        MockPlayer player = new MockPlayer(0);
-
-        // Test if setPlayer doesn't crash with various situations.
-        mSession.updatePlayer(mPlayer, null, null);
-        assertEquals(mPlayer, mSession.getPlayer());
-        MediaPlaylistAgent agent = mSession.getPlaylistAgent();
-        assertNotNull(agent);
-
-        mSession.updatePlayer(player, null, null);
-        assertEquals(player, mSession.getPlayer());
-        assertNotNull(mSession.getPlaylistAgent());
-        assertNotEquals(agent, mSession.getPlaylistAgent());
-
-        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PLAYING);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-   }
-
-    @Test
-    public void testSetPlayer_playbackInfo() throws Exception {
-        MockPlayer player = new MockPlayer(0);
-        AudioAttributes attrs = new AudioAttributes.Builder()
-                .setContentType(CONTENT_TYPE_MUSIC)
-                .build();
-        player.setAudioAttributes(attrs);
-
-        final int maxVolume = 100;
-        final int currentVolume = 23;
-        final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
-        VolumeProvider2 volumeProvider =
-                new VolumeProvider2(volumeControlType, maxVolume, currentVolume) { };
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onPlaybackInfoChanged(MediaController2 controller,
-                    PlaybackInfo info) {
-                Assert.assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-                assertEquals(attrs, info.getAudioAttributes());
-                assertEquals(volumeControlType, info.getPlaybackType());
-                assertEquals(maxVolume, info.getMaxVolume());
-                assertEquals(currentVolume, info.getCurrentVolume());
-                latch.countDown();
-            }
-        };
-
-        mSession.updatePlayer(player, null, null);
-
-        final MediaController2 controller = createController(mSession.getToken(), true, callback);
-        PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        int localVolumeControlType = manager.isVolumeFixed()
-                ? VolumeProvider2.VOLUME_CONTROL_FIXED : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
-        assertEquals(localVolumeControlType, info.getControlType());
-        assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
-        assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
-
-        mSession.updatePlayer(player, null, volumeProvider);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-        info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-        assertEquals(attrs, info.getAudioAttributes());
-        assertEquals(volumeControlType, info.getControlType());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    public void testPlay() throws Exception {
-        sHandler.postAndSync(() -> {
-            mSession.play();
-            assertTrue(mPlayer.mPlayCalled);
-        });
-    }
-
-    @Test
-    public void testPause() throws Exception {
-        sHandler.postAndSync(() -> {
-            mSession.pause();
-            assertTrue(mPlayer.mPauseCalled);
-        });
-    }
-
-    @Ignore
-    @Test
-    public void testStop() throws Exception {
-        sHandler.postAndSync(() -> {
-            mSession.stop();
-            assertTrue(mPlayer.mStopCalled);
-        });
-    }
-
-    @Test
-    public void testSkipToPreviousItem() {
-        mSession.skipToPreviousItem();
-        assertTrue(mMockAgent.mSkipToPreviousItemCalled);
-    }
-
-    @Test
-    public void testSkipToNextItem() throws Exception {
-        mSession.skipToNextItem();
-        assertTrue(mMockAgent.mSkipToNextItemCalled);
-    }
-
-    @Test
-    public void testSkipToPlaylistItem() throws Exception {
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mSession.skipToPlaylistItem(testMediaItem);
-        assertTrue(mMockAgent.mSkipToPlaylistItemCalled);
-        assertSame(testMediaItem, mMockAgent.mItem);
-    }
-
-    @Test
-    public void testGetPlayerState() {
-        final int state = MediaPlayerBase.PLAYER_STATE_PLAYING;
-        mPlayer.mLastPlayerState = state;
-        assertEquals(state, mSession.getPlayerState());
-    }
-
-    @Test
-    public void testGetPosition() {
-        final long position = 150000;
-        mPlayer.mCurrentPosition = position;
-        assertEquals(position, mSession.getCurrentPosition());
-    }
-
-    @Test
-    public void testGetBufferedPosition() {
-        final long bufferedPosition = 900000;
-        mPlayer.mBufferedPosition = bufferedPosition;
-        assertEquals(bufferedPosition, mSession.getBufferedPosition());
-    }
-
-    @Test
-    public void testSetPlaylist() {
-        final List<MediaItem2> list = TestUtils.createPlaylist(2);
-        mSession.setPlaylist(list, null);
-        assertTrue(mMockAgent.mSetPlaylistCalled);
-        assertSame(list, mMockAgent.mPlaylist);
-        assertNull(mMockAgent.mMetadata);
-    }
-
-    @Test
-    public void testGetPlaylist() {
-        final List<MediaItem2> list = TestUtils.createPlaylist(2);
-        mMockAgent.mPlaylist = list;
-        assertEquals(list, mSession.getPlaylist());
-    }
-
-    @Test
-    public void testUpdatePlaylistMetadata() {
-        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
-        mSession.updatePlaylistMetadata(testMetadata);
-        assertTrue(mMockAgent.mUpdatePlaylistMetadataCalled);
-        assertSame(testMetadata, mMockAgent.mMetadata);
-    }
-
-    @Test
-    public void testGetPlaylistMetadata() {
-        final MediaMetadata2 testMetadata = TestUtils.createMetadata();
-        mMockAgent.mMetadata = testMetadata;
-        assertEquals(testMetadata, mSession.getPlaylistMetadata());
-    }
-
-    @Test
-    public void testSessionCallback_onPlaylistChanged() throws InterruptedException {
-        final List<MediaItem2> list = TestUtils.createPlaylist(2);
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public List<MediaItem2> getPlaylist() {
-                return list;
-            }
-        };
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public void onPlaylistChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
-                    List<MediaItem2> playlist, MediaMetadata2 metadata) {
-                assertEquals(agent, playlistAgent);
-                assertEquals(list, playlist);
-                assertNull(metadata);
-                latch.countDown();
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testSessionCallback")
+    public void testCallback_onConnect_onDisconnect() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
                 .setSessionCallback(sHandlerExecutor, sessionCallback)
                 .build()) {
-            agent.notifyPlaylistChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
+            // Test onConnect
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller = new MediaController2(
+                    mContext, session.getSessionToken(), sHandlerExecutor, controllerCallback);
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
 
-    @Test
-    public void testAddPlaylistItem() {
-        final int testIndex = 12;
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mSession.addPlaylistItem(testIndex, testMediaItem);
-        assertTrue(mMockAgent.mAddPlaylistItemCalled);
-        assertEquals(testIndex, mMockAgent.mIndex);
-        assertSame(testMediaItem, mMockAgent.mItem);
-    }
-
-    @Test
-    public void testRemovePlaylistItem() {
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mSession.removePlaylistItem(testMediaItem);
-        assertTrue(mMockAgent.mRemovePlaylistItemCalled);
-        assertSame(testMediaItem, mMockAgent.mItem);
-    }
-
-    @Test
-    public void testReplacePlaylistItem() throws InterruptedException {
-        final int testIndex = 12;
-        final MediaItem2 testMediaItem = TestUtils.createMediaItemWithMetadata();
-        mSession.replacePlaylistItem(testIndex, testMediaItem);
-        assertTrue(mMockAgent.mReplacePlaylistItemCalled);
-        assertEquals(testIndex, mMockAgent.mIndex);
-        assertSame(testMediaItem, mMockAgent.mItem);
-    }
-
-    /**
-     * This also tests {@link SessionCallback#onShuffleModeChanged(
-     * MediaSession2, MediaPlaylistAgent, int)}
-     */
-    @Test
-    public void testGetShuffleMode() throws InterruptedException {
-        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public int getShuffleMode() {
-                return testShuffleMode;
-            }
-        };
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public void onShuffleModeChanged(MediaSession2 session,
-                    MediaPlaylistAgent playlistAgent, int shuffleMode) {
-                assertEquals(agent, playlistAgent);
-                assertEquals(testShuffleMode, shuffleMode);
-                latch.countDown();
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testGetShuffleMode")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            agent.notifyShuffleModeChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testSetShuffleMode() {
-        final int testShuffleMode = MediaPlaylistAgent.SHUFFLE_MODE_GROUP;
-        mSession.setShuffleMode(testShuffleMode);
-        assertTrue(mMockAgent.mSetShuffleModeCalled);
-        assertEquals(testShuffleMode, mMockAgent.mShuffleMode);
-    }
-
-    /**
-     * This also tests {@link SessionCallback#onShuffleModeChanged(
-     * MediaSession2, MediaPlaylistAgent, int)}
-     */
-    @Test
-    public void testGetRepeatMode() throws InterruptedException {
-        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
-        final MediaPlaylistAgent agent = new MediaPlaylistAgent() {
-            @Override
-            public int getRepeatMode() {
-                return testRepeatMode;
-            }
-        };
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public void onRepeatModeChanged(MediaSession2 session, MediaPlaylistAgent playlistAgent,
-                    int repeatMode) {
-                assertEquals(agent, playlistAgent);
-                assertEquals(testRepeatMode, repeatMode);
-                latch.countDown();
-            }
-        };
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setPlaylistAgent(agent)
-                .setId("testGetRepeatMode")
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            agent.notifyRepeatModeChanged();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testSetRepeatMode() {
-        final int testRepeatMode = MediaPlaylistAgent.REPEAT_MODE_GROUP;
-        mSession.setRepeatMode(testRepeatMode);
-        assertTrue(mMockAgent.mSetRepeatModeCalled);
-        assertEquals(testRepeatMode, mMockAgent.mRepeatMode);
-    }
-
-    // TODO (jaewan): Revisit
-    @Test
-    public void testBadPlayer() throws InterruptedException {
-        // TODO(jaewan): Add equivalent tests again
-        final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
-        final BadPlayer player = new BadPlayer(0);
-
-        mSession.updatePlayer(player, null, null);
-        mSession.updatePlayer(mPlayer, null, null);
-        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_PAUSED);
-        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    // This bad player will keep push events to the listener that is previously
-    // registered by session.setPlayer().
-    private static class BadPlayer extends MockPlayer {
-        public BadPlayer(int count) {
-            super(count);
-        }
-
-        @Override
-        public void unregisterPlayerEventCallback(
-                @NonNull MediaPlayerBase.PlayerEventCallback listener) {
-            // No-op.
-        }
-    }
-
-    @Test
-    public void testOnCommandCallback() throws InterruptedException {
-        final MockOnCommandCallback callback = new MockOnCommandCallback();
-        sHandler.postAndSync(() -> {
-            mSession.close();
-            mPlayer = new MockPlayer(1);
-            mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, callback).build();
-        });
-        MediaController2 controller = createController(mSession.getToken());
-        controller.pause();
-        assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(1, callback.commands.size());
-        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE,
-                (long) callback.commands.get(0).getCommandCode());
-
-        controller.play();
-        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mPlayCalled);
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(2, callback.commands.size());
-        assertEquals(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY,
-                (long) callback.commands.get(1).getCommandCode());
-    }
-
-    @Test
-    public void testOnConnectCallback() throws InterruptedException {
-        final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
-        sHandler.postAndSync(() -> {
-            mSession.close();
-            mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
-                    .setSessionCallback(sHandlerExecutor, sessionCallback).build();
-        });
-        MediaController2 controller = createController(mSession.getToken(), false, null);
-        assertNotNull(controller);
-        waitForConnect(controller, false);
-        waitForDisconnect(controller, true);
-    }
-
-    @Test
-    public void testOnDisconnectCallback() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testOnDisconnectCallback")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public void onDisconnected(MediaSession2 session,
-                            ControllerInfo controller) {
-                        assertEquals(Process.myUid(), controller.getUid());
-                        latch.countDown();
-                    }
-                }).build()) {
-            MediaController2 controller = createController(session.getToken());
+            // Test onDisconnect
             controller.close();
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
         }
     }
 
     @Test
-    public void testSetCustomLayout() throws InterruptedException {
-        final List<CommandButton> buttons = new ArrayList<>();
-        buttons.add(new CommandButton.Builder()
-                .setCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY))
-                .setDisplayName("button").build());
-        final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback sessionCallback = new SessionCallback() {
-            @Override
-            public SessionCommandGroup2 onConnect(MediaSession2 session,
-                    ControllerInfo controller) {
-                if (mContext.getPackageName().equals(controller.getPackageName())) {
-                    mSession.setCustomLayout(controller, buttons);
-                }
-                return super.onConnect(session, controller);
-            }
-        };
+    public void testCallback_onSessionCommand() {
+        Session2Callback sessionCallback = new Session2Callback();
 
-        try (final MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setPlayer(mPlayer)
-                .setId("testSetCustomLayout")
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
                 .setSessionCallback(sHandlerExecutor, sessionCallback)
                 .build()) {
-            if (mSession != null) {
-                mSession.close();
-                mSession = session;
-            }
-            final ControllerCallback callback = new ControllerCallback() {
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller = new MediaController2(
+                    mContext, session.getSessionToken(), sHandlerExecutor, controllerCallback);
+            // Wait for connection
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            // Test onSessionCommand
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            controller.sendSessionCommand(command, commandArg);
+
+            assertTrue(sessionCallback.awaitOnSessionCommand(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
+            assertEquals(commandStr, sessionCallback.mCommand.getCustomCommand());
+            assertEquals(commandExtraValue,
+                    sessionCallback.mCommand.getExtras().getString(commandExtraKey));
+            assertEquals(commandArgValue, sessionCallback.mCommandArgs.getString(commandArgKey));
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCallback_onCommandResult() {
+        Session2Callback sessionCallback = new Session2Callback();
+
+        int resultCode = 100;
+        String commandResultKey = "test_result_key";
+        String commandResultValue = "test_result_value";
+        Bundle resultData = new Bundle();
+        resultData.putString(commandResultKey, commandResultValue);
+        Session2Command.Result commandResult = new Session2Command.Result(resultCode, resultData);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback() {
                 @Override
-                public void onCustomLayoutChanged(MediaController2 controller2,
-                        List<CommandButton> layout) {
-                    assertEquals(layout.size(), buttons.size());
-                    for (int i = 0; i < layout.size(); i++) {
-                        assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
-                        assertEquals(layout.get(i).getDisplayName(),
-                                buttons.get(i).getDisplayName());
-                    }
-                    latch.countDown();
+                public Session2Command.Result onSessionCommand(MediaController2 controller,
+                        Session2Command command, Bundle args) {
+                    return commandResult;
                 }
             };
-            final MediaController2 controller =
-                    createController(session.getToken(), true, callback);
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            MediaController2 controller = new MediaController2(
+                    mContext, session.getSessionToken(), sHandlerExecutor, controllerCallback);
+            // Wait for connection
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            // Test onCommandResult
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            session.sendSessionCommand(controllerInfo, command, commandArg);
+
+            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
+            assertEquals(resultCode, sessionCallback.mCommandResult.getResultCode());
+            assertEquals(commandResultValue,
+                    sessionCallback.mCommandResult.getResultData().getString(commandResultKey));
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
         }
     }
 
     @Test
-    public void testSetAllowedCommands() throws InterruptedException {
-        final SessionCommandGroup2 commands = new SessionCommandGroup2();
-        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY));
-        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE));
-        commands.addCommand(new SessionCommand2(SessionCommand2.COMMAND_CODE_PLAYBACK_STOP));
+    public void testSetPlaybackActive() {
+        final boolean testInitialPlaybackActive = true;
+        final boolean testPlaybackActive = false;
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            session.setPlaybackActive(testInitialPlaybackActive);
 
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onAllowedCommandsChanged(MediaController2 controller,
-                    SessionCommandGroup2 commandsOut) {
-                assertNotNull(commandsOut);
-                Set<SessionCommand2> expected = commands.getCommands();
-                Set<SessionCommand2> actual = commandsOut.getCommands();
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller = new MediaController2(mContext, session.getSessionToken(),
+                    sHandlerExecutor, controllerCallback);
+            // Wait for connection
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
 
-                assertNotNull(actual);
-                assertEquals(expected.size(), actual.size());
-                for (SessionCommand2 command : expected) {
-                    assertTrue(actual.contains(command));
-                }
-                latch.countDown();
-            }
-        };
+            // Check initial value
+            assertEquals(testInitialPlaybackActive, controller.isPlaybackActive());
 
-        final MediaController2 controller = createController(mSession.getToken(), true, callback);
-        ControllerInfo controllerInfo = getTestControllerInfo();
-        assertNotNull(controllerInfo);
+            // Change playback active change and wait for changes
+            session.setPlaybackActive(testPlaybackActive);
+            assertTrue(controllerCallback.awaitOnPlaybackActiveChanged(WAIT_TIME_MS));
 
-        mSession.setAllowedCommands(controllerInfo, commands);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
+            assertEquals(testPlaybackActive, controllerCallback.getNotifiedPlaybackActive());
+            assertEquals(testPlaybackActive, controller.isPlaybackActive());
 
-    @Test
-    public void testSendCustomAction() throws InterruptedException {
-        final SessionCommand2 testCommand = new SessionCommand2(
-                SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE);
-        final Bundle testArgs = new Bundle();
-        testArgs.putString("args", "testSendCustomAction");
-
-        final CountDownLatch latch = new CountDownLatch(2);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
-                    Bundle args, ResultReceiver receiver) {
-                assertEquals(testCommand, command);
-                assertTrue(TestUtils.equals(testArgs, args));
-                assertNull(receiver);
-                latch.countDown();
-            }
-        };
-        final MediaController2 controller =
-                createController(mSession.getToken(), true, callback);
-        // TODO(jaewan): Test with multiple controllers
-        mSession.sendCustomCommand(testCommand, testArgs);
-
-        ControllerInfo controllerInfo = getTestControllerInfo();
-        assertNotNull(controllerInfo);
-        // TODO(jaewan): Test receivers as well.
-        mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testNotifyError() throws InterruptedException {
-        final int errorCode = MediaSession2.ERROR_CODE_NOT_AVAILABLE_IN_REGION;
-        final Bundle extras = new Bundle();
-        extras.putString("args", "testNotifyError");
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ControllerCallback callback = new ControllerCallback() {
-            @Override
-            public void onError(MediaController2 controller, int errorCodeOut, Bundle extrasOut) {
-                assertEquals(errorCode, errorCodeOut);
-                assertTrue(TestUtils.equals(extras, extrasOut));
-                latch.countDown();
-            }
-        };
-        final MediaController2 controller = createController(mSession.getToken(), true, callback);
-        // TODO(jaewan): Test with multiple controllers
-        mSession.notifyError(errorCode, extras);
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private ControllerInfo getTestControllerInfo() {
-        List<ControllerInfo> controllers = mSession.getConnectedControllers();
-        assertNotNull(controllers);
-        for (int i = 0; i < controllers.size(); i++) {
-            if (Process.myUid() == controllers.get(i).getUid()) {
-                return controllers.get(i);
-            }
-        }
-        fail("Failed to get test controller info");
-        return null;
-    }
-
-    public class MockOnConnectCallback extends SessionCallback {
-        @Override
-        public SessionCommandGroup2 onConnect(MediaSession2 session,
-                ControllerInfo controllerInfo) {
-            if (Process.myUid() != controllerInfo.getUid()) {
-                return null;
-            }
-            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
-            assertEquals(Process.myUid(), controllerInfo.getUid());
-            assertFalse(controllerInfo.isTrusted());
-            // Reject all
-            return null;
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
         }
     }
 
-    public class MockOnCommandCallback extends SessionCallback {
-        public final ArrayList<SessionCommand2> commands = new ArrayList<>();
+    @Test
+    public void testCancelSessionCommand() {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller = new MediaController2(mContext, session.getSessionToken(),
+                    sHandlerExecutor, controllerCallback);
+            // Wait for connection
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            String commandStr = "test_command_";
+            String commandExtraKey = "test_extra_key_";
+            String commandExtraValue = "test_extra_value_";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key_";
+            String commandArgValue = "test_arg_value_";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            synchronized (sTestLock) {
+                Object token = session.sendSessionCommand(controllerInfo, command, commandArg);
+                session.cancelSessionCommand(controllerInfo, token);
+            }
+            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(Session2Command.RESULT_INFO_SKIPPED,
+                    sessionCallback.mCommandResult.getResultCode());
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    class Controller2Callback extends MediaController2.ControllerCallback {
+        private final CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnPlaybackActiveChangedLatch = new CountDownLatch(1);
+
+        private boolean mPlaybackActive;
 
         @Override
-        public boolean onCommandRequest(MediaSession2 session, ControllerInfo controllerInfo,
-                SessionCommand2 command) {
-            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
-            assertEquals(Process.myUid(), controllerInfo.getUid());
-            assertFalse(controllerInfo.isTrusted());
-            commands.add(command);
-            if (command.getCommandCode() == SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE) {
+        public void onConnected(MediaController2 controller,
+                Session2CommandGroup allowedCommands) {
+            mOnConnectedLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            mOnDisconnectedLatch.countDown();
+        }
+
+        @Override
+        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+            mPlaybackActive = playbackActive;
+            mOnPlaybackActiveChangedLatch.countDown();
+        }
+
+        public boolean awaitOnConnected(long waitTimeMs) {
+            try {
+                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
                 return false;
             }
-            return true;
+        }
+
+        public boolean awaitOnDisconnected(long waitTimeMs) {
+            try {
+                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnPlaybackActiveChanged(long waitTimeMs) {
+            try {
+                return mOnPlaybackActiveChangedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean getNotifiedPlaybackActive() {
+            return mPlaybackActive;
         }
     }
 
-    private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
-        if (a == null || b == null) {
-            assertEquals(a, b);
-        }
-        assertEquals(a.size(), b.size());
+    class Session2Callback extends MediaSession2.SessionCallback {
+        private final CountDownLatch mOnConnectLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnDisconnectLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
 
-        for (int i = 0; i < a.size(); i++) {
-            MediaItem2 aItem = a.get(i);
-            MediaItem2 bItem = b.get(i);
+        MediaSession2 mSession;
+        MediaSession2.ControllerInfo mController;
+        Session2Command mCommand;
+        Bundle mCommandArgs;
+        Session2Command.Result mCommandResult;
 
-            if (aItem == null || bItem == null) {
-                assertEquals(aItem, bItem);
-                continue;
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            if (controller.getUid() != Process.myUid()) {
+                return null;
             }
+            mSession = session;
+            mController = controller;
+            mOnConnectLatch.countDown();
+            return new Session2CommandGroup.Builder().build();
+        }
 
-            assertEquals(aItem.getMediaId(), bItem.getMediaId());
-            assertEquals(aItem.getFlags(), bItem.getFlags());
-            TestUtils.equals(aItem.getMetadata().toBundle(), bItem.getMetadata().toBundle());
+        @Override
+        public void onDisconnected(MediaSession2 session, MediaSession2.ControllerInfo controller) {
+            if (controller.getUid() != Process.myUid()) {
+                return;
+            }
+            mSession = session;
+            mController = controller;
+            mOnDisconnectLatch.countDown();
+        }
 
-            // Note: Here it does not check whether DataSourceDesc are equal,
-            // since there DataSourceDec is not comparable.
+        @Override
+        public Session2Command.Result onSessionCommand(MediaSession2 session,
+                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
+            if (controller.getUid() != Process.myUid()) {
+                return null;
+            }
+            mSession = session;
+            mController = controller;
+            mCommand = command;
+            mCommandArgs = args;
+            mOnSessionCommandLatch.countDown();
+
+            int resultCode = 100;
+            String commandResultKey = "test_result_key";
+            String commandResultValue = "test_result_value";
+            Bundle resultData = new Bundle();
+            resultData.putString(commandResultKey, commandResultValue);
+            Session2Command.Result commandResult =
+                    new Session2Command.Result(resultCode, resultData);
+            return commandResult;
+        }
+
+        @Override
+        public void onCommandResult(MediaSession2 session, MediaSession2.ControllerInfo controller,
+                Object token, Session2Command command, Session2Command.Result result) {
+            if (controller.getUid() != Process.myUid()) {
+                return;
+            }
+            mSession = session;
+            mController = controller;
+            mCommand = command;
+            mCommandResult = result;
+            mOnCommandResultLatch.countDown();
+        }
+
+        public boolean awaitOnConnect(long waitTimeMs) {
+            try {
+                return mOnConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnDisconnect(long waitTimeMs) {
+            try {
+                return mOnDisconnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnSessionCommand(long waitTimeMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnCommandResult(long waitTimeMs) {
+            try {
+                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
         }
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2TestBase.java b/tests/tests/media/src/android/media/cts/MediaSession2TestBase.java
deleted file mode 100644
index 828c659..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSession2TestBase.java
+++ /dev/null
@@ -1,293 +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 static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.SessionCommand2;
-import android.media.MediaSession2.CommandButton;
-import android.media.SessionCommandGroup2;
-import android.media.SessionToken2;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.ResultReceiver;
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSession2TestBase {
-    // Expected success
-    static final int WAIT_TIME_MS = 1000;
-
-    // Expected timeout
-    static final int TIMEOUT_MS = 500;
-
-    static TestUtils.SyncHandler sHandler;
-    static Executor sHandlerExecutor;
-
-    Context mContext;
-    private List<MediaController2> mControllers = new ArrayList<>();
-
-    interface TestControllerInterface {
-        ControllerCallback getCallback();
-    }
-
-    interface WaitForConnectionInterface {
-        void waitForConnect(boolean expect) throws InterruptedException;
-        void waitForDisconnect(boolean expect) throws InterruptedException;
-    }
-
-    @BeforeClass
-    public static void setUpThread() {
-        if (sHandler == null) {
-            HandlerThread handlerThread = new HandlerThread("MediaSession2TestBase");
-            handlerThread.start();
-            sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
-            sHandlerExecutor = (runnable) -> {
-                sHandler.post(runnable);
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        if (sHandler != null) {
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @CallSuper
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-    }
-
-    @CallSuper
-    public void cleanUp() throws Exception {
-        for (int i = 0; i < mControllers.size(); i++) {
-            mControllers.get(i).close();
-        }
-    }
-
-    final MediaController2 createController(SessionToken2 token) throws InterruptedException {
-        return createController(token, true, null);
-    }
-
-    final MediaController2 createController(@NonNull SessionToken2 token,
-            boolean waitForConnect, @Nullable ControllerCallback callback)
-            throws InterruptedException {
-        TestControllerInterface instance = onCreateController(token, callback);
-        if (!(instance instanceof MediaController2)) {
-            throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
-                    + instance);
-        }
-        MediaController2 controller = (MediaController2) instance;
-        mControllers.add(controller);
-        if (waitForConnect) {
-            waitForConnect(controller, true);
-        }
-        return controller;
-    }
-
-    private static WaitForConnectionInterface getWaitForConnectionInterface(
-            MediaController2 controller) {
-        if (!(controller instanceof TestControllerInterface)) {
-            throw new RuntimeException("Test has a bug. Expected controller implemented"
-                    + " TestControllerInterface but got " + controller);
-        }
-        ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
-        if (!(callback instanceof WaitForConnectionInterface)) {
-            throw new RuntimeException("Test has a bug. Expected controller with callback "
-                    + " implemented WaitForConnectionInterface but got " + controller);
-        }
-        return (WaitForConnectionInterface) callback;
-    }
-
-    public static void waitForConnect(MediaController2 controller, boolean expected)
-            throws InterruptedException {
-        getWaitForConnectionInterface(controller).waitForConnect(expected);
-    }
-
-    public static void waitForDisconnect(MediaController2 controller, boolean expected)
-            throws InterruptedException {
-        getWaitForConnectionInterface(controller).waitForDisconnect(expected);
-    }
-
-    TestControllerInterface onCreateController(@NonNull SessionToken2 token,
-            @Nullable ControllerCallback callback) {
-        if (callback == null) {
-            callback = new ControllerCallback() {};
-        }
-        return new TestMediaController(mContext, token, new TestControllerCallback(callback));
-    }
-
-    // TODO(jaewan): (Can be Post-P): Deprecate this
-    public static class TestControllerCallback extends MediaController2.ControllerCallback
-            implements WaitForConnectionInterface {
-        public final ControllerCallback mCallbackProxy;
-        public final CountDownLatch connectLatch = new CountDownLatch(1);
-        public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-
-        TestControllerCallback(@NonNull ControllerCallback callbackProxy) {
-            if (callbackProxy == null) {
-                throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
-            }
-            mCallbackProxy = callbackProxy;
-        }
-
-        @CallSuper
-        @Override
-        public void onConnected(MediaController2 controller, SessionCommandGroup2 commands) {
-            connectLatch.countDown();
-        }
-
-        @CallSuper
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            disconnectLatch.countDown();
-        }
-
-        @Override
-        public void waitForConnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-
-        @Override
-        public void waitForDisconnect(boolean expect) throws InterruptedException {
-            if (expect) {
-                assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            } else {
-                assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-
-        @Override
-        public void onCustomCommand(MediaController2 controller, SessionCommand2 command,
-                Bundle args, ResultReceiver receiver) {
-            mCallbackProxy.onCustomCommand(controller, command, args, receiver);
-        }
-
-        @Override
-        public void onPlaybackInfoChanged(MediaController2 controller,
-                MediaController2.PlaybackInfo info) {
-            mCallbackProxy.onPlaybackInfoChanged(controller, info);
-        }
-
-        @Override
-        public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
-            mCallbackProxy.onCustomLayoutChanged(controller, layout);
-        }
-
-        @Override
-        public void onAllowedCommandsChanged(MediaController2 controller,
-                SessionCommandGroup2 commands) {
-            mCallbackProxy.onAllowedCommandsChanged(controller, commands);
-        }
-
-        @Override
-        public void onPlayerStateChanged(MediaController2 controller, int state) {
-            mCallbackProxy.onPlayerStateChanged(controller, state);
-        }
-
-        @Override
-        public void onSeekCompleted(MediaController2 controller, long position) {
-            mCallbackProxy.onSeekCompleted(controller, position);
-        }
-
-        @Override
-        public void onPlaybackSpeedChanged(MediaController2 controller, float speed) {
-            mCallbackProxy.onPlaybackSpeedChanged(controller, speed);
-        }
-
-        @Override
-        public void onBufferingStateChanged(MediaController2 controller, MediaItem2 item,
-                int state) {
-            mCallbackProxy.onBufferingStateChanged(controller, item, state);
-        }
-
-        @Override
-        public void onError(MediaController2 controller, int errorCode, Bundle extras) {
-            mCallbackProxy.onError(controller, errorCode, extras);
-        }
-
-        @Override
-        public void onCurrentMediaItemChanged(MediaController2 controller, MediaItem2 item) {
-            mCallbackProxy.onCurrentMediaItemChanged(controller, item);
-        }
-
-        @Override
-        public void onPlaylistChanged(MediaController2 controller,
-                List<MediaItem2> list, MediaMetadata2 metadata) {
-            mCallbackProxy.onPlaylistChanged(controller, list, metadata);
-        }
-
-        @Override
-        public void onPlaylistMetadataChanged(MediaController2 controller,
-                MediaMetadata2 metadata) {
-            mCallbackProxy.onPlaylistMetadataChanged(controller, metadata);
-        }
-
-        @Override
-        public void onShuffleModeChanged(MediaController2 controller, int shuffleMode) {
-            mCallbackProxy.onShuffleModeChanged(controller, shuffleMode);
-        }
-
-        @Override
-        public void onRepeatModeChanged(MediaController2 controller, int repeatMode) {
-            mCallbackProxy.onRepeatModeChanged(controller, repeatMode);
-        }
-    }
-
-    public class TestMediaController extends MediaController2 implements TestControllerInterface {
-        private final ControllerCallback mCallback;
-
-        public TestMediaController(@NonNull Context context, @NonNull SessionToken2 token,
-                @NonNull ControllerCallback callback) {
-            super(context, token, sHandlerExecutor, callback);
-            mCallback = callback;
-        }
-
-        @Override
-        public ControllerCallback getCallback() {
-            return mCallback;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2_PermissionTest.java b/tests/tests/media/src/android/media/cts/MediaSession2_PermissionTest.java
deleted file mode 100644
index 6b08fb3..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSession2_PermissionTest.java
+++ /dev/null
@@ -1,544 +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 static android.media.SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_REWIND;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO;
-import static android.media.SessionCommand2.COMMAND_CODE_SET_VOLUME;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_NEXT_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_PREV_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYBACK_STOP;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
-import static android.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH;
-import static android.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI;
-import static android.media.MediaSession2.ControllerInfo;
-
-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.annotation.NonNull;
-import android.media.MediaController2;
-import android.media.MediaItem2;
-import android.media.MediaSession2;
-import android.media.SessionCommand2;
-import android.media.SessionCommandGroup2;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests whether {@link MediaSession2} receives commands that hasn't allowed.
- */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-@Ignore
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSession2_PermissionTest extends MediaSession2TestBase {
-    private static final String SESSION_ID = "MediaSession2Test_permission";
-
-    private MockPlayer mPlayer;
-    private MediaSession2 mSession;
-    private MySessionCallback mCallback;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-        mPlayer = null;
-        mCallback = null;
-    }
-
-    private MediaSession2 createSessionWithAllowedActions(final SessionCommandGroup2 commands) {
-        mPlayer = new MockPlayer(0);
-        mCallback = new MySessionCallback() {
-            @Override
-            public SessionCommandGroup2 onConnect(MediaSession2 session,
-                    ControllerInfo controller) {
-                if (Process.myUid() != controller.getUid()) {
-                    return null;
-                }
-                return commands == null ? new SessionCommandGroup2() : commands;
-            }
-        };
-        if (mSession != null) {
-            mSession.close();
-        }
-        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer).setId(SESSION_ID)
-                .setSessionCallback(sHandlerExecutor, mCallback).build();
-        return mSession;
-    }
-
-    private SessionCommandGroup2 createCommandGroupWith(int commandCode) {
-        SessionCommandGroup2 commands = new SessionCommandGroup2();
-        commands.addCommand(new SessionCommand2(commandCode));
-        return commands;
-    }
-
-    private SessionCommandGroup2 createCommandGroupWithout(int commandCode) {
-        SessionCommandGroup2 commands = new SessionCommandGroup2();
-        commands.addAllPredefinedCommands();
-        commands.removeCommand(new SessionCommand2(commandCode));
-        return commands;
-    }
-
-    private void testOnCommandRequest(int commandCode, PermissionTestRunnable runnable)
-            throws InterruptedException {
-        createSessionWithAllowedActions(createCommandGroupWith(commandCode));
-        runnable.run(createController(mSession.getToken()));
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnCommandRequestCalled);
-        assertEquals(commandCode, mCallback.mCommand.getCommandCode());
-
-        createSessionWithAllowedActions(createCommandGroupWithout(commandCode));
-        runnable.run(createController(mSession.getToken()));
-
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnCommandRequestCalled);
-    }
-
-    @Test
-    public void testPlay() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYBACK_PLAY, (controller) -> {
-            controller.play();
-        });
-    }
-
-    @Test
-    public void testPause() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYBACK_PAUSE, (controller) -> {
-            controller.pause();
-        });
-    }
-
-    @Test
-    public void testStop() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYBACK_STOP, (controller) -> {
-            controller.stop();
-        });
-    }
-
-    @Test
-    public void testFastForward() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_SESSION_FAST_FORWARD, (controller) -> {
-            controller.fastForward();
-        });
-    }
-
-    @Test
-    public void testRewind() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_SESSION_REWIND, (controller) -> {
-            controller.rewind();
-        });
-    }
-
-    @Test
-    public void testSeekTo() throws InterruptedException {
-        final long position = 10;
-        testOnCommandRequest(COMMAND_CODE_PLAYBACK_SEEK_TO, (controller) -> {
-            controller.seekTo(position);
-        });
-    }
-
-    @Test
-    public void testSkipToNext() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_SKIP_NEXT_ITEM, (controller) -> {
-            controller.skipToNextItem();
-        });
-    }
-
-    @Test
-    public void testSkipToPrevious() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_SKIP_PREV_ITEM, (controller) -> {
-            controller.skipToPreviousItem();
-        });
-    }
-
-    @Test
-    public void testSkipToPlaylistItem() throws InterruptedException {
-        MediaItem2 testItem = TestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, (controller) -> {
-            controller.skipToPlaylistItem(testItem);
-        });
-    }
-
-    @Test
-    public void testSetPlaylist() throws InterruptedException {
-        List<MediaItem2> list = TestUtils.createPlaylist(2);
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_SET_LIST, (controller) -> {
-            controller.setPlaylist(list, null);
-        });
-    }
-
-    @Test
-    public void testUpdatePlaylistMetadata() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_SET_LIST_METADATA, (controller) -> {
-            controller.updatePlaylistMetadata(null);
-        });
-    }
-
-    @Test
-    public void testAddPlaylistItem() throws InterruptedException {
-        MediaItem2 testItem = TestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_ADD_ITEM, (controller) -> {
-            controller.addPlaylistItem(0, testItem);
-        });
-    }
-
-    @Test
-    public void testRemovePlaylistItem() throws InterruptedException {
-        MediaItem2 testItem = TestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_REMOVE_ITEM, (controller) -> {
-            controller.removePlaylistItem(testItem);
-        });
-    }
-
-    @Test
-    public void testReplacePlaylistItem() throws InterruptedException {
-        MediaItem2 testItem = TestUtils.createMediaItemWithMetadata();
-        testOnCommandRequest(COMMAND_CODE_PLAYLIST_REPLACE_ITEM, (controller) -> {
-            controller.replacePlaylistItem(0, testItem);
-        });
-    }
-
-    @Test
-    public void testSetVolume() throws InterruptedException {
-        testOnCommandRequest(COMMAND_CODE_SET_VOLUME, (controller) -> {
-            controller.setVolumeTo(0, 0);
-        });
-    }
-
-    @Test
-    public void testPlayFromMediaId() throws InterruptedException {
-        final String mediaId = "testPlayFromMediaId";
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID));
-        createController(mSession.getToken()).playFromMediaId(mediaId, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPlayFromMediaIdCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID));
-        createController(mSession.getToken()).playFromMediaId(mediaId, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPlayFromMediaIdCalled);
-    }
-
-    @Test
-    public void testPlayFromUri() throws InterruptedException {
-        final Uri uri = Uri.parse("play://from.uri");
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PLAY_FROM_URI));
-        createController(mSession.getToken()).playFromUri(uri, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPlayFromUriCalled);
-        assertEquals(uri, mCallback.mUri);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PLAY_FROM_URI));
-        createController(mSession.getToken()).playFromUri(uri, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPlayFromUriCalled);
-    }
-
-    @Test
-    public void testPlayFromSearch() throws InterruptedException {
-        final String query = "testPlayFromSearch";
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PLAY_FROM_SEARCH));
-        createController(mSession.getToken()).playFromSearch(query, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPlayFromSearchCalled);
-        assertEquals(query, mCallback.mQuery);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PLAY_FROM_SEARCH));
-        createController(mSession.getToken()).playFromSearch(query, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPlayFromSearchCalled);
-    }
-
-    @Test
-    public void testPrepareFromMediaId() throws InterruptedException {
-        final String mediaId = "testPrepareFromMediaId";
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID));
-        createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
-        assertEquals(mediaId, mCallback.mMediaId);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID));
-        createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPrepareFromMediaIdCalled);
-    }
-
-    @Test
-    public void testPrepareFromUri() throws InterruptedException {
-        final Uri uri = Uri.parse("prepare://from.uri");
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_URI));
-        createController(mSession.getToken()).prepareFromUri(uri, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPrepareFromUriCalled);
-        assertEquals(uri, mCallback.mUri);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_URI));
-        createController(mSession.getToken()).prepareFromUri(uri, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPrepareFromUriCalled);
-    }
-
-    @Test
-    public void testPrepareFromSearch() throws InterruptedException {
-        final String query = "testPrepareFromSearch";
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
-        createController(mSession.getToken()).prepareFromSearch(query, null);
-
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPrepareFromSearchCalled);
-        assertEquals(query, mCallback.mQuery);
-        assertNull(mCallback.mExtras);
-
-        createSessionWithAllowedActions(
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
-        createController(mSession.getToken()).prepareFromSearch(query, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertFalse(mCallback.mOnPrepareFromSearchCalled);
-    }
-
-    @Test
-    public void testChangingPermissionWithSetAllowedCommands() throws InterruptedException {
-        final String query = "testChangingPermissionWithSetAllowedCommands";
-        createSessionWithAllowedActions(
-                createCommandGroupWith(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
-
-        ControllerCallbackForPermissionChange controllerCallback =
-                new ControllerCallbackForPermissionChange();
-        MediaController2 controller =
-                createController(mSession.getToken(), true, controllerCallback);
-
-        controller.prepareFromSearch(query, null);
-        assertTrue(mCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mCallback.mOnPrepareFromSearchCalled);
-        assertEquals(query, mCallback.mQuery);
-        assertNull(mCallback.mExtras);
-        mCallback.reset();
-
-        // Change allowed commands.
-        mSession.setAllowedCommands(getTestControllerInfo(),
-                createCommandGroupWithout(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH));
-        assertTrue(controllerCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        controller.prepareFromSearch(query, null);
-        assertFalse(mCallback.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private ControllerInfo getTestControllerInfo() {
-        List<ControllerInfo> controllers = mSession.getConnectedControllers();
-        assertNotNull(controllers);
-        for (int i = 0; i < controllers.size(); i++) {
-            if (Process.myUid() == controllers.get(i).getUid()) {
-                return controllers.get(i);
-            }
-        }
-        fail("Failed to get test controller info");
-        return null;
-    }
-
-    @FunctionalInterface
-    private interface PermissionTestRunnable {
-        void run(@NonNull MediaController2 controller);
-    }
-
-    public class MySessionCallback extends MediaSession2.SessionCallback {
-        public CountDownLatch mCountDownLatch;
-
-        public SessionCommand2 mCommand;
-        public String mMediaId;
-        public String mQuery;
-        public Uri mUri;
-        public Bundle mExtras;
-
-        public boolean mOnCommandRequestCalled;
-        public boolean mOnPlayFromMediaIdCalled;
-        public boolean mOnPlayFromSearchCalled;
-        public boolean mOnPlayFromUriCalled;
-        public boolean mOnPrepareFromMediaIdCalled;
-        public boolean mOnPrepareFromSearchCalled;
-        public boolean mOnPrepareFromUriCalled;
-
-
-        public MySessionCallback() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        public void reset() {
-            mCountDownLatch = new CountDownLatch(1);
-
-            mCommand = null;
-            mMediaId = null;
-            mQuery = null;
-            mUri = null;
-            mExtras = null;
-
-            mOnCommandRequestCalled = false;
-            mOnPlayFromMediaIdCalled = false;
-            mOnPlayFromSearchCalled = false;
-            mOnPlayFromUriCalled = false;
-            mOnPrepareFromMediaIdCalled = false;
-            mOnPrepareFromSearchCalled = false;
-            mOnPrepareFromUriCalled = false;
-        }
-
-        @Override
-        public boolean onCommandRequest(MediaSession2 session, ControllerInfo controller,
-                SessionCommand2 command) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnCommandRequestCalled = true;
-            mCommand = command;
-            mCountDownLatch.countDown();
-            return super.onCommandRequest(session, controller, command);
-        }
-
-        @Override
-        public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
-                String mediaId, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPlayFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller,
-                String query, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPlayFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onPlayFromUri(MediaSession2 session, ControllerInfo controller,
-                Uri uri, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPlayFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
-                String mediaId, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPrepareFromMediaIdCalled = true;
-            mMediaId = mediaId;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
-                String query, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPrepareFromSearchCalled = true;
-            mQuery = query;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        @Override
-        public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller,
-                Uri uri, Bundle extras) {
-            assertEquals(Process.myUid(), controller.getUid());
-            mOnPrepareFromUriCalled = true;
-            mUri = uri;
-            mExtras = extras;
-            mCountDownLatch.countDown();
-        }
-
-        // TODO(jaewan): Add permission test for setRating()
-    }
-
-    public class ControllerCallbackForPermissionChange extends MediaController2.ControllerCallback {
-        public CountDownLatch mCountDownLatch = new CountDownLatch(1);
-
-        @Override
-        public void onAllowedCommandsChanged(MediaController2 controller,
-                SessionCommandGroup2 commands) {
-            mCountDownLatch.countDown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index 926abd8..88d8264 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -22,6 +22,9 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.media.MediaSession2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
@@ -29,14 +32,17 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.test.InstrumentationTestCase;
 import android.test.UiThreadTest;
 import android.view.KeyEvent;
+import android.util.Log;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 @AppModeFull(reason = "TODO: evaluate and port to instant")
@@ -54,6 +60,11 @@
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
     public void testGetActiveSessions() throws Exception {
         try {
             List<MediaController> controllers = mSessionManager.getActiveSessions(null);
@@ -114,10 +125,7 @@
             // events directly to the audio service to change the system volume.
             return;
         }
-
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        Handler handler = new Handler(handlerThread.getLooper());
+        Handler handler = createHandler();
 
         // Ensure that the listener is called for long-press.
         VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
@@ -144,13 +152,12 @@
         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
         assertEquals(listener.mKeyEvents.size(), 0);
+
+        removeHandler(handler);
     }
 
     public void testSetOnMediaKeyListener() throws Exception {
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        Handler handler = new Handler(handlerThread.getLooper());
-
+        Handler handler = createHandler();
         MediaSession session = null;
         try {
             session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
@@ -210,14 +217,13 @@
             if (session != null) {
                 session.release();
             }
+            removeHandler(handler);
         }
     }
 
     public void testRemoteUserInfo() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        Handler handler = new Handler(handlerThread.getLooper());
+        Handler handler = createHandler();
 
         MediaSession session = null;
         try {
@@ -264,9 +270,109 @@
             if (session != null) {
                 session.release();
             }
+            removeHandler(handler);
         }
     }
 
+    public void testGetSession2Tokens() {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+
+        MediaSession2.SessionCallback sessionCallback = new MediaSession2.SessionCallback() {};
+        try (MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(new HandlerExecutor(handler.getLooper()), sessionCallback)
+                .build()) {
+            Session2Token currentToken = session.getSessionToken();
+            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken));
+        }
+    }
+
+    public void testAddAndRemoveSession2TokensListener() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+
+        Session2TokenListener listener1 = new Session2TokenListener();
+        mSessionManager.addOnSession2TokensChangedListener(listener1, handler);
+
+        MediaSession2.SessionCallback sessionCallback = new MediaSession2.SessionCallback() {};
+        try (MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(new HandlerExecutor(handler.getLooper()), sessionCallback)
+                .build()) {
+            assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            Session2Token currentToken = session.getSessionToken();
+            assertTrue(listContainsToken(listener1.mTokens, currentToken));
+
+            // Test removing listener
+            listener1.resetCountDownLatch();
+            Session2TokenListener listener2 = new Session2TokenListener();
+            mSessionManager.addOnSession2TokensChangedListener(listener2, handler);
+            mSessionManager.removeOnSession2TokensChangedListener(listener1);
+
+            session.close();
+            assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    public void testNotifySession2Created_withDestroyedToken_shouldThrowIAE() {
+        final Context context = getInstrumentation().getTargetContext();
+
+        MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(new HandlerExecutor(createHandler().getLooper()),
+                        new MediaSession2.SessionCallback() {})
+                .build();
+        session.close();
+        Session2Token destroyedToken = session.getSessionToken();
+
+        try {
+            mSessionManager.notifySession2Created(destroyedToken);
+            fail("notifySession2Created should throw IAE");
+        } catch (IllegalArgumentException ex) {
+            // Expected.
+        }
+    }
+
+    public void testNotifySession2Destroyed_withLiveToken_shouldThrowIAE() {
+        final Context context = getInstrumentation().getTargetContext();
+
+        MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(new HandlerExecutor(createHandler().getLooper()),
+                        new MediaSession2.SessionCallback() {})
+                .build();
+        Session2Token liveToken = session.getSessionToken();
+
+        try {
+            mSessionManager.notifySession2Destroyed(liveToken);
+            fail("notifySession2Destroyed should throw IAE");
+        } catch (IllegalArgumentException ex) {
+            // Expected.
+        } finally {
+            session.close();
+        }
+    }
+
+    private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
+        for (int i = 0; i < tokens.size(); i++) {
+            if (tokens.get(i).equals(token)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Handler createHandler() {
+        HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
+    private void removeHandler(Handler handler) {
+        if (handler == null) {
+            return;
+        }
+        handler.getLooper().quitSafely();
+    }
+
     private class VolumeKeyLongPressListener
             implements MediaSessionManager.OnVolumeKeyLongPressListener {
         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
@@ -330,4 +436,24 @@
             return true;
         }
     }
+
+    private class Session2TokenListener implements
+            MediaSessionManager.OnSession2TokensChangedListener {
+        private CountDownLatch mCountDownLatch;
+        private List<Session2Token> mTokens;
+
+        private Session2TokenListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onSession2TokensChanged(List<Session2Token> tokens) {
+            mTokens = tokens;
+            mCountDownLatch.countDown();
+        }
+
+        public void resetCountDownLatch() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManager_MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSessionManager_MediaSession2Test.java
deleted file mode 100644
index 1a3cb7c..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSessionManager_MediaSession2Test.java
+++ /dev/null
@@ -1,338 +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 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.Context;
-import android.media.MediaController2;
-import android.media.MediaPlayerBase;
-import android.media.MediaSession2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.SessionCommandGroup2;
-import android.media.SessionToken2;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.OnSessionTokensChangedListener;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSessionManager} with {@link MediaSession2} specific APIs.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Ignore
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSessionManager_MediaSession2Test extends MediaSession2TestBase {
-    private static final String TAG = "MediaSessionManager_MediaSession2Test";
-
-    private MediaSessionManager mManager;
-    private MediaSession2 mSession;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
-        // Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
-        // per test thread differs across the {@link MediaSession2} with the same TAG.
-        final MockPlayer player = new MockPlayer(1);
-        mSession = new MediaSession2.Builder(mContext)
-                .setPlayer(player)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() { })
-                .setId(TAG)
-                .build();
-    }
-
-    @After
-    @Override
-    public void cleanUp() throws Exception {
-        super.cleanUp();
-        sHandler.removeCallbacksAndMessages(null);
-        mSession.close();
-    }
-
-    // TODO(jaewan): Make this host-side test to see per-user behavior.
-    @Ignore
-    @Test
-    public void testGetMediaSession2Tokens_hasMediaController() throws InterruptedException {
-        final MockPlayer player = (MockPlayer) mSession.getPlayer();
-        player.notifyPlaybackState(MediaPlayerBase.PLAYER_STATE_IDLE);
-
-        MediaController2 controller = null;
-        List<SessionToken2> tokens = mManager.getActiveSessionTokens();
-        assertNotNull(tokens);
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            if (mContext.getPackageName().equals(token.getPackageName())
-                    && TAG.equals(token.getId())) {
-                assertNull(controller);
-                controller = createController(token);
-            }
-        }
-        assertNotNull(controller);
-
-        // Test if the found controller is correct one.
-        assertEquals(MediaPlayerBase.PLAYER_STATE_IDLE, controller.getPlayerState());
-        controller.play();
-
-        assertTrue(player.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(player.mPlayCalled);
-    }
-
-    /**
-     * Test if server recognizes a session even if the session refuses the connection from server.
-     *
-     * @throws InterruptedException
-     */
-    @Test
-    public void testGetSessionTokens_sessionRejected() throws InterruptedException {
-        mSession.close();
-        mSession = new MediaSession2.Builder(mContext).setPlayer(new MockPlayer(0))
-                .setId(TAG).setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public SessionCommandGroup2 onConnect(
-                            MediaSession2 session, ControllerInfo controller) {
-                        // Reject all connection request.
-                        return null;
-                    }
-                }).build();
-
-        boolean foundSession = false;
-        List<SessionToken2> tokens = mManager.getActiveSessionTokens();
-        assertNotNull(tokens);
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            if (mContext.getPackageName().equals(token.getPackageName())
-                    && TAG.equals(token.getId())) {
-                assertFalse(foundSession);
-                foundSession = true;
-            }
-        }
-        assertTrue(foundSession);
-    }
-
-    @Test
-    public void testGetMediaSession2Tokens_sessionClosed() throws InterruptedException {
-        mSession.close();
-
-        // When a session is closed, it should lose binder connection between server immediately.
-        // So server will forget the session.
-        List<SessionToken2> tokens = mManager.getActiveSessionTokens();
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            assertFalse(mContext.getPackageName().equals(token.getPackageName())
-                    && TAG.equals(token.getId()));
-        }
-    }
-
-    @Test
-    public void testGetMediaSessionService2Token() throws InterruptedException {
-        boolean foundTestSessionService = false;
-        boolean foundTestLibraryService = false;
-        List<SessionToken2> tokens = mManager.getSessionServiceTokens();
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            if (mContext.getPackageName().equals(token.getPackageName())
-                    && MockMediaSessionService2.ID.equals(token.getId())) {
-                assertFalse(foundTestSessionService);
-                assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
-                foundTestSessionService = true;
-            } else if (mContext.getPackageName().equals(token.getPackageName())
-                    && MockMediaLibraryService2.ID.equals(token.getId())) {
-                assertFalse(foundTestLibraryService);
-                assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
-                foundTestLibraryService = true;
-            }
-        }
-        assertTrue(foundTestSessionService);
-        assertTrue(foundTestLibraryService);
-    }
-
-    @Test
-    public void testGetAllSessionTokens() throws InterruptedException {
-        boolean foundTestSession = false;
-        boolean foundTestSessionService = false;
-        boolean foundTestLibraryService = false;
-        List<SessionToken2> tokens = mManager.getAllSessionTokens();
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            if (!mContext.getPackageName().equals(token.getPackageName())) {
-                continue;
-            }
-            switch (token.getId()) {
-                case TAG:
-                    assertFalse(foundTestSession);
-                    foundTestSession = true;
-                    break;
-                case MockMediaSessionService2.ID:
-                    assertFalse(foundTestSessionService);
-                    foundTestSessionService = true;
-                    assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
-                    break;
-                case MockMediaLibraryService2.ID:
-                    assertFalse(foundTestLibraryService);
-                    assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
-                    foundTestLibraryService = true;
-                    break;
-                default:
-                    fail("Unexpected session " + token + " exists in the package");
-            }
-        }
-        assertTrue(foundTestSession);
-        assertTrue(foundTestSessionService);
-        assertTrue(foundTestLibraryService);
-    }
-
-    @Test
-    public void testAddOnSessionTokensChangedListener() throws InterruptedException {
-        TokensChangedListener listener = new TokensChangedListener();
-        mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
-
-        listener.reset();
-        MediaSession2 session1 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertTrue(listener.await());
-        assertTrue(listener.findToken(session1.getToken()));
-
-        listener.reset();
-        session1.close();
-        assertTrue(listener.await());
-        assertFalse(listener.findToken(session1.getToken()));
-
-        listener.reset();
-        MediaSession2 session2 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertTrue(listener.await());
-        assertFalse(listener.findToken(session1.getToken()));
-        assertTrue(listener.findToken(session2.getToken()));
-
-        listener.reset();
-        MediaSession2 session3 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertTrue(listener.await());
-        assertFalse(listener.findToken(session1.getToken()));
-        assertTrue(listener.findToken(session2.getToken()));
-        assertTrue(listener.findToken(session3.getToken()));
-
-        listener.reset();
-        session2.close();
-        assertTrue(listener.await());
-        assertFalse(listener.findToken(session1.getToken()));
-        assertFalse(listener.findToken(session2.getToken()));
-        assertTrue(listener.findToken(session3.getToken()));
-
-        listener.reset();
-        session3.close();
-        assertTrue(listener.await());
-        assertFalse(listener.findToken(session1.getToken()));
-        assertFalse(listener.findToken(session2.getToken()));
-        assertFalse(listener.findToken(session3.getToken()));
-
-        mManager.removeOnSessionTokensChangedListener(listener);
-    }
-
-    @Test
-    public void testRemoveOnSessionTokensChangedListener() throws InterruptedException {
-        TokensChangedListener listener = new TokensChangedListener();
-        mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
-
-        listener.reset();
-        MediaSession2 session1 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertTrue(listener.await());
-
-        mManager.removeOnSessionTokensChangedListener(listener);
-
-        listener.reset();
-        session1.close();
-        assertFalse(listener.await());
-
-        listener.reset();
-        MediaSession2 session2 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertFalse(listener.await());
-
-        listener.reset();
-        MediaSession2 session3 = new MediaSession2.Builder(mContext)
-                .setPlayer(new MockPlayer(0))
-                .setId(UUID.randomUUID().toString())
-                .build();
-        assertFalse(listener.await());
-
-        listener.reset();
-        session2.close();
-        assertFalse(listener.await());
-
-        listener.reset();
-        session3.close();
-        assertFalse(listener.await());
-    }
-
-    private class TokensChangedListener implements OnSessionTokensChangedListener {
-        private CountDownLatch mLatch;
-        private List<SessionToken2> mTokens;
-
-        private void reset() {
-            mLatch = new CountDownLatch(1);
-            mTokens = null;
-        }
-
-        private boolean await() throws InterruptedException {
-            return mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        }
-
-        private boolean findToken(SessionToken2 token) {
-            return mTokens.contains(token);
-        }
-
-        @Override
-        public void onSessionTokensChanged(List<SessionToken2> tokens) {
-            mTokens = tokens;
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSyncTest.java b/tests/tests/media/src/android/media/cts/MediaSyncTest.java
index fe752f8..cc71e1e 100644
--- a/tests/tests/media/src/android/media/cts/MediaSyncTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSyncTest.java
@@ -36,6 +36,8 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.support.test.filters.SmallTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.Surface;
@@ -58,6 +60,8 @@
  * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
  */
+@SmallTest
+@RequiresDevice
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class MediaSyncTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
     private static final String LOG_TAG = "MediaSyncTest";
diff --git a/tests/tests/media/src/android/media/cts/MockMediaLibraryService2.java b/tests/tests/media/src/android/media/cts/MockMediaLibraryService2.java
deleted file mode 100644
index 4c4b3f6..0000000
--- a/tests/tests/media/src/android/media/cts/MockMediaLibraryService2.java
+++ /dev/null
@@ -1,236 +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 static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaLibraryService2;
-import android.media.MediaMetadata2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
-import android.media.SessionToken2;
-import android.media.cts.TestUtils.SyncHandler;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-import java.util.List;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Mock implementation of {@link MediaLibraryService2} for testing.
- */
-public class MockMediaLibraryService2 extends MediaLibraryService2 {
-    // Keep in sync with the AndroidManifest.xml
-    public static final String ID = "TestLibrary";
-
-    public static final String ROOT_ID = "rootId";
-    public static final Bundle EXTRAS = new Bundle();
-
-    public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
-
-    public static final String PARENT_ID = "parent_id";
-    public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
-    public static final String PARENT_ID_ERROR = "parent_id_error";
-
-    public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>();
-    public static final int CHILDREN_COUNT = 100;
-
-    public static final String SEARCH_QUERY = "search_query";
-    public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
-    public static final int SEARCH_TIME_IN_MS = 5000;
-    public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
-
-    public static final List<MediaItem2> SEARCH_RESULT = new ArrayList<>();
-    public static final int SEARCH_RESULT_COUNT = 50;
-
-    private static final DataSourceDesc DATA_SOURCE_DESC =
-            new DataSourceDesc.Builder().setDataSource(new FileDescriptor()).build();
-
-    private static final String TAG = "MockMediaLibrarySvc2";
-
-    static {
-        EXTRAS.putString(ROOT_ID, ROOT_ID);
-    }
-    @GuardedBy("MockMediaLibraryService2.class")
-    private static SessionToken2 sToken;
-
-    private MediaLibrarySession mSession;
-
-    public MockMediaLibraryService2() {
-        super();
-        GET_CHILDREN_RESULT.clear();
-        String getChildrenMediaIdPrefix = "get_children_media_id_";
-        for (int i = 0; i < CHILDREN_COUNT; i++) {
-            GET_CHILDREN_RESULT.add(createMediaItem(getChildrenMediaIdPrefix + i));
-        }
-
-        SEARCH_RESULT.clear();
-        String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
-        for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
-            SEARCH_RESULT.add(createMediaItem(getSearchResultMediaIdPrefix + i));
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-    }
-
-    @Override
-    public MediaLibrarySession onCreateSession(String sessionId) {
-        final MockPlayer player = new MockPlayer(1);
-        final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
-        final Executor executor = (runnable) -> handler.post(runnable);
-        MediaLibrarySessionCallback librarySessionCallback = (MediaLibrarySessionCallback)
-                TestServiceRegistry.getInstance().getSessionCallback();
-        if (librarySessionCallback == null) {
-            // Use default callback
-            librarySessionCallback = new TestLibrarySessionCallback();
-        }
-        mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, executor,
-                librarySessionCallback).setPlayer(player).setId(sessionId).build();
-        return mSession;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    public static SessionToken2 getToken(Context context) {
-        synchronized (MockMediaLibraryService2.class) {
-            if (sToken == null) {
-                sToken = new SessionToken2(context, context.getPackageName(),
-                        MockMediaLibraryService2.class.getName());
-                assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, sToken.getType());
-            }
-            return sToken;
-        }
-    }
-
-    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
-        @Override
-        public LibraryRoot onGetLibraryRoot(MediaLibrarySession session, ControllerInfo controller,
-                Bundle rootHints) {
-            return new LibraryRoot(ROOT_ID, EXTRAS);
-        }
-
-        @Override
-        public MediaItem2 onGetItem(MediaLibrarySession session, ControllerInfo controller,
-                String mediaId) {
-            if (MEDIA_ID_GET_ITEM.equals(mediaId)) {
-                return createMediaItem(mediaId);
-            } else {
-                return null;
-            }
-        }
-
-        @Override
-        public List<MediaItem2> onGetChildren(MediaLibrarySession session,
-                ControllerInfo controller, String parentId, int page, int pageSize, Bundle extras) {
-            if (PARENT_ID.equals(parentId)) {
-                return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize);
-            } else if (PARENT_ID_ERROR.equals(parentId)) {
-                return null;
-            }
-            // Includes the case of PARENT_ID_NO_CHILDREN.
-            return new ArrayList<>();
-        }
-
-        @Override
-        public void onSearch(MediaLibrarySession session, ControllerInfo controllerInfo,
-                String query, Bundle extras) {
-            if (SEARCH_QUERY.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
-                        extras);
-            } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
-                // Searching takes some time. Notify after 5 seconds.
-                Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSession.notifySearchResultChanged(
-                                controllerInfo, query, SEARCH_RESULT_COUNT, extras);
-                    }
-                }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
-            } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
-                mSession.notifySearchResultChanged(controllerInfo, query, 0, extras);
-            } else {
-                // TODO: For the error case, how should we notify the browser?
-            }
-        }
-
-        @Override
-        public List<MediaItem2> onGetSearchResult(MediaLibrarySession session,
-                ControllerInfo controllerInfo, String query, int page, int pageSize,
-                Bundle extras) {
-            if (SEARCH_QUERY.equals(query)) {
-                return getPaginatedResult(SEARCH_RESULT, page, pageSize);
-            } else {
-                return null;
-            }
-        }
-    }
-
-    private List<MediaItem2> getPaginatedResult(List<MediaItem2> items, int page, int pageSize) {
-        if (items == null) {
-            return null;
-        } else if (items.size() == 0) {
-            return new ArrayList<>();
-        }
-
-        final int totalItemCount = items.size();
-        int fromIndex = (page - 1) * pageSize;
-        int toIndex = Math.min(page * pageSize, totalItemCount);
-
-        List<MediaItem2> paginatedResult = new ArrayList<>();
-        try {
-            // The case of (fromIndex >= totalItemCount) will throw exception below.
-            paginatedResult = items.subList(fromIndex, toIndex);
-        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
-            Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
-                    + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
-        }
-        return paginatedResult;
-    }
-
-    private MediaItem2 createMediaItem(String mediaId) {
-        Context context = MockMediaLibraryService2.this;
-        return new MediaItem2.Builder(0 /* Flags */)
-                .setMediaId(mediaId)
-                .setDataSourceDesc(DATA_SOURCE_DESC)
-                .setMetadata(new MediaMetadata2.Builder()
-                                .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
-                                .build())
-                .build();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MockMediaSessionService2.java b/tests/tests/media/src/android/media/cts/MockMediaSessionService2.java
deleted file mode 100644
index 330637e..0000000
--- a/tests/tests/media/src/android/media/cts/MockMediaSessionService2.java
+++ /dev/null
@@ -1,92 +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.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.media.MediaSession2;
-import android.media.MediaSession2.SessionCallback;
-import android.media.MediaSessionService2;
-import android.media.cts.TestUtils.SyncHandler;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-/**
- * Mock implementation of {@link MediaSessionService2} for testing.
- */
-public class MockMediaSessionService2 extends MediaSessionService2 {
-    // Keep in sync with the AndroidManifest.xml
-    public static final String ID = "TestSession";
-
-    private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
-    private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
-
-    private NotificationChannel mDefaultNotificationChannel;
-    private MediaSession2 mSession;
-    private NotificationManager mNotificationManager;
-
-    @Override
-    public void onCreate() {
-        TestServiceRegistry.getInstance().setServiceInstance(this);
-        super.onCreate();
-        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-
-    @Override
-    public MediaSession2 onCreateSession(String sessionId) {
-        final MockPlayer player = new MockPlayer(1);
-        final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
-        final Executor executor = (runnable) -> handler.post(runnable);
-        SessionCallback sessionCallback = TestServiceRegistry.getInstance().getSessionCallback();
-        if (sessionCallback == null) {
-            // Ensures non-null
-            sessionCallback = new SessionCallback() {};
-        }
-        mSession = new MediaSession2.Builder(this)
-                .setPlayer(player)
-                .setSessionCallback(executor, sessionCallback)
-                .setId(sessionId).build();
-        return mSession;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TestServiceRegistry.getInstance().cleanUp();
-    }
-
-    @Override
-    public MediaNotification onUpdateNotification() {
-        if (mDefaultNotificationChannel == null) {
-            mDefaultNotificationChannel = new NotificationChannel(
-                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
-                    DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
-                    NotificationManager.IMPORTANCE_DEFAULT);
-            mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
-        }
-        Notification notification = new Notification.Builder(
-                this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
-                .setContentTitle(getPackageName())
-                .setContentText("Dummt test notification")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
-        return new MediaNotification(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MockPlayer.java b/tests/tests/media/src/android/media/cts/MockPlayer.java
deleted file mode 100644
index f73f706..0000000
--- a/tests/tests/media/src/android/media/cts/MockPlayer.java
+++ /dev/null
@@ -1,237 +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.AudioAttributes;
-import android.media.DataSourceDesc;
-import android.media.MediaPlayerBase;
-import androidx.annotation.NonNull;
-import android.util.ArrayMap;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * A mock implementation of {@link MediaPlayerBase} for testing.
- */
-public class MockPlayer extends MediaPlayerBase {
-    public final CountDownLatch mCountDownLatch;
-
-    public boolean mPlayCalled;
-    public boolean mPauseCalled;
-    public boolean mStopCalled;
-    public boolean mPrepareCalled;
-    public boolean mSeekToCalled;
-    public long mSeekPosition;
-    public long mCurrentPosition;
-    public long mBufferedPosition;
-    public @PlayerState int mLastPlayerState;
-
-    public ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
-
-    private AudioAttributes mAudioAttributes;
-
-    public MockPlayer(int count) {
-        mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
-    }
-
-    @Override
-    public void close() {
-        // no-op
-    }
-
-    @Override
-    public void reset() {
-        // no-op
-    }
-
-    @Override
-    public void play() {
-        mPlayCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    @Override
-    public void pause() {
-        mPauseCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    // TODO: Uncomment or remove
-    /*
-    @Override
-    public void stop() {
-        mStopCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-    */
-
-    @Override
-    public void prepare() {
-        mPrepareCalled = true;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    @Override
-    public void seekTo(long pos) {
-        mSeekToCalled = true;
-        mSeekPosition = pos;
-        if (mCountDownLatch != null) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    @Override
-    public void skipToNext() {
-        // No-op. This skipToNext() means 'skip to next item in the setNextDataSources()'
-    }
-
-    @Override
-    public int getPlayerState() {
-        return mLastPlayerState;
-    }
-
-    @Override
-    public long getCurrentPosition() {
-        return mCurrentPosition;
-    }
-
-    @Override
-    public long getBufferedPosition() {
-        return mBufferedPosition;
-    }
-
-    @Override
-    public int getBufferingState() {
-        // TODO: implement this
-        return -1;
-    }
-
-    @Override
-    public void registerPlayerEventCallback(@NonNull Executor executor,
-            @NonNull PlayerEventCallback callback) {
-        mCallbacks.put(callback, executor);
-    }
-
-    @Override
-    public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback callback) {
-        mCallbacks.remove(callback);
-    }
-
-    public void notifyPlaybackState(final int state) {
-        mLastPlayerState = state;
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            final PlayerEventCallback callback = mCallbacks.keyAt(i);
-            final Executor executor = mCallbacks.valueAt(i);
-            executor.execute(() -> callback.onPlayerStateChanged(this, state));
-        }
-    }
-
-    public void notifyCurrentDataSourceChanged(DataSourceDesc dsd) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            final PlayerEventCallback callback = mCallbacks.keyAt(i);
-            final Executor executor = mCallbacks.valueAt(i);
-            executor.execute(() -> callback.onCurrentDataSourceChanged(this, dsd));
-        }
-    }
-
-    public void notifyMediaPrepared(DataSourceDesc dsd) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            final PlayerEventCallback callback = mCallbacks.keyAt(i);
-            final Executor executor = mCallbacks.valueAt(i);
-            executor.execute(() -> callback.onMediaPrepared(this, dsd));
-        }
-    }
-
-    public void notifyBufferingStateChanged(DataSourceDesc dsd, @BuffState int buffState) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            final PlayerEventCallback callback = mCallbacks.keyAt(i);
-            final Executor executor = mCallbacks.valueAt(i);
-            executor.execute(() -> callback.onBufferingStateChanged(this, dsd, buffState));
-        }
-    }
-
-    public void notifyError(int what) {
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            final PlayerEventCallback callback = mCallbacks.keyAt(i);
-            final Executor executor = mCallbacks.valueAt(i);
-            // TODO: Uncomment or remove
-            //executor.execute(() -> callback.onError(null, what, 0));
-        }
-    }
-
-    @Override
-    public void setAudioAttributes(AudioAttributes attributes) {
-        mAudioAttributes = attributes;
-    }
-
-    @Override
-    public AudioAttributes getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    @Override
-    public void setDataSource(@NonNull DataSourceDesc dsd) {
-        // TODO: Implement this
-    }
-
-    @Override
-    public void setNextDataSource(@NonNull DataSourceDesc dsd) {
-        // TODO: Implement this
-    }
-
-    @Override
-    public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
-        // TODO: Implement this
-    }
-
-    @Override
-    public DataSourceDesc getCurrentDataSource() {
-        // TODO: Implement this
-        return null;
-    }
-
-    @Override
-    public void loopCurrent(boolean loop) {
-        // TODO: implement this
-    }
-
-    @Override
-    public void setPlaybackSpeed(float speed) {
-        // TODO: implement this
-    }
-
-    @Override
-    public void setPlayerVolume(float volume) {
-        // TODO: implement this
-    }
-
-    @Override
-    public float getPlayerVolume() {
-        // TODO: implement this
-        return -1;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MockPlaylistAgent.java b/tests/tests/media/src/android/media/cts/MockPlaylistAgent.java
deleted file mode 100644
index ca7bc92..0000000
--- a/tests/tests/media/src/android/media/cts/MockPlaylistAgent.java
+++ /dev/null
@@ -1,143 +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.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.MediaPlaylistAgent;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * A mock implementation of {@link MediaPlaylistAgent} for testing.
- * <p>
- * Do not use mockito for {@link MediaPlaylistAgent}. Instead, use this.
- * Mocks created from mockito should not be shared across different threads.
- */
-public class MockPlaylistAgent extends MediaPlaylistAgent {
-    public final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-
-    public List<MediaItem2> mPlaylist;
-    public MediaMetadata2 mMetadata;
-    public MediaItem2 mItem;
-    public int mIndex = -1;
-    public @RepeatMode int mRepeatMode = -1;
-    public @ShuffleMode int mShuffleMode = -1;
-
-    public boolean mSetPlaylistCalled;
-    public boolean mUpdatePlaylistMetadataCalled;
-    public boolean mAddPlaylistItemCalled;
-    public boolean mRemovePlaylistItemCalled;
-    public boolean mReplacePlaylistItemCalled;
-    public boolean mSkipToPlaylistItemCalled;
-    public boolean mSkipToPreviousItemCalled;
-    public boolean mSkipToNextItemCalled;
-    public boolean mSetRepeatModeCalled;
-    public boolean mSetShuffleModeCalled;
-
-    @Override
-    public List<MediaItem2> getPlaylist() {
-        return mPlaylist;
-    }
-
-    @Override
-    public void setPlaylist(List<MediaItem2> list, MediaMetadata2 metadata) {
-        mSetPlaylistCalled = true;
-        mPlaylist = list;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public MediaMetadata2 getPlaylistMetadata() {
-        return mMetadata;
-    }
-
-    @Override
-    public void updatePlaylistMetadata(MediaMetadata2 metadata) {
-        mUpdatePlaylistMetadataCalled = true;
-        mMetadata = metadata;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void addPlaylistItem(int index, MediaItem2 item) {
-        mAddPlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void removePlaylistItem(MediaItem2 item) {
-        mRemovePlaylistItemCalled = true;
-        mItem = item;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void replacePlaylistItem(int index, MediaItem2 item) {
-        mReplacePlaylistItemCalled = true;
-        mIndex = index;
-        mItem = item;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void skipToPlaylistItem(MediaItem2 item) {
-        mSkipToPlaylistItemCalled = true;
-        mItem = item;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void skipToPreviousItem() {
-        mSkipToPreviousItemCalled = true;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void skipToNextItem() {
-        mSkipToNextItemCalled = true;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public int getRepeatMode() {
-        return mRepeatMode;
-    }
-
-    @Override
-    public void setRepeatMode(int repeatMode) {
-        mSetRepeatModeCalled = true;
-        mRepeatMode = repeatMode;
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public int getShuffleMode() {
-        return mShuffleMode;
-    }
-
-    @Override
-    public void setShuffleMode(int shuffleMode) {
-        mSetShuffleModeCalled = true;
-        mShuffleMode = shuffleMode;
-        mCountDownLatch.countDown();
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index 313cfe3..52b6d5d 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -27,10 +27,13 @@
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
+import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.support.test.filters.SmallTest;
 import android.util.Log;
 import android.view.Surface;
 import android.webkit.cts.CtsTestServer;
@@ -44,14 +47,28 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.RandomAccessFile;
+import java.net.Socket;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Adler32;
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.CharArrayBuffer;
 
+@SmallTest
+@RequiresDevice
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class NativeDecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
@@ -85,6 +102,25 @@
 
     // check that native extractor behavior matches java extractor
 
+    private void compareArrays(String message, int[] a1, int[] a2) {
+        if (a1 == a2) {
+            return;
+        }
+
+        assertNotNull(message + ": array 1 is null", a1);
+        assertNotNull(message + ": array 2 is null", a2);
+
+        assertEquals(message + ": arraylengths differ", a1.length, a2.length);
+        int length = a1.length;
+
+        for (int i = 0; i < length; i++)
+            if (a1[i] != a2[i]) {
+                Log.i("@@@@", Arrays.toString(a1));
+                Log.i("@@@@", Arrays.toString(a2));
+                fail(message + ": at index " + i);
+            }
+    }
+
     public void testExtractor() throws Exception {
         testExtractor(R.raw.sinesweepogg);
         testExtractor(R.raw.sinesweepmp3lame);
@@ -103,14 +139,44 @@
         testExtractor(foo.getAssetUrl("noiseandchirps.ogg"));
         testExtractor(foo.getAssetUrl("ringer.mp3"));
         testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"));
+
+        String[] keys = new String[] {"header0", "header1"};
+        String[] values = new String[] {"value0", "value1"};
+        testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), keys, values);
+        HttpRequest req = foo.getLastRequest("noiseandchirps.ogg");
+        for (int i = 0; i < keys.length; i++) {
+            String key = keys[i];
+            String value = values[i];
+            Header[] header = req.getHeaders(key);
+            assertTrue("expecting " + key + ":" + value + ", saw " + Arrays.toString(header),
+                    header.length == 1 && header[0].getValue().equals(value));
+        }
+
+        String[] emptyArray = new String[0];
+        testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), emptyArray, emptyArray);
     }
 
     private void testExtractor(String path) throws Exception {
-        int[] jsizes = getSampleSizes(path);
-        int[] nsizes = getSampleSizesNativePath(path);
+        testExtractor(path, null, null);
+    }
 
-        //Log.i("@@@", Arrays.toString(jsizes));
-        assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
+    /**
+     * |keys| and |values| should be arrays of the same length.
+     *
+     * If keys or values is null, test {@link MediaExtractor#setDataSource(String)}
+     * and NDK counter part, i.e. set data source without headers.
+     *
+     * If keys or values is zero length, test {@link MediaExtractor#setDataSource(String, Map))}
+     * and NDK counter part with null headers.
+     *
+     */
+    private void testExtractor(String path, String[] keys, String[] values) throws Exception {
+        int[] jsizes = getSampleSizes(path, keys, values);
+        int[] nsizes = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ false);
+        int[] nsizes2 = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ true);
+
+        compareArrays("different samplesizes", jsizes, nsizes);
+        compareArrays("different samplesizes native source", jsizes, nsizes2);
     }
 
     private void testExtractor(int res) throws Exception {
@@ -122,13 +188,27 @@
                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
 
         fd.close();
-        //Log.i("@@@", Arrays.toString(jsizes));
-        assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
+        compareArrays("different samples", jsizes, nsizes);
     }
 
-    private static int[] getSampleSizes(String path) throws IOException {
+    private static int[] getSampleSizes(String path, String[] keys, String[] values) throws IOException {
         MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(path);
+        if (keys == null || values == null) {
+            ex.setDataSource(path);
+        } else {
+            Map<String, String> headers = null;
+            int numheaders = Math.min(keys.length, values.length);
+            for (int i = 0; i < numheaders; i++) {
+                if (headers == null) {
+                    headers = new HashMap<>();
+                }
+                String key = keys[i];
+                String value = values[i];
+                headers.put(key, value);
+            }
+            ex.setDataSource(path, headers);
+        }
+
         return getSampleSizes(ex);
     }
 
@@ -172,6 +252,9 @@
             foo.add(ex.getSampleTrackIndex());
             foo.add(ex.getSampleFlags());
             foo.add((int)ex.getSampleTime()); // just the low bits should be OK
+            byte foobar[] = new byte[n];
+            buf.get(foobar, 0, n);
+            foo.add((int)adler32(foobar));
             ex.advance();
         }
 
@@ -183,7 +266,8 @@
     }
 
     private static native int[] getSampleSizesNative(int fd, long offset, long size);
-    private static native int[] getSampleSizesNativePath(String path);
+    private static native int[] getSampleSizesNativePath(
+            String path, String[] keys, String[] values, boolean testNativeSource);
 
     public void testExtractorFileDurationNative() throws Exception {
         int res = R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz;
@@ -220,11 +304,14 @@
 
     public void testExtractorCachedDurationNative() throws Exception {
         CtsTestServer foo = new CtsTestServer(mContext);
-        long cachedDurationUs = getExtractorCachedDurationNative(foo.getAssetUrl("ringer.mp3"));
+        String url = foo.getAssetUrl("ringer.mp3");
+        long cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ false);
         assertTrue("cached duration negative", cachedDurationUs >= 0);
+        cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ true);
+        assertTrue("cached duration negative native source", cachedDurationUs >= 0);
     }
 
-    private static native long getExtractorCachedDurationNative(String uri);
+    private static native long getExtractorCachedDurationNative(String uri, boolean testNativeSource);
 
     public void testDecoder() throws Exception {
         int testsRun =
@@ -253,6 +340,18 @@
         }
     }
 
+    public void testDataSourceAudioOnly() throws Exception {
+        int testsRun = testDecoder(
+                R.raw.loudsoftmp3,
+                /* wrapFd */ true, /* useCallback */ false) +
+                testDecoder(
+                        R.raw.loudsoftaac,
+                        /* wrapFd */ false, /* useCallback */ false);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
+    }
+
     public void testDataSourceWithCallback() throws Exception {
         int testsRun = testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz,
                 /* wrapFd */ true, /* useCallback */ true);
@@ -272,17 +371,21 @@
 
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
 
-        int[] jdata = getDecodedData(
+        int[] jdata1 = getDecodedData(
                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        int[] ndata = getDecodedDataNative(
+        int[] jdata2 = getDecodedData(
+                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        int[] ndata1 = getDecodedDataNative(
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
+                useCallback);
+        int[] ndata2 = getDecodedDataNative(
                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
                 useCallback);
 
         fd.close();
-        Log.i("@@@", Arrays.toString(jdata));
-        Log.i("@@@", Arrays.toString(ndata));
-        assertEquals("number of samples differs", jdata.length, ndata.length);
-        assertTrue("different decoded data", Arrays.equals(jdata, ndata));
+        compareArrays("inconsistent java decoder", jdata1, jdata2);
+        compareArrays("inconsistent native decoder", ndata1, ndata2);
+        compareArrays("different decoded data", jdata1, ndata1);
         return 1;
     }
 
@@ -329,7 +432,7 @@
                     Log.i("@@@@", "track " + t + " buffer " + bufidx);
                     ByteBuffer buf = inbuffers[t][bufidx];
                     int sampleSize = ex.readSampleData(buf, 0);
-                    Log.i("@@@@", "read " + sampleSize);
+                    Log.i("@@@@", "read " + sampleSize + " @ " + ex.getSampleTime());
                     if (sampleSize < 0) {
                         sampleSize = 0;
                         sawInputEOS[t] = true;
@@ -428,21 +531,12 @@
         dst.add( (int) (sum & 0xffffffff));
     }
 
+    private final static Adler32 checksummer = new Adler32(); 
     // simple checksum computed over every decoded buffer
-    static long adler32(byte[] input) {
-        int a = 1;
-        int b = 0;
-        for (int i = 0; i < input.length; i++) {
-            int unsignedval = input[i];
-            if (unsignedval < 0) {
-                unsignedval = 256 + unsignedval;
-            }
-            a += unsignedval;
-            b += a;
-        }
-        a = a % 65521;
-        b = b % 65521;
-        long ret = b * 65536 + a;
+    static int adler32(byte[] input) {
+        checksummer.reset();
+        checksummer.update(input);
+        int ret = (int) checksummer.getValue();
         Log.i("@@@", "adler " + input.length + "/" + ret);
         return ret;
     }
@@ -686,5 +780,144 @@
     }
 
     private static native boolean testCryptoInfoNative();
+
+    public void testMediaFormat() throws Exception {
+        assertTrue("native mediaformat failed, see log for details", testMediaFormatNative());
+    }
+
+    private static native boolean testMediaFormatNative();
+
+    public void testAMediaDataSourceClose() throws Throwable {
+
+        final CtsTestServer slowServer = new SlowCtsTestServer();
+        final String url = slowServer.getAssetUrl("noiseandchirps.ogg");
+        final long ds = createAMediaDataSource(url);
+        final long ex = createAMediaExtractor();
+
+        try {
+            setAMediaExtractorDataSourceAndFailIfAnr(ex, ds);
+        } finally {
+            slowServer.shutdown();
+            deleteAMediaExtractor(ex);
+            deleteAMediaDataSource(ds);
+        }
+
+    }
+
+    private void setAMediaExtractorDataSourceAndFailIfAnr(final long ex, final long ds)
+            throws Throwable {
+        final Monitor setAMediaExtractorDataSourceDone = new Monitor();
+        final int HEAD_START_MILLIS = 1000;
+        final int ANR_TIMEOUT_MILLIS = 2500;
+        final int JOIN_TIMEOUT_MILLIS = 1500;
+
+        Thread setAMediaExtractorDataSourceThread = new Thread() {
+            public void run() {
+                setAMediaExtractorDataSource(ex, ds);
+                setAMediaExtractorDataSourceDone.signal();
+            }
+        };
+
+        try {
+            setAMediaExtractorDataSourceThread.start();
+            Thread.sleep(HEAD_START_MILLIS);
+            closeAMediaDataSource(ds);
+            boolean closed = setAMediaExtractorDataSourceDone.waitForSignal(ANR_TIMEOUT_MILLIS);
+            assertTrue("close took longer than " + ANR_TIMEOUT_MILLIS, closed);
+        } finally {
+            setAMediaExtractorDataSourceThread.join(JOIN_TIMEOUT_MILLIS);
+        }
+
+    }
+
+    private class SlowCtsTestServer extends CtsTestServer {
+
+        private static final int SERVER_DELAY_MILLIS = 5000;
+        private final CountDownLatch mDisconnected = new CountDownLatch(1);
+
+        SlowCtsTestServer() throws Exception {
+            super(mContext);
+        }
+
+        @Override
+        protected DefaultHttpServerConnection createHttpServerConnection() {
+            return new SlowHttpServerConnection(mDisconnected, SERVER_DELAY_MILLIS);
+        }
+
+        @Override
+        public void shutdown() {
+            mDisconnected.countDown();
+            super.shutdown();
+        }
+    }
+
+    private static class SlowHttpServerConnection extends DefaultHttpServerConnection {
+
+        private final CountDownLatch mDisconnected;
+        private final int mDelayMillis;
+
+        public SlowHttpServerConnection(CountDownLatch disconnected, int delayMillis) {
+            mDisconnected = disconnected;
+            mDelayMillis = delayMillis;
+        }
+
+        @Override
+        protected SessionOutputBuffer createHttpDataTransmitter(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return createSessionOutputBuffer(socket, buffersize, params);
+        }
+
+        SessionOutputBuffer createSessionOutputBuffer(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return new SocketOutputBuffer(socket, buffersize, params) {
+                @Override
+                public void write(byte[] b) throws IOException {
+                    write(b, 0, b.length);
+                }
+
+                @Override
+                public void write(byte[] b, int off, int len) throws IOException {
+                    while (len-- > 0) {
+                        write(b[off++]);
+                    }
+                }
+
+                @Override
+                public void writeLine(String s) throws IOException {
+                    delay();
+                    super.writeLine(s);
+                }
+
+                @Override
+                public void writeLine(CharArrayBuffer buffer) throws IOException {
+                    delay();
+                    super.writeLine(buffer);
+                }
+
+                @Override
+                public void write(int b) throws IOException {
+                    delay();
+                    super.write(b);
+                }
+
+                private void delay() throws IOException {
+                    try {
+                        mDisconnected.await(mDelayMillis, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException e) {
+                        // Ignored
+                    }
+                }
+
+            };
+        }
+    }
+
+    private static native long createAMediaExtractor();
+    private static native long createAMediaDataSource(String url);
+    private static native int  setAMediaExtractorDataSource(long ex, long ds);
+    private static native void closeAMediaDataSource(long ds);
+    private static native void deleteAMediaExtractor(long ex);
+    private static native void deleteAMediaDataSource(long ds);
+
 }
 
diff --git a/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
index b1790b5..c1df612 100644
--- a/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
@@ -177,6 +177,14 @@
         assertEquals("ClearKey CDM", value.toString());
     }
 
+    public void testPropertyByteArray() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
     public void testUnknownPropertyString() throws Exception {
         StringBuffer value = new StringBuffer();
 
@@ -275,6 +283,8 @@
     private static native boolean testGetPropertyStringNative(final byte[] uuid,
             final String name, StringBuffer value);
 
+    private static native boolean testPropertyByteArrayNative(final byte[] uuid);
+
     private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
 
     private static native boolean testQueryKeyStatusNative(final byte[] uuid);
@@ -297,3 +307,4 @@
             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
     }
 }
+
diff --git a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
index 864a0fa..3ff68c3 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
@@ -16,6 +16,7 @@
 package android.media.cts;
 
 import android.app.ActivityManager;
+import android.content.res.AssetFileDescriptor;
 import android.media.cts.R;
 
 
@@ -33,6 +34,9 @@
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class RingtoneManagerTest
         extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
@@ -103,27 +107,21 @@
         super.tearDown();
     }
 
-    private boolean hasAudioOutput() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    private boolean isTV() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
+    private boolean isSupportedDevice() {
+        final PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
     }
 
     public void testConstructors() {
+        if (!isSupportedDevice()) return;
+
         new RingtoneManager(mActivity);
         new RingtoneManager(mContext);
     }
 
     public void testAccessMethods() {
-        if (isTV()) {
-            return;
-        }
-        if (!hasAudioOutput()) {
-            Log.i(TAG, "Skipping testAccessMethods(): device doesn't have audio output.");
-            return;
-        }
+        if (!isSupportedDevice()) return;
 
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
@@ -139,6 +137,27 @@
         assertEquals(uri, RingtoneManager.getActualDefaultRingtoneUri(mContext,
                 RingtoneManager.TYPE_RINGTONE));
 
+        try (AssetFileDescriptor afd = RingtoneManager.openDefaultRingtoneUri(
+                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
+            assertNotNull(afd);
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+
+        Uri bogus = Uri.parse("content://a_bogus_uri");
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, bogus);
+        assertEquals(bogus, RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                RingtoneManager.TYPE_RINGTONE));
+
+        try (AssetFileDescriptor ignored = RingtoneManager.openDefaultRingtoneUri(
+                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
+            fail("FileNotFoundException should be thrown for a bogus Uri.");
+        } catch (FileNotFoundException e) {
+            // Expected.
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+
         assertEquals(Settings.System.DEFAULT_RINGTONE_URI,
                 RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE));
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI,
@@ -151,6 +170,8 @@
     }
 
     public void testSetType() {
+        if (!isSupportedDevice()) return;
+
         mRingtoneManager.setType(RingtoneManager.TYPE_ALARM);
         assertEquals(AudioManager.STREAM_ALARM, mRingtoneManager.inferStreamType());
         Cursor c = mRingtoneManager.getCursor();
@@ -160,13 +181,7 @@
     }
 
     public void testStopPreviousRingtone() {
-        if (isTV()) {
-            return;
-        }
-        if (!hasAudioOutput()) {
-            Log.i(TAG, "Skipping testStopPreviousRingtone(): device doesn't have audio output.");
-            return;
-        }
+        if (!isSupportedDevice()) return;
 
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
@@ -186,4 +201,15 @@
         mRingtoneManager.stopPreviousRingtone();
         assertFalse(newRingtone.isPlaying());
     }
+
+    public void testQuery() {
+        if (!isSupportedDevice()) return;
+
+        final Cursor c = mRingtoneManager.getCursor();
+        assertTrue(c.moveToFirst());
+        assertTrue(c.getInt(RingtoneManager.ID_COLUMN_INDEX) >= 0);
+        assertTrue(c.getString(RingtoneManager.TITLE_COLUMN_INDEX) != null);
+        assertTrue(c.getString(RingtoneManager.URI_COLUMN_INDEX),
+                c.getString(RingtoneManager.URI_COLUMN_INDEX).startsWith("content://"));
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index 28d9564..840dab5 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
 
 import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
@@ -27,13 +28,19 @@
 import android.media.AudioRouting;
 import android.media.AudioTrack;
 import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
+import android.media.DataSourceDesc;
+import android.media.FileDataSourceDesc;
 import android.media.MediaFormat;
 import android.media.MediaRecorder;
+import android.media.cts.TestUtils.Monitor;
 
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
 
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
@@ -49,6 +56,8 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
@@ -740,6 +749,221 @@
         }
     }
 
+    private MediaPlayer2 allocMediaPlayer2() throws Exception {
+        final int resid = R.raw.testmp3_2;
+        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(resid);
+
+        MediaPlayer2 mediaPlayer2 = new MediaPlayer2(mContext);
+        mediaPlayer2.setAudioAttributes(new AudioAttributes.Builder().build());
+        mediaPlayer2.setDataSource(new FileDataSourceDesc.Builder()
+                .setDataSource(ParcelFileDescriptor.dup(afd.getFileDescriptor()),
+                    afd.getStartOffset(), afd.getLength())
+                .build());
+        afd.close();
+
+        Monitor onPrepareCalled = new Monitor();
+        Monitor onPlayCalled = new Monitor();
+        Monitor onSeekToCalled = new Monitor();
+        Monitor onLoopCurrentCalled = new Monitor();
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+
+        MediaPlayer2.EventCallback ecb = new MediaPlayer2.EventCallback() {
+            @Override
+            public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                    onPrepareCalled.signal();
+                }
+            }
+
+            @Override
+            public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) {
+                if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
+                    onPlayCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_LOOP_CURRENT) {
+                    onLoopCurrentCalled.signal();
+                } else if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO) {
+                    onSeekToCalled.signal();
+                }
+            }
+        };
+
+        mediaPlayer2.registerEventCallback(executor, ecb);
+        onPrepareCalled.reset();
+        mediaPlayer2.prepare();
+        onPrepareCalled.waitForSignal();
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        PowerManager.WakeLock wakeLock =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                        "RoutingTest");
+        mediaPlayer2.setWakeLock(wakeLock);
+
+        assertFalse(mediaPlayer2.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
+        onPlayCalled.reset();
+        mediaPlayer2.play();
+        onPlayCalled.waitForSignal();
+        assertTrue(mediaPlayer2.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
+
+        mediaPlayer2.unregisterEventCallback(ecb);
+        executor.shutdown();
+
+        return mediaPlayer2;
+    }
+
+    public void test_mediaPlayer2_preferredDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer2 mediaPlayer2 = allocMediaPlayer2();
+
+        // None selected (new MediaPlayer2), so check for default
+        assertNull(mediaPlayer2.getPreferredDevice());
+        // resets to default
+        assertTrue(mediaPlayer2.setPreferredDevice(null));
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+        for (int index = 0; index < deviceList.length; index++) {
+            assertTrue(mediaPlayer2.setPreferredDevice(deviceList[index]));
+            assertTrue(mediaPlayer2.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(mediaPlayer2.setPreferredDevice(null));
+        assertNull(mediaPlayer2.getPreferredDevice());
+        mediaPlayer2.close();
+    }
+
+    public void test_mediaPlayer2_getRoutedDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer2 mediaPlayer2 = allocMediaPlayer2();
+
+        // Sleep for 1s to ensure the output device open
+        SystemClock.sleep(1000);
+
+        // No explicit route
+        AudioDeviceInfo routedDevice = mediaPlayer2.getRoutedDevice();
+        assertNotNull(routedDevice);
+
+        mediaPlayer2.close();
+    }
+
+    public void test_MediaPlayer2_RoutingListener() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer2 mediaPlayer2 = allocMediaPlayer2();
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+        // add a listener
+        mediaPlayer2.addOnRoutingChangedListener(listener, null);
+
+        // remove listeners
+        // remove a listener we didn't add
+        mediaPlayer2.removeOnRoutingChangedListener(someOtherListener);
+        // remove a valid listener
+        mediaPlayer2.removeOnRoutingChangedListener(listener);
+
+        Looper myLooper = prepareIfNeededLooper();
+
+        mediaPlayer2.addOnRoutingChangedListener(listener, new Handler());
+        mediaPlayer2.removeOnRoutingChangedListener(listener);
+
+        mediaPlayer2.close();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+
+    public void test_MediaPlayer2_RoutingChangedCallback() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer2 mediaPlayer2 = allocMediaPlayer2();
+        AudioRoutingListener listener = new AudioRoutingListener();
+        mediaPlayer2.addOnRoutingChangedListener(listener, null);
+
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (deviceList.length < 2) {
+            // The available output device is less than 2, we can't switch output device.
+            return;
+        }
+        for (int index = 0; index < deviceList.length; index++) {
+            assertTrue(mediaPlayer2.setPreferredDevice(deviceList[index]));
+            boolean routingChanged = false;
+            for (int i = 0; i < MAX_WAITING_ROUTING_CHANGED_COUNT; i++) {
+                // Create a new CountDownLatch in case it is triggered by previous routing change.
+                mRoutingChangedLatch = new CountDownLatch(1);
+                try {
+                    mRoutingChangedLatch.await(WAIT_ROUTING_CHANGE_TIME_MS, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                }
+                AudioDeviceInfo routedDevice = mediaPlayer2.getRoutedDevice();
+                if (routedDevice == null) {
+                    continue;
+                }
+                if (routedDevice.getId() == deviceList[index].getId()) {
+                    routingChanged = true;
+                    break;
+                }
+            }
+            assertTrue("Switching to device" + deviceList[index].getType() + " failed",
+                    routingChanged);
+        }
+
+        mediaPlayer2.removeOnRoutingChangedListener(listener);
+        mediaPlayer2.close();
+    }
+
+    public void test_mediaPlayer2_incallMusicRoutingPermissions() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        // only apps with MODIFY_PHONE_STATE permission can route playback
+        // to the uplink stream during a phone call, so this test makes sure that
+        // audio is re-routed to default device when the permission is missing
+
+        AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
+        if (telephonyDevice == null) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer2 mediaPlayer2 = null;
+
+        try {
+            mediaPlayer2 = allocMediaPlayer2();
+
+            mediaPlayer2.setPreferredDevice(telephonyDevice);
+            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY,
+                    mediaPlayer2.getPreferredDevice().getType());
+
+            // Sleep for 1s to ensure the output device open
+            SystemClock.sleep(1000);
+            assertTrue(mediaPlayer2.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
+
+        } finally {
+            if (mediaPlayer2 != null) {
+                mediaPlayer2.close();
+            }
+            mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+    }
+
     private MediaRecorder allocMediaRecorder() throws Exception {
         final String outputPath = new File(Environment.getExternalStorageDirectory(),
             "record.out").getAbsolutePath();
diff --git a/tests/tests/media/src/android/media/cts/SafeWaitObject.java b/tests/tests/media/src/android/media/cts/SafeWaitObject.java
new file mode 100644
index 0000000..2346c0d
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/SafeWaitObject.java
@@ -0,0 +1,44 @@
+/*
+ * 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.media.cts;
+
+/**
+ * Helper class to simplify the handling of spurious wakeups in Object.wait()
+ */
+final class SafeWaitObject {
+    private boolean mQuit = false;
+
+    public void safeNotify() {
+        synchronized (this) {
+            mQuit = true;
+            this.notify();
+        }
+    }
+
+    public void safeWait(long millis) throws InterruptedException {
+        final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+        synchronized (this) {
+            while (!mQuit) {
+                final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+                if (timeToWait < 0) {
+                    break;
+                }
+                this.wait(timeToWait);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java b/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java
new file mode 100644
index 0000000..cff9f08
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.media.cts;
+
+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 android.media.Session2Command;
+import android.media.Session2CommandGroup;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.Session2CommandGroup}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Session2CommandGroupTest {
+    private final int TEST_COMMAND_CODE_1 = 10000;
+    private final int TEST_COMMAND_CODE_2 = 10001;
+    private final int TEST_COMMAND_CODE_3 = 10002;
+
+    @Test
+    public void testHasCommand() {
+        Session2Command testCommand = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(TEST_COMMAND_CODE_1);
+        Session2CommandGroup commandGroup = builder.build();
+        assertTrue(commandGroup.hasCommand(TEST_COMMAND_CODE_1));
+        assertTrue(commandGroup.hasCommand(testCommand));
+        assertFalse(commandGroup.hasCommand(TEST_COMMAND_CODE_2));
+    }
+
+    @Test
+    public void testGetCommands() {
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(TEST_COMMAND_CODE_1);
+        Session2CommandGroup commandGroup = builder.build();
+
+        Set<Session2Command> commands = commandGroup.getCommands();
+        assertTrue(commands.contains(new Session2Command(TEST_COMMAND_CODE_1)));
+        assertFalse(commands.contains(new Session2Command(TEST_COMMAND_CODE_2)));
+    }
+
+    @Test
+    public void testDescribeContents() {
+        final int expected = 0;
+        Session2Command command = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(TEST_COMMAND_CODE_1);
+        Session2CommandGroup commandGroup = builder.build();
+        assertEquals(expected, commandGroup.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(TEST_COMMAND_CODE_1)
+                .addCommand(TEST_COMMAND_CODE_2);
+        Session2CommandGroup commandGroup = builder.build();
+        Parcel dest = Parcel.obtain();
+        commandGroup.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        Session2CommandGroup commandGroupFromParcel =
+            Session2CommandGroup.CREATOR.createFromParcel(dest);
+        assertEquals(commandGroup.getCommands(), commandGroupFromParcel.getCommands());
+        dest.recycle();
+    }
+
+    @Test
+    public void testBuilder() {
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(TEST_COMMAND_CODE_1);
+        Session2CommandGroup commandGroup = builder.build();
+        Session2CommandGroup.Builder newBuilder = new Session2CommandGroup.Builder(commandGroup);
+        Session2CommandGroup newCommandGroup = newBuilder.build();
+        assertEquals(commandGroup.getCommands(), newCommandGroup.getCommands());
+    }
+
+    @Test
+    public void testAddAndRemoveCommand() {
+        Session2Command testCommand = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(testCommand)
+                .addCommand(TEST_COMMAND_CODE_2)
+                .addCommand(TEST_COMMAND_CODE_3);
+        builder.removeCommand(testCommand)
+                .removeCommand(TEST_COMMAND_CODE_2);
+        Session2CommandGroup commandGroup = builder.build();
+        assertFalse(commandGroup.hasCommand(testCommand));
+        assertFalse(commandGroup.hasCommand(TEST_COMMAND_CODE_2));
+        assertTrue(commandGroup.hasCommand(TEST_COMMAND_CODE_3));
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/Session2CommandTest.java b/tests/tests/media/src/android/media/cts/Session2CommandTest.java
new file mode 100644
index 0000000..fb66242
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Session2CommandTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.Session2Command;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.Session2Command}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Session2CommandTest {
+    private final int TEST_COMMAND_CODE = 10000;
+    private final int TEST_RESULT_CODE = 0;
+    private final String TEST_CUSTOM_ACTION = "testAction";
+    private final Bundle TEST_EXTRA = new Bundle();
+    private final Bundle TEST_RESULT_DATA = new Bundle();
+
+    @Test
+    public void testConstructorWithCommandCodeCustom() {
+        try {
+            Session2Command command = new Session2Command(Session2Command.COMMAND_CODE_CUSTOM);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testConstructorWithNullAction() {
+        try {
+            Session2Command command = new Session2Command(null, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testGetCommandCode() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(TEST_COMMAND_CODE, commandWithCode.getCommandCode());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION, TEST_EXTRA);
+        assertEquals(Session2Command.COMMAND_CODE_CUSTOM, commandWithAction.getCommandCode());
+    }
+
+    @Test
+    public void testGetCustomCommand() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertNull(commandWithCode.getCustomCommand());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION, TEST_EXTRA);
+        assertEquals(TEST_CUSTOM_ACTION, commandWithAction.getCustomCommand());
+    }
+
+    @Test
+    public void testGetExtras() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertNull(commandWithCode.getExtras());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION, TEST_EXTRA);
+        assertEquals(TEST_EXTRA, commandWithAction.getExtras());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        final int expected = 0;
+        Session2Command command = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(expected, command.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Session2Command command = new Session2Command(TEST_CUSTOM_ACTION, null);
+        Parcel dest = Parcel.obtain();
+        command.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        assertEquals(command.getCommandCode(), dest.readInt());
+        assertEquals(command.getCustomCommand(), dest.readString());
+        assertEquals(command.getExtras(), dest.readBundle());
+    }
+
+    @Test
+    public void testEquals() {
+        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
+        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
+        assertTrue(commandWithCode1.equals(commandWithCode2));
+
+        Session2Command commandWithAction1 = new Session2Command(TEST_CUSTOM_ACTION, TEST_EXTRA);
+        Session2Command commandWithAction2 = new Session2Command(TEST_CUSTOM_ACTION, TEST_EXTRA);
+        assertTrue(commandWithAction1.equals(commandWithAction2));
+    }
+
+    @Test
+    public void testHashCode() {
+        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
+        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(commandWithCode1.hashCode(), commandWithCode2.hashCode());
+    }
+
+    @Test
+    public void testGetResultCodeAndData() {
+        Session2Command.Result result = new Session2Command.Result(TEST_RESULT_CODE,
+                TEST_RESULT_DATA);
+        assertEquals(TEST_RESULT_CODE, result.getResultCode());
+        assertEquals(TEST_RESULT_DATA, result.getResultData());
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/SessionToken2Test.java b/tests/tests/media/src/android/media/cts/SessionToken2Test.java
deleted file mode 100644
index 33f416d..0000000
--- a/tests/tests/media/src/android/media/cts/SessionToken2Test.java
+++ /dev/null
@@ -1,68 +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 static junit.framework.Assert.assertEquals;
-
-import android.content.Context;
-import android.media.SessionToken2;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link SessionToken2}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Ignore
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class SessionToken2Test {
-    private Context mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-    }
-
-    @Test
-    public void testConstructor_sessionService() {
-        SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
-                MockMediaSessionService2.class.getCanonicalName());
-        assertEquals(MockMediaSessionService2.ID, token.getId());
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
-    }
-
-    @Test
-    public void testConstructor_libraryService() {
-        SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
-                MockMediaLibraryService2.class.getCanonicalName());
-        assertEquals(MockMediaLibraryService2.ID, token.getId());
-        assertEquals(mContext.getPackageName(), token.getPackageName());
-        assertEquals(Process.myUid(), token.getUid());
-        assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java b/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
new file mode 100644
index 0000000..0dad9fa
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.media.cts;
+
+import android.media.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolHapticTest extends SoundPoolTest {
+    // Test files are mocked audio-haptic coupled ogg files for regression testing.
+    // TODO: Update with actual audio-haptic ogg files.
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.a_4_haptic;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.c_sharp_5_haptic;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.e_5_haptic;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.b_5_haptic;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.g_sharp_5_haptic;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "a_4_haptic.ogg";
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayer2Test.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayer2Test.java
index c1769ac..73e36cb 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayer2Test.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayer2Test.java
@@ -15,8 +15,10 @@
  */
 package android.media.cts;
 
+import android.content.Context;
 import android.media.BufferingParams;
 import android.media.DataSourceDesc;
+import android.media.UriDataSourceDesc;
 import android.media.MediaFormat;
 import android.media.MediaPlayer2;
 import android.media.MediaPlayer2.TrackInfo;
@@ -356,7 +358,7 @@
             }
 
             final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
+            mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
 
@@ -364,8 +366,8 @@
             mPlayer.setScreenOnWhilePlaying(true);
 
             mOnBufferingUpdateCalled.reset();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         fail("Media player had error " + what + " playing " + name);
@@ -380,7 +382,7 @@
                         }
                     }
                 };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mPlayer.registerEventCallback(mExecutor, ecb);
 
             assertFalse(mOnBufferingUpdateCalled.isSignalled());
 
@@ -390,13 +392,13 @@
             if (nolength) {
                 mPlayer.play();
                 Thread.sleep(LONG_SLEEP_TIME);
-                assertFalse(mPlayer.isPlaying());
+                assertFalse(mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
             } else {
                 mOnBufferingUpdateCalled.waitForSignal();
                 mPlayer.play();
                 Thread.sleep(SLEEP_TIME);
             }
-            mPlayer.stop();
+            mPlayer.pause();
             mPlayer.reset();
         } finally {
             mServer.shutdown();
@@ -419,7 +421,7 @@
             }
 
             final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
+            mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
 
@@ -428,8 +430,8 @@
 
             mOnBufferingUpdateCalled.reset();
 
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         mOnErrorCalled.signal();
@@ -444,7 +446,7 @@
                         }
                     }
                 };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mPlayer.registerEventCallback(mExecutor, ecb);
 
             assertFalse(mOnBufferingUpdateCalled.isSignalled());
             try {
@@ -459,6 +461,7 @@
     }
 
     // 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);
@@ -471,8 +474,8 @@
             }
 
             Monitor onSetBufferingParamsCalled = new Monitor();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         fail("Media player had error " + what + " playing " + name);
@@ -492,7 +495,7 @@
                         }
                     }
                 };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mPlayer.registerEventCallback(mExecutor, ecb);
 
             // getBufferingParams should be called after setDataSource.
             try {
@@ -514,7 +517,7 @@
             assertTrue(mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR);
 
             final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
+            mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
 
@@ -547,6 +550,7 @@
             mServer.shutdown();
         }
     }
+    */
 
     public void testPlayHlsStream() throws Throwable {
         if (IGNORE_TESTS) {
@@ -594,27 +598,31 @@
             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()
+            mPlayer.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
             mPlayer.setDisplay(getActivity().getSurfaceHolder());
             mPlayer.setScreenOnWhilePlaying(true);
-            mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager.WakeLock wakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+                            "StreamingMediaPlayer2Test");
+            mPlayer.setWakeLock(wakeLock);
 
             final Object completion = new Object();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     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) {
+                        } else if (what == MediaPlayer2.MEDIA_INFO_DATA_SOURCE_END) {
                             if (run++ == 0) {
                                 mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
                                 mPlayer.play();
                             } else {
-                                mPlayer.stop();
+                                mPlayer.pause();
                                 synchronized (completion) {
                                     completion.notify();
                                 }
@@ -662,7 +670,7 @@
                     }
                 }
             };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mPlayer.registerEventCallback(mExecutor, ecb);
 
             mPlayer.prepare();
             mOnPrepareCalled.waitForSignal();
@@ -670,10 +678,11 @@
             mOnPlayCalled.reset();
             mPlayer.play();
             mOnPlayCalled.waitForSignal();
-            assertTrue("MediaPlayer2 not playing", mPlayer.isPlaying());
+            assertTrue("MediaPlayer2 not playing",
+                    mPlayer.getState() == MediaPlayer2.PLAYER_STATE_PLAYING);
 
             int i = -1;
-            List<TrackInfo> trackInfos = mPlayer.getTrackInfo();
+            List<TrackInfo> trackInfos = mPlayer.getTrackInfo(mPlayer.getCurrentDataSource());
             for (i = 0; i < trackInfos.size(); i++) {
                 TrackInfo trackInfo = trackInfos.get(i);
                 if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) {
@@ -681,7 +690,7 @@
                 }
             }
             assertTrue("Stream has no timed ID3 track", i >= 0);
-            mPlayer.selectTrack(i);
+            mPlayer.selectTrack(mPlayer.getCurrentDataSource(), i);
 
             synchronized (completion) {
                 completion.wait();
@@ -696,6 +705,7 @@
     }
 
     private static class WorkerWithPlayer implements Runnable {
+        private Context mContext;
         private final Object mLock = new Object();
         private Looper mLooper;
         private MediaPlayer2 mPlayer;
@@ -705,7 +715,8 @@
          * then runs a {@link android.os.Looper}.
          * @param name A name for the new thread
          */
-        WorkerWithPlayer(String name) {
+        WorkerWithPlayer(Context context, String name) {
+            mContext = context;
             Thread t = new Thread(null, this, name);
             t.setPriority(Thread.MIN_PRIORITY);
             t.start();
@@ -728,7 +739,7 @@
             synchronized (mLock) {
                 Looper.prepare();
                 mLooper = Looper.myLooper();
-                mPlayer = MediaPlayer2.create();
+                mPlayer = new MediaPlayer2(mContext);
                 mLock.notifyAll();
             }
             Looper.loop();
@@ -747,18 +758,18 @@
 
         mServer = new CtsTestServer(mContext);
 
-        WorkerWithPlayer worker = new WorkerWithPlayer("player");
+        WorkerWithPlayer worker = new WorkerWithPlayer(mContext, "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()
+            mp.setDataSource(new UriDataSourceDesc.Builder()
                     .setDataSource(mContext, uri)
                     .build());
 
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
+            MediaPlayer2.EventCallback ecb =
+                new MediaPlayer2.EventCallback() {
                     @Override
                     public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
                         if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
@@ -766,7 +777,7 @@
                         }
                     }
                 };
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
+            mp.registerEventCallback(mExecutor, ecb);
 
             mp.prepare();
             Thread.sleep(1000);
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 9f73c25..b20b601 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -15,7 +15,6 @@
  */
 package android.media.cts;
 
-import android.media.BufferingParams;
 import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.TrackInfo;
@@ -381,81 +380,6 @@
         }
     }
 
-    // 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
-            }
-
-            // getBufferingParams should be called after setDataSource.
-            try {
-                BufferingParams params = mMediaPlayer.getBufferingParams();
-                fail("MediaPlayer failed to check state for getBufferingParams");
-            } catch (IllegalStateException e) {
-                // expected
-            }
-
-            // setBufferingParams should be called after setDataSource.
-            try {
-                BufferingParams params = new BufferingParams.Builder()
-                        .setInitialMarkMs(2)
-                        .setResumePlaybackMarkMs(3)
-                        .build();
-                mMediaPlayer.setBufferingParams(params);
-                fail("MediaPlayer failed to check state for setBufferingParams");
-            } catch (IllegalStateException e) {
-                // expected
-            }
-
-            mMediaPlayer.setDataSource(stream_url);
-
-            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mMediaPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-            mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
-                @Override
-                public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                    mOnBufferingUpdateCalled.signal();
-                }
-            });
-            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-                @Override
-                public boolean onError(MediaPlayer mp, int what, int extra) {
-                    fail("Media player had error " + what + " playing " + name);
-                    return true;
-                }
-            });
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-
-            BufferingParams params = mMediaPlayer.getBufferingParams();
-
-            int newMark = params.getInitialMarkMs() + 2;
-            BufferingParams newParams =
-                    new BufferingParams.Builder(params).setInitialMarkMs(newMark).build();
-
-            mMediaPlayer.setBufferingParams(newParams);
-
-            int checkMark = -1;
-            BufferingParams checkParams = mMediaPlayer.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.
-
-            mMediaPlayer.reset();
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
     public void testPlayHlsStream() throws Throwable {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
             return; // skip
diff --git a/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java b/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java
new file mode 100644
index 0000000..c2bd7ef
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java
@@ -0,0 +1,136 @@
+/*
+ * 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.MediaSession2;
+import android.media.MediaSession2Service;
+import android.media.Session2CommandGroup;
+import android.os.Process;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+
+/**
+ * Stub implementation of {@link MediaSession2Service} for testing.
+ */
+public class StubMediaSession2Service extends MediaSession2Service {
+    /**
+     * ID of the session that this service will create.
+     */
+    private static final String ID = "StubMediaSession2Service";
+
+    @GuardedBy("StubMediaSession2Service.class")
+    private static HandlerExecutor sHandlerExecutor;
+    @GuardedBy("StubMediaSession2Service.class")
+    private static StubMediaSession2Service sInstance;
+    @GuardedBy("StubMediaSession2Service.class")
+    private static TestInjector sTestInjector;
+
+    public static void setHandlerExecutor(HandlerExecutor handlerExecutor) {
+        synchronized (StubMediaSession2Service.class) {
+            sHandlerExecutor = handlerExecutor;
+        }
+    }
+
+    public static StubMediaSession2Service getInstance() {
+        synchronized (StubMediaSession2Service.class) {
+            return sInstance;
+        }
+    }
+
+    public static void setTestInjector(TestInjector injector) {
+        synchronized (StubMediaSession2Service.class) {
+            sTestInjector = injector;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (StubMediaSession2Service.class) {
+            sInstance = this;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        List<MediaSession2> sessions = getSessions();
+        for (MediaSession2 session : sessions) {
+            session.close();
+        }
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                sTestInjector.onServiceDestroyed();
+            }
+            sInstance = null;
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public MediaNotification onUpdateNotification(MediaSession2 session) {
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                sTestInjector.onUpdateNotification(session);
+            }
+            sInstance = null;
+        }
+        return null;
+    }
+
+    @Override
+    public MediaSession2 onGetPrimarySession() {
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                MediaSession2 session = sTestInjector.onGetPrimarySession();
+                if (session != null) {
+                    return session;
+                }
+            }
+        }
+        if (getSessions().size() > 0) {
+            return getSessions().get(0);
+        }
+        return new MediaSession2.Builder(this)
+                .setId(ID)
+                .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
+                    @Override
+                    public Session2CommandGroup onConnect(MediaSession2 session,
+                            MediaSession2.ControllerInfo controller) {
+                        if (controller.getUid() == Process.myUid()) {
+                            return new Session2CommandGroup.Builder().build();
+                        }
+                        return null;
+                    }
+                }).build();
+    }
+
+    public static abstract class TestInjector {
+        MediaSession2 onGetPrimarySession() {
+            return null;
+        }
+
+        MediaNotification onUpdateNotification(MediaSession2 session) {
+            return null;
+        }
+
+        void onServiceDestroyed() {
+            // no-op
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/TestDataSourceCallback.java b/tests/tests/media/src/android/media/cts/TestDataSourceCallback.java
new file mode 100644
index 0000000..067e281
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/TestDataSourceCallback.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.media.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.cts.TestUtils.Monitor;
+import android.media.DataSourceCallback;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * A DataSourceCallback that reads from a byte array for use in tests.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class TestDataSourceCallback extends DataSourceCallback {
+    private static final String TAG = "TestDataSourceCallback";
+
+    private byte[] mData;
+
+    private boolean mThrowFromReadAt;
+    private boolean mThrowFromGetSize;
+    private Integer mReturnFromReadAt;
+    private Long mReturnFromGetSize;
+    private boolean mIsClosed;
+
+    // Read an asset fd into a new byte array data source. Closes afd.
+    public static TestDataSourceCallback fromAssetFd(AssetFileDescriptor afd) throws IOException {
+        try {
+            InputStream in = afd.createInputStream();
+            final int size = (int) afd.getDeclaredLength();
+            byte[] data = new byte[(int) size];
+            int writeIndex = 0;
+            int numRead = 0;
+            do {
+                numRead = in.read(data, writeIndex, size - writeIndex);
+                writeIndex += numRead;
+            } while (numRead >= 0);
+            return new TestDataSourceCallback(data);
+        } finally {
+            afd.close();
+        }
+    }
+
+    public TestDataSourceCallback(byte[] data) {
+        mData = data;
+    }
+
+    @Override
+    public synchronized int readAt(long position, byte[] buffer, int offset, int size)
+            throws IOException {
+        if (mThrowFromReadAt) {
+            throw new IOException("Test exception from readAt()");
+        }
+        if (mReturnFromReadAt != null) {
+            return mReturnFromReadAt;
+        }
+
+        // Clamp reads past the end of the source.
+        if (position >= mData.length) {
+            return -1; // -1 indicates EOF
+        }
+        if (position + size > mData.length) {
+            size -= (position + size) - mData.length;
+        }
+        System.arraycopy(mData, (int)position, buffer, offset, size);
+        return size;
+    }
+
+    @Override
+    public synchronized long getSize() throws IOException {
+        if (mThrowFromGetSize) {
+            throw new IOException("Test exception from getSize()");
+        }
+        if (mReturnFromGetSize != null) {
+            return mReturnFromGetSize;
+        }
+
+        Log.v(TAG, "getSize: " + mData.length);
+        return mData.length;
+    }
+
+    // Note: it's fine to keep using this data source after closing it.
+    @Override
+    public synchronized void close() {
+        Log.v(TAG, "close()");
+        mIsClosed = true;
+    }
+
+    // Whether close() has been called.
+    public synchronized boolean isClosed() {
+        return mIsClosed;
+    }
+
+    public void throwFromReadAt() {
+        mThrowFromReadAt = true;
+    }
+
+    public void throwFromGetSize() {
+        mThrowFromGetSize = true;
+    }
+
+    public void returnFromReadAt(int numRead) {
+        mReturnFromReadAt = numRead;
+    }
+
+    public void returnFromGetSize(long size) {
+        mReturnFromGetSize = size;
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/TestMedia2DataSource.java b/tests/tests/media/src/android/media/cts/TestMedia2DataSource.java
deleted file mode 100644
index d6b59b5..0000000
--- a/tests/tests/media/src/android/media/cts/TestMedia2DataSource.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.cts.TestUtils.Monitor;
-import android.media.Media2DataSource;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-
-/**
- * A Media2DataSource that reads from a byte array for use in tests.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class TestMedia2DataSource extends Media2DataSource {
-    private static final String TAG = "TestMedia2DataSource";
-
-    private byte[] mData;
-
-    private boolean mThrowFromReadAt;
-    private boolean mThrowFromGetSize;
-    private Integer mReturnFromReadAt;
-    private Long mReturnFromGetSize;
-    private boolean mIsClosed;
-
-    // Read an asset fd into a new byte array data source. Closes afd.
-    public static TestMedia2DataSource fromAssetFd(AssetFileDescriptor afd) throws IOException {
-        try {
-            InputStream in = afd.createInputStream();
-            final int size = (int) afd.getDeclaredLength();
-            byte[] data = new byte[(int) size];
-            int writeIndex = 0;
-            int numRead = 0;
-            do {
-                numRead = in.read(data, writeIndex, size - writeIndex);
-                writeIndex += numRead;
-            } while (numRead >= 0);
-            return new TestMedia2DataSource(data);
-        } finally {
-            afd.close();
-        }
-    }
-
-    public TestMedia2DataSource(byte[] data) {
-        mData = data;
-    }
-
-    @Override
-    public synchronized int readAt(long position, byte[] buffer, int offset, int size)
-            throws IOException {
-        if (mThrowFromReadAt) {
-            throw new IOException("Test exception from readAt()");
-        }
-        if (mReturnFromReadAt != null) {
-            return mReturnFromReadAt;
-        }
-
-        // Clamp reads past the end of the source.
-        if (position >= mData.length) {
-            return -1; // -1 indicates EOF
-        }
-        if (position + size > mData.length) {
-            size -= (position + size) - mData.length;
-        }
-        System.arraycopy(mData, (int)position, buffer, offset, size);
-        return size;
-    }
-
-    @Override
-    public synchronized long getSize() throws IOException {
-        if (mThrowFromGetSize) {
-            throw new IOException("Test exception from getSize()");
-        }
-        if (mReturnFromGetSize != null) {
-            return mReturnFromGetSize;
-        }
-
-        Log.v(TAG, "getSize: " + mData.length);
-        return mData.length;
-    }
-
-    // Note: it's fine to keep using this data source after closing it.
-    @Override
-    public synchronized void close() {
-        Log.v(TAG, "close()");
-        mIsClosed = true;
-    }
-
-    // Whether close() has been called.
-    public synchronized boolean isClosed() {
-        return mIsClosed;
-    }
-
-    public void throwFromReadAt() {
-        mThrowFromReadAt = true;
-    }
-
-    public void throwFromGetSize() {
-        mThrowFromGetSize = true;
-    }
-
-    public void returnFromReadAt(int numRead) {
-        mReturnFromReadAt = numRead;
-    }
-
-    public void returnFromGetSize(long size) {
-        mReturnFromGetSize = size;
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/TestServiceRegistry.java b/tests/tests/media/src/android/media/cts/TestServiceRegistry.java
deleted file mode 100644
index a904be4..0000000
--- a/tests/tests/media/src/android/media/cts/TestServiceRegistry.java
+++ /dev/null
@@ -1,134 +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 static org.junit.Assert.fail;
-
-import android.media.MediaSession2.SessionCallback;
-import android.media.MediaSessionService2;
-import android.media.cts.TestUtils.SyncHandler;
-import android.os.Handler;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Keeps the instance of currently running {@link MockMediaSessionService2}. And also provides
- * a way to control them in one place.
- * <p>
- * It only support only one service at a time.
- */
-public class TestServiceRegistry {
-    @GuardedBy("TestServiceRegistry.class")
-    private static TestServiceRegistry sInstance;
-    @GuardedBy("TestServiceRegistry.class")
-    private MediaSessionService2 mService;
-    @GuardedBy("TestServiceRegistry.class")
-    private SyncHandler mHandler;
-    @GuardedBy("TestServiceRegistry.class")
-    private SessionCallback mSessionCallback;
-    @GuardedBy("TestServiceRegistry.class")
-    private SessionServiceCallback mSessionServiceCallback;
-
-    /**
-     * Callback for session service's lifecyle (onCreate() / onDestroy())
-     */
-    public interface SessionServiceCallback {
-        default void onCreated() {}
-        default void onDestroyed() {}
-    }
-
-    public static TestServiceRegistry getInstance() {
-        synchronized (TestServiceRegistry.class) {
-            if (sInstance == null) {
-                sInstance = new TestServiceRegistry();
-            }
-            return sInstance;
-        }
-    }
-
-    public void setHandler(Handler handler) {
-        synchronized (TestServiceRegistry.class) {
-            mHandler = new SyncHandler(handler.getLooper());
-        }
-    }
-
-    public Handler getHandler() {
-        synchronized (TestServiceRegistry.class) {
-            return mHandler;
-        }
-    }
-
-    public void setSessionServiceCallback(SessionServiceCallback sessionServiceCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionServiceCallback = sessionServiceCallback;
-        }
-    }
-
-    public void setSessionCallback(SessionCallback sessionCallback) {
-        synchronized (TestServiceRegistry.class) {
-            mSessionCallback = sessionCallback;
-        }
-    }
-
-    public SessionCallback getSessionCallback() {
-        synchronized (TestServiceRegistry.class) {
-            return mSessionCallback;
-        }
-    }
-
-    public void setServiceInstance(MediaSessionService2 service) {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                fail("Previous service instance is still running. Clean up manually to ensure"
-                        + " previoulsy running service doesn't break current test");
-            }
-            mService = service;
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onCreated();
-            }
-        }
-    }
-
-    public MediaSessionService2 getServiceInstance() {
-        synchronized (TestServiceRegistry.class) {
-            return mService;
-        }
-    }
-
-    public void cleanUp() {
-        synchronized (TestServiceRegistry.class) {
-            if (mService != null) {
-                // TODO(jaewan): Remove this, and override SessionService#onDestroy() to do this
-                mService.getSession().close();
-                // stopSelf() would not kill service while the binder connection established by
-                // bindService() exists, and close() above will do the job instead.
-                // So stopSelf() isn't really needed, but just for sure.
-                mService.stopSelf();
-                mService = null;
-            }
-            if (mHandler != null) {
-                mHandler.removeCallbacksAndMessages(null);
-            }
-            mSessionCallback = null;
-            if (mSessionServiceCallback != null) {
-                mSessionServiceCallback.onDestroyed();
-                mSessionServiceCallback = null;
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/TestUtils.java b/tests/tests/media/src/android/media/cts/TestUtils.java
index f86f150..b64a856 100644
--- a/tests/tests/media/src/android/media/cts/TestUtils.java
+++ b/tests/tests/media/src/android/media/cts/TestUtils.java
@@ -21,9 +21,6 @@
 
 import android.content.Context;
 import android.media.DataSourceDesc;
-import android.media.MediaItem2;
-import android.media.MediaMetadata2;
-import android.media.SessionToken2;
 import android.media.session.MediaSessionManager;
 import android.os.Bundle;
 import android.os.Handler;
@@ -44,28 +41,6 @@
     private static final int WAIT_SERVICE_TIME_MS = 5000;
 
     /**
-     * Finds the session with id in this test package.
-     *
-     * @param context
-     * @param id
-     * @return
-     */
-    public static SessionToken2 getServiceToken(Context context, String id) {
-        MediaSessionManager manager =
-                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        List<SessionToken2> tokens = manager.getSessionServiceTokens();
-        for (int i = 0; i < tokens.size(); i++) {
-            SessionToken2 token = tokens.get(i);
-            if (context.getPackageName().equals(token.getPackageName())
-                    && id.equals(token.getId())) {
-                return token;
-            }
-        }
-        fail("Failed to find service");
-        return null;
-    }
-
-    /**
      * Compares contents of two bundles.
      *
      * @param a a bundle
@@ -92,75 +67,6 @@
         return true;
     }
 
-    /**
-     * Create a playlist for testing purpose
-     * <p>
-     * Caller's method name will be used for prefix of each media item's media id.
-     *
-     * @param size lits size
-     * @return the newly created playlist
-     */
-    public static List<MediaItem2> createPlaylist(int size) {
-        final List<MediaItem2> list = new ArrayList<>();
-        String caller = Thread.currentThread().getStackTrace()[1].getMethodName();
-        for (int i = 0; i < size; i++) {
-            list.add(new MediaItem2.Builder(MediaItem2.FLAG_PLAYABLE)
-                    .setMediaId(caller + "_item_" + (size + 1))
-                    .setDataSourceDesc(
-                            new DataSourceDesc.Builder()
-                                    .setDataSource(new FileDescriptor())
-                                    .build())
-                    .build());
-        }
-        return list;
-    }
-
-    /**
-     * Create a media item with the metadata for testing purpose.
-     *
-     * @return the newly created media item
-     * @see #createMetadata()
-     */
-    public static MediaItem2 createMediaItemWithMetadata() {
-        return new MediaItem2.Builder(MediaItem2.FLAG_PLAYABLE)
-                .setMetadata(createMetadata()).build();
-    }
-
-    /**
-     * Create a media metadata for testing purpose.
-     * <p>
-     * Caller's method name will be used for the media id.
-     *
-     * @return the newly created media item
-     */
-    public static MediaMetadata2 createMetadata() {
-        String mediaId = Thread.currentThread().getStackTrace()[1].getMethodName();
-        return new MediaMetadata2.Builder()
-                .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId).build();
-    }
-
-    /**
-     * Handler that always waits until the Runnable finishes.
-     */
-    public static class SyncHandler extends Handler {
-        public SyncHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void postAndSync(Runnable runnable) throws InterruptedException {
-            if (getLooper() == Looper.myLooper()) {
-                runnable.run();
-            } else {
-                final CountDownLatch latch = new CountDownLatch(1);
-                post(()->{
-                    runnable.run();
-                    latch.countDown();
-                });
-                assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            }
-        }
-    }
-
     public static class Monitor {
         private int mNumSignal;
 
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
new file mode 100644
index 0000000..2691775
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.media.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.ThumbnailUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Size;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class ThumbnailUtilsTest {
+    private static final Size[] TEST_SIZES = new Size[] {
+            new Size(50, 50),
+            new Size(500, 500),
+            new Size(5000, 5000),
+    };
+
+    private File mDir;
+
+    @Before
+    public void setUp() {
+        mDir = InstrumentationRegistry.getTargetContext().getExternalCacheDir();
+        mDir.mkdirs();
+        deleteContents(mDir);
+    }
+
+    @After
+    public void tearDown() {
+        deleteContents(mDir);
+    }
+
+    @Test
+    public void testCreateAudioThumbnail() throws Exception {
+        final File file = stageFile(R.raw.testmp3, new File(mDir, "cts.mp3"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateAudioThumbnail_SeparateFile() throws Exception {
+        final File file = stageFile(R.raw.monotestmp3, new File(mDir, "audio.mp3"));
+        stageFile(R.raw.volantis, new File(mDir, "AlbumArt.jpg"));
+
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateAudioThumbnail_None() throws Exception {
+        final File file = stageFile(R.raw.monotestmp3, new File(mDir, "cts.mp3"));
+        try {
+            ThumbnailUtils.createAudioThumbnail(file, TEST_SIZES[0], null);
+            fail("Somehow made a thumbnail out of nothing?");
+        } catch (IOException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateImageThumbnail() throws Exception {
+        final File file = stageFile(R.raw.volantis, new File(mDir, "cts.jpg"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateVideoThumbnail() throws Exception {
+        final File file = stageFile(
+                R.raw.bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz,
+                new File(mDir, "cts.mp4"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createVideoThumbnail(file, size, null));
+        }
+    }
+
+    private static File stageFile(int resId, File file) throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        try (InputStream source = context.getResources().openRawResource(resId);
+                OutputStream target = new FileOutputStream(file)) {
+            android.os.FileUtils.copy(source, target);
+        }
+        return file;
+    }
+
+    private static void deleteContents(File dir) {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    deleteContents(file);
+                }
+                file.delete();
+            }
+        }
+    }
+
+    private static void assertSaneThumbnail(Size expected, Bitmap actualBitmap) {
+        final Size actual = new Size(actualBitmap.getWidth(), actualBitmap.getHeight());
+        final int maxWidth = (expected.getWidth() * 3) / 2;
+        final int maxHeight = (expected.getHeight() * 3) / 2;
+        if ((actual.getWidth() > maxWidth) || (actual.getHeight() > maxHeight)) {
+            fail("Actual " + actual + " differs too much from expected " + expected);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
index ce97379..57ff9d3 100644
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
@@ -40,6 +40,7 @@
 import java.util.Arrays;
 import java.util.LinkedList;
 
+@MediaHeavyPresubmitTest
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class VideoDecoderPerfTest extends MediaPlayerTestBase {
     private static final String TAG = "VideoDecoderPerfTest";
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
index 3a2165d..818c95d 100644
--- a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -55,6 +55,7 @@
 import java.util.Map;
 import java.util.Set;
 
+@MediaHeavyPresubmitTest
 @AppModeFull(reason = "TODO: evaluate and port to instant")
 public class VideoEncoderTest extends MediaPlayerTestBase {
     private static final int MAX_SAMPLE_SIZE = 256 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/VisualizerTest.java b/tests/tests/media/src/android/media/cts/VisualizerTest.java
index 92aa202..5645a92 100644
--- a/tests/tests/media/src/android/media/cts/VisualizerTest.java
+++ b/tests/tests/media/src/android/media/cts/VisualizerTest.java
@@ -441,8 +441,11 @@
                     energy += tmp*tmp;
                 }
             } else {
-                energy = (int)data[0] * (int)data[0];
-                for (int i = 2; i < data.length; i += 2) {
+                // Note that data[0] is real part of FFT at DC
+                // and data[1] is real part of FFT at Nyquist,
+                // but for the purposes of energy calculation we
+                // don't need to treat them specially.
+                for (int i = 0; i < data.length; i += 2) {
                     int real = (int)data[i];
                     int img = (int)data[i + 1];
                     energy += real * real + img * img;
diff --git a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java b/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
index 38a8403..654875f 100644
--- a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
@@ -36,6 +36,7 @@
  * 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 {
 
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index 2edb2ec..595fcd5 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -17,6 +17,7 @@
 <configuration description="Config for CTS Multiuser 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="instant_app" />
     <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/nativehardware/AndroidTest.xml b/tests/tests/nativehardware/AndroidTest.xml
index 43457b3..24b0301 100644
--- a/tests/tests/nativehardware/AndroidTest.xml
+++ b/tests/tests/nativehardware/AndroidTest.xml
@@ -15,7 +15,7 @@
 -->
 <configuration description="Config for CTS Native Hardware test cases">
     <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="vr" />
+    <option name="config-descriptor:metadata" key="component" value="graphics" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsNativeHardwareTestCases.apk" />
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index 622c336..eecd4fd 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -69,8 +69,9 @@
         GL_FORMAT_CASE(GL_SRGB8_ALPHA8);
         GL_FORMAT_CASE(GL_RGBA16F);
         GL_FORMAT_CASE(GL_RGB10_A2);
-        GL_FORMAT_CASE(GL_STENCIL_INDEX8);
+        GL_FORMAT_CASE(GL_DEPTH_COMPONENT16);
         GL_FORMAT_CASE(GL_DEPTH24_STENCIL8);
+        GL_FORMAT_CASE(GL_STENCIL_INDEX8);
     }
     return "";
 }
@@ -155,16 +156,19 @@
 void UploadData(const AHardwareBuffer_Desc& desc, GLenum format, GLenum type, const void* data) {
     if (desc.layers <= 1) {
         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.width, desc.height, format, type, data);
+        ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "glTexSubImage2D failed";
     } else {
         for (uint32_t layer = 0; layer < desc.layers; ++layer) {
             glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, desc.width, desc.height, 1,
                             format, type, data);
+            ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "glTexSubImage3D failed";
         }
     }
 }
 
 // Uploads opaque red to the currently bound texture.
 void UploadRedPixels(const AHardwareBuffer_Desc& desc) {
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
     const bool use_srgb = desc.stride & kUseSrgb;
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     switch (desc.format) {
@@ -224,7 +228,6 @@
         }
         default: FAIL() << "Unrecognized AHardwareBuffer format"; break;
     }
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 }
 
 // Draws the following checkerboard pattern using glScissor and glClear.
@@ -267,7 +270,7 @@
     glClear(all_bits);
 
     glDisable(GL_SCISSOR_TEST);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 }
 
 enum GoldenColor {
@@ -291,14 +294,14 @@
 template <typename T>
 void CheckGoldenPixel(int x, int y, const std::array<T, 4>& golden,
                       const std::array<T, 4>& actual) {
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
     EXPECT_EQ(golden, actual) << "Pixel doesn't match at X=" << x << ", Y=" << y;
 }
 
 template <typename T>
 void CheckGoldenPixel(int x, int y, const std::array<T, 4>& minimum,
                       const std::array<T, 4>& maximum, const std::array<T, 4>& actual) {
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
     bool in_range = true;
     for (int i = 0; i < 4; ++i) {
         if (actual[i] < minimum[i] || actual[i] > maximum[i]) {
@@ -306,9 +309,11 @@
             break;
         }
     }
-    if (!in_range) {
-        EXPECT_NE(actual, actual) << "Pixel out of acceptable range at X=" << x << ", Y=" << y;
-    }
+    EXPECT_TRUE(in_range) << "Pixel out of acceptable range at X=" << x << ", Y=" << y
+        << "; actual value: {" << actual[0] << ", " << actual[1] << ", " << actual[2] << ", " << actual[3]
+        << "}, minimum: {" << minimum[0] << ", " << minimum[1] << ", " << minimum[2] << ", " << minimum[3]
+        << "}, maximum: {" << maximum[0] << ", " << maximum[1] << ", " << maximum[2] << ", " << maximum[3]
+        << "}";
 }
 
 void CheckGoldenPixel(const GoldenPixel& golden, const std::array<uint8_t, 4>& pixel,
@@ -403,7 +408,7 @@
     if (FormatIsFloat(format)) {
         std::unique_ptr<float[]> pixels(new float[width * height * 4]);
         glReadPixels(left, bottom, width, height, GL_RGBA, GL_FLOAT, pixels.get());
-        EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+        ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
         for (const GoldenPixel& golden : goldens) {
             float* pixel = pixels.get() + ((golden.y - bottom) * width + golden.x - left) * 4;
             std::array<float, 4> pixel_array;
@@ -413,7 +418,7 @@
     } else {
         std::unique_ptr<uint8_t[]> pixels(new uint8_t[width * height * 4]);
         glReadPixels(left, bottom, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
-        EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+        ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
         for (const GoldenPixel& golden : goldens) {
             uint8_t* pixel = pixels.get() + ((golden.y - bottom) * width + golden.x - left) * 4;
             std::array<uint8_t, 4> pixel_array;
@@ -851,10 +856,33 @@
     int result = AHardwareBuffer_allocate(&desc, &mBuffer);
     // Skip if this format cannot be allocated.
     if (result != NO_ERROR) {
-        ALOGI("Test skipped: %s not supported",
+        EXPECT_FALSE(AHardwareBuffer_isSupported(&desc)) <<
+            "AHardwareBuffer_isSupported returned true, but buffer allocation failed. "
+            "Potential gralloc bug or resource exhaustion.";
+        ALOGI("Test skipped: format %s could not be allocated",
               AHBFormatAsString(desc.format));
         return false;
     }
+    EXPECT_TRUE(AHardwareBuffer_isSupported(&desc)) <<
+        "AHardwareBuffer_isSupported returned false, but buffer allocation succeeded. "
+        "This is most likely a bug in the gralloc implementation.";
+
+    // The code below will only execute if allocating an AHardwareBuffer succeeded.
+    // Fail early if the buffer is mipmapped or a cube map, but the GL extension required
+    // to actually access it from GL is not present.
+    if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP &&
+        !HasGLExtension("GL_EXT_EGL_image_storage")) {
+        ADD_FAILURE() << "Cube map AHardwareBuffer allocation succeeded, but the extension "
+            "GL_EXT_EGL_image_storage is not present";
+        return false;
+    }
+    if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE &&
+            !HasGLExtension("GL_EXT_EGL_image_storage")) {
+        ADD_FAILURE() << "Mipmapped AHardwareBuffer allocation succeeded, but the extension "
+            "GL_EXT_EGL_image_storage is not present";
+        return false;
+    }
+
     // Do not create the EGLImage if this is a blob format.
     if (desc.format == AHARDWAREBUFFER_FORMAT_BLOB) return true;
 
@@ -866,7 +894,10 @@
     mEGLImage = eglCreateImageKHR(
         mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
         eglGetNativeClientBufferANDROID(mBuffer), attrib_list);
-    EXPECT_NE(EGL_NO_IMAGE_KHR, mEGLImage);
+    EXPECT_NE(EGL_NO_IMAGE_KHR, mEGLImage) <<
+        "AHardwareBuffer allocation succeeded, but binding it to an EGLImage failed. "
+        "This is usually caused by a version mismatch between the gralloc implementation and "
+        "the OpenGL/EGL driver. Please contact your GPU vendor to resolve this problem.";
     return mEGLImage != EGL_NO_IMAGE_KHR;
 }
 
@@ -898,7 +929,7 @@
     glDeleteShader(vertex_shader);
     glDeleteShader(fragment_shader);
     glUseProgram(mProgram);
-    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during shader program setup";
 
     GLint a_position_location = glGetAttribLocation(mProgram, "aPosition");
     GLint a_depth_location = glGetAttribLocation(mProgram, "aDepth");
@@ -930,6 +961,7 @@
         glUniform1f(u_layer_location, static_cast<float>(GetParam().layers - 1));
     }
     mFaceVectorLocation = glGetUniformLocation(mProgram, "uFaceVector");
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during shader uniform setup";
 }
 
 void AHardwareBufferGLTest::SetUpTexture(const AHardwareBuffer_Desc& desc, int unit) {
@@ -1017,12 +1049,7 @@
             glEGLImageTargetTexture2DOES(mTexTarget, static_cast<GLeglImageOES>(mEGLImage));
         }
     }
-    // If the texture does not have mipmaps, set a filter that does not require them.
-    if (!(desc.usage & AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE)) {
-        glTexParameteri(mTexTarget, GL_TEXTURE_MAX_LEVEL, 0);
-        glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    }
-    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during texture setup";
 }
 
 void AHardwareBufferGLTest::SetUpBufferObject(uint32_t size, GLenum target, GLbitfield flags) {
@@ -1030,7 +1057,7 @@
     glBindBuffer(target, mBufferObjects[mWhich]);
     glBufferStorageExternalEXT(target, 0, size,
                                eglGetNativeClientBufferANDROID(mBuffer), flags);
-    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during buffer object setup";
 }
 
 void AHardwareBufferGLTest::SetUpFramebuffer(int width, int height, int layer,
@@ -1097,9 +1124,9 @@
             default: FAIL() << "Unrecognized binding type";
         }
     }
-    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during framebuffer setup";
     ASSERT_EQ(GLenum{GL_FRAMEBUFFER_COMPLETE},
-              glCheckFramebufferStatus(GL_FRAMEBUFFER));
+              glCheckFramebufferStatus(GL_FRAMEBUFFER)) << "Framebuffer not complete";
     glViewport(0, 0, width, height);
 }
 
@@ -1138,12 +1165,14 @@
     desc.usage = AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER;
     if (!SetUpBuffer(desc)) return;
 
-    SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 0.5f);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 0.5f));
 
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpBufferObject(desc.width, GL_ARRAY_BUFFER,
-                          GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpBufferObject(desc.width, GL_ARRAY_BUFFER,
+                              GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT));
     }
     float* data = static_cast<float*>(
         glMapBufferRange(GL_ARRAY_BUFFER, 0, desc.width,
@@ -1154,11 +1183,11 @@
     glFinish();
 
     MakeCurrent(0);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     GLint a_position_location = glGetAttribLocation(mProgram, "aPosition");
     glVertexAttribPointer(a_position_location, 2, GL_FLOAT, GL_TRUE, 0, 0);
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. There should be a red square in the middle.
     std::vector<GoldenPixel> goldens{
@@ -1177,12 +1206,14 @@
     desc.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY | AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER;
     if (!SetUpBuffer(desc)) return;
 
-    SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 0.5f);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 0.5f));
 
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpBufferObject(desc.width, GL_ARRAY_BUFFER,
-                          GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpBufferObject(desc.width, GL_ARRAY_BUFFER,
+                              GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT));
     }
 
     // Clear the buffer to zero
@@ -1200,11 +1231,11 @@
 
     // Render the buffer in the other context
     MakeCurrent(0);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     GLint a_position_location = glGetAttribLocation(mProgram, "aPosition");
     glVertexAttribPointer(a_position_location, 2, GL_FLOAT, GL_TRUE, 0, 0);
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. There should be a red square in the middle.
     std::vector<GoldenPixel> goldens{
@@ -1231,8 +1262,9 @@
 
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpBufferObject(desc.width, GL_SHADER_STORAGE_BUFFER,
-                          GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_READ_BIT);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpBufferObject(desc.width, GL_SHADER_STORAGE_BUFFER,
+                              GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_READ_BIT));
     }
 
     // Clear the buffer to zero
@@ -1255,22 +1287,22 @@
     glDetachShader(mProgram, shader);
     glDeleteShader(shader);
     glUseProgram(mProgram);
-    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during compute shader setup";
     glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, mBufferObjects[mWhich]);
     glDispatchCompute(kBufferElements, 1, 1);
     glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
     glFinish();
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError()) << "GL error during compute shader execution";
 
     // Inspect the data written into the buffer using CPU access.
     MakeCurrent(0);
     unsigned int* data = nullptr;
     int result = AHardwareBuffer_lock(mBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
                                       -1, nullptr, reinterpret_cast<void**>(&data));
-    ASSERT_EQ(NO_ERROR, result);
+    ASSERT_EQ(NO_ERROR, result) << "AHardwareBuffer_lock failed with error " << result;
     std::ostringstream s;
     for (int i = 0; i < kBufferElements; ++i) {
-		expected_data[i] = static_cast<unsigned int>(i * 3);
+        expected_data[i] = static_cast<unsigned int>(i * 3);
         s << data[i] << ", ";
     }
     EXPECT_EQ(0, memcmp(expected_data.data(), data, desc.width)) << s.str();
@@ -1311,7 +1343,8 @@
 
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpFramebuffer(desc.width, desc.height, 0, kBufferAsRenderbuffer);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(desc.width, desc.height, 0, kBufferAsRenderbuffer));
     }
 
     // Draw a simple checkerboard pattern in the second context, which will
@@ -1340,7 +1373,8 @@
     if (!SetUpBuffer(desc)) return;
 
     MakeCurrent(1);
-    SetUpFramebuffer(desc.width, desc.height, 0, kBufferAsRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(desc.width, desc.height, 0, kBufferAsRenderbuffer));
     // Draw a simple checkerboard pattern in the second context, which will
     // be current after the loop above, then read it in the first.
     DrawCheckerboard(desc.width, desc.height);
@@ -1352,7 +1386,7 @@
     void* data = nullptr;
     int result = AHardwareBuffer_lock(mBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
                                       -1, nullptr, &data);
-    ASSERT_EQ(NO_ERROR, result);
+    ASSERT_EQ(NO_ERROR, result) << "AHardwareBuffer_lock failed with error " << result;
 
     std::vector<GoldenPixel> goldens{
         {0, 9, kRed},  {4, 9, kRed},  {5, 9, kBlue},  {9, 9, kBlue},
@@ -1434,28 +1468,31 @@
     const int kTextureUnit = 6 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(mTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     }
     // In the second context, upload opaque red to the texture.
-    UploadRedPixels(desc);
+    ASSERT_NO_FATAL_FAILURE(UploadRedPixels(desc));
     glFinish();
 
     // In the first context, draw a quad that samples from the texture.
     MakeCurrent(0);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClear(GL_COLOR_BUFFER_BIT);
 
     if (desc.layers > 1) {
-        SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x, kArrayFragmentShaderEs30,
-                     kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x,
+                         kArrayFragmentShaderEs30, kQuadPositions, 0.5f, kTextureUnit));
     } else {
-        SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions,
+                         0.5f, kTextureUnit));
     }
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. There should be a red square in the middle.
     GoldenColor color = kRed;
@@ -1482,30 +1519,34 @@
     const int kTextureUnit = 1 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(mTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     }
 
     // In the second context, draw a checkerboard pattern.
-    SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kBufferAsTexture);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kBufferAsTexture));
     DrawCheckerboard(desc.width, desc.height);
     glFinish();
 
     // In the first context, draw a quad that samples from the texture.
     MakeCurrent(0);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClear(GL_COLOR_BUFFER_BIT);
 
     if (desc.layers > 1) {
-        SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x, kArrayFragmentShaderEs30,
-                     kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x,
+                         kArrayFragmentShaderEs30, kQuadPositions, 0.5f, kTextureUnit));
     } else {
-        SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions,
+                         0.5f, kTextureUnit));
     }
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. The lower left area of the checkerboard will
     // be either transparent or opaque black depending on whether the texture
@@ -1540,14 +1581,16 @@
     const int kTextureUnit = 7 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
     }
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Draw checkerboard for mipmapping.
     const int kTileWidth = desc.width / kNumTiles;
     const int kTileHeight = desc.height / kNumTiles;
-    SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kBufferAsTexture);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kBufferAsTexture));
     glEnable(GL_SCISSOR_TEST);
     for (int i = 0; i < kNumTiles; ++i) {
         for (int j = 0; j < kNumTiles; ++j) {
@@ -1560,10 +1603,12 @@
     glDisable(GL_SCISSOR_TEST);
     glGenerateMipmap(mTexTarget);
     glFinish();
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     MakeCurrent(0);
-    SetUpFramebuffer(1, 1, desc.layers - 1, kBufferAsTexture, kNone, kNone, kNone,
-                     MipLevelCount(desc.width, desc.height) - 1);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(1, 1, desc.layers - 1, kBufferAsTexture, kNone, kNone, kNone,
+                         MipLevelCount(desc.width, desc.height) - 1));
     std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
     CheckGoldenPixels(goldens, desc.format);
 }
@@ -1581,23 +1626,26 @@
     const int kTextureUnit = 4 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
     }
 
     for (int i = 0; i < 6; ++i) {
-        SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + i, kBufferAsTexture);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + i, kBufferAsTexture));
         DrawCheckerboard(desc.width, desc.height);
     }
     glFinish();
 
     MakeCurrent(0);
     if (desc.layers > 6) {
-        SetUpProgram(std::string("#version 320 es") + kVertexShaderEs3x,
-                     kCubeMapArrayFragmentShaderEs32, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(std::string("#version 320 es") + kVertexShaderEs3x,
+                         kCubeMapArrayFragmentShaderEs32, kQuadPositions, 0.5f, kTextureUnit));
     } else {
-        SetUpProgram(kVertexShader, kCubeMapFragmentShader, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(kVertexShader, kCubeMapFragmentShader, kQuadPositions, 0.5f, kTextureUnit));
     }
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     for (int i = 0; i < 6; ++i) {
         float face_vector[3] = {0.f, 0.f, 0.f};
         face_vector[i / 2] = (i % 2) ? -1.f : 1.f;
@@ -1636,15 +1684,17 @@
     desc.layers *= 6;
     if (!SetUpBuffer(desc)) return;
 
+    const int kTextureUnit = 5 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, 5);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
     }
 
     const int kTileSize = desc.width / kNumTiles;
     glEnable(GL_SCISSOR_TEST);
     for (int face = 0; face < 6; ++face) {
-        SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + face, kBufferAsTexture);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + face, kBufferAsTexture));
         for (int i = 0; i < kNumTiles; ++i) {
                 for (int j = 0; j < kNumTiles; ++j) {
                 const float v = (i & 1) ^ (j & 1) ? 1.f : 0.f;
@@ -1660,8 +1710,9 @@
 
     MakeCurrent(0);
     for (int face = 0; face < 6; ++face) {
-        SetUpFramebuffer(1, 1, desc.layers - 6 + face, kBufferAsTexture, kNone, kNone, kNone,
-                         MipLevelCount(desc.width, desc.height) - 1);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(1, 1, desc.layers - 6 + face, kBufferAsTexture, kNone, kNone, kNone,
+                             MipLevelCount(desc.width, desc.height) - 1));
         std::vector<GoldenPixel> goldens{{0, 0, (desc.stride & kUseSrgb) ? kRed50Srgb : kRed50}};
         CheckGoldenPixels(goldens, desc.format);
     }
@@ -1674,8 +1725,7 @@
         AHardwareBuffer_Desc{75, 33, 1, GL_RGB8, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{64, 80, 1, GL_RGBA8, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{49, 23, 1, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
-        // TODO: enable for Android Q.
-        // AHardwareBuffer_Desc{63, 78, 1, GL_RGB565, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{63, 78, 1, GL_RGB565, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{42, 41, 1, GL_RGBA16F, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{37, 63, 1, GL_RGB10_A2, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{33, 20, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
@@ -1696,8 +1746,7 @@
         AHardwareBuffer_Desc{64, 80, 6, GL_RGBA8, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{33, 28, 4, GL_SRGB8_ALPHA8, 0, kGlFormat | kUseSrgb, 0, 0},
         AHardwareBuffer_Desc{42, 41, 3, GL_RGBA16F, 0, kGlFormat, 0, 0},
-        // TODO: enable for Android Q.
-        // AHardwareBuffer_Desc{63, 78, 3, GL_RGB565, 0, kGlFormat, 0, 0},
+        AHardwareBuffer_Desc{63, 78, 3, GL_RGB565, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{37, 63, 4, GL_RGB10_A2, 0, kGlFormat, 0, 0},
         AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, 0, 0, 0},
         AHardwareBuffer_Desc{25, 77, 7, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 0, kUseSrgb, 0, 0},
@@ -1728,7 +1777,8 @@
     // The depth buffer is shared, but the color buffer is not.
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpFramebuffer(40, 40, 0, kRenderbuffer, kBufferAsRenderbuffer);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(40, 40, 0, kRenderbuffer, kBufferAsRenderbuffer));
     }
 
     // In the second context, clear the depth buffer to a checkerboard pattern.
@@ -1737,13 +1787,14 @@
 
     // In the first context, clear the color buffer only, then draw a red pyramid.
     MakeCurrent(0);
-    SetUpProgram(kVertexShader, kColorFragmentShader, kPyramidPositions, 1.f);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpProgram(kVertexShader, kColorFragmentShader, kPyramidPositions, 1.f));
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClear(GL_COLOR_BUFFER_BIT);
     glEnable(GL_DEPTH_TEST);
     glDepthFunc(GL_LESS);
     glDrawArrays(GL_TRIANGLES, 0, kPyramidVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check golden pixels.
     std::vector<GoldenPixel> goldens{
@@ -1766,31 +1817,34 @@
     const int kTextureUnit = 3 % mMaxTextureUnits;
     for (int i = 0; i < 2; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(mTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     }
 
     // In the second context, attach the depth texture to the framebuffer and clear to 1.
-    SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kNone, kBufferAsTexture);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(desc.width, desc.height, desc.layers - 1, kNone, kBufferAsTexture));
     glClearDepthf(1.f);
     glClear(GL_DEPTH_BUFFER_BIT);
     glFinish();
 
     // In the first context, draw a quad using the depth texture.
     MakeCurrent(0);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClear(GL_COLOR_BUFFER_BIT);
     if (desc.layers > 1) {
-        SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x, kArrayFragmentShaderEs30,
-                     kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x, kArrayFragmentShaderEs30,
+                         kQuadPositions, 0.5f, kTextureUnit));
     } else {
-        SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(kVertexShader, kTextureFragmentShader, kQuadPositions, 0.5f, kTextureUnit));
     }
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
     glFinish();
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check the rendered pixels. There should be a square in the middle.
     const GoldenColor kDepth = mGLVersion < 30 ? kWhite : kRed;
@@ -1816,14 +1870,15 @@
     const int kTextureUnit = 9 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(mTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     }
 
     glEnable(GL_SCISSOR_TEST);
     for (int i = 0; i < 6; ++i) {
-        SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + i, kNone, kBufferAsTexture);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(desc.width, desc.height, desc.layers - 6 + i, kNone, kBufferAsTexture));
         glClearDepthf(0.f);
         glScissor(0, 0, desc.width, desc.height);
         glClear(GL_DEPTH_BUFFER_BIT);
@@ -1835,15 +1890,18 @@
     }
     glDisable(GL_SCISSOR_TEST);
     glFinish();
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     MakeCurrent(0);
     if (desc.layers > 6) {
-        SetUpProgram(std::string("#version 320 es") + kVertexShaderEs3x,
-                     kCubeMapArrayFragmentShaderEs32, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(std::string("#version 320 es") + kVertexShaderEs3x,
+                         kCubeMapArrayFragmentShaderEs32, kQuadPositions, 0.5f, kTextureUnit));
     } else {
-        SetUpProgram(kVertexShader, kCubeMapFragmentShader, kQuadPositions, 0.5f, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpProgram(kVertexShader, kCubeMapFragmentShader, kQuadPositions, 0.5f, kTextureUnit));
     }
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     const GoldenColor kDepth = mGLVersion < 30 ? kWhite: kRed;
     for (int i = 0; i < 6; ++i) {
         float face_vector[3] = {0.f, 0.f, 0.f};
@@ -1852,6 +1910,7 @@
         glClearColor(0.f, 0.f, 0.f, 0.f);
         glClear(GL_COLOR_BUFFER_BIT);
         glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
+        ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
         std::vector<GoldenPixel> goldens{
             {5, 35, kZero}, {15, 35, kZero},  {25, 35, kZero},  {35, 35, kZero},
@@ -1905,16 +1964,19 @@
     // The depth buffer is shared, but the color buffer is not.
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpFramebuffer(40, 40, 0, kRenderbuffer, kNone, kBufferAsRenderbuffer);
+        ASSERT_NO_FATAL_FAILURE(
+            SetUpFramebuffer(40, 40, 0, kRenderbuffer, kNone, kBufferAsRenderbuffer));
     }
 
     // In the second context, clear the stencil buffer to a checkerboard pattern.
     DrawCheckerboard(40, 40);
     glFinish();
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // In the first context, clear the color buffer only, then draw a flat quad.
     MakeCurrent(0);
-    SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 1.f);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpProgram(kVertexShader, kColorFragmentShader, kQuadPositions, 1.f));
     glClearColor(0.f, 0.f, 0.f, 0.f);
     glClear(GL_COLOR_BUFFER_BIT);
     glEnable(GL_STENCIL_TEST);
@@ -1929,7 +1991,7 @@
     glStencilFunc(GL_EQUAL, 4, 0xFF);
     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check golden pixels.
     std::vector<GoldenPixel> goldens{
@@ -1959,7 +2021,7 @@
     const int kTextureUnit = 8 % mMaxTextureUnits;
     for (int i = 0; i < mContextCount; ++i) {
         MakeCurrent(i);
-        SetUpTexture(desc, kTextureUnit);
+        ASSERT_NO_FATAL_FAILURE(SetUpTexture(desc, kTextureUnit));
         glTexParameteri(mTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glTexParameteri(mTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         if (!kPureStencil) {
@@ -1968,19 +2030,22 @@
     }
 
     // In the second context, clear the stencil buffer to a checkerboard pattern.
-    SetUpFramebuffer(desc.width, desc.height, desc.layers - 1,
-                     kNone, kNone, kBufferAsTexture);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpFramebuffer(desc.width, desc.height, desc.layers - 1,
+                         kNone, kNone, kBufferAsTexture));
     DrawCheckerboard(desc.width, desc.height);
     glFinish();
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // In the first context, reconstruct the checkerboard with a special shader.
     MakeCurrent(0);
-    SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x,
-                 desc.layers > 1 ? kStencilArrayFragmentShaderEs30 : kStencilFragmentShaderEs30,
-                 kQuadPositions, 1.f, kTextureUnit);
-    SetUpFramebuffer(40, 40, 0, kRenderbuffer);
+    ASSERT_NO_FATAL_FAILURE(
+        SetUpProgram(std::string("#version 300 es") + kVertexShaderEs3x,
+                     desc.layers > 1 ? kStencilArrayFragmentShaderEs30 : kStencilFragmentShaderEs30,
+                     kQuadPositions, 1.f, kTextureUnit));
+    ASSERT_NO_FATAL_FAILURE(SetUpFramebuffer(40, 40, 0, kRenderbuffer));
     glDrawArrays(GL_TRIANGLES, 0, kQuadVertexCount);
-    EXPECT_EQ(GLenum{GL_NO_ERROR}, glGetError());
+    ASSERT_EQ(GLenum{GL_NO_ERROR}, glGetError());
 
     // Check golden pixels.
     std::vector<GoldenPixel> goldens{
diff --git a/tests/tests/nativehardware/jni/Android.mk b/tests/tests/nativehardware/jni/Android.mk
index a4df04f..6f41a5d 100644
--- a/tests/tests/nativehardware/jni/Android.mk
+++ b/tests/tests/nativehardware/jni/Android.mk
@@ -21,6 +21,7 @@
 LOCAL_SRC_FILES := \
     AHardwareBufferGLTest.cpp \
     AHardwareBufferTest.cpp \
+    AtomicBufferStateTest.cpp \
     GTestMain.cpp
 
 LOCAL_SHARED_LIBRARIES := libandroid liblog libEGL libGLESv2 libGLESv3
diff --git a/tests/tests/nativehardware/jni/AtomicBufferStateTest.cpp b/tests/tests/nativehardware/jni/AtomicBufferStateTest.cpp
new file mode 100644
index 0000000..be08a11
--- /dev/null
+++ b/tests/tests/nativehardware/jni/AtomicBufferStateTest.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+#include <gtest/gtest.h>
+
+namespace android {
+
+namespace {
+using AtomicBufferStateTest = ::testing::Test;
+}  // namespace
+
+// Part of the buffer synchronization mechanism is achieved by using atomic
+// variables, e.g. buffer state, located in Android shared memory. This test is
+// to makes sure that a variable of type std::atomic<uint32_t> is lock-free, and
+// hopefully address-free, so that they would be suitable for communication
+// between processes using shared memory.
+TEST(AtomicBufferStateTest, AtomicsRequiresLockFree) {
+  std::atomic<uint32_t> fake_buffer_state(~0U);
+  EXPECT_TRUE(fake_buffer_state.is_lock_free());
+}
+
+}  // namespace android
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index 141b867..cf8bcff 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -184,8 +184,7 @@
     // Allocate a buffer for the audio data.
     // TODO handle possibility of other data formats
     size_t dataSizeSamples = framesPerBurst() * actual().channelCount;
-    mData.reset(new int16_t[dataSizeSamples]);
-    memset(&mData[0], 0, dataSizeSamples);
+    mData.reset(new int16_t[dataSizeSamples]{});
 }
 
 TEST_P(AAudioOutputStreamTest, testWriting) {
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
index d48aeab..4e4d022 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
@@ -130,6 +130,7 @@
     AAUDIO_INPUT_PRESET_VOICE_RECOGNITION,
     AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION,
     AAUDIO_INPUT_PRESET_UNPROCESSED,
+    AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE,
 };
 
 
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
index 01ed9f6..9bb5484 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
@@ -99,4 +99,5 @@
     static_assert(6 == AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, ENUM_CANNOT_CHANGE);
     static_assert(7 == AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, ENUM_CANNOT_CHANGE);
     static_assert(9 == AAUDIO_INPUT_PRESET_UNPROCESSED, ENUM_CANNOT_CHANGE);
+    static_assert(10 == AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE, ENUM_CANNOT_CHANGE);
 }
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
index a3aa75d..626422a 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
@@ -17,9 +17,51 @@
 #define LOG_NDEBUG 0
 #define LOG_TAG "AAudioTest"
 
+#include <cstring>
+
 #include <aaudio/AAudio.h>
 #include <android/log.h>
 #include <gtest/gtest.h>
+#include <sys/system_properties.h>
+
+// This was copied from "system/core/libcutils/properties.cpp" because the linker says
+// "libnativeaaudiotest (native:ndk:libc++:static) should not link to libcutils (native:platform)"
+static int8_t my_property_get_bool(const char *key, int8_t default_value) {
+    if (!key) {
+        return default_value;
+    }
+
+    int8_t result = default_value;
+    char buf[PROP_VALUE_MAX] = {'\0'};
+
+    int len = __system_property_get(key, buf);
+    if (len == 1) {
+        char ch = buf[0];
+        if (ch == '0' || ch == 'n') {
+            result = false;
+        } else if (ch == '1' || ch == 'y') {
+            result = true;
+        }
+    } else if (len > 1) {
+        if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
+            result = false;
+        } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) {
+            result = true;
+        }
+    }
+
+    return result;
+}
+
+/**
+ * See https://source.android.com/devices/tech/perf/low-ram
+ * for more details.
+ *
+ * @return true if running on low memory device
+ */
+static bool isLowRamDevice() {
+    return (bool) my_property_get_bool("ro.config.low_ram", false);
+}
 
 // Creates a builder, the caller takes ownership
 static void create_stream_builder(AAudioStreamBuilder** aaudioBuilder) {
@@ -110,11 +152,18 @@
 };
 
 TEST_P(AAudioStreamBuilderSamplingRateTest, openStream) {
+    const int32_t sampleRate = GetParam();
+    const bool isSampleRateValid = isValidSamplingRate(sampleRate);
+    // Opening a stream with a high sample rates can fail because the required buffer size
+    // is bigger than the heap size. This is a limitation in AudioFlinger.  b/112528380
+    if (isSampleRateValid && isLowRamDevice() && (sampleRate > 192000)) {
+        return; // skip this test
+    }
     AAudioStreamBuilder *aaudioBuilder = nullptr;
     create_stream_builder(&aaudioBuilder);
-    AAudioStreamBuilder_setSampleRate(aaudioBuilder, GetParam());
+    AAudioStreamBuilder_setSampleRate(aaudioBuilder, sampleRate);
     try_opening_audio_stream(
-            aaudioBuilder, isValidSamplingRate(GetParam()) ? Expect::SUCCEED : Expect::FAIL);
+            aaudioBuilder, isSampleRateValid ? Expect::SUCCEED : Expect::FAIL);
 }
 
 INSTANTIATE_TEST_CASE_P(SR, AAudioStreamBuilderSamplingRateTest,
diff --git a/tests/tests/nativemidi/Android.mk b/tests/tests/nativemidi/Android.mk
new file mode 100755
index 0000000..5da14e4
--- /dev/null
+++ b/tests/tests/nativemidi/Android.mk
@@ -0,0 +1,46 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+#
+# NativeMidiEchoTest
+#
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_PACKAGE_NAME := CtsNativeMidiTestCases
+LOCAL_MULTILIB := both
+
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/nativemidi/AndroidManifest.xml b/tests/tests/nativemidi/AndroidManifest.xml
new file mode 100755
index 0000000..5c42c52
--- /dev/null
+++ b/tests/tests/nativemidi/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.nativemidi.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <uses-feature android:name="android.software.midi" android:required="true"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <service android:name="NativeMidiEchoTestService"
+                android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+            <intent-filter>
+                <action android:name="android.media.midi.MidiDeviceService" />
+            </intent-filter>
+            <meta-data android:name="android.media.midi.MidiDeviceService"
+                android:resource="@xml/echo_device_info" />
+        </service>
+
+        <activity android:name="android.nativemidi.cts.NativeMidiEchoTest"
+                  android:label="NativeMidiEchoTest"/>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS Native MIDI tests"
+        android:targetPackage="android.nativemidi.cts" >
+        <meta-data
+            android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/nativemidi/AndroidTest.xml b/tests/tests/nativemidi/AndroidTest.xml
new file mode 100644
index 0000000..9ad78ec
--- /dev/null
+++ b/tests/tests/nativemidi/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<configuration description="Config for CTS MIDI test cases">
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="test-suite-tag" value="cts" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsNativeMidiTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.nativemidi.cts" />
+        <option name="runtime-hint" value="8m" />
+    </test>
+</configuration>
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
new file mode 100644
index 0000000..1de9703
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -0,0 +1,544 @@
+/*
+ * 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.nativemidi.cts;
+
+import android.app.Activity;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDevice.MidiConnection;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceInfo.PortInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+
+import android.os.Bundle;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.test.AndroidTestCase;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.junit.Assert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/*
+ * Test Class
+ */
+@RunWith(AndroidJUnit4.class)
+public class NativeMidiEchoTest {
+    private static final String TAG = "NativeMidiEchoTest";
+
+    public static final String TEST_MANUFACTURER = "AndroidCTS";
+    public static final String ECHO_PRODUCT = "NativeMidiEcho";
+
+    private static final long NANOS_PER_MSEC = 1000L * 1000L;
+
+    // This number seems excessively large and it is not clear if there is a linear
+    // relationship between the number of messages sent and the time required to send them
+    private static final int TIMEOUT_PER_MESSAGE_MS = 10;
+
+    // This timeout value is very generous.
+    private static final int TIMEOUT_OPEN_MSEC = 2000; // arbitrary
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private MidiManager mMidiManager;
+
+    private MidiDevice mEchoDevice;
+
+    private Random mRandom = new Random(1972941337);
+
+    // (Native code) attributes associated with a test/EchoServer instance.
+    private long mTestContext;
+
+    static {
+        System.loadLibrary("nativemidi_jni");
+    }
+
+    /*
+     * Helpers
+     */
+    private boolean hasMidiSupport() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_MIDI);
+    }
+
+    private byte[] generateRandomMessage(int len) {
+        byte[] buffer = new byte[len];
+        for(int index = 0; index < len; index++) {
+            buffer[index] = (byte)(mRandom.nextInt() & 0xFF);
+        }
+        return buffer;
+    }
+
+    private void generateRandomBufers(byte[][] buffers, long timestamps[], int numMessages) {
+        int messageLen;
+        int maxMessageLen = 128;
+        for(int buffIndex = 0; buffIndex < numMessages; buffIndex++) {
+            messageLen = (int)(mRandom.nextFloat() * (maxMessageLen-1)) + 1;
+            buffers[buffIndex] = generateRandomMessage(messageLen);
+            timestamps[buffIndex] = mRandom.nextLong();
+        }
+    }
+
+    private void compareMessages(byte[] buffer, long timestamp, NativeMidiMessage nativeMsg) {
+        Assert.assertEquals("byte count of message", buffer.length, nativeMsg.len);
+        Assert.assertEquals("timestamp in message", timestamp, nativeMsg.timestamp);
+
+        for (int index = 0; index < buffer.length; index++) {
+            Assert.assertEquals("message byte[" + index + "]", buffer[index] & 0x0FF,
+                    nativeMsg.buffer[index] & 0x0FF);
+        }
+    }
+
+    /*
+     * Echo Server
+     */
+    // Listens for an asynchronous device open and notifies waiting foreground
+    // test.
+    class MyTestOpenCallback implements MidiManager.OnDeviceOpenedListener {
+        private volatile MidiDevice mDevice;
+
+        @Override
+        public synchronized void onDeviceOpened(MidiDevice device) {
+            mDevice = device;
+            notifyAll();
+        }
+
+        public synchronized MidiDevice waitForOpen(int msec)
+              throws InterruptedException {
+            long deadline = System.currentTimeMillis() + msec;
+            long timeRemaining = msec;
+            while (mDevice == null && timeRemaining > 0) {
+                wait(timeRemaining);
+                timeRemaining = deadline - System.currentTimeMillis();
+            }
+            return mDevice;
+        }
+    }
+
+     protected void setUpEchoServer() throws Exception {
+        Log.i(TAG, "++ setUpEchoServer()");
+        MidiDeviceInfo echoInfo = findEchoDevice();
+
+        // Open device.
+        MyTestOpenCallback callback = new MyTestOpenCallback();
+        mMidiManager.openDevice(echoInfo, callback, null);
+        mEchoDevice = callback.waitForOpen(TIMEOUT_OPEN_MSEC);
+        Assert.assertNotNull("could not open " + ECHO_PRODUCT, mEchoDevice);
+
+        // Query echo service directly to see if it is getting status updates.
+        NativeMidiEchoTestService echoService = NativeMidiEchoTestService.getInstance();
+
+        mTestContext = allocTestContext();
+        Assert.assertTrue("couldn't allocate test context.", mTestContext != 0);
+
+        // Open Device
+        int result = openNativeMidiDevice(mTestContext, mEchoDevice);
+        Assert.assertEquals("Bad open native MIDI device", 0, result);
+
+        // Open Input
+        result = startWritingMidi(mTestContext, 0/*mPortNumber*/);
+        Assert.assertEquals("Bad start writing (native) MIDI", 0, result);
+
+        // Open Output
+        result = startReadingMidi(mTestContext, 0/*mPortNumber*/);
+        Assert.assertEquals("Bad start Reading (native) MIDI", 0, result);
+    }
+
+    protected void tearDownEchoServer() throws IOException {
+        Log.i(TAG, "++ tearDownEchoServer()");
+        // Query echo service directly to see if it is getting status updates.
+        NativeMidiEchoTestService echoService = NativeMidiEchoTestService.getInstance();
+
+        int result;
+
+        // Stop inputs
+        result = stopReadingMidi(mTestContext);
+        Assert.assertEquals("Bad stop reading (native) MIDI", 0, result);
+
+        // Stop outputs
+        result = stopWritingMidi(mTestContext);
+        Assert.assertEquals("Bad stop writing (native) MIDI", 0, result);
+
+        // Close Device
+        result = closeNativeMidiDevice(mTestContext);
+        Assert.assertEquals("Bad close native MIDI device", 0, result);
+
+        freeTestContext(mTestContext);
+        mTestContext = 0;
+
+        mEchoDevice.close();
+    }
+
+    // Search through the available devices for the ECHO loop-back device.
+    protected MidiDeviceInfo findEchoDevice() {
+        MidiDeviceInfo[] infos = mMidiManager.getDevices();
+        MidiDeviceInfo echoInfo = null;
+        for (MidiDeviceInfo info : infos) {
+            Bundle properties = info.getProperties();
+            String manufacturer = (String) properties.get(
+                    MidiDeviceInfo.PROPERTY_MANUFACTURER);
+
+            if (TEST_MANUFACTURER.equals(manufacturer)) {
+                String product = (String) properties.get(
+                        MidiDeviceInfo.PROPERTY_PRODUCT);
+                if (ECHO_PRODUCT.equals(product)) {
+                    echoInfo = info;
+                    break;
+                }
+            }
+        }
+        Assert.assertNotNull("could not find " + ECHO_PRODUCT, echoInfo);
+        return echoInfo;
+    }
+
+    @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);
+
+        setUpEchoServer();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        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
+        }
+
+        Assert.assertNotNull("MidiManager not supported.", mMidiManager);
+
+        // There should be at least one device for the Echo server.
+        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_B_SendData() throws Exception {
+        Log.i(TAG, "++++ test_B_SendData() this:" + System.identityHashCode(this));
+
+        Assert.assertEquals("Didn't start with 0 sends", 0, getNumSends(mTestContext));
+        Assert.assertEquals("Didn't start with 0 bytes sent", 0, getNumBytesSent(mTestContext));
+
+        final byte[] buffer = {
+                (byte) 0x93, 0x47, 0x52
+        };
+        long timestamp = 0x0123765489ABFEDCL;
+        writeMidi(mTestContext, buffer, 0, buffer.length);
+
+        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
+        }
+
+        final byte[] buffer = {
+                (byte) 0x93, 0x47, 0x52
+        };
+        long timestamp = 0x0123765489ABFEDCL;
+
+        writeMidiWithTimestamp(mTestContext, buffer, 0, 0, timestamp); // should be a NOOP
+        writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, timestamp);
+        writeMidiWithTimestamp(mTestContext, buffer, 0, 0, timestamp); // should be a NOOP
+
+        // Wait for message to pass quickly through echo service.
+        final int numMessages = 1;
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+        Assert.assertEquals("number of messages.",
+                numMessages, getNumReceivedMessages(mTestContext));
+
+        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
+        }
+
+        int numMessages = 100;
+        byte[][] buffers = new byte[numMessages][];
+        long timestamps[] = new long[numMessages];
+        generateRandomBufers(buffers, timestamps, numMessages);
+
+        for(int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+                    timestamps[msgIndex]);
+        }
+
+        // Wait for message to pass quickly through echo service.
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+
+        // correct number of messages
+        Assert.assertEquals("number of messages.",
+                numMessages, getNumReceivedMessages(mTestContext));
+
+        // correct data & order?
+        for(int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            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
+        }
+
+        int numMessages = 7;
+        byte[][] buffers = new byte[numMessages][];
+        long timestamps[] = new long[numMessages];
+        generateRandomBufers(buffers, timestamps, numMessages);
+
+        for(int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+              timestamps[msgIndex]);
+        }
+
+        // Wait for message to pass through echo service.
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+
+        int result = flushSentMessages(mTestContext);
+        Assert.assertEquals("flush messages failed", 0, result);
+
+        // correct number of messages
+        Assert.assertEquals("number of messages.",
+                numMessages, getNumReceivedMessages(mTestContext));
+
+        // correct data & order?
+        for(int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            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
+        }
+
+        // Arbitrarily large message.
+        int hugeMessageLen = 1024 * 10;
+        byte[] buffer = generateRandomMessage(hugeMessageLen);
+        int result = writeMidi(mTestContext, buffer, 0, buffer.length);
+        Assert.assertEquals("Huge write failed.", hugeMessageLen, result);
+
+        int kindaHugeMessageLen = 1024 * 2;
+        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");
+    }
+
+    /**
+     * Check a large timeout for the echoed messages to come through. If they exceed this
+     * or don't come through at all, something is wrong.
+     */
+    @Test
+    public void test_G_NativeEchoTime() throws Exception {
+        Log.i(TAG, "++++ test_G_NativeEchoTime() this:" + System.identityHashCode(this));
+        if (!hasMidiSupport()) {
+            return; // nothing to test
+        }
+
+        final int numMessages = 10;
+        final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+        byte[] buffer = { (byte) 0x93, 0, 64 };
+
+        // Send multiple messages in a burst.
+        for (int index = 0; index < numMessages; index++) {
+            buffer[1] = (byte) (60 + index);
+            writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, System.nanoTime());
+        }
+
+        // Wait for messages to pass quickly through echo service.
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+        Assert.assertEquals("number of messages.",
+                numMessages, getNumReceivedMessages(mTestContext));
+
+        for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
+            Assert.assertEquals("message index", (byte) (60 + msgIndex), message.buffer[1]);
+            long elapsedNanos = message.timeReceived - message.timestamp;
+            // If this test fails then there may be a problem with the thread scheduler
+            // or there may be kernel activity that is blocking execution at the user level.
+            Assert.assertTrue("MIDI round trip latency index:" + msgIndex
+                    + " too large, " + elapsedNanos
+                    + " nanoseconds " +
+                    "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
+        }
+
+        int numMessages = 2;
+        byte[][] buffers = new byte[numMessages][];
+        long timestamps[] = new long[numMessages];
+        generateRandomBufers(buffers, timestamps, numMessages);
+
+        for(int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+            writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
+                    timestamps[msgIndex]);
+        }
+
+        // Wait for message to pass quickly through echo service.
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+
+        int result = matchNativeMessages(mTestContext);
+        Assert.assertEquals("Native Compare Test Failed", result, 0);
+
+        Log.i(TAG, "++++ test_H_EchoNMessages_PureNative() - DONE");
+    }
+
+    /**
+     * Check a large timeout for the echoed messages to come through. If they exceed this
+     * or don't come through at all, something is wrong.
+     */
+    @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
+        }
+
+        final int numMessages = 10;
+        final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+        byte[] buffer = { (byte) 0x93, 0, 64 };
+
+        // Send multiple messages in a burst.
+        for (int index = 0; index < numMessages; index++) {
+            buffer[1] = (byte) (60 + index);
+            writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, System.nanoTime());
+        }
+
+        // Wait for messages to pass through echo service.
+        final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
+        Thread.sleep(timeoutMs);
+        Assert.assertEquals("number of messages.",
+                numMessages, getNumReceivedMessages(mTestContext));
+
+        int result = checkNativeLatency(mTestContext, maxLatencyNanos);
+        Assert.assertEquals("failed pure native latency test.", 0, result);
+
+        Log.i(TAG, "++++ test_I_NativeEchoTime_PureNative() - DONE");
+    }
+
+    // Native Routines
+    public static native void initN();
+
+    public static native long allocTestContext();
+    public static native void freeTestContext(long context);
+
+    public native int openNativeMidiDevice(long ctx, MidiDevice device);
+    public native int closeNativeMidiDevice(long ctx);
+
+    public native int startReadingMidi(long ctx, int portNumber);
+    public native int stopReadingMidi(long ctx);
+
+    public native int startWritingMidi(long ctx, int portNumber);
+    public native int stopWritingMidi(long ctx);
+
+    public native int writeMidi(long ctx, byte[] data, int offset, int length);
+    public native int writeMidiWithTimestamp(long ctx, byte[] data, int offset, int length,
+            long timestamp);
+    public native int flushSentMessages(long ctx);
+
+    // Status - Counters
+    public native int getNumSends(long ctx);
+    public native int getNumBytesSent(long ctx);
+    public native int getNumReceives(long ctx);
+    public native int getNumBytesReceived(long ctx);
+
+    // Status - Received Messages
+    public native int getNumReceivedMessages(long ctx);
+    public native NativeMidiMessage getReceivedMessageAt(long ctx, int index);
+
+    // Pure Native Checks
+    public native int matchNativeMessages(long ctx);
+    public native int checkNativeLatency(long ctx, long maxLatencyNanos);
+}
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java
new file mode 100644
index 0000000..1984419
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTestService.java
@@ -0,0 +1,91 @@
+/*
+ * 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.nativemidi.cts;
+
+import android.content.Context;
+
+import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Virtual MIDI Device that copies its input to its output.
+ * This is a Java Service that is used to test the Native MIDI API.
+ */
+
+public class NativeMidiEchoTestService extends MidiDeviceService {
+    private static final String TAG = "NativeMidiEchoTestService";
+
+    // Other apps will write to this port.
+    private MidiReceiver mInputReceiver = new MyReceiver();
+    // This app will copy the data to this port.
+    private MidiReceiver mOutputReceiver;
+    private static NativeMidiEchoTestService mInstance;
+
+    // These are public so we can easily read them from CTS test.
+    public int statusChangeCount;
+    public boolean inputOpened;
+    public int outputOpenCount;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mInstance = this;
+        MidiManager midiManager = (MidiManager)getSystemService(Context.MIDI_SERVICE);
+        if (midiManager == null) {
+            Log.e(TAG, "No MIDI Manager!");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    // For CTS testing, so I can read test fields.
+    public static NativeMidiEchoTestService getInstance() {
+        return mInstance;
+    }
+
+    @Override
+    public MidiReceiver[] onGetInputPortReceivers() {
+        return new MidiReceiver[] { mInputReceiver };
+    }
+
+    class MyReceiver extends MidiReceiver {
+        @Override
+        public void onSend(byte[] data, int offset, int count, long timestamp)
+                throws IOException {
+            if (mOutputReceiver == null) {
+                mOutputReceiver = getOutputPortReceivers()[0];
+            }
+            // Copy input to output.
+            mOutputReceiver.send(data, offset, count, timestamp);
+        }
+    }
+
+    @Override
+    public void onDeviceStatusChanged(MidiDeviceStatus status) {
+        statusChangeCount++;
+        inputOpened = status.isInputPortOpen(0);
+        outputOpenCount = status.getOutputPortOpenCount(0);
+    }
+}
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java
new file mode 100644
index 0000000..d8186fe
--- /dev/null
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiMessage.java
@@ -0,0 +1,46 @@
+/*
+ * 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.nativemidi.cts;
+
+public class NativeMidiMessage {
+    public static final int AMIDI_PACKET_SIZE = 1024;
+    public static final int AMIDI_PACKET_OVERHEAD = 9;
+    public static final int AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD;
+
+    public int opcode;
+    public byte[] buffer = new byte[AMIDI_BUFFER_SIZE];
+    public int len;
+    public long timestamp;
+    public long timeReceived;
+
+    public NativeMidiMessage() {}
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("{opcode:" + opcode + " [len:" + len + "|");
+        for(int index = 0; index < len; index++) {
+            sb.append("0x" + Integer.toHexString(buffer[index] & 0x00FF));
+            if (index < len - 1) {
+                sb.append(" ");
+            }
+        }
+        sb.append("] timestamp:" + Long.toHexString(timestamp));
+
+        return sb.toString();
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/nativemidi/jni/Android.mk b/tests/tests/nativemidi/jni/Android.mk
new file mode 100644
index 0000000..bb62dec
--- /dev/null
+++ b/tests/tests/nativemidi/jni/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := libnativemidi_jni
+LOCAL_MULTILIB := both
+
+LOCAL_SRC_FILES := native-lib.cpp
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
+
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+
+LOCAL_SHARED_LIBRARIES := libamidi liblog
+LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativemidi/jni/native-lib.cpp b/tests/tests/nativemidi/jni/native-lib.cpp
new file mode 100644
index 0000000..e68859c
--- /dev/null
+++ b/tests/tests/nativemidi/jni/native-lib.cpp
@@ -0,0 +1,534 @@
+/*
+ * 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.
+ */
+
+#include <atomic>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string>
+#include <thread>
+#include <time.h>
+#include <vector>
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMidiManager-JNI"
+#include <android/log.h>
+
+#include <jni.h>
+
+#include <amidi/midi.h>
+
+extern "C" {
+
+/*
+ * Structures for storing data flowing through the echo server.
+ */
+#define SIZE_DATABUFFER 256
+/*
+ * Received Messages
+ */
+typedef struct {
+    std::unique_ptr<uint8_t[]> dataBuff;
+    size_t numDataBytes;
+    int32_t opCode;
+    int64_t timestamp;
+    int64_t timeReceived;
+} ReceivedMessageRecord;
+
+/*
+ * Sent Messages
+ */
+typedef struct {
+    std::unique_ptr<uint8_t[]> dataBuff;
+    size_t numDataBytes;
+    int64_t timestamp;
+    long timeSent;
+} SentMessageRecord;
+
+/*
+ * Context
+ * Holds the state of a given test and native MIDI I/O setup for that test.
+ * NOTE: There is one of these per test (and therefore unique to each test thread).
+ */
+class TestContext {
+private:
+    // counters
+    std::atomic<int> mNumSends;
+    std::atomic<int> mNumBytesSent;
+    std::atomic<int> mNumReceives;
+    std::atomic<int> mNumBytesReceived;
+
+    std::vector<ReceivedMessageRecord> mReceivedMsgs;
+    std::vector<SentMessageRecord> mSentMsgs;
+
+    // Java NativeMidiMessage class stuff, for passing messages back out to the Java client.
+    jclass mClsNativeMidiMessage;
+    jmethodID mMidNativeMidiMessage_ctor;
+    jfieldID mFid_opcode;
+    jfieldID mFid_buffer;
+    jfieldID mFid_len;
+    jfieldID mFid_timestamp;
+    jfieldID mFid_timeReceived;
+
+    std::mutex lock;
+
+public:
+    // read Thread
+    std::unique_ptr<std::thread> mReadThread;
+    std::atomic<bool> mReading;
+
+    AMidiDevice* nativeDevice;
+    std::atomic<AMidiOutputPort*> midiOutputPort;
+    std::atomic<AMidiInputPort*> midiInputPort;
+
+    TestContext() :
+        mNumSends(0),
+        mNumBytesSent(0),
+        mNumReceives(0),
+        mNumBytesReceived(0),
+        mClsNativeMidiMessage(0),
+        mMidNativeMidiMessage_ctor(0),
+        mFid_opcode(0),
+        mFid_buffer(0),
+        mFid_len(0),
+        mFid_timestamp(0),
+        mFid_timeReceived(0),
+        mReading(false),
+        nativeDevice(nullptr),
+        midiOutputPort(nullptr),
+        midiInputPort(nullptr)
+    {}
+
+    bool initN(JNIEnv* env);
+
+    int getNumSends() { return mNumSends; }
+    void incNumSends() { mNumSends++; }
+
+    int getNumBytesSent() { return mNumBytesSent; }
+    void incNumBytesSent(int numBytes) { mNumBytesSent += numBytes; }
+
+    int getNumReceives() { return mNumReceives; }
+    void incNumReceives() { mNumReceives++; }
+
+    int getNumBytesReceived() { return mNumBytesReceived; }
+    void incNumBytesReceived(int numBytes) { mNumBytesReceived += numBytes; }
+
+    void addSent(SentMessageRecord&& msg) { mSentMsgs.push_back(std::move(msg)); }
+    size_t getNumSentMsgs() { return mSentMsgs.size(); }
+
+    void addReceived(ReceivedMessageRecord&& msg) { mReceivedMsgs.push_back(std::move(msg)); }
+    size_t getNumReceivedMsgs() { return mReceivedMsgs.size(); }
+
+    jobject transferReceiveMsgAt(JNIEnv* env, int index);
+
+    static const int COMPARE_SUCCESS = 0;
+    static const int COMPARE_COUNTMISSMATCH = 1;
+    static const int COMPARE_DATALENMISMATCH = 2;
+    static const int COMPARE_DATAMISMATCH = 3;
+    static const int COMPARE_TIMESTAMPMISMATCH = 4;
+    int compareInsAndOuts();
+
+    static const int CHECKLATENCY_SUCCESS = 0;
+    static const int CHECKLATENCY_COUNTMISSMATCH = 1;
+    static const int CHECKLATENCY_LATENCYEXCEEDED = 2;
+    int checkInOutLatency(long maxLatencyNanos);
+};
+
+//
+// Helpers
+//
+static long System_nanoTime() {
+    // this code is the implementation of System.nanoTime()
+    // from system/code/ojluni/src/main/native/System.
+    struct timespec now;
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+bool TestContext::initN(JNIEnv* env) {
+    static const char* clsSigNativeMidiMessage = "android/nativemidi/cts/NativeMidiMessage";
+
+    jclass cls = env->FindClass(clsSigNativeMidiMessage);
+    if (cls == NULL) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                "JNI Error - couldn't find NativeMidiMessage class");
+        return false; // we are doomed, so bail.
+    }
+    mClsNativeMidiMessage = (jclass)env->NewGlobalRef(cls);
+    if (mClsNativeMidiMessage == NULL) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                "JNI Error - couldn't allocate NativeMidiMessage");
+        return false; // we are doomed, so bail.
+    }
+
+    mMidNativeMidiMessage_ctor = env->GetMethodID(mClsNativeMidiMessage, "<init>", "()V");
+    mFid_opcode = env->GetFieldID(mClsNativeMidiMessage, "opcode", "I");
+    mFid_buffer = env->GetFieldID(mClsNativeMidiMessage, "buffer", "[B");
+    mFid_len = env->GetFieldID( mClsNativeMidiMessage, "len", "I");
+    mFid_timestamp = env->GetFieldID(mClsNativeMidiMessage, "timestamp", "J");
+    mFid_timeReceived = env->GetFieldID(mClsNativeMidiMessage, "timeReceived", "J");
+    if (mMidNativeMidiMessage_ctor == NULL ||
+        mFid_opcode == NULL ||
+        mFid_buffer == NULL ||
+        mFid_len == NULL ||
+        mFid_timestamp == NULL ||
+        mFid_timeReceived == NULL) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                "JNI Error - couldn't load Field IDs");
+        return false; // we are doomed, so bail.
+    }
+
+    return true;
+}
+
+jobject TestContext::transferReceiveMsgAt(JNIEnv* env, int index) {
+    jobject msg = NULL;
+
+    if (index < (int)mReceivedMsgs.size()) {
+        ReceivedMessageRecord receiveRec = std::move(mReceivedMsgs.at(index));
+        msg = env->NewObject(mClsNativeMidiMessage, mMidNativeMidiMessage_ctor);
+
+        env->SetIntField(msg, mFid_opcode, receiveRec.opCode);
+        env->SetIntField(msg, mFid_len, receiveRec.numDataBytes);
+        jobject buffer_array = env->GetObjectField(msg, mFid_buffer);
+        env->SetByteArrayRegion(reinterpret_cast<jbyteArray>(buffer_array), 0,
+                receiveRec.numDataBytes, (jbyte*)receiveRec.dataBuff.get());
+        env->SetLongField(msg, mFid_timestamp, receiveRec.timestamp);
+        env->SetLongField(msg, mFid_timeReceived, receiveRec.timeReceived);
+    }
+
+    return msg;
+}
+
+int TestContext::compareInsAndOuts() {
+    // Number of messages sent/received
+    if (mReceivedMsgs.size() != mSentMsgs.size()) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- COMPARE_COUNTMISSMATCH r:%zu s:%zu",
+                mReceivedMsgs.size(), mSentMsgs.size());
+       return COMPARE_COUNTMISSMATCH;
+    }
+
+    // we know that both vectors have the same number of messages from the test above.
+    size_t numMessages = mSentMsgs.size();
+    for (size_t msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+        // Data Length?
+        if (mReceivedMsgs[msgIndex].numDataBytes != mSentMsgs[msgIndex].numDataBytes) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                    "---- COMPARE_DATALENMISMATCH r:%zu s:%zu",
+                    mReceivedMsgs[msgIndex].numDataBytes, mSentMsgs[msgIndex].numDataBytes);
+            return COMPARE_DATALENMISMATCH;
+        }
+
+        // Timestamps
+        if (mReceivedMsgs[msgIndex].timestamp != mSentMsgs[msgIndex].timestamp) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- COMPARE_TIMESTAMPMISMATCH");
+            return COMPARE_TIMESTAMPMISMATCH;
+        }
+
+        // we know that the data in both messages have the same number of bytes from the test above.
+        int dataLen = mReceivedMsgs[msgIndex].numDataBytes;
+        for (int dataIndex = 0; dataIndex < dataLen; dataIndex++) {
+            // Data Values?
+            if (mReceivedMsgs[msgIndex].dataBuff[dataIndex] !=
+                    mSentMsgs[msgIndex].dataBuff[dataIndex]) {
+                __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                        "---- COMPARE_DATAMISMATCH r:%d s:%d",
+                        (int)mReceivedMsgs[msgIndex].dataBuff[dataIndex],
+                        (int)mSentMsgs[msgIndex].dataBuff[dataIndex]);
+                return COMPARE_DATAMISMATCH;
+            }
+        }
+    }
+
+    return COMPARE_SUCCESS;
+}
+
+int TestContext::checkInOutLatency(long maxLatencyNanos) {
+    if (mReceivedMsgs.size() != mSentMsgs.size()) {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "  ---- CHECKLATENCY_COUNTMISSMATCH");
+        return CHECKLATENCY_COUNTMISSMATCH;
+    }
+
+    // we know that both vectors have the same number of messages
+    // from the test above.
+    int numMessages = mSentMsgs.size();
+    for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
+        long timeDelta =  mSentMsgs[msgIndex].timeSent - mReceivedMsgs[msgIndex].timestamp;
+        if (timeDelta > maxLatencyNanos) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                    "  ---- CHECKLATENCY_LATENCYEXCEEDED %ld", timeDelta);
+            return CHECKLATENCY_LATENCYEXCEEDED;
+        }
+    }
+
+    return CHECKLATENCY_SUCCESS;
+}
+
+JNIEXPORT jlong JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_allocTestContext(
+        JNIEnv* env, jclass) {
+    TestContext* context = new TestContext;
+    if (!context->initN(env)) {
+        delete context;
+        context = NULL;
+    }
+
+    return (jlong)context;
+}
+
+JNIEXPORT void JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_freeTestContext(
+        JNIEnv*, jclass, jlong context) {
+    delete (TestContext*)context;
+}
+
+/*
+ * Receiving API
+ */
+//static void DumpDataMessage(ReceivedMessageRecord* msg) {
+//    char midiDumpBuffer[SIZE_DATABUFFER * 4]; // more than enough
+//    memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
+//    int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+//            "%" PRIx64 " ", msg->timestamp);
+//    for (uint8_t *b = msg->buffer, *e = b + msg->numDataBytes; b < e; ++b) {
+//        pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
+//                "%02x ", *b);
+//    }
+//    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "---- DUMP %s", midiDumpBuffer);
+//}
+
+void readThreadRoutine(TestContext* context) {
+    int32_t opCode;
+    uint8_t inDataBuffer[SIZE_DATABUFFER];
+    size_t numDataBytes;
+    int64_t timestamp;
+
+    while (context->mReading) {
+        AMidiOutputPort* outputPort = context->midiOutputPort.load();
+        if (outputPort != nullptr) {
+            ssize_t numMessages =
+                AMidiOutputPort_receive(outputPort, &opCode,
+                    inDataBuffer, sizeof(inDataBuffer), &numDataBytes, &timestamp);
+
+            if (numMessages > 0) {
+                context->incNumReceives();
+                context->incNumBytesReceived(numDataBytes);
+                ReceivedMessageRecord receiveRec;
+                receiveRec.timeReceived = System_nanoTime();
+                receiveRec.numDataBytes = numDataBytes;
+                receiveRec.dataBuff.reset(new uint8_t[receiveRec.numDataBytes]);
+                memcpy(receiveRec.dataBuff.get(), inDataBuffer, receiveRec.numDataBytes);
+                receiveRec.opCode = opCode;
+                receiveRec.timestamp = timestamp;
+                context->addReceived(std::move(receiveRec));
+            }
+        }
+    }
+}
+
+static media_status_t commonDeviceOpen(JNIEnv *env, jobject midiDeviceObj, AMidiDevice** device) {
+    media_status_t status = AMidiDevice_fromJava(env, midiDeviceObj, device);
+    if (status == AMEDIA_OK) {
+        // __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+        //      "---- Obtained device token for obj %p: dev %p", midiDeviceObj, device);
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                "---- Could not obtain device token for obj %p: status:%d", midiDeviceObj, status);
+    }
+
+    return status;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_openNativeMidiDevice(
+        JNIEnv* env, jobject, jlong ctx, jobject deviceObj) {
+    TestContext* context = (TestContext*)ctx;
+    media_status_t status = commonDeviceOpen(env, deviceObj, &context->nativeDevice);
+    return status;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_closeNativeMidiDevice(
+        JNIEnv*, jobject, jlong ctx) {
+    TestContext* context = (TestContext*)ctx;
+    media_status_t status = AMidiDevice_release(context->nativeDevice);
+    return status;
+}
+
+/*
+ * Sending API
+ */
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startWritingMidi(
+        JNIEnv*, jobject, jlong ctx, jint portNumber) {
+
+    TestContext* context = (TestContext*)ctx;
+
+    AMidiInputPort* inputPort;
+    media_status_t status = AMidiInputPort_open(context->nativeDevice, portNumber, &inputPort);
+    if (status == AMEDIA_OK) {
+        // __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
+        //      "---- Opened INPUT port %d: token %p", portNumber, inputPort);
+        context->midiInputPort.store(inputPort);
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "---- Could not open INPUT port %p:%d %d",
+                context->nativeDevice, portNumber, status);
+        return status;
+    }
+
+    return AMEDIA_OK;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopWritingMidi(
+        JNIEnv*, jobject, jlong ctx) {
+
+    TestContext* context = (TestContext*)ctx;
+
+    AMidiInputPort* inputPort = context->midiInputPort.exchange(nullptr);
+    if (inputPort == nullptr) {
+        return -1;
+    }
+
+    AMidiInputPort_close(inputPort);
+
+    return 0;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
+        JNIEnv* env, jobject,
+        jlong ctx, jbyteArray data, jint offset, jint numBytes, jlong timestamp) {
+
+    TestContext* context = (TestContext*)ctx;
+    context->incNumSends();
+    context->incNumBytesSent(numBytes);
+
+    jbyte* bufferPtr = env->GetByteArrayElements(data, NULL);
+    if (bufferPtr == NULL) {
+        return -1;
+    }
+
+    int numWritten =  AMidiInputPort_sendWithTimestamp(
+            context->midiInputPort, (uint8_t*)bufferPtr + offset, numBytes, timestamp);
+    if (numWritten > 0) {
+        // Don't save a send record if we didn't send!
+        SentMessageRecord sendRec;
+        sendRec.numDataBytes = numBytes;
+        sendRec.dataBuff.reset(new uint8_t[sendRec.numDataBytes]);
+        memcpy(sendRec.dataBuff.get(), (uint8_t*)bufferPtr + offset, numBytes);
+        sendRec.timestamp = timestamp;
+        sendRec.timeSent = System_nanoTime();
+        context->addSent(std::move(sendRec));
+    }
+
+    env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT);
+
+    return numWritten;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidi(
+        JNIEnv* env, jobject j_object, jlong ctx, jbyteArray data, jint offset, jint numBytes) {
+    return Java_android_nativemidi_cts_NativeMidiEchoTest_writeMidiWithTimestamp(
+            env, j_object, ctx, data, offset, numBytes, 0L);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_flushSentMessages(
+        JNIEnv*, jobject, jlong ctx) {
+    TestContext* context = (TestContext*)ctx;
+    return AMidiInputPort_sendFlush(context->midiInputPort);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumSends(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->getNumSends();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesSent(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->getNumBytesSent();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceives(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->getNumReceives();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumBytesReceived(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->getNumBytesReceived();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_startReadingMidi(
+        JNIEnv*, jobject, jlong ctx, jint portNumber) {
+
+    // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ startReadingMidi()");
+    TestContext* context = (TestContext*)ctx;
+
+    AMidiOutputPort* outputPort;
+    media_status_t status = AMidiOutputPort_open(context->nativeDevice, portNumber, &outputPort);
+    if (status == AMEDIA_OK) {
+        context->midiOutputPort.store(outputPort);
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                "---- Could not open OUTPUT port %p : %d %d",
+                context->nativeDevice, portNumber, status);
+        return status;
+    }
+
+    // Start read thread
+    context->mReading = true;
+    context->mReadThread.reset(new std::thread(readThreadRoutine, context));
+    std::this_thread::yield(); // let the read thread startup.
+
+    return status;
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_stopReadingMidi(
+        JNIEnv*, jobject, jlong ctx) {
+
+    // __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "++++ stopReadingMidi()");
+    TestContext* context = (TestContext*)ctx;
+    context->mReading = false;
+
+    context->mReadThread->join();
+
+    AMidiOutputPort* outputPort = context->midiOutputPort.exchange(nullptr);
+    if (outputPort == nullptr) {
+        return -1;
+    }
+
+    AMidiOutputPort_close(outputPort);
+
+    return 0;
+}
+
+/*
+ * Messages
+ */
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getNumReceivedMessages(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->getNumReceivedMsgs();
+}
+
+JNIEXPORT jobject JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getReceivedMessageAt(
+        JNIEnv* env, jobject, jlong ctx, jint index) {
+    return ((TestContext*)ctx)->transferReceiveMsgAt(env, index);
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_matchNativeMessages(
+        JNIEnv*, jobject, jlong ctx) {
+    return ((TestContext*)ctx)->compareInsAndOuts();
+}
+
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_checkNativeLatency(
+        JNIEnv*, jobject, jlong ctx, jlong maxLatencyNanos) {
+    return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos);
+}
+
+} // extern "C"
diff --git a/tests/tests/nativemidi/res/xml/echo_device_info.xml b/tests/tests/nativemidi/res/xml/echo_device_info.xml
new file mode 100644
index 0000000..9f2ebee
--- /dev/null
+++ b/tests/tests/nativemidi/res/xml/echo_device_info.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<devices>
+    <device manufacturer="AndroidCTS" product="NativeMidiEcho" tags="echo,test">
+        <input-port name="input" />
+        <output-port name="output" />
+    </device>
+</devices>
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index 6e4f34e..01a7951 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -57,6 +57,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.internal.R;
 import com.android.internal.telephony.PhoneConstants;
 
@@ -670,7 +671,7 @@
 
         boolean connected = false;
         try {
-            assertTrue(mWifiManager.setWifiEnabled(true));
+            SystemUtil.runShellCommand("svc wifi enable");
             // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
             wifiNetwork = callback.waitForAvailable();
             assertNotNull(wifiNetwork);
@@ -736,7 +737,7 @@
 
         boolean disconnected = false;
         try {
-            assertTrue(mWifiManager.setWifiEnabled(false));
+            SystemUtil.runShellCommand("svc wifi disable");
             // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
             lostWifiNetwork = callback.waitForLost();
             assertNotNull(lostWifiNetwork);
diff --git a/tests/tests/net/src/android/net/wifi/aware/OWNERS b/tests/tests/net/src/android/net/wifi/aware/OWNERS
index 4afc47f..cf116f8 100644
--- a/tests/tests/net/src/android/net/wifi/aware/OWNERS
+++ b/tests/tests/net/src/android/net/wifi/aware/OWNERS
@@ -1,2 +1 @@
 etancohen@google.com
-satk@google.com
\ No newline at end of file
diff --git a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 7277553..61b4f91 100644
--- a/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -41,6 +41,8 @@
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -363,7 +365,7 @@
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
         if (!mWifiManager.isWifiEnabled()) {
-            mWifiManager.setWifiEnabled(true);
+            SystemUtil.runShellCommand("svc wifi enable");
         }
 
         mConnectivityManager = (ConnectivityManager) getContext().getSystemService(
@@ -433,7 +435,7 @@
         // 1. Disable Wi-Fi
         WifiAwareBroadcastReceiver receiver1 = new WifiAwareBroadcastReceiver();
         mContext.registerReceiver(receiver1, intentFilter);
-        mWifiManager.setWifiEnabled(false);
+        SystemUtil.runShellCommand("svc wifi disable");
 
         assertTrue("Timeout waiting for Wi-Fi Aware to change status",
                 receiver1.waitForStateChange());
@@ -442,7 +444,7 @@
         // 2. Enable Wi-Fi
         WifiAwareBroadcastReceiver receiver2 = new WifiAwareBroadcastReceiver();
         mContext.registerReceiver(receiver2, intentFilter);
-        mWifiManager.setWifiEnabled(true);
+        SystemUtil.runShellCommand("svc wifi enable");
 
         assertTrue("Timeout waiting for Wi-Fi Aware to change status",
                 receiver2.waitForStateChange());
diff --git a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
index a066ba8..5e91366 100644
--- a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -31,6 +31,8 @@
 import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -84,7 +86,7 @@
         mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
         assertNotNull(mWifiManager);
         if (mWifiManager.isWifiEnabled()) {
-            assertTrue(mWifiManager.setWifiEnabled(false));
+            SystemUtil.runShellCommand("svc wifi disable");
             Thread.sleep(DURATION);
         }
         assertTrue(!mWifiManager.isWifiEnabled());
@@ -124,7 +126,7 @@
      */
     private void enableWifi() throws InterruptedException {
         if (!mWifiManager.isWifiEnabled()) {
-            assertTrue(mWifiManager.setWifiEnabled(true));
+            SystemUtil.runShellCommand("svc wifi enable");
         }
 
         ConnectivityManager cm =
@@ -159,7 +161,7 @@
         }
 
         // Enable wifi
-        assertTrue(mWifiManager.setWifiEnabled(true));
+        SystemUtil.runShellCommand("svc wifi enable");
 
         waitForBroadcasts();
 
diff --git a/tests/tests/net/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/net/src/android/net/wifi/cts/ScanResultTest.java
index 8a22bef..836df61 100644
--- a/tests/tests/net/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/ScanResultTest.java
@@ -28,6 +28,8 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 public class ScanResultTest extends AndroidTestCase {
     private static class MySync {
         int expectedState = STATE_NULL;
@@ -121,7 +123,11 @@
     private void setWifiEnabled(boolean enable) throws Exception {
         synchronized (mMySync) {
             mMySync.expectedState = STATE_WIFI_CHANGING;
-            assertTrue(mWifiManager.setWifiEnabled(enable));
+            if (enable) {
+                SystemUtil.runShellCommand("svc wifi enable");
+            } else {
+                SystemUtil.runShellCommand("svc wifi disable");
+            }
             waitForBroadcast(TIMEOUT_MSEC, STATE_WIFI_CHANGED);
        }
     }
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index d3235da..0fce1ee 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -18,13 +18,14 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
 import android.net.wifi.WifiManager;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.io.ByteArrayInputStream;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
@@ -33,7 +34,7 @@
 import java.security.spec.PKCS8EncodedKeySpec;
 
 public class WifiEnterpriseConfigTest extends AndroidTestCase {
-    private  WifiManager mWifiManager;
+    private WifiManager mWifiManager;
 
     private static final String SSID = "\"TestSSID\"";
     private static final String IDENTITY = "identity";
@@ -687,7 +688,7 @@
         mWifiManager = (WifiManager) mContext
                 .getSystemService(Context.WIFI_SERVICE);
         assertNotNull(mWifiManager);
-        mWifiManager.setWifiEnabled(true);
+        SystemUtil.runShellCommand("svc wifi enable");
         Thread.sleep(ENABLE_DELAY);
         if (hasWifi()) {
             assertTrue(mWifiManager.isWifiEnabled());
@@ -774,33 +775,6 @@
         assertTrue(config.getDomainSuffixMatch().equals(DOM_SUBJECT_MATCH));
     }
 
-    public void testAddEapNetwork() {
-        if (!hasWifi()) {
-            return;
-        }
-
-        WifiConfiguration config = new WifiConfiguration();
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(Eap.PWD);
-        enterpriseConfig.setIdentity(IDENTITY);
-        enterpriseConfig.setPassword(PASSWORD);
-        config.SSID = SSID;
-        config.enterpriseConfig = enterpriseConfig;
-
-        int netId = mWifiManager.addNetwork(config);
-        assertTrue(doesSsidExist(SSID));
-        mWifiManager.removeNetwork(netId);
-        assertFalse(doesSsidExist(SSID));
-    }
-
-    private boolean doesSsidExist(String ssid) {
-        for (final WifiConfiguration w : mWifiManager.getConfiguredNetworks()) {
-            if (w.SSID.equals(ssid))
-                return true;
-        }
-        return false;
-    }
-
     public void testEnterpriseConfigDoesNotPrintPassword() {
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         final String identity = "IdentityIsOkayToBeDisplayedHere";
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
index 5983cb7..77598ed 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiInfoTest.java
@@ -29,6 +29,7 @@
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.concurrent.Callable;
 
@@ -104,7 +105,11 @@
     private void setWifiEnabled(boolean enable) throws Exception {
         synchronized (mMySync) {
             mMySync.expectedState = STATE_WIFI_CHANGING;
-            assertTrue(mWifiManager.setWifiEnabled(enable));
+            if (enable) {
+                SystemUtil.runShellCommand("svc wifi enable");
+            } else {
+                SystemUtil.runShellCommand("svc wifi disable");
+            }
             long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
             while (System.currentTimeMillis() < timeout
                     && mMySync.expectedState == STATE_WIFI_CHANGING)
@@ -134,6 +139,8 @@
         wifiInfo.getBSSID();
         wifiInfo.getIpAddress();
         wifiInfo.getLinkSpeed();
+        wifiInfo.getTxLinkSpeedMbps();
+        wifiInfo.getRxLinkSpeedMbps();
         wifiInfo.getRssi();
         wifiInfo.getHiddenSSID();
         wifiInfo.getMacAddress();
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 af91fbf..4297a73 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -21,7 +21,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
@@ -32,11 +35,17 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.Process;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.WifiConfigCreator;
 
 import java.net.HttpURLConnection;
@@ -45,9 +54,11 @@
 import java.security.cert.X509Certificate;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 public class WifiManagerTest extends AndroidTestCase {
     private static class MySync {
@@ -60,6 +71,7 @@
     private List<ScanResult> mScanResults = null;
     private NetworkInfo mNetworkInfo;
     private Object mLOHSLock = new Object();
+    private UiDevice mUiDevice;
 
     // Please refer to WifiManager
     private static final int MIN_RSSI = -100;
@@ -74,9 +86,6 @@
 
     private static final String TAG = "WifiManagerTest";
     private static final String SSID1 = "\"WifiManagerTest\"";
-    private static final String SSID2 = "\"WifiManagerTestModified\"";
-    private static final String PROXY_TEST_SSID = "SomeProxyAp";
-    private static final String ADD_NETWORK_EXCEPTION_SUBSTR = "addNetwork";
     // A full single scan duration is about 6-7 seconds if country code is set
     // to US. If country code is set to world mode (00), we would expect a scan
     // duration of roughly 8 seconds. So we set scan timeout as 9 seconds here.
@@ -84,11 +93,14 @@
     private static final int TIMEOUT_MSEC = 6000;
     private static final int WAIT_MSEC = 60;
     private static final int DURATION = 10000;
+    private static final int DURATION_SCREEN_TOGGLE = 2000;
     private static final int WIFI_SCAN_TEST_INTERVAL_MILLIS = 60 * 1000;
     private static final int WIFI_SCAN_TEST_CACHE_DELAY_MILLIS = 3 * 60 * 1000;
     private static final int WIFI_SCAN_TEST_ITERATIONS = 5;
 
     private static final String TEST_PAC_URL = "http://www.example.com/proxy.pac";
+    private static final String MANAGED_PROVISIONING_PACKAGE_NAME
+            = "com.android.managedprovisioning";
 
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -156,6 +168,8 @@
         mWifiLock.acquire();
         if (!mWifiManager.isWifiEnabled())
             setWifiEnabled(true);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        turnScreenOnNoDelay();
         Thread.sleep(DURATION);
         assertTrue(mWifiManager.isWifiEnabled());
         synchronized (mMySync) {
@@ -186,8 +200,8 @@
             } else {
                 mMySync.expectedState = (enable ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
             }
-            // now trigger the change
-            assertTrue(mWifiManager.setWifiEnabled(enable));
+            // now trigger the change using shell commands.
+            SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
             waitForExpectedWifiState(enable);
         }
     }
@@ -262,20 +276,13 @@
     }
 
     /**
-     * test point of wifiManager actions:
-     * 1.reconnect
-     * 2.reassociate
-     * 3.disconnect
-     * 4.createWifiLock
+     * Test creation of WifiManager Lock.
      */
-    public void testWifiManagerActions() throws Exception {
+    public void testWifiManagerLock() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
         }
-        assertTrue(mWifiManager.reconnect());
-        assertTrue(mWifiManager.reassociate());
-        assertTrue(mWifiManager.disconnect());
         final String TAG = "Test";
         assertNotNull(mWifiManager.createWifiLock(TAG));
         assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
@@ -396,123 +403,8 @@
         return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION);
     }
 
-    /**
-     * test point of wifiManager NetWork:
-     * 1.add NetWork
-     * 2.update NetWork
-     * 3.remove NetWork
-     * 4.enable NetWork
-     * 5.disable NetWork
-     * 6.configured Networks
-     * 7.save configure;
-     */
-    public void testWifiManagerNetWork() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
-
-        // store the list of enabled networks, so they can be re-enabled after test completes
-        Set<String> enabledSsids = getEnabledNetworks(mWifiManager.getConfiguredNetworks());
-        try {
-            WifiConfiguration wifiConfiguration;
-            // add a WifiConfig
-            final int notExist = -1;
-            List<WifiConfiguration> wifiConfiguredNetworks = mWifiManager.getConfiguredNetworks();
-            int pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
-            if (notExist != pos) {
-                wifiConfiguration = wifiConfiguredNetworks.get(pos);
-                mWifiManager.removeNetwork(wifiConfiguration.networkId);
-            }
-            pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
-            assertEquals(notExist, pos);
-            final int size = wifiConfiguredNetworks.size();
-
-            wifiConfiguration = new WifiConfiguration();
-            wifiConfiguration.SSID = SSID1;
-            wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-            int netId = mWifiManager.addNetwork(wifiConfiguration);
-            assertTrue(existSSID(SSID1));
-
-            wifiConfiguredNetworks = mWifiManager.getConfiguredNetworks();
-            assertEquals(size + 1, wifiConfiguredNetworks.size());
-            pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
-            assertTrue(notExist != pos);
-
-            // Enable & disable network
-            boolean disableOthers = true;
-            assertTrue(mWifiManager.enableNetwork(netId, disableOthers));
-            wifiConfiguration = mWifiManager.getConfiguredNetworks().get(pos);
-            assertEquals(Status.ENABLED, wifiConfiguration.status);
-
-            assertTrue(mWifiManager.disableNetwork(netId));
-            wifiConfiguration = mWifiManager.getConfiguredNetworks().get(pos);
-            assertEquals(Status.DISABLED, wifiConfiguration.status);
-
-            // Update a WifiConfig
-            wifiConfiguration = wifiConfiguredNetworks.get(pos);
-            wifiConfiguration.SSID = SSID2;
-            netId = mWifiManager.updateNetwork(wifiConfiguration);
-            assertFalse(existSSID(SSID1));
-            assertTrue(existSSID(SSID2));
-
-            // Remove a WifiConfig
-            assertTrue(mWifiManager.removeNetwork(netId));
-            assertFalse(mWifiManager.removeNetwork(notExist));
-            assertFalse(existSSID(SSID1));
-            assertFalse(existSSID(SSID2));
-
-            assertTrue(mWifiManager.saveConfiguration());
-        } finally {
-            reEnableNetworks(enabledSsids, mWifiManager.getConfiguredNetworks());
-            mWifiManager.saveConfiguration();
-        }
-    }
-
-    /**
-     * Verifies that addNetwork() fails for WifiConfigurations containing a non-null http proxy when
-     * the caller doesn't have OVERRIDE_WIFI_CONFIG permission, DeviceOwner or ProfileOwner device
-     * management policies
-     */
-    public void testSetHttpProxy_PermissionFail() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
-        WifiConfigCreator configCreator = new WifiConfigCreator(getContext());
-        boolean exceptionThrown = false;
-        try {
-            configCreator.addHttpProxyNetworkVerifyAndRemove(
-                    PROXY_TEST_SSID, TEST_PAC_URL);
-        } catch (IllegalStateException e) {
-            // addHttpProxyNetworkVerifyAndRemove throws three IllegalStateException,
-            // expect it to throw for the addNetwork operation
-            if (e.getMessage().contains(ADD_NETWORK_EXCEPTION_SUBSTR)) {
-                exceptionThrown = true;
-            }
-        }
-        assertTrue(exceptionThrown);
-    }
-
-    private Set<String> getEnabledNetworks(List<WifiConfiguration> configuredNetworks) {
-        Set<String> ssids = new HashSet<String>();
-        for (WifiConfiguration wifiConfig : configuredNetworks) {
-            if (Status.ENABLED == wifiConfig.status || Status.CURRENT == wifiConfig.status) {
-                ssids.add(wifiConfig.SSID);
-                Log.i(TAG, String.format("remembering enabled network %s", wifiConfig.SSID));
-            }
-        }
-        return ssids;
-    }
-
-    private void reEnableNetworks(Set<String> enabledSsids,
-            List<WifiConfiguration> configuredNetworks) {
-        for (WifiConfiguration wifiConfig : configuredNetworks) {
-            if (enabledSsids.contains(wifiConfig.SSID)) {
-                mWifiManager.enableNetwork(wifiConfig.networkId, false);
-                Log.i(TAG, String.format("re-enabling network %s", wifiConfig.SSID));
-            }
-        }
+    private boolean hasAutomotiveFeature() {
+        return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
     }
 
     public void testSignal() {
@@ -835,6 +727,11 @@
             // check if we got the callback
             assertTrue(callback.onStartedCalled);
             assertNotNull(callback.reservation.getWifiConfiguration());
+            if (!hasAutomotiveFeature()) {
+                assertEquals(
+                        WifiConfiguration.AP_BAND_2GHZ,
+                        callback.reservation.getWifiConfiguration().apBand);
+            }
             assertFalse(callback.onFailedCalled);
             assertFalse(callback.onStoppedCalled);
         }
@@ -881,37 +778,37 @@
     }
 
     /**
-     * Verify calls to setWifiEnabled from a non-settings app while softap mode is active do not
-     * exit softap mode.
-     *
-     * This test uses the LocalOnlyHotspot API to enter softap mode.  This should also be true when
-     * tethering is started.
-     * Note: Location mode must be enabled for this test.
+     * Verify calls to deprecated API's all fail for non-settings apps targeting >= Q SDK.
      */
-    public void testSetWifiEnabledByAppDoesNotStopHotspot() throws Exception {
+    public void testDeprecatedApis() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
         }
-        // check that softap mode is supported by the device
-        if (!mWifiManager.isPortableHotspotSupported()) {
-            return;
-        }
+        setWifiEnabled(true);
+        connectWifi(); // ensures that there is at-least 1 saved network on the device.
+
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = SSID1;
+        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID,
+                mWifiManager.addNetwork(wifiConfiguration));
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID,
+                mWifiManager.updateNetwork(wifiConfiguration));
+        assertFalse(mWifiManager.enableNetwork(0, true));
+        assertFalse(mWifiManager.disableNetwork(0));
+        assertFalse(mWifiManager.removeNetwork(0));
+        assertFalse(mWifiManager.disconnect());
+        assertFalse(mWifiManager.reconnect());
+        assertFalse(mWifiManager.reassociate());
+        assertTrue(mWifiManager.getConfiguredNetworks().isEmpty());
 
         boolean wifiEnabled = mWifiManager.isWifiEnabled();
-
-        if (wifiEnabled) {
-            // disable wifi so we have something to turn on (some devices may be able to run
-            // simultaneous modes)
-            setWifiEnabled(false);
-        }
-
-        TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
-
-        // now we should fail to turn on wifi
-        assertFalse(mWifiManager.setWifiEnabled(true));
-
-        stopLocalOnlyHotspot(callback, wifiEnabled);
+        // now we should fail to toggle wifi state.
+        assertFalse(mWifiManager.setWifiEnabled(!wifiEnabled));
+        Thread.sleep(DURATION);
+        assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
     }
 
     /**
@@ -951,4 +848,287 @@
 
         stopLocalOnlyHotspot(callback, wifiEnabled);
     }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#NETWORK_STACK} permission is never held by
+     * any package.
+     * <p>
+     * No apps should <em>ever</em> attempt to acquire this permission, since it would give those
+     * apps extremely broad access to connectivity functionality.
+     */
+    public void testNetworkStackPermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.NETWORK_STACK
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        for (PackageInfo pi : holding) {
+            fail("The NETWORK_STACK permission must not be held by " + pi.packageName
+                    + " and must be revoked for security reasons");
+        }
+    }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#NETWORK_SETTINGS} permission is
+     * never held by any package.
+     * <p>
+     * Only Settings, SysUi, NetworkStack and shell apps should <em>ever</em> attempt to acquire
+     * this permission, since it would give those apps extremely broad access to connectivity
+     * functionality.  The permission is intended to be granted to only those apps with direct user
+     * access and no others.
+     */
+    public void testNetworkSettingsPermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        final ArraySet<String> allowedPackages = new ArraySet();
+        final ArraySet<Integer> allowedUIDs = new ArraySet();
+        // explicitly add allowed UIDs
+        allowedUIDs.add(Process.SYSTEM_UID);
+        allowedUIDs.add(Process.SHELL_UID);
+        allowedUIDs.add(Process.PHONE_UID);
+        allowedUIDs.add(Process.NETWORK_STACK_UID);
+
+        // only quick settings is allowed to bind to the BIND_QUICK_SETTINGS_TILE permission, using
+        // this fact to determined allowed package name for sysui
+        String validPkg = "";
+        final List<PackageInfo> sysuiPackage = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+        if (sysuiPackage.size() > 1) {
+            fail("The BIND_QUICK_SETTINGS_TILE permission must only be held by one package");
+        }
+
+        if (sysuiPackage.size() == 1) {
+            validPkg = sysuiPackage.get(0).packageName;
+            allowedPackages.add(validPkg);
+        }
+
+        // the captive portal flow also currently holds the NETWORK_SETTINGS permission
+        final Intent intent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
+        final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+        if (ri != null) {
+            allowedPackages.add(ri.activityInfo.packageName);
+        }
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.NETWORK_SETTINGS
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        for (PackageInfo pi : holding) {
+            String packageName = pi.packageName;
+            if (allowedPackages.contains(packageName)) {
+                // this is an explicitly allowed package
+            } else {
+                // now check if the packages are from allowed UIDs
+                boolean allowed = false;
+                try {
+                    if (allowedUIDs.contains(pm.getPackageUid(packageName, 0))) {
+                        allowed = true;
+                        Log.d(TAG, packageName + " is on an allowed UID");
+                    }
+                } catch (PackageManager.NameNotFoundException e) { }
+                if (!allowed) {
+                    fail("The NETWORK_SETTINGS permission must not be held by "
+                            + packageName + " and must be revoked for security reasons");
+                }
+            }
+        }
+    }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#NETWORK_SETUP_WIZARD} permission is
+     * only held by the device setup wizard application.
+     * <p>
+     * Only the SetupWizard app should <em>ever</em> attempt to acquire this
+     * permission, since it would give those apps extremely broad access to connectivity
+     * functionality.  The permission is intended to be granted to only the device setup wizard.
+     */
+    public void testNetworkSetupWizardPermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
+        final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+        String validPkg = "";
+        if (ri != null) {
+            validPkg = ri.activityInfo.packageName;
+        }
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.NETWORK_SETUP_WIZARD
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        for (PackageInfo pi : holding) {
+            if (!Objects.equals(pi.packageName, validPkg)) {
+                fail("The NETWORK_SETUP_WIZARD permission must not be held by " + pi.packageName
+                    + " and must be revoked for security reasons [" + validPkg +"]");
+            }
+        }
+    }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#NETWORK_MANAGED_PROVISIONING} permission
+     * is only held by the device managed provisioning application.
+     * <p>
+     * Only the ManagedProvisioning app should <em>ever</em> attempt to acquire this
+     * permission, since it would give those apps extremely broad access to connectivity
+     * functionality.  The permission is intended to be granted to only the device managed
+     * provisioning.
+     */
+    public void testNetworkManagedProvisioningPermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        // TODO(b/115980767): Using hardcoded package name. Need a better mechanism to find the
+        // managed provisioning app.
+        // Ensure that the package exists.
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(MANAGED_PROVISIONING_PACKAGE_NAME);
+        final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+        String validPkg = "";
+        if (ri != null) {
+            validPkg = ri.activityInfo.packageName;
+        }
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.NETWORK_MANAGED_PROVISIONING
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        for (PackageInfo pi : holding) {
+            if (!Objects.equals(pi.packageName, validPkg)) {
+                fail("The NETWORK_MANAGED_PROVISIONING permission must not be held by "
+                        + pi.packageName + " and must be revoked for security reasons ["
+                        + validPkg +"]");
+            }
+        }
+    }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#WIFI_SET_DEVICE_MOBILITY_STATE} permission
+     * is held by at most one application.
+     */
+    public void testWifiSetDeviceMobilityStatePermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+        List<String> uniquePackageNames = holding
+                .stream()
+                .map(pi -> pi.packageName)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (uniquePackageNames.size() > 1) {
+            fail("The WIFI_SET_DEVICE_MOBILITY_STATE permission must not be held by more than one "
+                    + "application, but is held by " + uniquePackageNames.size() + " applications: "
+                    + String.join(", ", uniquePackageNames));
+        }
+    }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#WIFI_UPDATE_USABILITY_STATS_SCORE}
+     * permission is held by at most one application.
+     */
+    public void testUpdateWifiUsabilityStatsScorePermission() {
+        final PackageManager pm = getContext().getPackageManager();
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+                android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE
+        }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+        List<String> uniquePackageNames = holding
+                .stream()
+                .map(pi -> pi.packageName)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (uniquePackageNames.size() > 1) {
+            fail("The WIFI_UPDATE_USABILITY_STATS_SCORE permission must not be held by more than "
+                + "one application, but is held by " + uniquePackageNames.size() + " applications: "
+                + String.join(", ", uniquePackageNames));
+        }
+    }
+
+    private void turnScreenOnNoDelay() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+    }
+
+    private void turnScreenOn() throws Exception {
+        turnScreenOnNoDelay();
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    private void turnScreenOff() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    /**
+     * Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is disabled
+     * but location is on.
+     * @throws Exception
+     */
+    public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiDisabled() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!hasLocationFeature()) {
+            // skip the test if location is not supported
+            return;
+        }
+        if (!isLocationEnabled()) {
+            fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+                    + " empty when location is disabled!");
+        }
+        if(!mWifiManager.isScanAlwaysAvailable()) {
+            fail("Please enable Wi-Fi scanning for this test!");
+        }
+        setWifiEnabled(false);
+        turnScreenOn();
+        assertWifiScanningIsOn();
+        // Toggle screen and verify Wi-Fi scanning is still on.
+        turnScreenOff();
+        assertWifiScanningIsOn();
+        turnScreenOn();
+        assertWifiScanningIsOn();
+    }
+
+    /**
+     * Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is enabled.
+     * @throws Exception
+     */
+    public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiEnabled() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!hasLocationFeature()) {
+            // skip the test if location is not supported
+            return;
+        }
+        if (!isLocationEnabled()) {
+            fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+                    + " empty when location is disabled!");
+        }
+        if(!mWifiManager.isScanAlwaysAvailable()) {
+            fail("Please enable Wi-Fi scanning for this test!");
+        }
+        setWifiEnabled(true);
+        turnScreenOn();
+        assertWifiScanningIsOn();
+        // Toggle screen and verify Wi-Fi scanning is still on.
+        turnScreenOff();
+        assertWifiScanningIsOn();
+        turnScreenOn();
+        assertWifiScanningIsOn();
+    }
+
+    private void assertWifiScanningIsOn() {
+        if(!mWifiManager.isScanAlwaysAvailable()) {
+            fail("Wi-Fi scanning should be on.");
+        }
+    }
 }
diff --git a/tests/tests/net/src/android/net/wifi/rtt/OWNERS b/tests/tests/net/src/android/net/wifi/rtt/OWNERS
index 4afc47f..cf116f8 100644
--- a/tests/tests/net/src/android/net/wifi/rtt/OWNERS
+++ b/tests/tests/net/src/android/net/wifi/rtt/OWNERS
@@ -1,2 +1 @@
 etancohen@google.com
-satk@google.com
\ No newline at end of file
diff --git a/tests/tests/net/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/net/src/android/net/wifi/rtt/cts/TestBase.java
index 57ea2a5..07d5718 100644
--- a/tests/tests/net/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/net/src/android/net/wifi/rtt/cts/TestBase.java
@@ -32,6 +32,8 @@
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -93,7 +95,7 @@
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
         if (!mWifiManager.isWifiEnabled()) {
-            mWifiManager.setWifiEnabled(true);
+            SystemUtil.runShellCommand("svc wifi enable");
         }
 
         IntentFilter intentFilter = new IntentFilter();
diff --git a/tests/tests/net/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
index 74a0c3d..20791c0 100644
--- a/tests/tests/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -16,11 +16,9 @@
 
 package android.net.wifi.rtt.cts;
 
-import android.content.IntentFilter;
 import android.net.wifi.ScanResult;
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
-import android.net.wifi.rtt.WifiRttManager;
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
@@ -64,7 +62,10 @@
 
         // Scan for IEEE 802.11mc supporting APs
         ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
-        assertTrue("Cannot find test AP", testAp != null);
+        assertTrue(
+                "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+                        + "your test setup includes them!",
+                testAp != null);
 
         // Perform RTT operations
         RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
index 7a11df4..032bcde 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
@@ -33,4 +33,6 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 26
+
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/notificationlegacy/Android.mk b/tests/tests/notificationlegacy/Android.mk
index c1fb3fa..9af9f44 100644
--- a/tests/tests/notificationlegacy/Android.mk
+++ b/tests/tests/notificationlegacy/Android.mk
@@ -12,31 +12,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsLegacyNotificationTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner \
-    android-support-test \
-    junit
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(call all-subdir-makefiles)
diff --git a/tests/tests/notificationlegacy/AndroidManifest.xml b/tests/tests/notificationlegacy/AndroidManifest.xml
deleted file mode 100644
index dd96eac..0000000
--- a/tests/tests/notificationlegacy/AndroidManifest.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.app.notification.legacy.cts">
-    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
-
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-
-        <service android:name="android.app.notification.legacy.cts.MockNotificationListener"
-                 android:exported="true"
-                 android:label="MockNotificationListener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
-            </intent-filter>
-        </service>
-
-        <service android:name="android.app.notification.legacy.cts.LegacyConditionProviderService"
-                 android:exported="true"
-                 android:label="Legacy"
-                 android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.ConditionProviderService" />
-            </intent-filter>
-        </service>
-
-        <service android:name="android.app.notification.legacy.cts.SecondaryConditionProviderService"
-                 android:exported="true"
-                 android:label="Secondary"
-                 android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.ConditionProviderService" />
-            </intent-filter>
-        </service>
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.app.notification.legacy.cts"
-                     android:label="CTS tests for legacy notification behavior">
-        <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-</manifest>
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.mk b/tests/tests/notificationlegacy/notificationlegacy27/Android.mk
new file mode 100644
index 0000000..d2b1425
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLegacyNotification27TestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    android-support-test \
+    junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MIN_SDK_VERSION := 27
+
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/AndroidManifest.xml b/tests/tests/notificationlegacy/notificationlegacy27/AndroidManifest.xml
new file mode 100644
index 0000000..3be24c5
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.app.notification.legacy.cts">
+    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
+
+    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <service android:name="android.app.notification.legacy.cts.TestNotificationListener"
+                 android:exported="true"
+                 android:label="TestNotificationListener"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <service android:name="android.app.notification.legacy.cts.SecondaryNotificationListener"
+                 android:exported="true"
+                 android:label="MockNotificationListener"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <service android:name="android.app.notification.legacy.cts.LegacyConditionProviderService"
+                 android:exported="true"
+                 android:label="Legacy"
+                 android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.ConditionProviderService" />
+            </intent-filter>
+        </service>
+
+        <service android:name="android.app.notification.legacy.cts.SecondaryConditionProviderService"
+                 android:exported="true"
+                 android:label="Secondary"
+                 android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.ConditionProviderService" />
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.app.notification.legacy.cts"
+                     android:label="CTS tests for notification behavior (API 27)">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/AndroidTest.xml b/tests/tests/notificationlegacy/notificationlegacy27/AndroidTest.xml
new file mode 100644
index 0000000..15402f2
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Config for CTS Notification API 27 test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLegacyNotification27TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.app.notification.legacy.cts" />
+        <option name="runtime-hint" value="5m" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/notificationlegacy/res/drawable/icon_black.jpg b/tests/tests/notificationlegacy/notificationlegacy27/res/drawable/icon_black.jpg
similarity index 100%
rename from tests/tests/notificationlegacy/res/drawable/icon_black.jpg
rename to tests/tests/notificationlegacy/notificationlegacy27/res/drawable/icon_black.jpg
Binary files differ
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
new file mode 100644
index 0000000..86b1ba0
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.app.notification.legacy.cts;
+
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.service.notification.NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.TestCase.fail;
+
+import android.app.ActivityManager;
+import android.app.AutomaticZenRule;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class ConditionProviderServiceTest {
+    private static String TAG = "CpsTest";
+
+    private NotificationManager mNm;
+    private ActivityManager mActivityManager;
+    private Context mContext;
+    private ArraySet<String> ids = new ArraySet<>();
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
+        SecondaryConditionProviderService.requestRebind(SecondaryConditionProviderService.getId());
+        mNm = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        try {
+            for (String id : ids) {
+                if (id != null) {
+                    if (!mNm.removeAutomaticZenRule(id)) {
+                        throw new Exception("Could not remove rule " + id);
+                    }
+
+                    assertNull(mNm.getAutomaticZenRule(id));
+                }
+            }
+        } finally {
+            toggleNotificationPolicyAccess(mContext.getPackageName(),
+                    InstrumentationRegistry.getInstrumentation(), false);
+            pollForConnection(LegacyConditionProviderService.class, false);
+            pollForConnection(SecondaryConditionProviderService.class, false);
+        }
+    }
+
+    @Test
+    public void testUnboundCPSMaintainsCondition_addsNewRule() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        // make sure service get bound
+        pollForConnection(SecondaryConditionProviderService.class, true);
+
+        final ComponentName cn = SecondaryConditionProviderService.getId();
+
+        // add rule
+        addRule(cn, INTERRUPTION_FILTER_ALARMS, true);
+        pollForSubscribe(SecondaryConditionProviderService.getInstance());
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // unbind service
+        SecondaryConditionProviderService.getInstance().requestUnbind();
+
+        // verify that DND state doesn't change
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // add a new rule
+        addRule(cn, INTERRUPTION_FILTER_NONE, true);
+
+        // verify that the unbound service maintains it's DND vote
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+    }
+
+    @Test
+    public void testUnboundCPSMaintainsCondition_otherConditionChanges() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        // make sure both services get bound
+        pollForConnection(LegacyConditionProviderService.class, true);
+        pollForConnection(SecondaryConditionProviderService.class, true);
+
+        // add rules for both
+        addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
+        pollForSubscribe(LegacyConditionProviderService.getInstance());
+
+        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
+        pollForSubscribe(SecondaryConditionProviderService.getInstance());
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // unbind one of the services
+        SecondaryConditionProviderService.getInstance().requestUnbind();
+
+        // verify that DND state doesn't change
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // trigger a change in the bound service's condition
+        ((LegacyConditionProviderService) LegacyConditionProviderService.getInstance())
+                .toggleDND(false);
+
+        // verify that the unbound service maintains it's DND vote
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+    }
+
+    @Test
+    public void testUnboundCPSMaintainsCondition_otherProviderRuleChanges() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        // make sure both services get bound
+        pollForConnection(LegacyConditionProviderService.class, true);
+        pollForConnection(SecondaryConditionProviderService.class, true);
+
+        // add rules for both
+        addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
+        pollForSubscribe(LegacyConditionProviderService.getInstance());
+
+        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
+        pollForSubscribe(SecondaryConditionProviderService.getInstance());
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // unbind one of the services
+        SecondaryConditionProviderService.getInstance().requestUnbind();
+
+        // verify that DND state doesn't change
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+
+        // trigger a change in the bound service's rule
+        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, false);
+
+        // verify that the unbound service maintains it's DND vote
+        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
+    }
+
+    @Test
+    public void testRequestRebindWhenLostAccess() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        // make sure it gets bound
+        pollForConnection(LegacyConditionProviderService.class, true);
+
+        // request unbind
+        LegacyConditionProviderService.getInstance().requestUnbind();
+
+        // make sure it unbinds
+        pollForConnection(LegacyConditionProviderService.class, false);
+
+        // lose dnd access
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), false);
+
+        // try to rebind
+        LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
+
+        // make sure it isn't rebound
+        try {
+            pollForConnection(LegacyConditionProviderService.class, true);
+            fail("Service got rebound after permission lost");
+        } catch (Exception e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testRequestRebindWhenStillHasAccess() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        // make sure it gets bound
+        pollForConnection(LegacyConditionProviderService.class, true);
+
+        // request unbind
+        LegacyConditionProviderService.getInstance().requestUnbind();
+
+        // make sure it unbinds
+        pollForConnection(LegacyConditionProviderService.class, false);
+
+        // try to rebind
+        LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
+
+        // make sure it did rebind
+        try {
+            pollForConnection(LegacyConditionProviderService.class, true);
+        } catch (Exception e) {
+            fail("Service should've been able to rebind");
+        }
+    }
+
+    private void addRule(ComponentName cn, int filter, boolean enabled) {
+        String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name",
+                cn, Uri.EMPTY, filter, enabled));
+        Log.d(TAG, "Created rule with id " + id);
+        ids.add(id);
+    }
+
+    private void toggleNotificationPolicyAccess(String packageName,
+            Instrumentation instrumentation, boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+
+        runCommand(command, instrumentation);
+
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        Assert.assertEquals("Notification Policy Access Grant is " +
+                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+                nm.isNotificationPolicyAccessGranted());
+    }
+
+    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+        UiAutomation uiAutomation = instrumentation.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();
+        }
+    }
+
+    private void pollForSubscribe(PollableConditionProviderService service)  throws Exception {
+        int tries = 30;
+        int delayMs = 200;
+
+        while (tries-- > 0 && !service.subscribed) {
+            try {
+                Thread.sleep(delayMs);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        if (!service.subscribed) {
+            Log.d(TAG, "not subscribed");
+            throw new Exception("Service never got onSubscribe()");
+        }
+    }
+
+    private void pollForConnection(Class<? extends PollableConditionProviderService> service,
+            boolean waitForConnection) throws Exception {
+        int tries = 100;
+        int delayMs = 200;
+
+        PollableConditionProviderService instance =
+                (PollableConditionProviderService) service.getMethod("getInstance").invoke(null);
+
+        while (tries-- > 0 && (waitForConnection ? instance == null : instance != null)) {
+            try {
+                Thread.sleep(delayMs);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            instance = (PollableConditionProviderService) service.getMethod("getInstance")
+                    .invoke(null);
+        }
+
+        if (waitForConnection && instance == null) {
+            Log.d(TAG, service.getName() + " not bound");
+            throw new Exception("CPS never bound");
+        } else if (!waitForConnection && instance != null) {
+            Log.d(TAG, service.getName() + " still bound");
+            throw new Exception("CPS still bound");
+        } else {
+            Log.d(TAG, service.getName() + " has a correct bind state");
+        }
+    }
+}
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/LegacyConditionProviderService.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyConditionProviderService.java
similarity index 100%
rename from tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/LegacyConditionProviderService.java
rename to tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyConditionProviderService.java
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
new file mode 100644
index 0000000..ae17ebd
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -0,0 +1,394 @@
+/*
+ * 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.app.notification.legacy.cts;
+
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+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 junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony.Threads;
+import android.service.notification.NotificationListenerService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Home for tests that need to verify behavior for apps that target old sdk versions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LegacyNotificationManagerTest {
+    final String TAG = "LegacyNoManTest";
+
+    final String NOTIFICATION_CHANNEL_ID = "LegacyNotificationManagerTest";
+    private NotificationManager mNotificationManager;
+    private ActivityManager mActivityManager;
+    private Context mContext;
+
+    private SecondaryNotificationListener mSecondaryListener;
+    private TestNotificationListener mListener;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+        toggleListenerAccess(SecondaryNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+        toggleListenerAccess(SecondaryNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+        Thread.sleep(500); // wait for listener to disconnect
+        assertTrue(mListener == null || !mListener.isConnected);
+        assertTrue(mSecondaryListener == null || !mSecondaryListener.isConnected);
+    }
+
+    @Test
+    public void testPrePCannotToggleAlarmsAndMediaTest() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        // Pre-P cannot toggle alarms and media
+        NotificationManager.Policy origPolicy = mNotificationManager.getNotificationPolicy();
+        int alarmBit = origPolicy.priorityCategories & NotificationManager.Policy
+                .PRIORITY_CATEGORY_ALARMS;
+        int mediaBit = origPolicy.priorityCategories & NotificationManager.Policy
+                .PRIORITY_CATEGORY_MEDIA;
+        int systemBit = origPolicy.priorityCategories & NotificationManager.Policy
+                .PRIORITY_CATEGORY_SYSTEM;
+
+        // attempt to toggle off alarms, media, system:
+        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+        NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
+        assertEquals(alarmBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
+        assertEquals(mediaBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
+        assertEquals(systemBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
+
+        // attempt to toggle on alarms, media, system:
+        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+                        | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
+        policy = mNotificationManager.getNotificationPolicy();
+        assertEquals(alarmBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
+        assertEquals(mediaBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
+        assertEquals(systemBit, policy.priorityCategories
+                & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
+
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), false);
+    }
+
+    @Test
+    public void testSetNotificationPolicy_preP_setOldFields() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
+
+        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
+                SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
+        mNotificationManager.setNotificationPolicy(appPolicy);
+
+        int expected = userPolicy.suppressedVisualEffects
+                | SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF
+                | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_LIGHTS
+                | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+
+        assertEquals(expected,
+                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
+    }
+
+    @Test
+    public void testSetNotificationPolicy_preP_setNewFields() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
+
+        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
+                SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+        mNotificationManager.setNotificationPolicy(appPolicy);
+
+        int expected = userPolicy.suppressedVisualEffects;
+        expected &= ~ SUPPRESSED_EFFECT_SCREEN_OFF & ~ SUPPRESSED_EFFECT_SCREEN_ON;
+        assertEquals(expected,
+                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
+
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), false);
+    }
+
+    @Test
+    public void testSuspendPackage() throws Exception {
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        Assert.assertNotNull(mListener);
+
+        sendNotification(1, R.drawable.icon_black);
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+        mListener.resetData();
+
+        // suspend package, listener receives onRemoved
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                true);
+        Thread.sleep(500); // wait for notification listener to get response
+        assertEquals(1, mListener.mRemoved.size());
+
+        // unsuspend package, listener receives onPosted
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                false);
+        Thread.sleep(500); // wait for notification listener to get response
+        assertEquals(1, mListener.mPosted.size());
+
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+
+        mListener.resetData();
+    }
+
+    @Test
+    public void testSuspendedPackageSendNotification() throws Exception {
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        Assert.assertNotNull(mListener);
+
+        // suspend package
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                true);
+        Thread.sleep(500); // wait for notification listener to get response
+
+        sendNotification(1, R.drawable.icon_black);
+        Thread.sleep(500); // wait for notification listener in case it receives notification
+        assertEquals(0, mListener.mPosted.size()); // shouldn't see any notifications posted
+
+        // unsuspend package, listener should receive onPosted
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                false);
+        Thread.sleep(500); // wait for notification listener to get response
+        assertEquals(1, mListener.mPosted.size());
+
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), false);
+
+        mListener.resetData();
+    }
+
+    @Test
+    public void testResetListenerHints_singleListener() throws Exception {
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        Assert.assertNotNull(mListener);
+
+        mListener.requestListenerHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
+
+        assertEquals(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS,
+                mListener.getCurrentListenerHints());
+
+        mListener.clearRequestedListenerHints();
+
+        assertEquals(0, mListener.getCurrentListenerHints());
+    }
+
+    @Test
+    public void testResetListenerHints_multiListener() throws Exception {
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        toggleListenerAccess(SecondaryNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        mSecondaryListener = SecondaryNotificationListener.getInstance();
+        Assert.assertNotNull(mListener);
+        Assert.assertNotNull(mSecondaryListener);
+
+        mListener.requestListenerHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
+        mSecondaryListener.requestListenerHints(
+                NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS
+                        | NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
+
+        assertEquals(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS
+                | NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
+                mListener.getCurrentListenerHints());
+
+        mSecondaryListener.clearRequestedListenerHints();
+        assertEquals(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS,
+                mSecondaryListener.getCurrentListenerHints());
+    }
+
+    @Test
+    public void testSetNotificationPolicy_preP_setOldNewFields() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+
+        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
+
+        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
+                SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
+        mNotificationManager.setNotificationPolicy(appPolicy);
+
+        int expected = userPolicy.suppressedVisualEffects
+                | SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_PEEK;
+        expected &= ~ SUPPRESSED_EFFECT_SCREEN_OFF;
+        assertEquals(expected,
+                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
+
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), false);
+    }
+
+    private void sendNotification(final int id, final int icon) throws Exception {
+        sendNotification(id, null, icon);
+    }
+
+    private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
+
+        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(mContext, 0, intent, 0);
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .setContentIntent(pendingIntent)
+                        .setGroup(groupKey)
+                        .build();
+        mNotificationManager.notify(id, notification);
+    }
+
+    private void toggleNotificationPolicyAccess(String packageName,
+            Instrumentation instrumentation, boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+
+        runCommand(command, instrumentation);
+
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        Assert.assertEquals("Notification Policy Access Grant is " +
+                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+                nm.isNotificationPolicyAccessGranted());
+    }
+
+    private void suspendPackage(String packageName,
+            Instrumentation instrumentation, boolean suspend) throws IOException {
+        String command = " cmd package " + (suspend ? "suspend "
+                : "unsuspend ") + packageName;
+
+        runCommand(command, instrumentation);
+    }
+
+    private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
+            boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
+                + componentName;
+
+        runCommand(command, instrumentation);
+
+        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        final ComponentName listenerComponent = TestNotificationListener.getComponentName();
+        Assert.assertTrue(listenerComponent + " has not been granted access",
+                nm.isNotificationListenerAccessGranted(listenerComponent) == on);
+    }
+
+    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+        UiAutomation uiAutomation = instrumentation.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();
+        }
+    }
+}
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/PollableConditionProviderService.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/PollableConditionProviderService.java
similarity index 100%
rename from tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/PollableConditionProviderService.java
rename to tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/PollableConditionProviderService.java
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/SecondaryConditionProviderService.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/SecondaryConditionProviderService.java
similarity index 100%
rename from tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/SecondaryConditionProviderService.java
rename to tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/SecondaryConditionProviderService.java
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/SecondaryNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/SecondaryNotificationListener.java
new file mode 100644
index 0000000..8db875b
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/SecondaryNotificationListener.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app.notification.legacy.cts;
+
+import android.content.ComponentName;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import java.util.ArrayList;
+
+public class SecondaryNotificationListener extends NotificationListenerService {
+    public static final String TAG = "SecondaryNLS";
+    public static final String PKG = "android.app.notification.legacy.cts";
+
+    private ArrayList<String> mTestPackages = new ArrayList<>();
+
+    public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
+    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public RankingMap mRankingMap;
+
+    private static SecondaryNotificationListener sNotificationListenerInstance = null;
+    boolean isConnected;
+
+    public static String getId() {
+        return String.format("%s/%s", SecondaryNotificationListener.class.getPackage().getName(),
+                SecondaryNotificationListener.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(SecondaryNotificationListener.class.getPackage().getName(),
+                SecondaryNotificationListener.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mTestPackages.add(PKG);
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationListenerInstance = this;
+        isConnected = true;
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        isConnected = false;
+    }
+
+    public static SecondaryNotificationListener getInstance() {
+        return sNotificationListenerInstance;
+    }
+
+    public void resetData() {
+        mPosted.clear();
+        mRemoved.clear();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mPosted.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mRemoved.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(RankingMap rankingMap) {
+        mRankingMap = rankingMap;
+    }
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
new file mode 100644
index 0000000..c174d81
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
@@ -0,0 +1,93 @@
+/*
+ * 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.app.notification.legacy.cts;
+
+import android.content.ComponentName;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class TestNotificationListener extends NotificationListenerService {
+    public static final String TAG = "TestNotificationListener";
+    public static final String PKG = "android.app.notification.legacy.cts";
+
+    private ArrayList<String> mTestPackages = new ArrayList<>();
+
+    public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
+    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public RankingMap mRankingMap;
+
+    private static TestNotificationListener sNotificationListenerInstance = null;
+    boolean isConnected;
+
+    public static String getId() {
+        return String.format("%s/%s", TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mTestPackages.add(PKG);
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationListenerInstance = this;
+        isConnected = true;
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        isConnected = false;
+    }
+
+    public static TestNotificationListener getInstance() {
+        return sNotificationListenerInstance;
+    }
+
+    public void resetData() {
+        mPosted.clear();
+        mRemoved.clear();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mRankingMap = rankingMap;
+        mPosted.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
+        mRankingMap = rankingMap;
+        mRemoved.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(RankingMap rankingMap) {
+        mRankingMap = rankingMap;
+    }
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/Android.mk b/tests/tests/notificationlegacy/notificationlegacy28/Android.mk
new file mode 100644
index 0000000..dff96fd
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy28/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLegacyNotification28TestCases
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    android-support-test \
+    junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MIN_SDK_VERSION := 28
+
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/AndroidManifest.xml b/tests/tests/notificationlegacy/notificationlegacy28/AndroidManifest.xml
new file mode 100644
index 0000000..867d4ef
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy28/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.app.notification.legacy28.cts">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.app.notification.legacy28.cts"
+                     android:label="CTS tests for notification behavior (API 28)">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/AndroidTest.xml b/tests/tests/notificationlegacy/notificationlegacy28/AndroidTest.xml
new file mode 100644
index 0000000..6cced29
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy28/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Config for CTS Notification API 28 test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLegacyNotification28TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.app.notification.legacy28.cts" />
+        <option name="runtime-hint" value="5m" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
new file mode 100644
index 0000000..e77612a
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -0,0 +1,105 @@
+/*
+ * 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.app.notification.legacy28.cts;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Home for tests that need to verify behavior for apps that target old sdk versions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NotificationManager28Test {
+    final String TAG = "LegacyNoManTest28";
+
+    final String NOTIFICATION_CHANNEL_ID = "LegacyNoManTest28";
+    private NotificationManager mNotificationManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+    }
+
+    @Test
+    public void testPostFullScreenIntent_noPermission() {
+        // No Full screen intent permission; but full screen intent should still be allowed
+        int id = 6000;
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.id.icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setFullScreenIntent(getPendingIntent(), true)
+                        .setContentText("This is #FSI notification")
+                        .setContentIntent(getPendingIntent())
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        StatusBarNotification n = findPostedNotification(id);
+        assertNotNull(n);
+        assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
+    }
+
+    private StatusBarNotification findPostedNotification(int id) {
+        // 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();
+            for (StatusBarNotification sbn : sbns) {
+                Log.d(TAG, "Found " + sbn.getKey());
+                if (sbn.getId() == id) {
+                    n = sbn;
+                    break;
+                }
+            }
+            if (n != null) break;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return n;
+    }
+
+    private PendingIntent getPendingIntent() {
+        return PendingIntent.getActivity(
+                mContext, 0, new Intent(mContext, this.getClass()), 0);
+    }
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/Android.mk b/tests/tests/notificationlegacy/notificationlegacy29/Android.mk
new file mode 100644
index 0000000..4caf2c9
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/Android.mk
@@ -0,0 +1,45 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLegacyNotification29TestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    android-support-test \
+    junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# TODO: replace with 29 once 29 finalized
+LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 29
+
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/AndroidManifest.xml b/tests/tests/notificationlegacy/notificationlegacy29/AndroidManifest.xml
new file mode 100644
index 0000000..fb804ef
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.app.notification.legacy29.cts">
+
+    <uses-sdk android:minSdkVersion="29"  />
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <service android:name="android.app.notification.legacy29.cts.TestNotificationListener"
+                 android:exported="true"
+                 android:label="TestNotificationListener"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <service android:name="android.app.notification.legacy29.cts.TestNotificationAssistant"
+                 android:exported="true"
+                 android:label="TestNotificationAssistant"
+                 android:permission="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationAssistantService" />
+            </intent-filter>
+        </service>
+
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.app.notification.legacy29.cts"
+                     android:label="CTS tests for notification behavior (API 29)">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/AndroidTest.xml b/tests/tests/notificationlegacy/notificationlegacy29/AndroidTest.xml
new file mode 100644
index 0000000..a3d8bb8
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Config for CTS Notification API 29 test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLegacyNotification29TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.app.notification.legacy29.cts" />
+        <option name="runtime-hint" value="5m" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
new file mode 100644
index 0000000..71cf3f2
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.notification.legacy29.cts;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationAssistantServiceTest {
+
+    final String TAG = "NotAsstServiceTest";
+    final String NOTIFICATION_CHANNEL_ID = "NotificationAssistantServiceTest";
+    final int ICON_ID = android.R.drawable.sym_def_app_icon;
+    final long SLEEP_TIME = 500; // milliseconds
+
+    private TestNotificationAssistant mNotificationAssistantService;
+    private TestNotificationListener mNotificationListenerService;
+    private NotificationManager mNotificationManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws IOException {
+        mContext = InstrumentationRegistry.getContext();
+        toggleAssistantAccess(false);
+        toggleListenerAccess(false);
+
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        if (mNotificationListenerService != null) mNotificationListenerService.resetData();
+        toggleListenerAccess(false);
+        toggleAssistantAccess(false);
+    }
+
+    @Test
+    public void testOnNotificationEnqueued() throws Exception {
+        toggleListenerAccess(true);
+        Thread.sleep(SLEEP_TIME);
+        mNotificationListenerService = TestNotificationListener.getInstance();
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        NotificationListenerService.Ranking out = new NotificationListenerService.Ranking();
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        // No modification because the Notification Assistant is not enabled
+        assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL,
+                out.getUserSentiment());
+        mNotificationListenerService.resetData();
+
+        toggleAssistantAccess(true);
+        Thread.sleep(SLEEP_TIME); // wait for listener and assistant to be allowed
+        mNotificationAssistantService = TestNotificationAssistant.getInstance();
+
+        sendNotification(1, ICON_ID);
+        sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        // Assistant modifies notification
+        assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE,
+                out.getUserSentiment());
+    }
+
+    @Test
+    public void testAdjustNotification_userSentimentKey() throws Exception {
+        setUpListeners();
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        NotificationListenerService.Ranking out = new NotificationListenerService.Ranking();
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE,
+                out.getUserSentiment());
+
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT,
+                NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
+        Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
+                sbn.getUserId());
+
+        mNotificationAssistantService.adjustNotification(adjustment);
+        Thread.sleep(SLEEP_TIME); // wait for adjustment to be processed
+
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE,
+                out.getUserSentiment());
+    }
+
+    @Test
+    public void testAdjustNotification_importanceKey() throws Exception {
+        setUpListeners();
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        NotificationListenerService.Ranking out = new NotificationListenerService.Ranking();
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        int currentImportance = out.getImportance();
+        int newImportance = currentImportance == NotificationManager.IMPORTANCE_DEFAULT
+                ? NotificationManager.IMPORTANCE_HIGH : NotificationManager.IMPORTANCE_DEFAULT;
+
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_IMPORTANCE, newImportance);
+        Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
+                sbn.getUserId());
+
+        mNotificationAssistantService.adjustNotification(adjustment);
+        Thread.sleep(SLEEP_TIME); // wait for adjustment to be processed
+
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        assertEquals(newImportance, out.getImportance());
+    }
+
+    @Test
+    public void testAdjustNotification_smartActionKey() throws Exception {
+        setUpListeners();
+        PendingIntent sendIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(Intent.ACTION_SEND), 0);
+        Notification.Action sendAction = new Notification.Action.Builder(ICON_ID, "SEND",
+                sendIntent).build();
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        NotificationListenerService.Ranking out = new NotificationListenerService.Ranking();
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        List<Notification.Action> smartActions = out.getSmartActions();
+        if (smartActions != null) {
+            for (int i = 0; i < smartActions.size(); i++) {
+                Notification.Action action = smartActions.get(i);
+                assertNotEquals(sendIntent, action.actionIntent);
+            }
+        }
+
+        ArrayList<Notification.Action> extraAction = new ArrayList<>();
+        extraAction.add(sendAction);
+        Bundle signals = new Bundle();
+        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+        Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
+                sbn.getUserId());
+
+        mNotificationAssistantService.adjustNotification(adjustment);
+        Thread.sleep(SLEEP_TIME); //wait for adjustment to be processed
+
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        boolean actionFound = false;
+        smartActions = out.getSmartActions();
+        for (int i = 0; i < smartActions.size(); i++) {
+            Notification.Action action = smartActions.get(i);
+            actionFound = actionFound || action.actionIntent.equals(sendIntent);
+        }
+        assertTrue(actionFound);
+    }
+
+    @Test
+    public void testAdjustNotification_smartReplyKey() throws Exception {
+        setUpListeners();
+        CharSequence smartReply = "Smart Reply!";
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+        NotificationListenerService.Ranking out = new NotificationListenerService.Ranking();
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        List<CharSequence> smartReplies = out.getSmartReplies();
+        if (smartReplies != null) {
+            for (int i = 0; i < smartReplies.size(); i++) {
+                CharSequence reply = smartReplies.get(i);
+                assertNotEquals(smartReply, reply);
+            }
+        }
+
+        ArrayList<CharSequence> extraReply = new ArrayList<>();
+        extraReply.add(smartReply);
+        Bundle signals = new Bundle();
+        signals.putCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES, extraReply);
+        Adjustment adjustment = new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
+                sbn.getUserId());
+
+        mNotificationAssistantService.adjustNotification(adjustment);
+        Thread.sleep(SLEEP_TIME); //wait for adjustment to be processed
+
+        mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
+
+        boolean replyFound = false;
+        smartReplies = out.getSmartReplies();
+        for (int i = 0; i < smartReplies.size(); i++) {
+            CharSequence reply = smartReplies.get(i);
+            replyFound = replyFound || reply.equals(smartReply);
+        }
+        assertTrue(replyFound);
+    }
+
+    private StatusBarNotification getFirstNotificationFromPackage(String PKG)
+            throws InterruptedException {
+        StatusBarNotification sbn = mNotificationListenerService.mPosted.poll(SLEEP_TIME,
+                TimeUnit.MILLISECONDS);
+        assertNotNull(sbn);
+        while (!sbn.getPackageName().equals(PKG)) {
+            sbn = mNotificationListenerService.mPosted.poll(SLEEP_TIME, TimeUnit.MILLISECONDS);
+        }
+        assertNotNull(sbn);
+        return sbn;
+    }
+
+    private void setUpListeners() throws Exception {
+        toggleListenerAccess(true);
+        toggleAssistantAccess(true);
+        Thread.sleep(2 * SLEEP_TIME); // wait for listener and assistant to be allowed
+
+        mNotificationListenerService = TestNotificationListener.getInstance();
+        mNotificationAssistantService = TestNotificationAssistant.getInstance();
+
+        assertNotNull(mNotificationListenerService);
+        assertNotNull(mNotificationAssistantService);
+    }
+
+    private void sendNotification(final int id, final int icon) throws Exception {
+        sendNotification(id, null, icon);
+    }
+
+    private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_MAIN, Telephony.Threads.CONTENT_URI);
+
+        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(mContext, 0, intent, 0);
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setContentTitle("notify#" + id)
+                        .setContentText("This is #" + id + "notification  ")
+                        .setContentIntent(pendingIntent)
+                        .setGroup(groupKey)
+                        .build();
+        mNotificationManager.notify(id, notification);
+    }
+
+    private void toggleListenerAccess(boolean on) throws IOException {
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        String componentName = TestNotificationListener.getId();
+
+        String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
+                + componentName;
+
+        runCommand(command, instrumentation);
+
+        final ComponentName listenerComponent = TestNotificationListener.getComponentName();
+        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        Assert.assertTrue(listenerComponent + " has not been " + (on ? "allowed" : "disallowed"),
+                nm.isNotificationListenerAccessGranted(listenerComponent) == on);
+    }
+
+    private void toggleAssistantAccess(boolean on) throws IOException {
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        String componentName = TestNotificationAssistant.getId();
+
+        String command = " cmd notification " + (on ? "allow_assistant " : "disallow_assistant ")
+                + componentName;
+
+        runCommand(command, instrumentation);
+
+        final ComponentName assistantComponent = TestNotificationAssistant.getComponentName();
+        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        assertTrue(assistantComponent + " has not been " + (on ? "allowed" : "disallowed"),
+                nm.isNotificationAssistantAccessGranted(assistantComponent) == on);
+    }
+
+    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();
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
new file mode 100644
index 0000000..6629e17
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -0,0 +1,105 @@
+/*
+ * 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.app.notification.legacy29.cts;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Home for tests that need to verify behavior for apps that target old sdk versions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NotificationManager29Test {
+    final String TAG = "LegacyNoManTest29";
+
+    final String NOTIFICATION_CHANNEL_ID = "LegacyNoManTest29";
+    private NotificationManager mNotificationManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+    }
+
+    @Test
+    public void testPostFullScreenIntent_noPermission() {
+        // no permission? no full screen intent
+        int id = 6000;
+        final Notification notification =
+                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.id.icon)
+                        .setWhen(System.currentTimeMillis())
+                        .setFullScreenIntent(getPendingIntent(), true)
+                        .setContentText("This is #FSI notification")
+                        .setContentIntent(getPendingIntent())
+                        .build();
+        mNotificationManager.notify(id, notification);
+
+        StatusBarNotification n = findPostedNotification(id);
+        assertNotNull(n);
+        assertNull(n.getNotification().fullScreenIntent);
+    }
+
+    private StatusBarNotification findPostedNotification(int id) {
+        // 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();
+            for (StatusBarNotification sbn : sbns) {
+                Log.d(TAG, "Found " + sbn.getKey());
+                if (sbn.getId() == id) {
+                    n = sbn;
+                    break;
+                }
+            }
+            if (n != null) break;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return n;
+    }
+
+    private PendingIntent getPendingIntent() {
+        return PendingIntent.getActivity(
+                mContext, 0, new Intent(mContext, this.getClass()), 0);
+    }
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
new file mode 100644
index 0000000..745046d
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.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 android.app.notification.legacy29.cts;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationAssistantService;
+import android.service.notification.StatusBarNotification;
+
+public class TestNotificationAssistant extends NotificationAssistantService {
+    public static final String TAG = "TestNotificationAssistant";
+    public static final String PKG = "android.app.notification.legacy29.cts";
+
+    private static TestNotificationAssistant sNotificationAssistantInstance = null;
+    boolean isConnected;
+
+    public static String getId() {
+        return String.format("%s/%s", TestNotificationAssistant.class.getPackage().getName(),
+                TestNotificationAssistant.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(TestNotificationAssistant.class.getPackage().getName(),
+                TestNotificationAssistant.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationAssistantInstance = this;
+        isConnected = true;
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        isConnected = false;
+    }
+
+    public static TestNotificationAssistant getInstance() {
+        return sNotificationAssistantInstance;
+    }
+
+    public void onNotificationSnoozedUntilContext(StatusBarNotification statusBarNotification,
+            String s) {
+    }
+
+    @Override
+    public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT, Ranking.USER_SENTIMENT_POSITIVE);
+        return new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
+                sbn.getUserId());
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
new file mode 100644
index 0000000..aea3914
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.notification.legacy29.cts;
+
+import android.content.ComponentName;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+public class TestNotificationListener extends NotificationListenerService {
+    public static final String TAG = "TestNotificationListener";
+    public static final String PKG = "android.app.notification.legacy29.cts";
+
+    private ArrayList<String> mTestPackages = new ArrayList<>();
+
+    public BlockingQueue<StatusBarNotification> mPosted = new ArrayBlockingQueue<>(10);
+    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public RankingMap mRankingMap;
+
+    private static TestNotificationListener
+            sNotificationListenerInstance = null;
+    boolean isConnected;
+
+    public static String getId() {
+        return String.format("%s/%s", TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(TestNotificationListener.class.getPackage().getName(),
+                TestNotificationListener.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mTestPackages.add(PKG);
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sNotificationListenerInstance = this;
+        isConnected = true;
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        isConnected = false;
+    }
+
+    public static TestNotificationListener getInstance() {
+        return sNotificationListenerInstance;
+    }
+
+    public void resetData() {
+        mPosted.clear();
+        mRemoved.clear();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!PKG.equals(sbn.getPackageName())) {
+            return;
+        }
+        mRankingMap = rankingMap;
+        mPosted.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (!mTestPackages.contains(sbn.getPackageName())) {
+            return;
+        }
+        mRankingMap = rankingMap;
+        mRemoved.add(sbn);
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(RankingMap rankingMap) {
+        mRankingMap = rankingMap;
+    }
+}
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java b/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
deleted file mode 100644
index 7a172e5..0000000
--- a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
+++ /dev/null
@@ -1,268 +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.app.notification.legacy.cts;
-
-import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
-import static android.service.notification.NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-
-import android.app.ActivityManager;
-import android.app.AutomaticZenRule;
-import android.app.Instrumentation;
-import android.app.NotificationManager;
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.ArraySet;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-@RunWith(AndroidJUnit4.class)
-public class ConditionProviderServiceTest {
-    private static String TAG = "CpsTest";
-
-    private NotificationManager mNm;
-    private ActivityManager mActivityManager;
-    private Context mContext;
-    private ArraySet<String> ids = new ArraySet<>();
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), true);
-        LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
-        SecondaryConditionProviderService.requestRebind(SecondaryConditionProviderService.getId());
-        mNm = (NotificationManager) mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        try {
-            for (String id : ids) {
-                if (id != null) {
-                    if (!mNm.removeAutomaticZenRule(id)) {
-                        throw new Exception("Could not remove rule " + id);
-                    }
-
-                    assertNull(mNm.getAutomaticZenRule(id));
-                }
-            }
-        } finally {
-            toggleNotificationPolicyAccess(mContext.getPackageName(),
-                    InstrumentationRegistry.getInstrumentation(), false);
-            pollForConnection(LegacyConditionProviderService.class, false);
-            pollForConnection(SecondaryConditionProviderService.class, false);
-        }
-    }
-
-    @Test
-    public void testUnboundCPSMaintainsCondition_addsNewRule() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-
-        // make sure service get bound
-        pollForConnection(SecondaryConditionProviderService.class, true);
-
-        final ComponentName cn = SecondaryConditionProviderService.getId();
-
-        // add rule
-        addRule(cn, INTERRUPTION_FILTER_ALARMS, true);
-        pollForSubscribe(SecondaryConditionProviderService.getInstance());
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // unbind service
-        SecondaryConditionProviderService.getInstance().requestUnbind();
-
-        // verify that DND state doesn't change
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // add a new rule
-        addRule(cn, INTERRUPTION_FILTER_NONE, true);
-
-        // verify that the unbound service maintains it's DND vote
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-    }
-
-    @Test
-    public void testUnboundCPSMaintainsCondition_otherConditionChanges() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-
-        // make sure both services get bound
-        pollForConnection(LegacyConditionProviderService.class, true);
-        pollForConnection(SecondaryConditionProviderService.class, true);
-
-        // add rules for both
-        addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
-        pollForSubscribe(LegacyConditionProviderService.getInstance());
-
-        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
-        pollForSubscribe(SecondaryConditionProviderService.getInstance());
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // unbind one of the services
-        SecondaryConditionProviderService.getInstance().requestUnbind();
-
-        // verify that DND state doesn't change
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // trigger a change in the bound service's condition
-        ((LegacyConditionProviderService) LegacyConditionProviderService.getInstance())
-                .toggleDND(false);
-
-        // verify that the unbound service maintains it's DND vote
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-    }
-
-    @Test
-    public void testUnboundCPSMaintainsCondition_otherProviderRuleChanges() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-
-        // make sure both services get bound
-        pollForConnection(LegacyConditionProviderService.class, true);
-        pollForConnection(SecondaryConditionProviderService.class, true);
-
-        // add rules for both
-        addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
-        pollForSubscribe(LegacyConditionProviderService.getInstance());
-
-        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
-        pollForSubscribe(SecondaryConditionProviderService.getInstance());
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // unbind one of the services
-        SecondaryConditionProviderService.getInstance().requestUnbind();
-
-        // verify that DND state doesn't change
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-
-        // trigger a change in the bound service's rule
-        addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, false);
-
-        // verify that the unbound service maintains it's DND vote
-        assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
-    }
-
-    private void addRule(ComponentName cn, int filter, boolean enabled) {
-        String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name",
-                cn, Uri.EMPTY, filter, enabled));
-        Log.d(TAG, "Created rule with id " + id);
-        ids.add(id);
-    }
-
-    private void toggleNotificationPolicyAccess(String packageName,
-            Instrumentation instrumentation, boolean on) throws IOException {
-
-        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
-
-        runCommand(command, instrumentation);
-
-        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        Assert.assertEquals("Notification Policy Access Grant is " +
-                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
-                nm.isNotificationPolicyAccessGranted());
-    }
-
-    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
-        UiAutomation uiAutomation = instrumentation.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();
-        }
-    }
-
-    private void pollForSubscribe(PollableConditionProviderService service)  throws Exception {
-        int tries = 30;
-        int delayMs = 200;
-
-        while (tries-- > 0 && !service.subscribed) {
-            try {
-                Thread.sleep(delayMs);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-
-        if (!service.subscribed) {
-            Log.d(TAG, "not subscribed");
-            throw new Exception("Service never got onSubscribe()");
-        }
-    }
-
-    private void pollForConnection(Class<? extends PollableConditionProviderService> service,
-            boolean waitForConnection) throws Exception {
-        int tries = 100;
-        int delayMs = 200;
-
-        PollableConditionProviderService instance =
-                (PollableConditionProviderService) service.getMethod("getInstance").invoke(null);
-
-        while (tries-- > 0 && (waitForConnection ? instance == null : instance != null)) {
-            try {
-                Thread.sleep(delayMs);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-            instance = (PollableConditionProviderService) service.getMethod("getInstance")
-                    .invoke(null);
-        }
-
-        if (waitForConnection && instance == null) {
-            Log.d(TAG, service.getName() + " not bound");
-            throw new Exception("CPS never bound");
-        } else if (!waitForConnection && instance != null) {
-            Log.d(TAG, service.getName() + " still bound");
-            throw new Exception("CPS still bound");
-        } else {
-            Log.d(TAG, service.getName() + " has a correct bind state");
-        }
-    }
-}
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
deleted file mode 100644
index 528d66f..0000000
--- a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ /dev/null
@@ -1,309 +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.app.notification.legacy.cts;
-
-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;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
-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 junit.framework.Assert.assertEquals;
-
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.ParcelFileDescriptor;
-import android.provider.Telephony.Threads;
-import android.service.notification.NotificationListenerService;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Home for tests that need to verify behavior for apps that target old sdk versions.
- */
-@RunWith(AndroidJUnit4.class)
-public class LegacyNotificationManagerTest {
-    final String TAG = "LegacyNoManTest";
-
-    final String NOTIFICATION_CHANNEL_ID = "LegacyNotificationManagerTest";
-    private NotificationManager mNotificationManager;
-    private ActivityManager mActivityManager;
-    private Context mContext;
-    private MockNotificationListener mListener;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        toggleListenerAccess(MockNotificationListener.getId(),
-                InstrumentationRegistry.getInstrumentation(), false);
-        mNotificationManager = (NotificationManager) mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mNotificationManager.createNotificationChannel(new NotificationChannel(
-                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        toggleListenerAccess(MockNotificationListener.getId(),
-                InstrumentationRegistry.getInstrumentation(), false);
-    }
-
-    @Test
-    public void testPrePCannotToggleAlarmsAndMediaTest() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), true);
-
-        // Pre-P cannot toggle alarms and media
-        NotificationManager.Policy origPolicy = mNotificationManager.getNotificationPolicy();
-        int alarmBit = origPolicy.priorityCategories & NotificationManager.Policy
-                .PRIORITY_CATEGORY_ALARMS;
-        int mediaBit = origPolicy.priorityCategories & NotificationManager.Policy
-                .PRIORITY_CATEGORY_MEDIA;
-        int systemBit = origPolicy.priorityCategories & NotificationManager.Policy
-                .PRIORITY_CATEGORY_SYSTEM;
-
-        // attempt to toggle off alarms, media, system:
-        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
-        NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
-        assertEquals(alarmBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
-        assertEquals(mediaBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
-        assertEquals(systemBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
-
-        // attempt to toggle on alarms, media, system:
-        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
-                NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
-                        | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
-        policy = mNotificationManager.getNotificationPolicy();
-        assertEquals(alarmBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS);
-        assertEquals(mediaBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA);
-        assertEquals(systemBit, policy.priorityCategories
-                & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM);
-
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
-    }
-
-    @Test
-    public void testSetNotificationPolicy_preP_setOldFields() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), true);
-
-        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
-
-        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
-                SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
-        mNotificationManager.setNotificationPolicy(appPolicy);
-
-        int expected = userPolicy.suppressedVisualEffects
-                | SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF
-                | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_LIGHTS
-                | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
-
-        assertEquals(expected,
-                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
-    }
-
-    @Test
-    public void testSetNotificationPolicy_preP_setNewFields() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), true);
-
-        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
-
-        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
-                SUPPRESSED_EFFECT_NOTIFICATION_LIST);
-        mNotificationManager.setNotificationPolicy(appPolicy);
-
-        int expected = userPolicy.suppressedVisualEffects;
-        expected &= ~ SUPPRESSED_EFFECT_SCREEN_OFF & ~ SUPPRESSED_EFFECT_SCREEN_ON;
-        assertEquals(expected,
-                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
-
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
-    }
-
-    @Test
-    public void testSuspendPackage() throws Exception {
-        toggleListenerAccess(MockNotificationListener.getId(),
-                InstrumentationRegistry.getInstrumentation(), true);
-        Thread.sleep(500); // wait for listener to be allowed
-
-        mListener = MockNotificationListener.getInstance();
-        Assert.assertNotNull(mListener);
-
-        sendNotification(1, R.drawable.icon_black);
-        Thread.sleep(500); // wait for notification listener to receive notification
-        assertEquals(1, mListener.mPosted.size());
-        mListener.resetData();
-
-        // suspend package, listener receives onRemoved
-        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
-                true);
-        Thread.sleep(500); // wait for notification listener to get response
-        assertEquals(1, mListener.mRemoved.size());
-
-        // unsuspend package, listener receives onPosted
-        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
-                false);
-        Thread.sleep(500); // wait for notification listener to get response
-        assertEquals(1, mListener.mPosted.size());
-
-        toggleListenerAccess(MockNotificationListener.getId(),
-                InstrumentationRegistry.getInstrumentation(), false);
-
-        mListener.resetData();
-    }
-
-    @Test
-    public void testSetNotificationPolicy_preP_setOldNewFields() throws Exception {
-        if (mActivityManager.isLowRamDevice()) {
-            return;
-        }
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), true);
-
-        NotificationManager.Policy userPolicy = mNotificationManager.getNotificationPolicy();
-
-        NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
-                SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
-        mNotificationManager.setNotificationPolicy(appPolicy);
-
-        int expected = userPolicy.suppressedVisualEffects
-                | SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_PEEK;
-        expected &= ~ SUPPRESSED_EFFECT_SCREEN_OFF;
-        assertEquals(expected,
-                mNotificationManager.getNotificationPolicy().suppressedVisualEffects);
-
-        toggleNotificationPolicyAccess(mContext.getPackageName(),
-                InstrumentationRegistry.getInstrumentation(), false);
-    }
-
-    private void sendNotification(final int id, final int icon) throws Exception {
-        sendNotification(id, null, icon);
-    }
-
-    private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
-        final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
-
-        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(mContext, 0, intent, 0);
-        final Notification notification =
-                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                        .setSmallIcon(icon)
-                        .setWhen(System.currentTimeMillis())
-                        .setContentTitle("notify#" + id)
-                        .setContentText("This is #" + id + "notification  ")
-                        .setContentIntent(pendingIntent)
-                        .setGroup(groupKey)
-                        .build();
-        mNotificationManager.notify(id, notification);
-    }
-
-    private void toggleNotificationPolicyAccess(String packageName,
-            Instrumentation instrumentation, boolean on) throws IOException {
-
-        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
-
-        runCommand(command, instrumentation);
-
-        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        Assert.assertEquals("Notification Policy Access Grant is " +
-                        nm.isNotificationPolicyAccessGranted() + " not " + on, on,
-                nm.isNotificationPolicyAccessGranted());
-    }
-
-    private void suspendPackage(String packageName,
-            Instrumentation instrumentation, boolean suspend) throws IOException {
-        String command = " cmd notification " + (suspend ? "suspend_package "
-                : "unsuspend_package ") + packageName;
-
-        runCommand(command, instrumentation);
-    }
-
-    private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
-            boolean on) throws IOException {
-
-        String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
-                + componentName;
-
-        runCommand(command, instrumentation);
-
-        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
-        final ComponentName listenerComponent = MockNotificationListener.getComponentName();
-        Assert.assertTrue(listenerComponent + " has not been granted access",
-                nm.isNotificationListenerAccessGranted(listenerComponent) == on);
-    }
-
-    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
-        UiAutomation uiAutomation = instrumentation.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();
-        }
-    }
-}
diff --git a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/MockNotificationListener.java b/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/MockNotificationListener.java
deleted file mode 100644
index 5a4359e..0000000
--- a/tests/tests/notificationlegacy/src/android/app/notification/legacy/cts/MockNotificationListener.java
+++ /dev/null
@@ -1,91 +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.app.notification.legacy.cts;
-
-import android.content.ComponentName;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-public class MockNotificationListener extends NotificationListenerService {
-    public static final String TAG = "TestNotificationListener";
-    public static final String PKG = "android.app.notification.legacy.cts";
-
-    private ArrayList<String> mTestPackages = new ArrayList<>();
-
-    public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
-    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
-    public RankingMap mRankingMap;
-
-    private static MockNotificationListener sNotificationListenerInstance = null;
-    boolean isConnected;
-
-    public static String getId() {
-        return String.format("%s/%s", MockNotificationListener.class.getPackage().getName(),
-                MockNotificationListener.class.getName());
-    }
-
-    public static ComponentName getComponentName() {
-        return new ComponentName(MockNotificationListener.class.getPackage().getName(),
-                MockNotificationListener.class.getName());
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mTestPackages.add(PKG);
-    }
-
-    @Override
-    public void onListenerConnected() {
-        super.onListenerConnected();
-        sNotificationListenerInstance = this;
-        isConnected = true;
-    }
-
-    @Override
-    public void onListenerDisconnected() {
-        isConnected = false;
-    }
-
-    public static MockNotificationListener getInstance() {
-        return sNotificationListenerInstance;
-    }
-
-    public void resetData() {
-        mPosted.clear();
-        mRemoved.clear();
-    }
-
-    @Override
-    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
-        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
-        mPosted.add(sbn);
-    }
-
-    @Override
-    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
-        if (!mTestPackages.contains(sbn.getPackageName())) { return; }
-        mRemoved.add(sbn);
-    }
-
-    @Override
-    public void onNotificationRankingUpdate(RankingMap rankingMap) {
-        mRankingMap = rankingMap;
-    }
-}
diff --git a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
index 5a2512d..322d60b 100644
--- a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
@@ -298,6 +298,63 @@
         }
     }
 
+    @CddTest(requirement="7.1.4.5/C-1-4")
+    @Test
+    public void testRequiredGLESVersion() {
+        // This requirement only applies if device claims to be wide color capable.
+        boolean isWideColorCapable =
+            mActivity.getResources().getConfiguration().isScreenWideColorGamut();
+        if (!isWideColorCapable)
+            return;
+
+        int reportedVersion = getVersionFromActivityManager(mActivity);
+        assertEquals("Reported OpenGL ES major version doesn't meet the requirement of" +
+            " CDD 7.1.4.5/C-1-4", 3, getMajorVersion(reportedVersion));
+        assertTrue("Reported OpenGL ES minor version doesn't meet the requirement of" +
+            " CDD 7.1.4.5/C-1-4", 1 == getMinorVersion(reportedVersion) ||
+                                  2 == getMinorVersion(reportedVersion));
+    }
+
+    @CddTest(requirement="7.1.4.5/C-1-5")
+    @Test
+    public void testRequiredEglExtensionsForWideColorDisplay() {
+        // See CDD section 7.1.4.5
+        // This test covers the EGL portion of the CDD requirement. The VK portion of the
+        // requirement is covered elsewhere.
+        final String requiredEglList[] = {
+            "EGL_KHR_no_config_context",
+            "EGL_EXT_pixel_format_float",
+            "EGL_KHR_gl_colorspace",
+            "EGL_EXT_gl_colorspace_scrgb",
+            "EGL_EXT_gl_colorspace_scrgb_linear",
+            "EGL_EXT_gl_colorspace_display_p3",
+            "EGL_EXT_gl_colorspace_display_p3_linear",
+            "EGL_EXT_gl_colorspace_display_p3_passthrough",
+        };
+
+        // This requirement only applies if device claims to be wide color capable.
+        boolean isWideColorCapable = mActivity.getResources().getConfiguration().isScreenWideColorGamut();
+        if (!isWideColorCapable)
+            return;
+
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+        if (egl.eglInitialize(display, null)) {
+            try {
+                String eglExtensions = egl.eglQueryString(display, EGL10.EGL_EXTENSIONS);
+                for (int i = 0; i < requiredEglList.length; ++i) {
+                    assertTrue("EGL extension required by CDD section 7.1.4.5 missing: " +
+                        requiredEglList[i], hasExtension(eglExtensions, requiredEglList[i]));
+                }
+            } finally {
+                egl.eglTerminate(display);
+            }
+        } else {
+            Log.e(TAG, "Couldn't initialize EGL.");
+        }
+    }
+
     private boolean isHandheld() {
         // handheld nature is not exposed to package manager, for now
         // we check for touchscreen and NOT watch and NOT tv
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index 9a6679e..a524005 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -48,9 +48,7 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
-# uncomment when b/13282254 is fixed
-#LOCAL_SDK_VERSION := current
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
 LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
 LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 0644dd0..b3fc376 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.POWER_SAVER" />
     <uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
 
     <application android:usesCleartextTraffic="true">
diff --git a/tests/tests/os/AndroidTest.xml b/tests/tests/os/AndroidTest.xml
index 8f8d997..45cfc4b 100644
--- a/tests/tests/os/AndroidTest.xml
+++ b/tests/tests/os/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Configuration for OS Tests">
     <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" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsOsTestCases.apk" />
diff --git a/tests/tests/os/src/android/os/cts/AbiTest.java b/tests/tests/os/src/android/os/cts/AbiTest.java
index 1765ae7..f6fb3d1 100644
--- a/tests/tests/os/src/android/os/cts/AbiTest.java
+++ b/tests/tests/os/src/android/os/cts/AbiTest.java
@@ -30,7 +30,8 @@
 
 public class AbiTest extends TestCase {
     public void testNo64() throws Exception {
-        ArraySet<String> abiDirs = new ArraySet(Arrays.asList(
+        ArraySet<String> abiDirs = new ArraySet<>();
+        abiDirs.addAll(Arrays.asList(
             "/sbin",
             "/system",
             "/vendor"));
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index b371ad1..0ebc438 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -22,16 +22,16 @@
 import android.os.Build;
 import android.platform.test.annotations.RestrictedBuildTest;
 
-import dalvik.system.VMRuntime;
-
 import junit.framework.TestCase;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Scanner;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 public class BuildTest extends TestCase {
@@ -40,7 +40,9 @@
     private static final String RO_PRODUCT_CPU_ABILIST32 = "ro.product.cpu.abilist32";
     private static final String RO_PRODUCT_CPU_ABILIST64 = "ro.product.cpu.abilist64";
 
-    /** Tests that check the values of {@link Build#CPU_ABI} and {@link Build#CPU_ABI2}. */
+    /**
+     * Verify that the values of the various CPU ABI fields are consistent.
+     */
     public void testCpuAbi() throws Exception {
         runTestCpuAbiCommon();
         if (android.os.Process.is64Bit()) {
@@ -50,6 +52,30 @@
         }
     }
 
+    /**
+     * Verify that the CPU ABI fields on device match the permitted ABIs defined by CDD.
+     */
+    public void testCpuAbi_valuesMatchPermitted() throws Exception {
+        // The permitted ABIs are listed in https://developer.android.com/ndk/guides/abis.
+        Set<String> just32 = new HashSet<>(Arrays.asList("armeabi", "armeabi-v7a", "x86"));
+        Set<String> just64 = new HashSet<>(Arrays.asList("x86_64", "arm64-v8a"));
+        Set<String> all = new HashSet<>();
+        all.addAll(just32);
+        all.addAll(just64);
+        Set<String> allAndEmpty = new HashSet<>(all);
+        allAndEmpty.add("");
+
+        // The cpu abi fields on the device must match the permitted values.
+        assertValueIsAllowed(all, Build.CPU_ABI);
+        // CPU_ABI2 will be empty when the device does not support a secondary CPU architecture.
+        assertValueIsAllowed(allAndEmpty, Build.CPU_ABI2);
+
+        // The supported abi fields on the device must match the permitted values.
+        assertValuesAreAllowed(all, Build.SUPPORTED_ABIS);
+        assertValuesAreAllowed(just32, Build.SUPPORTED_32_BIT_ABIS);
+        assertValuesAreAllowed(just64, Build.SUPPORTED_64_BIT_ABIS);
+    }
+
     private void runTestCpuAbiCommon() throws Exception {
         // The build property must match Build.SUPPORTED_ABIS exactly.
         final String[] abiListProperty = getStringList(RO_PRODUCT_CPU_ABILIST);
@@ -63,13 +89,13 @@
         // Every supported 32 bit ABI must be present in Build.SUPPORTED_ABIS.
         for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
             assertTrue(abiList.contains(abi));
-            assertFalse(VMRuntime.is64BitAbi(abi));
+            assertFalse(Build.is64BitAbi(abi));
         }
 
         // Every supported 64 bit ABI must be present in Build.SUPPORTED_ABIS.
         for (String abi : Build.SUPPORTED_64_BIT_ABIS) {
             assertTrue(abiList.contains(abi));
-            assertTrue(VMRuntime.is64BitAbi(abi));
+            assertTrue(Build.is64BitAbi(abi));
         }
 
         // Build.CPU_ABI and Build.CPU_ABI2 must be present in Build.SUPPORTED_ABIS.
@@ -170,6 +196,17 @@
         }
     }
 
+    private static void assertValueIsAllowed(Set<String> allowedValues, String actualValue) {
+        assertTrue("Expected one of " + allowedValues + ", but was: '" + actualValue + "'",
+                allowedValues.contains(actualValue));
+    }
+
+    private static void assertValuesAreAllowed(Set<String> allowedValues, String[] actualValues) {
+        for (String actualValue : actualValues) {
+            assertValueIsAllowed(allowedValues, actualValue);
+        }
+    }
+
     private static final Pattern BOARD_PATTERN =
         Pattern.compile("^([0-9A-Za-z._-]+)$");
     private static final Pattern BRAND_PATTERN =
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index d4ef860..f3ccf57 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import junit.framework.TestCase;
@@ -63,13 +64,12 @@
      */
     @RestrictedBuildTest
     public void testBuildFingerprint() {
-        final String fingerprint = Build.FINGERPRINT;
+        String fingerprint = Build.FINGERPRINT;
         Log.i(LOG_TAG, String.format("Testing fingerprint %s", fingerprint));
 
-        assertEquals("Build fingerprint must not include whitespace", -1,
-                fingerprint.indexOf(' '));
-        final String[] fingerprintSegs = fingerprint.split("/");
-        assertEquals("Build fingerprint does not match expected format", 6, fingerprintSegs.length);
+        verifyFingerprintStructure(fingerprint);
+
+        String[] fingerprintSegs = fingerprint.split("/");
         assertEquals(Build.BRAND, fingerprintSegs[0]);
         assertEquals(Build.PRODUCT, fingerprintSegs[1]);
 
@@ -80,13 +80,42 @@
 
         assertEquals(Build.ID, fingerprintSegs[3]);
 
-        assertTrue(fingerprintSegs[4].contains(":"));
         String[] buildNumberVariant = fingerprintSegs[4].split(":");
         String buildVariant = buildNumberVariant[1];
         assertEquals("Variant", EXPECTED_BUILD_VARIANT, buildVariant);
         assertEquals("Tag", EXPECTED_TAG, fingerprintSegs[5]);
     }
 
+    public void testPartitions() {
+        List<Build.Partition> partitions = Build.getFingerprintedPartitions();
+        Set<String> seenPartitions = new HashSet<>();
+        for (Build.Partition partition : partitions) {
+            if (partition.getName().equals(Build.Partition.PARTITION_NAME_SYSTEM)) {
+                assertEquals(Build.FINGERPRINT, partition.getFingerprint());
+            }
+            verifyFingerprintStructure(partition.getFingerprint());
+            assertTrue(partition.getBuildTimeMillis() > 0);
+            boolean unique = seenPartitions.add(partition.getName());
+            assertTrue("partitions not unique, " + partition.getName() + " is duplicated", unique);
+        }
+
+        assertTrue(seenPartitions.contains("system"));
+    }
+
+    private void verifyFingerprintStructure(String fingerprint) {
+        assertEquals("Build fingerprint must not include whitespace", -1, fingerprint.indexOf(' '));
+
+        String[] segments = fingerprint.split("/");
+        assertEquals("Build fingerprint does not match expected format", 6, segments.length);
+
+        String[] devicePlatform = segments[2].split(":");
+        assertEquals(2, devicePlatform.length);
+
+        assertTrue(segments[4].contains(":"));
+        String buildVariant = segments[4].split(":")[1];
+        assertTrue(buildVariant.length() > 0);
+    }
+
     private void assertNotEmpty(String value) {
         assertNotNull(value);
         assertFalse(value.isEmpty());
diff --git a/tests/tests/os/src/android/os/cts/BundleTest.java b/tests/tests/os/src/android/os/cts/BundleTest.java
index 58d6e37..ca70fe3 100644
--- a/tests/tests/os/src/android/os/cts/BundleTest.java
+++ b/tests/tests/os/src/android/os/cts/BundleTest.java
@@ -902,6 +902,29 @@
         }
     }
 
+    public void testBundleLengthNotAlignedByFour() {
+        mBundle.putBoolean(KEY, true);
+        assertEquals(1, mBundle.size());
+        Parcel p = Parcel.obtain();
+        final int lengthPos = p.dataPosition();
+        mBundle.writeToParcel(p, 0);
+        p.setDataPosition(lengthPos);
+        final int length = p.readInt();
+        assertTrue(length != 0);
+        assertTrue(length % 4 == 0);
+        // Corrupt the bundle length so it is not aligned by 4.
+        p.setDataPosition(lengthPos);
+        p.writeInt(length - 1);
+        p.setDataPosition(0);
+        final Bundle b = new Bundle();
+        try {
+            b.readFromParcel(p);
+            fail("Failed to get an IllegalStateException");
+        } catch (IllegalStateException e) {
+            // Expect IllegalStateException here.
+        }
+    }
+
     /** Create a Bundle with values, with autogenerated keys. */
     private static Bundle buildBundle(Object... values) {
         final Bundle result = new Bundle();
diff --git a/tests/tests/os/src/android/os/cts/DebugTest.java b/tests/tests/os/src/android/os/cts/DebugTest.java
index 0f19078..a7079cc 100644
--- a/tests/tests/os/src/android/os/cts/DebugTest.java
+++ b/tests/tests/os/src/android/os/cts/DebugTest.java
@@ -21,14 +21,12 @@
 
 import com.android.compatibility.common.util.TestThread;
 
-import dalvik.system.VMDebug;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import java.util.Map;
 
 public class DebugTest extends AndroidTestCase {
     private static final Logger Log = Logger.getLogger(DebugTest.class.getName());
@@ -51,7 +49,7 @@
         final String traceName = getFileName();
 
         final int bufSize = 1024 * 1024 * 2;
-        final int debug_flag = VMDebug.TRACE_COUNT_ALLOCS;
+        final int debug_flag = Debug.TRACE_COUNT_ALLOCS;
 
         Debug.startMethodTracing();
         Thread.sleep(debugTime);
diff --git a/tests/tests/os/src/android/os/cts/FileObserverTest.java b/tests/tests/os/src/android/os/cts/FileObserverTest.java
index 744963e..85af860 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverTest.java
@@ -81,9 +81,9 @@
 
     public void testConstructor() {
         // new the instance
-        new MockFileObserver(PATH);
+        new MockFileObserver(new File(PATH));
         // new the instance
-        new MockFileObserver(PATH, FileObserver.ACCESS);
+        new MockFileObserver(new File(PATH), FileObserver.ACCESS);
     }
 
     /*
@@ -110,21 +110,11 @@
         File moveDestFile;
         FileOutputStream out = null;
 
-        fileObserver = new MockFileObserver(testFile.getParent());
+        fileObserver = new MockFileObserver(testFile.getParentFile());
         try {
             fileObserver.startWatching();
-            out = new FileOutputStream(testFile);
 
-            out.write(FILE_DATA); // modify, open, write, modify
-            out.close(); // close_write
-
-            expected = new int[] {FileObserver.MODIFY, FileObserver.OPEN, FileObserver.MODIFY,
-                    FileObserver.CLOSE_WRITE};
-            moveEvents = waitForEvent(fileObserver);
-            if (isEmulated)
-                assertEventsContains(expected, moveEvents);
-            else
-                assertEventsEquals(expected, moveEvents);
+            verifyTriggeredEventsOnFile(fileObserver, testFile, isEmulated);
 
             fileObserver.stopWatching();
 
@@ -144,22 +134,10 @@
                 out.close();
             out = null;
         }
-        fileObserver = new MockFileObserver(testDir.getPath());
+        fileObserver = new MockFileObserver(testDir);
         try {
             fileObserver.startWatching();
-            testFile = new File(testDir, TEST_FILE);
-            assertTrue(testFile.createNewFile());
-            assertTrue(testFile.exists());
-            testFile.delete();
-            testDir.delete();
-            expected = new int[] {FileObserver.CREATE,
-                    FileObserver.OPEN, FileObserver.CLOSE_WRITE,
-                    FileObserver.DELETE, FileObserver.DELETE_SELF, UNDEFINED};
-            moveEvents = waitForEvent(fileObserver);
-            if (isEmulated)
-                assertEventsContains(expected, moveEvents);
-            else
-                assertEventsEquals(expected, moveEvents);
+            verifyTriggeredEventsOnDir(fileObserver, testDir, isEmulated);
         } finally {
             fileObserver.stopWatching();
         }
@@ -169,45 +147,82 @@
         testDir = new File(dir, TEST_DIR);
         testDir.mkdirs();
         moveDestFile = new File(testDir, TEST_FILE);
-        MockFileObserver movedFrom = new MockFileObserver(dir.getPath());
-        MockFileObserver movedTo = new MockFileObserver(testDir.getPath());
-        fileObserver = new MockFileObserver(testFile.getPath());
+        final MockFileObserver movedFileObserver = new MockFileObserver(Arrays.asList(
+                dir,
+                testDir,
+                testFile
+        ));
         try {
-            movedFrom.startWatching();
-            movedTo.startWatching();
-            fileObserver.startWatching();
+            movedFileObserver.startWatching();
+
             testFile.renameTo(moveDestFile);
 
-            expected = new int[] {FileObserver.MOVE_SELF};
-            moveEvents = waitForEvent(fileObserver);
-            if (isEmulated)
+            expected = new int[] {
+                    FileObserver.MOVED_FROM,
+                    FileObserver.MOVED_TO,
+                    FileObserver.MOVE_SELF,
+            };
+            moveEvents = waitForEvent(movedFileObserver);
+            if (isEmulated) {
                 assertEventsContains(expected, moveEvents);
-            else
+            } else {
                 assertEventsEquals(expected, moveEvents);
-
-            expected = new int[] {FileObserver.MOVED_FROM};
-            moveEvents = waitForEvent(movedFrom);
-            if (isEmulated)
-                assertEventsContains(expected, moveEvents);
-            else
-                assertEventsEquals(expected, moveEvents);
-
-            expected = new int[] {FileObserver.MOVED_TO};
-            moveEvents = waitForEvent(movedTo);
-            if (isEmulated)
-                assertEventsContains(expected, moveEvents);
-            else
-                assertEventsEquals(expected, moveEvents);
+            }
         } finally {
-            fileObserver.stopWatching();
-            movedTo.stopWatching();
-            movedFrom.stopWatching();
+            movedFileObserver.stopWatching();
         }
 
         // Because Javadoc didn't specify when should a event happened,
         // here ACCESS ATTRIB we found no way to test.
     }
 
+    private void verifyTriggeredEventsOnFile(MockFileObserver fileObserver,
+            File testFile, boolean isEmulated) throws Exception {
+        final FileOutputStream out = new FileOutputStream(testFile);
+
+        out.write(FILE_DATA); // modify, open, write, modify
+        out.close(); // close_write
+
+        final int[] expected = {
+                FileObserver.MODIFY,
+                FileObserver.OPEN,
+                FileObserver.MODIFY,
+                FileObserver.CLOSE_WRITE
+        };
+
+        final FileEvent[] moveEvents = waitForEvent(fileObserver);
+        if (isEmulated) {
+            assertEventsContains(expected, moveEvents);
+        } else {
+            assertEventsEquals(expected, moveEvents);
+        }
+    }
+
+    private void verifyTriggeredEventsOnDir(MockFileObserver fileObserver,
+            File testDir, boolean isEmulated) throws Exception {
+        final File testFile = new File(testDir, TEST_FILE);
+        assertTrue(testFile.createNewFile());
+        assertTrue(testFile.exists());
+        testFile.delete();
+        testDir.delete();
+
+        final int[] expected = {
+                FileObserver.CREATE,
+                FileObserver.OPEN,
+                FileObserver.CLOSE_WRITE,
+                FileObserver.DELETE,
+                FileObserver.DELETE_SELF,
+                UNDEFINED
+        };
+
+        final FileEvent[] moveEvents = waitForEvent(fileObserver);
+        if (isEmulated) {
+            assertEventsContains(expected, moveEvents);
+        } else {
+            assertEventsEquals(expected, moveEvents);
+        }
+    }
+
     public void testFileObserver() throws Exception {
         helpTestFileObserver(getContext().getFilesDir(), false);
     }
@@ -219,6 +234,38 @@
         helpTestFileObserver(getContext().getExternalFilesDir(null), true);
     }
 
+    public void testFileObserver_multipleFiles() throws Exception {
+        final File filesDir = getContext().getFilesDir();
+        final File externalFilesDir = getContext().getExternalFilesDir(null);
+
+        final MockFileObserver fileObserver1 = new MockFileObserver(Arrays.asList(
+                filesDir,
+                externalFilesDir
+        ));
+        try {
+            fileObserver1.startWatching();
+            verifyTriggeredEventsOnFile(fileObserver1,
+                    new File(filesDir, TEST_FILE), false);
+            verifyTriggeredEventsOnFile(fileObserver1,
+                    new File(externalFilesDir, TEST_FILE), true);
+        } finally {
+            fileObserver1.stopWatching();
+        }
+
+        final MockFileObserver fileObserver2 = new MockFileObserver(Arrays.asList(
+                new File(filesDir, TEST_DIR),
+                new File(externalFilesDir, TEST_DIR)
+        ));
+        try {
+            fileObserver2.startWatching();
+            verifyTriggeredEventsOnDir(fileObserver2,
+                    new File(filesDir, TEST_DIR), false);
+            verifyTriggeredEventsOnDir(fileObserver2,
+                    new File(externalFilesDir, TEST_DIR), true);
+        } finally {
+            fileObserver2.stopWatching();
+        }
+    }
 
     private void assertEventsEquals(final int[] expected, final FileEvent[] moveEvents) {
         List<Integer> expectedEvents = new ArrayList<Integer>();
@@ -281,12 +328,20 @@
 
         private List<FileEvent> mEvents = new ArrayList<FileEvent>();
 
-        public MockFileObserver(String path) {
-            super(path);
+        public MockFileObserver(File file) {
+            super(file);
         }
 
-        public MockFileObserver(String path, int mask) {
-            super(path, mask);
+        public MockFileObserver(File file, int mask) {
+            super(file, mask);
+        }
+
+        public MockFileObserver(List<File> files) {
+            super(files);
+        }
+
+        public MockFileObserver(List<File> files, int mask) {
+            super(files, mask);
         }
 
         @Override
diff --git a/tests/tests/os/src/android/os/cts/LaunchpadActivity.java b/tests/tests/os/src/android/os/cts/LaunchpadActivity.java
index 3919ece..d12d191 100644
--- a/tests/tests/os/src/android/os/cts/LaunchpadActivity.java
+++ b/tests/tests/os/src/android/os/cts/LaunchpadActivity.java
@@ -22,10 +22,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -505,7 +503,6 @@
     @SuppressWarnings("deprecation")
     private Intent makeBroadcastIntent(String action) {
         final Intent intent = new Intent(action, null);
-        intent.putExtra("caller", mCallTarget);
         return intent;
     }
 
@@ -552,26 +549,6 @@
         }
     };
 
-    static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
-    static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
-
-    private final Binder mCallTarget = new Binder() {
-        @Override
-        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
-            data.setDataPosition(0);
-            data.enforceInterface(LaunchpadActivity.LAUNCH);
-            if (code == GOT_RECEIVE_TRANSACTION) {
-                final String name = data.readString();
-                gotReceive(name, null);
-                return true;
-            } else if (code == ERROR_TRANSACTION) {
-                finishBad(data.readString());
-                return true;
-            }
-            return false;
-        }
-    };
-
     private final void gotReceive(String name, Intent intent) {
         synchronized (this) {
 
diff --git a/tests/tests/os/src/android/os/cts/LocaleListTest.java b/tests/tests/os/src/android/os/cts/LocaleListTest.java
index bc30e5c..89780b2 100644
--- a/tests/tests/os/src/android/os/cts/LocaleListTest.java
+++ b/tests/tests/os/src/android/os/cts/LocaleListTest.java
@@ -20,6 +20,8 @@
 import android.os.Parcel;
 import android.test.AndroidTestCase;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 public class LocaleListTest extends AndroidTestCase {
@@ -247,7 +249,7 @@
             assertEquals("ae,en,ja", LocaleList.getAdjustedDefault().toLanguageTags());
         } finally {
             // restore the original values
-            LocaleList.setDefault(originalLocaleList, originalLocaleList.indexOf(originalLocale));
+            resetDefaultLocaleList(originalLocale, originalLocaleList);
         }
     }
 
@@ -281,7 +283,7 @@
             assertEquals(locales, LocaleList.getAdjustedDefault());
         } finally {
             // restore the original values
-            LocaleList.setDefault(originalLocaleList, originalLocaleList.indexOf(originalLocale));
+            resetDefaultLocaleList(originalLocale, originalLocaleList);
         }
     }
 
@@ -297,7 +299,7 @@
             assertEquals(locales, LocaleList.getAdjustedDefault());
         } finally {
             // restore the original values
-            LocaleList.setDefault(originalLocaleList, originalLocaleList.indexOf(originalLocale));
+            resetDefaultLocaleList(originalLocale, originalLocaleList);
         }
     }
 
@@ -319,6 +321,26 @@
         LocaleList.getEmptyLocaleList().describeContents();
     }
 
+    private static void resetDefaultLocaleList(Locale topLocale, LocaleList localeList) {
+        final List<Locale> newLocales = new ArrayList<>();
+
+        if (topLocale != null) {
+            newLocales.add(topLocale);
+        }
+
+        if (localeList != null) {
+            for (int index = 0; index < localeList.size(); index++) {
+                final Locale locale = localeList.get(index);
+                if (topLocale != null && !topLocale.equals(locale)) {
+                    newLocales.add(locale);
+                }
+            }
+        }
+
+        final LocaleList result = new LocaleList(newLocales.toArray(new Locale[newLocales.size()]));
+        LocaleList.setDefault(result);
+    }
+
     private static LocaleList cloneViaParcel(final LocaleList original) {
         Parcel parcel = null;
         try {
diff --git a/tests/tests/os/src/android/os/cts/MessengerTest.java b/tests/tests/os/src/android/os/cts/MessengerTest.java
index 7612c14..9f21d9b 100644
--- a/tests/tests/os/src/android/os/cts/MessengerTest.java
+++ b/tests/tests/os/src/android/os/cts/MessengerTest.java
@@ -21,21 +21,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IInterface;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
 import android.test.AndroidTestCase;
 
-import java.io.FileDescriptor;
-
 public class MessengerTest extends AndroidTestCase {
 
     private Messenger mMessenger;
@@ -54,48 +50,7 @@
         }
     };
 
-    private final IBinder mIBinder = new IBinder() {
-
-        public String getInterfaceDescriptor() throws RemoteException {
-            return null;
-        }
-
-        public boolean isBinderAlive() {
-            return false;
-        }
-
-        public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException {
-        }
-
-        public boolean pingBinder() {
-            return false;
-        }
-
-        public IInterface queryLocalInterface(String descriptor) {
-            return null;
-        }
-
-        public boolean transact(int code, Parcel data, Parcel reply, int flags)
-                throws RemoteException {
-            return false;
-        }
-
-        public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
-            return false;
-        }
-
-        public void dump(FileDescriptor fd, String[] args) throws RemoteException {
-        }
-
-        public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
-        }
-
-        @Override
-        public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-                String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
-        }
-
-    };
+    private final IBinder mIBinder = new Binder();
 
     // Create another messenger to send msg.
     private ServiceConnection mConnection = new ServiceConnection() {
diff --git a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
index 38651d7..a4f956b 100644
--- a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
@@ -132,21 +132,6 @@
         done.get(5, TimeUnit.SECONDS);
     }
 
-    @Test
-    public void testFromData() throws IOException {
-        assertNull(ParcelFileDescriptor.fromData(null, null));
-        byte[] data = new byte[] { 0 };
-        assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null));
-        data = new byte[] { 0, 1, 2, 3 };
-        assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null));
-
-        // Check that modifying the data does not modify the data in the FD
-        data = new byte[] { 0, 1, 2, 3 };
-        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null);
-        data[1] = 42;
-        assertFileDescriptorContent(new byte[] { 0, 1, 2, 3 }, pfd);
-    }
-
     private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
         throws IOException {
         assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
@@ -166,28 +151,6 @@
     }
 
     @Test
-    public void testFromDataSkip() throws IOException {
-        byte[] data = new byte[] { 40, 41, 42, 43, 44, 45, 46 };
-        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null);
-        assertNotNull(pfd);
-        FileDescriptor fd = pfd.getFileDescriptor();
-        assertNotNull(fd);
-        assertTrue(fd.valid());
-        FileInputStream is = new FileInputStream(fd);
-        try {
-            assertEquals(1, is.skip(1));
-            assertEquals(41, is.read());
-            assertEquals(42, is.read());
-            assertEquals(2, is.skip(2));
-            assertEquals(45, is.read());
-            assertEquals(46, is.read());
-            assertEquals(-1, is.read());
-        } finally {
-            is.close();
-        }
-    }
-
-    @Test
     public void testToString() {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
         assertNotNull(pfd.toString());
diff --git a/tests/tests/os/src/android/os/cts/PowerManagerTest.java b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
index 50a3a4f..d451930 100644
--- a/tests/tests/os/src/android/os/cts/PowerManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
@@ -16,17 +16,26 @@
 
 package android.os.cts;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.os.PowerManager.WakeLock;
+import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
+import org.junit.After;
+import org.junit.Before;
 
 public class PowerManagerTest extends AndroidTestCase {
     private static final String TAG = "PowerManagerTest";
     public static final long TIME = 3000;
     public static final int MORE_TIME = 300;
 
+    private int mInitialPowerSaverMode;
+    private int mInitialDynamicPowerSavingsEnabled;
+    private int mInitialThreshold;
+
     /**
      * test points:
      * 1 Get a wake lock at the level of the flags parameter
@@ -43,35 +52,73 @@
         assertFalse(wl.isHeld());
 
         try {
-            pm.goToSleep(SystemClock.uptimeMillis());
-            fail("goToSleep should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            pm.wakeUp(SystemClock.uptimeMillis());
-            fail("wakeUp should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            pm.nap(SystemClock.uptimeMillis());
-            fail("nap should throw SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
             pm.reboot("Testing");
             fail("reboot should throw SecurityException");
         } catch (SecurityException e) {
             // expected
         }
+    }
 
-        // This method requires DEVICE_POWER but does not throw a SecurityException
-        // for historical reasons.  So this call should be a no-op.
-        pm.userActivity(SystemClock.uptimeMillis(), false);
+    @Before
+    public void setUp() {
+        // store the current value so we can restore it
+        ContentResolver resolver = getContext().getContentResolver();
+        mInitialPowerSaverMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, 0);
+        mInitialDynamicPowerSavingsEnabled =
+                Global.getInt(resolver, Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0);
+        mInitialThreshold =
+                Global.getInt(resolver, Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 0);
+
+    }
+
+    @After
+    public void tearDown() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ContentResolver resolver = getContext().getContentResolver();
+
+            // Verify we can change it to dynamic.
+            Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, mInitialPowerSaverMode);
+            Global.putInt(resolver,
+                    Global.DYNAMIC_POWER_SAVINGS_ENABLED, mInitialDynamicPowerSavingsEnabled);
+            Global.putInt(resolver,
+                    Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, mInitialThreshold);
+        });
+    }
+
+    public void testPowerManager_getPowerSaveMode() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            PowerManager manager = BatteryUtils.getPowerManager();
+            ContentResolver resolver = getContext().getContentResolver();
+
+            // Verify we can change it to percentage.
+            Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, 0);
+            assertEquals(
+                    PowerManager.POWER_SAVER_MODE_PERCENTAGE,
+                    manager.getPowerSaveMode());
+
+            // Verify we can change it to dynamic.
+            Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVER_MODE, 1);
+            assertEquals(
+                    PowerManager.POWER_SAVER_MODE_DYNAMIC,
+                    manager.getPowerSaveMode());
+        });
+    }
+
+    public void testPowerManager_setDynamicPowerSavings() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            PowerManager manager = BatteryUtils.getPowerManager();
+            ContentResolver resolver = getContext().getContentResolver();
+
+            // Verify settings are actually updated.
+            manager.setDynamicPowerSavings(true, 80);
+            assertEquals(1, Global.getInt(resolver, Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0));
+            assertEquals(80, Global.getInt(resolver,
+                    Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 0));
+
+            manager.setDynamicPowerSavings(false, 20);
+            assertEquals(0, Global.getInt(resolver, Global.DYNAMIC_POWER_SAVINGS_ENABLED, 1));
+            assertEquals(20, Global.getInt(resolver,
+                    Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 0));
+        });
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/PowerManager_ThermalTest.java b/tests/tests/os/src/android/os/cts/PowerManager_ThermalTest.java
new file mode 100644
index 0000000..f6b9dbd
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/PowerManager_ThermalTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.os.cts;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.ThermalStatusCallback;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import com.android.compatibility.common.util.ThermalUtils;
+
+import org.junit.After;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class PowerManager_ThermalTest {
+    private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
+    private UiDevice mUiDevice;
+    private Context mContext;
+    private PowerManager mPowerManager;
+    private Executor mExec = Executors.newSingleThreadExecutor();
+    @Mock
+    private ThermalStatusCallback mCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        ThermalUtils.overrideThermalNotThrottling();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        ThermalUtils.resetThermalStatus();
+    }
+
+    @Test
+    public void testGetThermalStatus() throws Exception {
+        int status = 0; // Temperature.THROTTLING_NONE
+        assertEquals(status, mPowerManager.getCurrentThermalStatus());
+        status = 3; // Temperature.THROTTLING_SEVERE
+        ThermalUtils.overrideThermalStatus(status);
+        assertEquals(status, mPowerManager.getCurrentThermalStatus());
+    }
+
+    @Test
+    public void testThermalStatusCallback() throws Exception {
+        // Initial override status is Temperature.THROTTLING_NONE (0)
+        int status = 0; // Temperature.THROTTLING_NONE
+        mPowerManager.registerThermalStatusCallback(mCallback, mExec);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(status);
+        reset(mCallback);
+        status = 3; // Temperature.THROTTLING_SEVERE
+        ThermalUtils.overrideThermalStatus(status);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(status);
+        reset(mCallback);
+        mPowerManager.unregisterThermalStatusCallback(mCallback);
+        status = 2; // THROTTLING_MODERATE
+        ThermalUtils.overrideThermalStatus(status);
+        verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onStatusChange(status);
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
index 7afeaa9..188bbc7 100644
--- a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
@@ -13,54 +13,46 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.os.cts;
-
 import com.android.compatibility.common.util.ApiLevelUtil;
-
 import android.os.Build;
 import android.os.SystemProperties;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
-
 /**
  * Tests for Security Patch String settings
  */
 public class SecurityPatchTest extends InstrumentationTestCase {
-
     private static final String TAG = SecurityPatchTest.class.getSimpleName();
     private static final String SECURITY_PATCH_ERROR =
             "ro.build.version.security_patch should be in the format \"YYYY-MM-DD\". Found \"%s\"";
-
+    private static final String SECURITY_PATCH_DATE_ERROR =
+            "ro.build.version.security_patch should be \"%d-%02d\" or later. Found \"%s\"";
+    private static final int SECURITY_PATCH_YEAR = 2016;
+    private static final int SECURITY_PATCH_MONTH = 12;
     private boolean mSkipTests = false;
-
     @Override
     protected void setUp() {
         mSkipTests = (ApiLevelUtil.isBefore(Build.VERSION_CODES.M));
     }
-
     /** Security patch string must exist in M or higher **/
     public void testSecurityPatchFound() {
         if (mSkipTests) {
             Log.w(TAG, "Skipping M+ Test.");
             return;
         }
-
         String buildSecurityPatch = Build.VERSION.SECURITY_PATCH;
         String error = String.format(SECURITY_PATCH_ERROR, buildSecurityPatch);
         assertTrue(error, !buildSecurityPatch.isEmpty());
     }
-
     /** Security patch should be of the form YYYY-MM-DD in M or higher */
     public void testSecurityPatchFormat() {
         if (mSkipTests) {
             Log.w(TAG, "Skipping M+ Test.");
             return;
         }
-
         String buildSecurityPatch = Build.VERSION.SECURITY_PATCH;
         String error = String.format(SECURITY_PATCH_ERROR, buildSecurityPatch);
-
         assertEquals(error, 10, buildSecurityPatch.length());
         assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(0)));
         assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(1)));
@@ -73,4 +65,27 @@
         assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(8)));
         assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(9)));
     }
+    /** Security patch should no older than the month this test was updated in M or higher **/
+    public void testSecurityPatchDate() {
+        if (mSkipTests) {
+            Log.w(TAG, "Skipping M+ Test.");
+            return;
+        }
+        String buildSecurityPatch = Build.VERSION.SECURITY_PATCH;
+        String error = String.format(SECURITY_PATCH_DATE_ERROR,
+                                     SECURITY_PATCH_YEAR,
+                                     SECURITY_PATCH_MONTH,
+                                     buildSecurityPatch);
+        int declaredYear = 0;
+        int declaredMonth = 0;
+        try {
+            declaredYear = Integer.parseInt(buildSecurityPatch.substring(0,4));
+            declaredMonth = Integer.parseInt(buildSecurityPatch.substring(5,7));
+        } catch (Exception e) {
+            assertTrue(error, false);
+        }
+        assertTrue(error, declaredYear >= SECURITY_PATCH_YEAR);
+        assertTrue(error, (declaredYear > SECURITY_PATCH_YEAR) ||
+                          (declaredMonth >= SECURITY_PATCH_MONTH));
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index a316e24..02c0d04 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -34,8 +34,16 @@
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy.Builder;
 import android.os.StrictMode.ViolationInfo;
+import android.os.strictmode.CleartextNetworkViolation;
 import android.os.strictmode.CustomViolation;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.ExplicitGcViolation;
 import android.os.strictmode.FileUriExposedViolation;
+import android.os.strictmode.InstanceCountViolation;
+import android.os.strictmode.LeakedClosableViolation;
+import android.os.strictmode.NetworkViolation;
+import android.os.strictmode.NonSdkApiUsedViolation;
 import android.os.strictmode.UntaggedSocketViolation;
 import android.os.strictmode.Violation;
 import android.support.test.InstrumentationRegistry;
@@ -46,6 +54,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -105,9 +114,9 @@
         final File test = File.createTempFile("foo", "bar");
         inspectViolation(
                 test::exists,
-                violation -> {
-                    assertThat(violation.getViolationDetails()).isNull();
-                    assertThat(violation.getStackTrace()).contains("DiskReadViolation");
+                info -> {
+                    assertThat(info.getViolationDetails()).isNull();
+                    assertThat(info.getStackTrace()).contains("DiskReadViolation");
                 });
     }
 
@@ -125,7 +134,8 @@
                     assertThat(info.getStackTrace())
                             .contains("Explicit termination method 'close' not called");
                     assertThat(info.getStackTrace()).contains("leakCloseable");
-                    assertPolicy(info, StrictMode.DETECT_VM_CLOSABLE_LEAKS);
+                    assertThat(info.getViolationClass())
+                            .isAssignableTo(LeakedClosableViolation.class);
                 });
     }
 
@@ -162,7 +172,8 @@
         references.add(new LimitedClass());
         inspectViolation(
                 StrictMode::conditionallyCheckInstanceCounts,
-                info -> assertPolicy(info, StrictMode.DETECT_VM_INSTANCE_LEAKS));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(InstanceCountViolation.class));
     }
 
     private static final class LimitedClass {}
@@ -182,13 +193,8 @@
                 () ->
                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
                                 .getResponseCode(),
-                info -> {
-                    assertThat(info.getViolationDetails())
-                            .contains("Detected cleartext network traffic from UID");
-                    assertThat(info.getViolationDetails())
-                            .startsWith(StrictMode.CLEARTEXT_DETECTED_MSG);
-                    assertPolicy(info, StrictMode.DETECT_VM_CLEARTEXT_NETWORK);
-                });
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(CleartextNetworkViolation.class));
     }
 
     /** Secure connection should be ignored */
@@ -221,8 +227,8 @@
                     intent.setDataAndType(badUri, "image/jpeg");
                     getContext().startActivity(intent);
                 },
-                violation -> {
-                    assertThat(violation.getStackTrace()).contains(badUri + " exposed beyond app");
+                info -> {
+                    assertThat(info.getStackTrace()).contains(badUri + " exposed beyond app");
                 });
 
         final Uri goodUri = Uri.parse("content://com.example/foobar");
@@ -251,8 +257,8 @@
                     intent.setDataAndType(uri, "image/jpeg");
                     getContext().startActivity(intent);
                 },
-                violation ->
-                        assertThat(violation.getStackTrace())
+                info ->
+                        assertThat(info.getStackTrace())
                                 .contains(uri + " exposed beyond app"));
 
         assertNoViolation(
@@ -279,9 +285,8 @@
                 () ->
                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
                                 .getResponseCode(),
-                violation ->
-                        assertThat(violation.getStackTrace())
-                                .contains(UntaggedSocketViolation.MESSAGE));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(UntaggedSocketViolation.class));
 
         assertNoViolation(
                 () -> {
@@ -321,9 +326,8 @@
                         socket.getOutputStream().close();
                     }
                 },
-                violation ->
-                        assertThat(violation.getStackTrace())
-                                .contains(UntaggedSocketViolation.MESSAGE));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(UntaggedSocketViolation.class));
     }
 
     private static final int PERMISSION_USER_ONLY = 0600;
@@ -341,13 +345,13 @@
                 new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build());
         inspectViolation(
                 test::exists,
-                violation -> {
-                    assertThat(violation.getViolationDetails()).isNull();
-                    assertThat(violation.getStackTrace()).contains("DiskReadViolation");
+                info -> {
+                    assertThat(info.getViolationDetails()).isNull();
+                    assertThat(info.getStackTrace()).contains("DiskReadViolation");
                 });
 
-        Consumer<ViolationInfo> assertDiskReadPolicy =
-                violation -> assertPolicy(violation, StrictMode.DETECT_DISK_READ);
+        Consumer<ViolationInfo> assertDiskReadPolicy = info -> assertThat(
+                info.getViolationClass()).isAssignableTo(DiskReadViolation.class);
         inspectViolation(test::exists, assertDiskReadPolicy);
         inspectViolation(test::length, assertDiskReadPolicy);
         inspectViolation(dir::list, assertDiskReadPolicy);
@@ -373,13 +377,13 @@
 
         inspectViolation(
                 file::createNewFile,
-                violation -> {
-                    assertThat(violation.getViolationDetails()).isNull();
-                    assertThat(violation.getStackTrace()).contains("DiskWriteViolation");
+                info -> {
+                    assertThat(info.getViolationDetails()).isNull();
+                    assertThat(info.getStackTrace()).contains("DiskWriteViolation");
                 });
 
-        Consumer<ViolationInfo> assertDiskWritePolicy =
-                violation -> assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
+        Consumer<ViolationInfo> assertDiskWritePolicy = info -> assertThat(
+                info.getViolationClass()).isAssignableTo(DiskWriteViolation.class);
 
         inspectViolation(() -> File.createTempFile("foo", "bar"), assertDiskWritePolicy);
         inspectViolation(() -> new FileOutputStream(file), assertDiskWritePolicy);
@@ -412,12 +416,14 @@
                         socket.getOutputStream().close();
                     }
                 },
-                violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(NetworkViolation.class));
         inspectViolation(
                 () ->
                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
                                 .getResponseCode(),
-                violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(NetworkViolation.class));
     }
 
     @Test
@@ -427,7 +433,8 @@
 
         inspectViolation(
                 () -> { Runtime.getRuntime().gc(); },
-                violation -> assertPolicy(violation, StrictMode.DETECT_EXPLICIT_GC));
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(ExplicitGcViolation.class));
     }
 
     @Test
@@ -441,18 +448,19 @@
                     try {
                         inspectViolation(
                                 () -> service.performDiskWrite(),
-                                (violation) -> {
-                                    assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
-                                    assertThat(violation.getViolationDetails())
+                                (info) -> {
+                                    assertThat(info.getViolationClass())
+                                            .isAssignableTo(DiskWriteViolation.class);
+                                    assertThat(info.getViolationDetails())
                                             .isNull(); // Disk write has no message.
-                                    assertThat(violation.getStackTrace())
+                                    assertThat(info.getStackTrace())
                                             .contains("DiskWriteViolation");
-                                    assertThat(violation.getStackTrace())
+                                    assertThat(info.getStackTrace())
                                             .contains(
                                                     "at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk");
-                                    assertThat(violation.getStackTrace())
+                                    assertThat(info.getStackTrace())
                                             .contains("# via Binder call with stack:");
-                                    assertThat(violation.getStackTrace())
+                                    assertThat(info.getStackTrace())
                                             .contains(
                                                     "at android.os.cts.ISecondary$Stub$Proxy.performDiskWrite");
                                 });
@@ -479,11 +487,12 @@
                   }
                 }
             },
-            violation -> {
-                assertThat(violation).isNotNull();
-                assertPolicy(violation, StrictMode.DETECT_VM_NON_SDK_API_USAGE);
-                assertThat(violation.getViolationDetails()).contains(methodName);
-                assertThat(violation.getStackTrace()).contains("checkNonSdkApiUsageViolation");
+            info -> {
+                assertThat(info).isNotNull();
+                assertThat(info.getViolationClass())
+                        .isAssignableTo(NonSdkApiUsedViolation.class);
+                assertThat(info.getViolationDetails()).contains(methodName);
+                assertThat(info.getStackTrace()).contains("checkNonSdkApiUsageViolation");
             }
         );
     }
@@ -580,16 +589,12 @@
     }
 
     private static void assertViolation(String expected, ThrowingRunnable r) throws Exception {
-        inspectViolation(r, violation -> assertThat(violation.getStackTrace()).contains(expected));
+        inspectViolation(r, info -> assertThat(info.getStackTrace()).contains(expected));
     }
 
     private static void assertNoViolation(ThrowingRunnable r) throws Exception {
         inspectViolation(
-                r, violation -> assertWithMessage("Unexpected violation").that(violation).isNull());
-    }
-
-    private void assertPolicy(ViolationInfo info, int policy) {
-        assertWithMessage("Policy bit incorrect").that(info.getViolationBit()).isEqualTo(policy);
+                r, info -> assertWithMessage("Unexpected violation").that(info).isNull());
     }
 
     private static void inspectViolation(
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 50237f0..babb0f0 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -16,25 +16,21 @@
 
 package android.os.cts;
 
-import android.content.Context;
-import android.media.AudioAttributes;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
 import android.os.Parcel;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class VibrationEffectTest {
@@ -51,13 +47,22 @@
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
     private static final VibrationEffect TEST_WAVEFORM_NO_AMPLITUDES =
             VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+    private static final VibrationEffect TEST_PREBAKED =
+            VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
 
 
     @Test
     public void testCreateOneShot() {
-        VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
-        VibrationEffect.createOneShot(1, 1);
-        VibrationEffect.createOneShot(1000, 255);
+        VibrationEffect e = VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+        assertEquals(100, e.getDuration());
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE,
+                ((VibrationEffect.OneShot)e).getAmplitude());
+        e = VibrationEffect.createOneShot(1, 1);
+        assertEquals(1, e.getDuration());
+        assertEquals(1, ((VibrationEffect.OneShot)e).getAmplitude());
+        e = VibrationEffect.createOneShot(1000, 255);
+        assertEquals(1000, e.getDuration());
+        assertEquals(255, ((VibrationEffect.OneShot)e).getAmplitude());
     }
 
     @Test
@@ -76,6 +81,11 @@
         } catch (IllegalArgumentException expected) { }
 
         try {
+            VibrationEffect.createOneShot(TEST_TIMING, 0);
+            fail("Invalid amplitude, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
             VibrationEffect.createOneShot(TEST_TIMING, 256);
             fail("Invalid amplitude, should throw IllegalArgumentException");
         } catch (IllegalArgumentException expected) { }
@@ -113,10 +123,42 @@
     }
 
     @Test
+    public void testCreatePrebaked() {
+        int[] ids = { VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK,
+                VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_THUD,
+                VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_HEAVY_CLICK };
+        boolean[] fallbacks = { false, true };
+        for (int id : ids) {
+            for (boolean fallback : fallbacks) {
+                VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
+                        VibrationEffect.get(id, fallback);
+                assertEquals(id, effect.getId());
+                assertEquals(fallback, effect.shouldFallback());
+                assertEquals(-1, effect.getDuration());
+            }
+        }
+    }
+
+    @Test
     public void testCreateWaveform() {
-        VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
-        VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
-        VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
+        VibrationEffect.Waveform effect = (VibrationEffect.Waveform)
+                VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
+        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
+        assertEquals(-1, effect.getRepeatIndex());
+        assertEquals(400, effect.getDuration());
+        effect = (VibrationEffect.Waveform)
+            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
+        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
+        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
+        assertEquals(0, effect.getRepeatIndex());
+        assertEquals(Long.MAX_VALUE, effect.getDuration());
+        effect = (VibrationEffect.Waveform)VibrationEffect.createWaveform(TEST_TIMINGS,
+                TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
+        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
+        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
+        assertEquals(TEST_AMPLITUDES.length - 1, effect.getRepeatIndex());
+        assertEquals(Long.MAX_VALUE, effect.getDuration());
     }
 
     @Test
@@ -278,17 +320,76 @@
     }
 
     @Test
-    public void testParceling() {
+    public void testParcelingOneShot() {
         Parcel p = Parcel.obtain();
         TEST_ONE_SHOT.writeToParcel(p, 0);
         p.setDataPosition(0);
         VibrationEffect parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
         assertEquals(TEST_ONE_SHOT, parceledEffect);
+    }
 
-        p.setDataPosition(0);
+    @Test
+    public void testParcelingWaveForm() {
+        Parcel p = Parcel.obtain();
         TEST_WAVEFORM.writeToParcel(p, 0);
         p.setDataPosition(0);
-        parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
+        VibrationEffect parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
         assertEquals(TEST_WAVEFORM, parceledEffect);
     }
+
+    @Test
+    public void testParcelingPrebaked() {
+        Parcel p = Parcel.obtain();
+        TEST_PREBAKED.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        VibrationEffect parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_PREBAKED, parceledEffect);
+    }
+
+    @Test
+    public void testDescribeContents() {
+        TEST_ONE_SHOT.describeContents();
+        TEST_WAVEFORM.describeContents();
+        TEST_WAVEFORM_NO_AMPLITUDES.describeContents();
+        TEST_PREBAKED.describeContents();
+    }
+
+    @Test
+    public void testSetStrength() {
+        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
+                VibrationEffect.EFFECT_CLICK, true);
+        int[] strengths = {
+                VibrationEffect.EFFECT_STRENGTH_LIGHT,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
+                VibrationEffect.EFFECT_STRENGTH_STRONG
+        };
+        for (int strength : strengths) {
+            effect.setEffectStrength(strength);
+            assertEquals(strength, effect.getEffectStrength());
+        }
+    }
+
+    @Test
+    public void testSetStrengthInvalid() {
+        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
+                VibrationEffect.EFFECT_CLICK, true);
+        try {
+            effect.setEffectStrength(239017);
+            fail("Illegal strength, should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+    }
+
+    @Test
+    public void testPrebakedEquals() {
+        VibrationEffect otherEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
+        assertEquals(TEST_PREBAKED, otherEffect);
+        assertEquals(TEST_PREBAKED.hashCode(), otherEffect.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        TEST_ONE_SHOT.toString();
+        TEST_WAVEFORM.toString();
+        TEST_PREBAKED.toString();
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
index e7df7be..9b6fa80 100644
--- a/tests/tests/os/src/android/os/cts/WorkSourceTest.java
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -15,45 +15,17 @@
  */
 package android.os.cts;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import java.util.Arrays;
-
 import android.os.WorkSource;
 import android.test.AndroidTestCase;
 
+import java.util.Arrays;
+
 public class WorkSourceTest extends AndroidTestCase {
-    private Constructor<WorkSource> mConstructWS;
-    private Object[] mConstructWSArgs = new Object[1];
-    private Method mAddUid;
-    private Object[] mAddUidArgs = new Object[1];
-    private Method mAddUidName;
-    private Object[] mAddUidNameArgs = new Object[2];
-    private Method mAddReturningNewbs;
-    private Object[] mAddReturningNewbsArgs = new Object[1];
-    private Method mSetReturningDiffs;
-    private Object[] mSetReturningDiffsArgs = new Object[1];
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mConstructWS = WorkSource.class.getConstructor(new Class[] { int.class });
-        mAddUid = WorkSource.class.getMethod("add", new Class[] { int.class });
-        mAddUidName = WorkSource.class.getMethod("add", new Class[] { int.class, String.class });
-        mAddReturningNewbs = WorkSource.class.getMethod("addReturningNewbs", new Class[] { WorkSource.class });
-        mSetReturningDiffs = WorkSource.class.getMethod("setReturningDiffs", new Class[] { WorkSource.class });
+    private WorkSource wsNew(int uid) {
+        return new WorkSource(uid);
     }
 
-    private WorkSource wsNew(int uid) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        mConstructWSArgs[0] = uid;
-        return mConstructWS.newInstance(mConstructWSArgs);
-    }
-
-    private WorkSource wsNew(int[] uids) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
+    private WorkSource wsNew(int[] uids) {
         WorkSource ws = new WorkSource();
         for (int i=0; i<uids.length; i++) {
             wsAdd(ws, uids[i]);
@@ -62,8 +34,7 @@
         return ws;
     }
 
-    private WorkSource wsNew(int[] uids, String[] names) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
+    private WorkSource wsNew(int[] uids, String[] names) {
         WorkSource ws = new WorkSource();
         for (int i=0; i<uids.length; i++) {
             wsAdd(ws, uids[i], names[i]);
@@ -72,29 +43,20 @@
         return ws;
     }
 
-    private boolean wsAdd(WorkSource ws, int uid) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        mAddUidArgs[0] = uid;
-        return (Boolean)mAddUid.invoke(ws, mAddUidArgs);
+    private boolean wsAdd(WorkSource ws, int uid) {
+        return ws.add(uid);
     }
 
-    private boolean wsAdd(WorkSource ws, int uid, String name) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        mAddUidNameArgs[0] = uid;
-        mAddUidNameArgs[1] = name;
-        return (Boolean)mAddUidName.invoke(ws, mAddUidNameArgs);
+    private boolean wsAdd(WorkSource ws, int uid, String name) {
+        return ws.add(uid, name);
     }
 
-    private WorkSource wsAddReturningNewbs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        mAddReturningNewbsArgs[0] = other;
-        return (WorkSource)mAddReturningNewbs.invoke(ws, mAddReturningNewbsArgs);
+    private WorkSource wsAddReturningNewbs(WorkSource ws, WorkSource other) {
+        return ws.addReturningNewbs(other);
     }
 
-    private WorkSource[] wsSetReturningDiffs(WorkSource ws, WorkSource other) throws IllegalArgumentException,
-            InstantiationException, IllegalAccessException, InvocationTargetException {
-        mSetReturningDiffsArgs[0] = other;
-        return (WorkSource[])mSetReturningDiffs.invoke(ws, mSetReturningDiffsArgs);
+    private WorkSource[] wsSetReturningDiffs(WorkSource ws, WorkSource other) {
+        return ws.setReturningDiffs(other);
     }
 
     private void printArrays(StringBuilder sb, int[] uids, String[] names) {
diff --git a/tests/tests/os/src/android/os/health/cts/SystemHealthManagerTest.java b/tests/tests/os/src/android/os/health/cts/SystemHealthManagerTest.java
index 1ce3086..4abd939 100644
--- a/tests/tests/os/src/android/os/health/cts/SystemHealthManagerTest.java
+++ b/tests/tests/os/src/android/os/health/cts/SystemHealthManagerTest.java
@@ -17,12 +17,11 @@
 package android.os.health.cts;
 
 import android.content.Context;
-import android.os.Parcel;
 import android.os.Process;
-import android.os.health.SystemHealthManager;
 import android.os.health.HealthStats;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.os.health.SystemHealthManager;
 import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 
 import junit.framework.Assert;
 
@@ -36,7 +35,7 @@
     @SmallTest
     public void testTakeMyUidSnapshot() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         Assert.assertNotNull(healthy.takeMyUidSnapshot());
     }
@@ -47,7 +46,7 @@
     @SmallTest
     public void testTakeUidSnapshotWithMe() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         Assert.assertNotNull(healthy.takeUidSnapshot(Process.myUid()));
     }
@@ -58,7 +57,7 @@
     @SmallTest
     public void testTakeMyUidSnapshotWithSystem() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         boolean threw = false;
         try {
@@ -76,7 +75,7 @@
     @SmallTest
     public void testTakeUidSnapshotsWithEmptyArray() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         final HealthStats[] result = healthy.takeUidSnapshots(new int[0]);
         Assert.assertEquals(0, result.length);
@@ -88,7 +87,7 @@
     @SmallTest
     public void testTakeUidSnapshotsWithMe() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         final HealthStats[] result = healthy.takeUidSnapshots(new int[] {
                     Process.myUid(),
@@ -105,7 +104,7 @@
     @SmallTest
     public void testTakeMyUidSnapshotsWithSystem() throws Exception {
         final Context context = getInstrumentation().getTargetContext();
-        final SystemHealthManager healthy = SystemHealthManager.from(context);
+        final SystemHealthManager healthy = context.getSystemService(SystemHealthManager.class);
 
         boolean threw = false;
         try {
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index d7f0853..83d9664 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -16,17 +16,16 @@
 
 package android.os.storage.cts;
 
-import android.os.cts.R;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.ProxyFileDescriptorCallback;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
+import android.os.cts.R;
 import android.os.storage.OnObbStateChangeListener;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
@@ -39,24 +38,22 @@
 
 import com.android.compatibility.common.util.FileUtils;
 
-import libcore.io.Streams;
+import junit.framework.AssertionFailedError;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.SyncFailedException;
-import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.SynchronousQueue;
-import junit.framework.AssertionFailedError;
 
 public class StorageManagerTest extends AndroidTestCase {
 
@@ -112,7 +109,12 @@
     }
 
     private void doAttemptMountNonObb(File outFile) {
-        mountObb(R.raw.test1_nosig, outFile, OnObbStateChangeListener.ERROR_INTERNAL);
+        try {
+            mountObb(R.raw.test1_nosig, outFile, OnObbStateChangeListener.ERROR_INTERNAL);
+            fail("mountObb should've failed with an exception");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
 
         assertFalse("OBB should not be mounted",
                 mStorageManager.isObbMounted(outFile.getPath()));
@@ -236,7 +238,7 @@
             }
         }
         assertNotNull("No primary volume on  " + volumes, primary);
-        assertStorageVolumesEquals(primary, mStorageManager.getPrimaryVolume());
+        assertStorageVolumesEquals(primary, mStorageManager.getPrimaryStorageVolume());
     }
 
     public void testGetStorageVolume() throws Exception {
@@ -706,7 +708,7 @@
     }
 
     private static void assertFileContains(File file, String contents) throws IOException {
-        byte[] actual = Streams.readFully(new FileInputStream(file));
+        byte[] actual = readFully(new FileInputStream(file));
         byte[] expected = contents.getBytes("UTF-8");
         assertEquals("unexpected size", expected.length, actual.length);
         for (int i = 0; i < expected.length; i++) {
@@ -860,4 +862,24 @@
             assertFalse("OBB should not be mounted", mStorageManager.isObbMounted(file.getPath()));
         }
     }
+
+    public static byte[] readFully(InputStream in) throws IOException {
+        // Shamelessly borrowed from libcore.io.Streams
+        try {
+            return readFullyNoClose(in);
+        } finally {
+            in.close();
+        }
+    }
+
+    public static byte[] readFullyNoClose(InputStream in) throws IOException {
+        // Shamelessly borrowed from libcore.io.Streams
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int count;
+        while ((count = in.read(buffer)) != -1) {
+            bytes.write(buffer, 0, count);
+        }
+        return bytes.toByteArray();
+    }
 }
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
index 2e4a4fe..184d7dd 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
@@ -17,6 +17,8 @@
 <configuration description="Config for CTS Admin Package Installer test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Device Owner-specific tests are not applicable to instant apps. -->
+    <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/cts/packageinstaller/" />
diff --git a/tests/tests/packageinstaller/emptytestapp/Android.mk b/tests/tests/packageinstaller/emptytestapp/Android.mk
index 7574d1f..4763e01 100644
--- a/tests/tests/packageinstaller/emptytestapp/Android.mk
+++ b/tests/tests/packageinstaller/emptytestapp/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 23
 
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
diff --git a/tests/tests/packageinstaller/externalsources/Android.mk b/tests/tests/packageinstaller/externalsources/Android.mk
deleted file mode 100755
index b7346b5..0000000
--- a/tests/tests/packageinstaller/externalsources/Android.mk
+++ /dev/null
@@ -1,37 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_PACKAGE_NAME := CtsExternalSourcesTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator \
-                               android-support-test \
-                               androidx.legacy_legacy-support-v4 \
-                               compatibility-device-util
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/packageinstaller/externalsources/AndroidManifest.xml b/tests/tests/packageinstaller/externalsources/AndroidManifest.xml
deleted file mode 100755
index 728ac72..0000000
--- a/tests/tests/packageinstaller/externalsources/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.packageinstaller.externalsources.cts"
-          android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
-
-    <application android:label="Cts External Sources Test">
-        <uses-library android:name="android.test.runner"/>
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.packageinstaller.externalsources.cts"
-                     android:label="External App Sources Tests"/>
-</manifest>
diff --git a/tests/tests/packageinstaller/externalsources/AndroidTest.xml b/tests/tests/packageinstaller/externalsources/AndroidTest.xml
deleted file mode 100644
index a39baab..0000000
--- a/tests/tests/packageinstaller/externalsources/AndroidTest.xml
+++ /dev/null
@@ -1,31 +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.
--->
-
-<configuration description="Config for CTS External Sources test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsExternalSourcesTestCases.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.packageinstaller.externalsources.cts" />
-        <option name="runtime-hint" value="10s" />
-    </test>
-
-</configuration>
diff --git a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesInstantAppsTest.java b/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesInstantAppsTest.java
deleted file mode 100644
index 7aa6a57..0000000
--- a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesInstantAppsTest.java
+++ /dev/null
@@ -1,82 +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.externalsources.cts;
-
-import static org.junit.Assert.assertFalse;
-
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.AppModeInstant;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.AppOpsUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@AppModeInstant
-public class ExternalSourcesInstantAppsTest {
-
-    private Context mContext;
-    private PackageManager mPm;
-    private String mPackageName;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mPm = mContext.getPackageManager();
-        mPackageName = mContext.getPackageName();
-    }
-
-    private void setAppOpsMode(int mode) throws IOException {
-        AppOpsUtils.setOpMode(mPackageName, "REQUEST_INSTALL_PACKAGES", mode);
-    }
-
-    @Test
-    public void blockedSourceTest() throws Exception {
-        setAppOpsMode(AppOpsManager.MODE_ERRORED);
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        assertFalse("Instant app " + mPackageName + " allowed to install packages", isTrusted);
-    }
-
-    @Test
-    public void allowedSourceTest() throws Exception {
-        setAppOpsMode(AppOpsManager.MODE_ALLOWED);
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        assertFalse("Instant app " + mPackageName + " allowed to install packages", isTrusted);
-    }
-
-    @Test
-    public void defaultSourceTest() throws Exception {
-        setAppOpsMode(AppOpsManager.MODE_DEFAULT);
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        assertFalse("Instant app " + mPackageName + " allowed to install packages", isTrusted);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        setAppOpsMode(AppOpsManager.MODE_DEFAULT);
-    }
-}
diff --git a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java b/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
deleted file mode 100644
index a29264b..0000000
--- a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
+++ /dev/null
@@ -1,98 +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.packageinstaller.externalsources.cts;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiDevice;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@AppModeFull
-public class ExternalSourcesTest {
-
-    private Context mContext;
-    private PackageManager mPm;
-    private String mPackageName;
-    private UiDevice mUiDevice;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mPm = mContext.getPackageManager();
-        mPackageName = mContext.getPackageName();
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-    }
-
-    private void setAppOpsMode(String mode) throws IOException {
-        final StringBuilder commandBuilder = new StringBuilder("appops set");
-        commandBuilder.append(" " + mPackageName);
-        commandBuilder.append(" REQUEST_INSTALL_PACKAGES");
-        commandBuilder.append(" " + mode);
-        mUiDevice.executeShellCommand(commandBuilder.toString());
-    }
-
-    @Test
-    public void blockedSourceTest() throws Exception {
-        setAppOpsMode("deny");
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        Assert.assertFalse("Package " + mPackageName
-                + " allowed to install packages after setting app op to errored", isTrusted);
-    }
-
-    @Test
-    public void allowedSourceTest() throws Exception {
-        setAppOpsMode("allow");
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        Assert.assertTrue("Package " + mPackageName
-                + " blocked from installing packages after setting app op to allowed", isTrusted);
-    }
-
-    @Test
-    public void defaultSourceTest() throws Exception {
-        setAppOpsMode("default");
-        final boolean isTrusted = mPm.canRequestPackageInstalls();
-        Assert.assertFalse("Package " + mPackageName
-                + " with default app ops state allowed to install packages", isTrusted);
-    }
-
-    @Test
-    public void testManageUnknownSourcesExists() {
-        Intent manageUnknownSources = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
-        ResolveInfo info = mPm.resolveActivity(manageUnknownSources, 0);
-        Assert.assertNotNull("No activity found for " + manageUnknownSources.getAction(), info);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        setAppOpsMode("default");
-    }
-}
diff --git a/tests/tests/packageinstaller/install/Android.bp b/tests/tests/packageinstaller/install/Android.bp
new file mode 100644
index 0000000..da0c2af
--- /dev/null
+++ b/tests/tests/packageinstaller/install/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+android_test {
+    name: "CtsPackageInstallTestCases",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "ub-uiautomator",
+        "android-support-test",
+        "compatibility-device-util",
+        "androidx.legacy_legacy-support-v4",
+        "platform-test-annotations",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/packageinstaller/install/AndroidManifest.xml b/tests/tests/packageinstaller/install/AndroidManifest.xml
new file mode 100644
index 0000000..0960672
--- /dev/null
+++ b/tests/tests/packageinstaller/install/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.packageinstaller.install.cts" >
+
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+    <application android:label="Cts Package Installer Tests">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".InstallConfirmDialogStarter" />
+
+        <provider android:authorities="android.packageinstaller.install.cts.fileprovider"
+                  android:name="androidx.core.content.FileProvider"
+                  android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.install.cts"
+                     android:label="Package Installer Tests"/>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/install/AndroidTest.xml b/tests/tests/packageinstaller/install/AndroidTest.xml
new file mode 100644
index 0000000..16f8228
--- /dev/null
+++ b/tests/tests/packageinstaller/install/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+<configuration description="Config for CTS Packageinstaller Session 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="instant_app" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/packageinstaller" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall android.packageinstaller.emptytestapp.cts" />
+        <option name="teardown-command" value="pm uninstall android.packageinstaller.emptytestapp.cts" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsEmptyTestApp.apk->/data/local/tmp/cts/packageinstaller/CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPackageInstallTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.install.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/packageinstaller/install/res/xml/file_paths.xml b/tests/tests/packageinstaller/install/res/xml/file_paths.xml
new file mode 100644
index 0000000..80b7e2d
--- /dev/null
+++ b/tests/tests/packageinstaller/install/res/xml/file_paths.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="apk" path="/" />
+</paths>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesInstantAppsTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesInstantAppsTest.kt
new file mode 100644
index 0000000..8fc30fc
--- /dev/null
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesInstantAppsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
+import android.platform.test.annotations.AppModeInstant
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.AppOpsUtils
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@AppModeInstant
+class ExternalSourcesInstantAppsTest {
+    private val pm = InstrumentationRegistry.getTargetContext().packageManager
+    private val packageName = InstrumentationRegistry.getTargetContext().packageName
+
+    private fun setAppOpsMode(mode: Int) {
+        AppOpsUtils.setOpMode(packageName, "REQUEST_INSTALL_PACKAGES", mode)
+    }
+
+    @Test
+    fun blockedSourceTest() {
+        setAppOpsMode(MODE_ERRORED)
+        assertFalse("Instant app $packageName allowed to install packages",
+                pm.canRequestPackageInstalls())
+    }
+
+    @Test
+    fun allowedSourceTest() {
+        setAppOpsMode(MODE_ALLOWED)
+        assertFalse("Instant app $packageName allowed to install packages",
+                pm.canRequestPackageInstalls())
+    }
+
+    @Test
+    fun defaultSourceTest() {
+        setAppOpsMode(MODE_DEFAULT)
+        assertFalse("Instant app $packageName allowed to install packages",
+                pm.canRequestPackageInstalls())
+    }
+
+    @After
+    fun resetAppOpsMode() {
+        setAppOpsMode(MODE_DEFAULT)
+    }
+}
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesTest.kt
new file mode 100644
index 0000000..6513768
--- /dev/null
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExternalSourcesTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
+import android.content.Intent
+import android.platform.test.annotations.AppModeFull
+import android.provider.Settings
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.MediumTest
+import android.support.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 androidx.core.content.FileProvider
+import com.android.compatibility.common.util.AppOpsUtils
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+private const val CONTENT_AUTHORITY = "android.packageinstaller.install.cts.fileprovider"
+private const val INSTALL_CONFIRM_TEXT_ID = "install_confirm_question"
+private const val ALERT_DIALOG_TITLE_ID = "android:id/alertTitle"
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@AppModeFull
+class ExternalSourcesTest : PackageInstallerTestBase() {
+    private val context = InstrumentationRegistry.getTargetContext()
+    private val pm = context.packageManager
+    private val packageName = context.packageName
+    private val apkFile = File(context.filesDir, TEST_APK_NAME)
+    private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @Before
+    fun copyTestApk() {
+        File(TEST_APK_EXTERNAL_LOCATION, TEST_APK_NAME).copyTo(target = apkFile, overwrite = true)
+    }
+
+    private fun setAppOpsMode(mode: Int) {
+        AppOpsUtils.setOpMode(packageName, APP_OP_STR, mode)
+    }
+
+    private fun startInstallationViaIntent() {
+        val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
+        intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
+        intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
+        context.startActivity(intent)
+    }
+
+    private fun assertUiObject(errorMessage: String, selector: BySelector) {
+        assertNotNull(errorMessage, uiDevice.wait(Until.findObject(selector), TIMEOUT))
+    }
+
+    private fun assertInstallAllowed(errorMessage: String) {
+        assertUiObject(errorMessage, By.res(PACKAGE_INSTALLER_PACKAGE_NAME,
+                INSTALL_CONFIRM_TEXT_ID))
+        uiDevice.pressBack()
+    }
+
+    private fun assertInstallBlocked(errorMessage: String) {
+        assertUiObject(errorMessage, By.res(ALERT_DIALOG_TITLE_ID))
+        uiDevice.pressBack()
+    }
+
+    private fun blockedSourceTest(startInstallation: () -> Unit) {
+        setAppOpsMode(MODE_ERRORED)
+        assertFalse("Package $packageName allowed to install packages after setting app op to " +
+                "errored", pm.canRequestPackageInstalls())
+
+        startInstallation()
+        assertInstallBlocked("Install blocking dialog not shown when app op set to errored")
+
+        assertTrue("Operation not logged", AppOpsUtils.rejectedOperationLogged(packageName,
+                APP_OP_STR))
+    }
+
+    @Test
+    fun blockedSourceTestViaIntent() {
+        blockedSourceTest { startInstallationViaIntent() }
+    }
+
+    @Test
+    fun blockedSourceTestViaSession() {
+        blockedSourceTest { startInstallationViaSession() }
+    }
+
+    private fun allowedSourceTest(startInstallation: () -> Unit) {
+        setAppOpsMode(MODE_ALLOWED)
+        assertTrue("Package $packageName blocked from installing packages after setting app op " +
+                "to allowed", pm.canRequestPackageInstalls())
+
+        startInstallation()
+        assertInstallAllowed("Install confirmation not shown when app op set to allowed")
+
+        assertTrue("Operation not logged", AppOpsUtils.allowedOperationLogged(packageName,
+                APP_OP_STR))
+    }
+
+    @Test
+    fun allowedSourceTestViaIntent() {
+        allowedSourceTest { startInstallationViaIntent() }
+    }
+
+    @Test
+    fun allowedSourceTestViaSession() {
+        allowedSourceTest { startInstallationViaSession() }
+    }
+
+    private fun defaultSourceTest(startInstallation: () -> Unit) {
+        assertFalse("Package $packageName with default app ops state allowed to install packages",
+                pm.canRequestPackageInstalls())
+
+        startInstallation()
+        assertInstallBlocked("Install blocking dialog not shown when app op set to default")
+
+        assertTrue("Operation not logged", AppOpsUtils.rejectedOperationLogged(packageName,
+                APP_OP_STR))
+    }
+
+    @Test
+    fun defaultSourceTestViaIntent() {
+        defaultSourceTest { startInstallationViaIntent() }
+    }
+
+    @Test
+    fun defaultSourceTestViaSession() {
+        defaultSourceTest { startInstallationViaSession() }
+    }
+
+    @Test
+    fun blockedSourceTest() {
+        setAppOpsMode(MODE_ERRORED)
+        assertFalse("Package $packageName allowed to install packages after setting app op to "
+                + "errored", pm.canRequestPackageInstalls())
+    }
+
+    @Test
+    fun allowedSourceTest() {
+        setAppOpsMode(MODE_ALLOWED)
+        assertTrue("Package $packageName blocked from installing packages after setting app op to "
+                + "allowed", pm.canRequestPackageInstalls())
+    }
+
+    @Test
+    fun defaultSourceTest() {
+        setAppOpsMode(MODE_DEFAULT)
+        assertFalse("Package $packageName with default app ops state allowed to install packages",
+                pm.canRequestPackageInstalls())
+    }
+
+    @Test
+    fun testManageUnknownSourcesExists() {
+        val manageUnknownSources = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+        assertNotNull("No activity found for ${manageUnknownSources.action}",
+                pm.resolveActivity(manageUnknownSources, 0))
+    }
+
+    @After
+    fun resetAppOpsMode() {
+        setAppOpsMode(MODE_DEFAULT)
+    }
+}
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
new file mode 100644
index 0000000..b026943
--- /dev/null
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt
@@ -0,0 +1,36 @@
+/*
+ * 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/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
new file mode 100644
index 0000000..9942281
--- /dev/null
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.PendingIntent
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.EXTRA_INTENT
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.IntentFilter
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
+import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
+import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
+import android.content.pm.PackageManager
+import android.support.test.InstrumentationRegistry
+import android.support.test.rule.ActivityTestRule
+import android.support.test.uiautomator.UiDevice
+import com.android.compatibility.common.util.AppOpsUtils
+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.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
+const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
+const val TEST_APK_EXTERNAL_LOCATION = "/data/local/tmp/cts/packageinstaller"
+const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb"
+
+const val PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller"
+const val SYSTEM_PACKAGE_NAME = "android"
+
+const val TIMEOUT = 60000L
+const val TIMEOUT_EXPECTED = 2000L
+const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
+
+open class PackageInstallerTestBase {
+    @get:Rule
+    val installDialogStarter = ActivityTestRule(InstallConfirmDialogStarter::class.java)
+
+    private val context = InstrumentationRegistry.getTargetContext()
+    private val pm = context.packageManager
+    private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    /** If a status was received the value of the status, otherwise null */
+    private var installSessionResult = LinkedBlockingQueue<Int>()
+
+    private val receiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
+
+            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)
+            }
+
+            installSessionResult.offer(status)
+        }
+    }
+
+    @Before
+    fun wakeUpScreen() {
+        if (!uiDevice.isScreenOn) {
+            uiDevice.wakeUp()
+        }
+        uiDevice.executeShellCommand("wm dismiss-keyguard")
+    }
+
+    @Before
+    fun assertTestPackageNotInstalled() {
+        try {
+            context.packageManager.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
+            Assert.fail("Package should not be installed")
+        } catch (expected: PackageManager.NameNotFoundException) {
+        }
+    }
+
+    @Before
+    fun registerInstallResultReceiver() {
+        context.registerReceiver(receiver, IntentFilter(INSTALL_ACTION_CB))
+    }
+
+    @Before
+    fun waitForUIIdle() {
+        uiDevice.waitForIdle()
+    }
+
+    /**
+     * Wait for session's install result and return it
+     */
+    protected fun getInstallSessionResult(timeout: Long = TIMEOUT): Int? {
+        return installSessionResult.poll(timeout, TimeUnit.MILLISECONDS)
+    }
+
+    /**
+     * Start an installation via a session
+     */
+    protected fun startInstallationViaSession(): PackageInstaller.Session {
+        val pi = pm.packageInstaller
+
+        // Create session
+        val sessionId = pi.createSession(PackageInstaller.SessionParams(MODE_FULL_INSTALL))
+        val session = pi.openSession(sessionId)!!
+
+        // Write data to session
+        File(TEST_APK_EXTERNAL_LOCATION, TEST_APK_NAME).inputStream().use { fileOnDisk ->
+            session.openWrite(TEST_APK_NAME, 0, -1).use { sessionFile ->
+                fileOnDisk.copyTo(sessionFile)
+            }
+        }
+
+        // Commit session
+        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
+    }
+
+    @After
+    fun unregisterInstallResultReceiver() {
+        try {
+            context.unregisterReceiver(receiver)
+        } catch (ignored: IllegalArgumentException) {
+        }
+    }
+
+    @After
+    fun uninstallTestPackage() {
+        uiDevice.executeShellCommand("pm uninstall $TEST_APK_PACKAGE_NAME")
+    }
+
+    @After
+    fun resetAppOps() {
+        AppOpsUtils.reset(context.packageName)
+    }
+}
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
new file mode 100644
index 0000000..bb0d342
--- /dev/null
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.RESULT_CANCELED
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Intent
+import android.content.pm.ApplicationInfo.CATEGORY_MAPS
+import android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED
+import android.content.pm.PackageInstaller.STATUS_FAILURE_ABORTED
+import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
+import android.content.pm.PackageInstaller.STATUS_SUCCESS
+import android.content.pm.PackageManager
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.Until
+import com.android.compatibility.common.util.AppOpsUtils
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+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"
+
+@RunWith(AndroidJUnit4::class)
+class SessionTest : PackageInstallerTestBase() {
+    private val context = InstrumentationRegistry.getTargetContext()
+    private val pm = context.packageManager
+    private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    @Before
+    fun allowToInstallPackages() {
+        AppOpsUtils.setOpMode(context.packageName, APP_OP_STR, MODE_ALLOWED)
+    }
+
+    /**
+     * Wait for result of install dialog and return it
+     */
+    private fun getInstallDialogResult(timeout: Long = TIMEOUT): Int? {
+        return installDialogResults.poll(timeout, TimeUnit.MILLISECONDS)
+    }
+
+    private fun assertInstalled() {
+        // Throws exception if package is not installed.
+        pm.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
+    }
+
+    private fun assertNotInstalled() {
+        try {
+            pm.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
+            fail("Package should not be installed")
+        } catch (expected: PackageManager.NameNotFoundException) {
+        }
+    }
+
+    /**
+     * Click a button in the UI of the installer app
+     *
+     * @param resId The resource ID of the button to click
+     */
+    private fun clickInstallerUIButton(resId: String) {
+        uiDevice.wait(Until.findObject(By.res(SYSTEM_PACKAGE_NAME, resId)), TIMEOUT)
+                .click()
+    }
+
+    /**
+     * Assert that there are no more callbacks from the install session or install dialog
+     */
+    private fun assertNoMoreInstallResults() {
+        assertNull(getInstallSessionResult(0))
+        assertEquals(0, installDialogResults.size)
+    }
+
+    /**
+     * Check that we can install an app via a package-installer session
+     */
+    @Test
+    fun confirmInstallation() {
+        startInstallationViaSession()
+        clickInstallerUIButton(INSTALL_BUTTON_ID)
+
+        // Install should have succeeded
+        assertEquals(STATUS_SUCCESS, getInstallSessionResult())
+        assertInstalled()
+
+        // Even when the install succeeds the install confirm dialog returns 'canceled'
+        assertEquals(RESULT_CANCELED, getInstallDialogResult())
+
+        assertNoMoreInstallResults()
+
+        assertTrue(AppOpsUtils.allowedOperationLogged(context.packageName, APP_OP_STR))
+    }
+
+    /**
+     * Check that we can set an app category for an app we installed
+     */
+    @Test
+    fun setAppCategory() {
+        startInstallationViaSession()
+        clickInstallerUIButton(INSTALL_BUTTON_ID)
+
+        // Wait for installation to finish
+        getInstallSessionResult()
+
+        assertEquals(CATEGORY_UNDEFINED, pm.getApplicationInfo(TEST_APK_PACKAGE_NAME, 0).category)
+
+        // This app installed the app, hence we can set the category
+        pm.setApplicationCategoryHint(TEST_APK_PACKAGE_NAME, CATEGORY_MAPS)
+
+        assertEquals(CATEGORY_MAPS, pm.getApplicationInfo(TEST_APK_PACKAGE_NAME, 0).category)
+    }
+
+    /**
+     * Install an app via a package-installer session, but then cancel it when the package installer
+     * pops open.
+     */
+    @Test
+    fun cancelInstallation() {
+        startInstallationViaSession()
+        clickInstallerUIButton(CANCEL_BUTTON_ID)
+
+        // Install should have been aborted
+        assertEquals(STATUS_FAILURE_ABORTED, getInstallSessionResult())
+        assertEquals(RESULT_CANCELED, getInstallDialogResult())
+        assertNotInstalled()
+
+        assertNoMoreInstallResults()
+    }
+}
diff --git a/tests/tests/packageinstaller/nopermission/Android.bp b/tests/tests/packageinstaller/nopermission/Android.bp
new file mode 100644
index 0000000..0912e53
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission/Android.bp
@@ -0,0 +1,43 @@
+// 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: "CtsNoPermissionTestCasesBase",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "ub-uiautomator",
+        "android-support-test",
+        "compatibility-device-util",
+        "androidx.legacy_legacy-support-v4",
+        "platform-test-annotations",
+    ],
+}
+
+android_test {
+    name: "CtsNoPermissionTestCases",
+    sdk_version: "test_current",
+
+    static_libs: [
+        "CtsNoPermissionTestCasesBase",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/packageinstaller/nopermission/AndroidManifest.xml b/tests/tests/packageinstaller/nopermission/AndroidManifest.xml
new file mode 100755
index 0000000..b20cdb0e
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.packageinstaller.nopermission.cts" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <provider android:authorities="android.packageinstaller.nopermission.cts.fileprovider"
+                  android:name="androidx.core.content.FileProvider"
+                  android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.nopermission.cts"
+                     android:label="Install with no permission Tests"/>
+</manifest>
diff --git a/tests/tests/packageinstaller/nopermission/AndroidTest.xml b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
new file mode 100644
index 0000000..52f8f4e
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+
+<configuration description="Config for CTS External Sources negative test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsEmptyTestApp.apk->/data/local/tmp/cts/nopermission/CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsNoPermissionTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.nopermission.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+
+</configuration>
diff --git a/tests/tests/packageinstaller/nopermission/res/xml/file_paths.xml b/tests/tests/packageinstaller/nopermission/res/xml/file_paths.xml
new file mode 100644
index 0000000..67f7795
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission/res/xml/file_paths.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="apk" path="/" />
+</paths>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
new file mode 100644
index 0000000..1ddc07b
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.nopermission.cts
+
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
+import android.os.Build
+import android.platform.test.annotations.AppModeFull
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.Until
+import androidx.core.content.FileProvider
+import com.android.compatibility.common.util.AppOpsUtils
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import java.lang.IllegalArgumentException
+
+private const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
+private const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
+private const val TEST_APK_EXTERNAL_LOCATION = "/data/local/tmp/cts/nopermission"
+private const val CONTENT_AUTHORITY = "android.packageinstaller.nopermission.cts.fileprovider"
+private const val PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller"
+private const val INSTALL_CONFIRM_TEXT_ID = "install_confirm_question"
+private const val WM_DISMISS_KEYGUARD_COMMAND = "wm dismiss-keyguard"
+
+private const val ACTION = "NoPermissionTests.install_cb"
+
+private const val WAIT_FOR_UI_TIMEOUT = 5000L
+private const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@AppModeFull
+class NoPermissionTests {
+    private var context = InstrumentationRegistry.getTargetContext()
+    private var pm = context.packageManager
+    private var packageName = context.packageName
+    private var apkFile = File(context.filesDir, TEST_APK_NAME)
+    private var uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+    private val receiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
+
+            if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
+                val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
+                activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                context.startActivity(activityIntent)
+            }
+        }
+    }
+
+    @Before
+    fun wakeUpScreen() {
+        if (!uiDevice.isScreenOn) {
+            uiDevice.wakeUp()
+        }
+        uiDevice.executeShellCommand(WM_DISMISS_KEYGUARD_COMMAND)
+    }
+
+    @Before
+    fun copyTestApk() {
+        File(TEST_APK_EXTERNAL_LOCATION, TEST_APK_NAME).copyTo(target = apkFile, overwrite = true)
+    }
+
+    @Before
+    fun allowToInstallPackages() {
+        // To make sure no other blocking dialogs appear
+        AppOpsUtils.setOpMode(context.packageName, APP_OP_STR, MODE_ALLOWED)
+    }
+
+    @Before
+    fun registerInstallResultReceiver() {
+        context.registerReceiver(receiver, IntentFilter(ACTION))
+    }
+
+    @Before
+    fun waitForUIIdle() {
+        uiDevice.waitForIdle()
+    }
+
+    private fun launchPackageInstallerViaIntent() {
+        val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
+        intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
+        intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
+        context.startActivity(intent)
+    }
+
+    private fun launchPackageInstallerViaSession() {
+        val pi = pm.packageInstaller
+
+        // Create session
+        val sessionId = pi.createSession(PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL))
+        val session = pi.openSession(sessionId)!!
+
+        // Write data to session
+        File(TEST_APK_EXTERNAL_LOCATION, TEST_APK_NAME).inputStream().use { fileOnDisk ->
+            session.openWrite(TEST_APK_NAME, 0, -1).use { sessionFile ->
+                fileOnDisk.copyTo(sessionFile)
+            }
+        }
+
+        // Commit session
+        val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION),
+                PendingIntent.FLAG_UPDATE_CURRENT)
+        session.commit(pendingIntent.intentSender)
+    }
+
+    private fun assertInstallSucceeded(errorMessage: String) {
+        val selector = By.res(PACKAGE_INSTALLER_PACKAGE_NAME, INSTALL_CONFIRM_TEXT_ID)
+        assertTrue(errorMessage, uiDevice.wait(Until.hasObject(selector), WAIT_FOR_UI_TIMEOUT))
+        uiDevice.pressBack()
+    }
+
+    private fun assertInstallFailed(errorMessage: String) {
+        val selector = By.res(PACKAGE_INSTALLER_PACKAGE_NAME, INSTALL_CONFIRM_TEXT_ID)
+        assertFalse(errorMessage, uiDevice.wait(Until.hasObject(selector), WAIT_FOR_UI_TIMEOUT))
+        uiDevice.pressBack()
+    }
+
+    @Test
+    fun noPermissionsTestIntent() {
+        launchPackageInstallerViaIntent()
+
+        if (pm.getPackageInfo(packageName, 0).applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.O) {
+            assertInstallFailed("Package Installer UI should not appear")
+        } else {
+            assertInstallSucceeded("Package Installer UI should appear")
+        }
+    }
+
+    @Test
+    fun noPermissionsTestSession() {
+        launchPackageInstallerViaSession()
+
+        if (pm.getPackageInfo(packageName, 0).applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.O) {
+            assertInstallFailed("Package Installer UI should not appear")
+        } else {
+            assertInstallSucceeded("Package Installer UI should appear")
+        }
+    }
+
+    @After
+    fun unregisterInstallResultReceiver() {
+        try {
+            context.unregisterReceiver(receiver)
+        } catch (ignored: IllegalArgumentException) {
+        }
+    }
+
+    @After
+    fun uninstallTestPackage() {
+        uiDevice.executeShellCommand("pm uninstall $TEST_APK_PACKAGE_NAME")
+    }
+
+    @After
+    fun resetAppOps() {
+        AppOpsUtils.reset(packageName)
+    }
+}
diff --git a/tests/tests/packageinstaller/nopermission25/Android.bp b/tests/tests/packageinstaller/nopermission25/Android.bp
new file mode 100644
index 0000000..bf397e0
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission25/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test {
+    name: "CtsNoPermissionTestCases25",
+    sdk_version: "test_current",
+
+    static_libs: [
+        "CtsNoPermissionTestCasesBase",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidManifest.xml b/tests/tests/packageinstaller/nopermission25/AndroidManifest.xml
new file mode 100755
index 0000000..e545945
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission25/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.packageinstaller.nopermission25.cts" >
+
+    <uses-sdk android:targetSdkVersion="25"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <provider android:authorities="android.packageinstaller.nopermission.cts.fileprovider"
+                  android:name="androidx.core.content.FileProvider"
+                  android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.nopermission25.cts"
+                     android:label="Install with no permission Tests (API 25)"/>
+</manifest>
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
new file mode 100644
index 0000000..cb01a1c
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<configuration description="Config for CTS External Sources negative test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsEmptyTestApp.apk->/data/local/tmp/cts/nopermission/CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsNoPermissionTestCases25.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.nopermission25.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+
+</configuration>
diff --git a/tests/tests/packageinstaller/nopermission25/res/xml/file_paths.xml b/tests/tests/packageinstaller/nopermission25/res/xml/file_paths.xml
new file mode 100644
index 0000000..67f7795
--- /dev/null
+++ b/tests/tests/packageinstaller/nopermission25/res/xml/file_paths.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="apk" path="/" />
+</paths>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/tapjacking/Android.mk b/tests/tests/packageinstaller/tapjacking/Android.mk
new file mode 100644
index 0000000..a45811c
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/Android.mk
@@ -0,0 +1,40 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsPackageInstallerTapjackingTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ub-uiautomator \
+    android-support-test \
+    compatibility-device-util \
+    platform-test-annotations \
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as test artifact for cts
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/packageinstaller/tapjacking/AndroidManifest.xml b/tests/tests/packageinstaller/tapjacking/AndroidManifest.xml
new file mode 100644
index 0000000..adb6f36
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.packageinstaller.tapjacking.cts" >
+
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+    <application android:label="Cts Package Installer Tapjacking Tests">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name=".OverlayingActivity"
+            android:theme="@style/OverlayTheme"
+            android:label="Overlaying Activity"/>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.tapjacking.cts"
+                     android:label="Package Installer Tapjacking Tests"/>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/tapjacking/AndroidTest.xml b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
new file mode 100644
index 0000000..8a541b2
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<configuration description="Config for CTS Packageinstaller Tapjacking test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPackageInstallerTapjackingTestCases.apk" />
+        <option name="test-file-name" value="CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.tapjacking.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+
+</configuration>
diff --git a/tests/tests/packageinstaller/tapjacking/res/drawable/border.xml b/tests/tests/packageinstaller/tapjacking/res/drawable/border.xml
new file mode 100644
index 0000000..f103e25
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/res/drawable/border.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners
+        android:radius="4dp"/>
+    <stroke
+        android:width="4dp"
+        android:color="@android:color/black" />
+</shape>
diff --git a/tests/tests/packageinstaller/tapjacking/res/layout/overlay_activity.xml b/tests/tests/packageinstaller/tapjacking/res/layout/overlay_activity.xml
new file mode 100644
index 0000000..2f91bab
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/res/layout/overlay_activity.xml
@@ -0,0 +1,29 @@
+<?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"
+              android:background="@drawable/border"
+              android:padding="8dp" >
+
+    <TextView android:id="@+id/overlay_description"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textColor="@android:color/black"
+              android:text="@string/app_description" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/tapjacking/res/values/strings.xml b/tests/tests/packageinstaller/tapjacking/res/values/strings.xml
new file mode 100644
index 0000000..80baa18
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources>
+    <string name="app_description">This activity attempts to tapjack the activity below.\nAny security sensitive controls below should not respond to taps as long as this activity is visible.</string>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/tapjacking/res/values/styles.xml b/tests/tests/packageinstaller/tapjacking/res/values/styles.xml
new file mode 100644
index 0000000..d17ada1
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<resources>
+    <style name="OverlayTheme" parent="android:Theme.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/OverlayingActivity.java b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/OverlayingActivity.java
new file mode 100644
index 0000000..db12fdb
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/OverlayingActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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.tapjacking.cts;
+
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class OverlayingActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.overlay_activity);
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        params.flags = FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCH_MODAL | FLAG_NOT_TOUCHABLE
+                | FLAG_KEEP_SCREEN_ON;
+    }
+}
diff --git a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
new file mode 100644
index 0000000..ddccb96
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.tapjacking.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.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.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@AppModeFull
+public class TapjackingTest {
+
+    private static final String LOG_TAG = TapjackingTest.class.getSimpleName();
+    private static final String SYSTEM_PACKAGE_NAME = "android";
+    private static final String INSTALL_BUTTON_ID = "button1";
+    private static final String OVERLAY_ACTIVITY_TEXT_VIEW_ID = "overlay_description";
+    private static final String WM_DISMISS_KEYGUARD_COMMAND = "wm dismiss-keyguard";
+    private static final String TEST_APP_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts";
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 5000;
+
+    private Context mContext;
+    private String mPackageName;
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mPackageName = mContext.getPackageName();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        if (!mUiDevice.isScreenOn()) {
+            mUiDevice.wakeUp();
+        }
+        mUiDevice.executeShellCommand(WM_DISMISS_KEYGUARD_COMMAND);
+        setUnknownSourcesEnabled(true);
+    }
+
+    private void setUnknownSourcesEnabled(boolean enabled) throws IOException {
+        setAppOpsMode(enabled ? "allow" : "default");
+    }
+
+    private void setAppOpsMode(String mode) throws IOException {
+        final StringBuilder commandBuilder = new StringBuilder("appops set");
+        commandBuilder.append(" " + mPackageName);
+        commandBuilder.append(" REQUEST_INSTALL_PACKAGES");
+        commandBuilder.append(" " + mode);
+        mUiDevice.executeShellCommand(commandBuilder.toString());
+    }
+
+    private void launchPackageInstaller() {
+        Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
+        intent.setData(Uri.parse("package:" + TEST_APP_PACKAGE_NAME));
+        intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    private void launchOverlayingActivity() {
+        Intent intent = new Intent(mContext, OverlayingActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    private UiObject2 waitForView(String packageName, String id) {
+        final BySelector selector = By.res(packageName, id);
+        return mUiDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+
+    @Test
+    public void testTapsDroppedWhenObscured() throws Exception {
+        Log.i(LOG_TAG, "launchPackageInstaller");
+        launchPackageInstaller();
+        UiObject2 installButton = waitForView(SYSTEM_PACKAGE_NAME, INSTALL_BUTTON_ID);
+        assertNotNull("Install button not shown", installButton);
+        Log.i(LOG_TAG, "launchOverlayingActivity");
+        launchOverlayingActivity();
+        assertNotNull("Overlaying activity not started",
+                waitForView(mPackageName, OVERLAY_ACTIVITY_TEXT_VIEW_ID));
+        installButton = waitForView(SYSTEM_PACKAGE_NAME, INSTALL_BUTTON_ID);
+        assertNotNull("Cannot find install button below overlay activity", installButton);
+        Log.i(LOG_TAG, "installButton.click");
+        installButton.click();
+        assertFalse("Tap on install button succeeded", mUiDevice.wait(
+                Until.gone(By.res(SYSTEM_PACKAGE_NAME, INSTALL_BUTTON_ID)),
+                WAIT_FOR_UI_TIMEOUT));
+        mUiDevice.pressBack();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mUiDevice.pressHome();
+        setUnknownSourcesEnabled(false);
+    }
+}
diff --git a/tests/tests/packageinstaller/uninstall/Android.mk b/tests/tests/packageinstaller/uninstall/Android.mk
new file mode 100644
index 0000000..1646d62
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PACKAGE_NAME := CtsPackageUninstallTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator \
+    android-support-test \
+    compatibility-device-util \
+    platform-test-annotations \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/packageinstaller/uninstall/AndroidManifest.xml b/tests/tests/packageinstaller/uninstall/AndroidManifest.xml
new file mode 100644
index 0000000..490ddd4
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.packageinstaller.uninstall.cts" >
+
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+
+    <application android:label="Cts Package Uninstaller Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.packageinstaller.uninstall.cts"
+                     android:label="Package Uninstaller Tests"/>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
new file mode 100644
index 0000000..424a981
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<configuration description="Config for CTS Packageinstaller Uninstall test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPackageUninstallTestCases.apk" />
+        <option name="test-file-name" value="CtsEmptyTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.packageinstaller.uninstall.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java
new file mode 100644
index 0000000..e2762bf
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.packageinstaller.uninstall.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class UninstallTest {
+    private static final String TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts";
+
+    private static final long TIMEOUT_MS = 30000;
+    private static final String APP_OP_STR = "REQUEST_DELETE_PACKAGES";
+
+    private Context mContext;
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setup() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        // Unblock UI
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        if (!mUiDevice.isScreenOn()) {
+            mUiDevice.wakeUp();
+        }
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        AppOpsUtils.reset(mContext.getPackageName());
+    }
+
+    @Test
+    public void testUninstall() throws Exception {
+        assertTrue(isInstalled());
+
+        Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
+        intent.setData(Uri.parse("package:" + TEST_APK_PACKAGE_NAME));
+        intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        // Confirm uninstall
+        assertNotNull("Uninstall prompt not shown",
+                mUiDevice.wait(Until.findObject(By.text("Do you want to uninstall this app?")),
+                        TIMEOUT_MS));
+        // The app's name should be shown to the user.
+        assertNotNull(mUiDevice.findObject(By.text("Empty Test App")));
+        mUiDevice.findObject(By.text("OK")).click();
+
+        for (int i = 0; i < 30; i++) {
+            // We can't detect the confirmation Toast with UiAutomator, so we'll poll
+            Thread.sleep(500);
+            if (!isInstalled()) {
+                break;
+            }
+        }
+        assertFalse("Package wasn't uninstalled.", isInstalled());
+        assertTrue(AppOpsUtils.allowedOperationLogged(mContext.getPackageName(), APP_OP_STR));
+    }
+
+    private boolean isInstalled() {
+        try {
+            mContext.getPackageManager().getPackageInfo(TEST_APK_PACKAGE_NAME, 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index 2075137..7ff8838 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -27,13 +27,14 @@
 # Include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := telephony-common
-
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
     guava \
     android-ex-camera2 \
-    compatibility-device-util
+    compatibility-device-util \
+    truth-prebuilt \
+    androidx.annotation_annotation \
+    platformprotosnano
 
 LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni libnativehelper_compat_libc++
 
@@ -41,9 +42,8 @@
 
 LOCAL_PACKAGE_NAME := CtsPermissionTestCases
 
-# uncomment when b/13249777 is fixed
-#LOCAL_SDK_VERSION := current
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
+
 LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
 LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index 118aeb5..928da95 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -32,6 +32,34 @@
                 android:permissionGroup="android.permission.cts.groupC"
                 android:description="@string/perm_c" />
 
+    <!-- for android.permission.cts.PermissionUsage -->
+    <permission android:name="android.permission.cts.D"
+                android:usageInfoRequired="true" />
+
+    <!-- for android.permission.cts.PermissionUsage and LocationAccessCheckTest -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
+      android:dataSentOffDevice="no"
+      android:dataSharedWithThirdParty="no"
+      android:dataUsedForMonetization="no"
+      android:dataRetentionTime="notRetained" />
+
+    <!-- for android.permission.cts.PermissionUsage -->
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"
+      android:dataSentOffDevice="yes"
+      android:dataSharedWithThirdParty="yes"
+      android:dataUsedForMonetization="yes"
+      android:dataRetentionTime="32" />
+
+    <!-- for android.permission.cts.PermissionUsage -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO"
+      android:dataSentOffDevice="userTriggered"
+      android:dataSharedWithThirdParty="userTriggered"
+      android:dataUsedForMonetization="userTriggered"
+      android:dataRetentionTime="unlimited" />
+
+    <!-- for android.permission.cts.PermissionUsage -->
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission-group android:description="@string/perm_group_b"
                       android:label="@string/perm_group_b"
@@ -52,6 +80,14 @@
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
+
+        <service android:name=".NotificationListener"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
     </application>
 
     <!--
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index c516a60..1911eb9 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -16,11 +16,13 @@
 <configuration description="Config for CTS Permission 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="instant_app" />
 
     <!-- Install main test suite apk -->
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPermissionTestCases.apk" />
+        <option name="test-file-name" value="CtsAppThatAccessesLocationOnCommand.apk" />
     </target_preparer>
 
     <!-- Create place to store apks -->
@@ -33,6 +35,16 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAppThatRequestsPermissionAandB.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsPermissionAandB.apk" />
         <option name="push" value="CtsAppThatRequestsPermissionAandC.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsPermissionAandC.apk" />
+        <option name="push" value="CtsAppThatRequestsContactsPermission16.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsPermission16.apk" />
+        <option name="push" value="CtsAppThatRequestsContactsPermission15.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsPermission15.apk" />
+        <option name="push" value="CtsAppThatRequestsContactsAndCallLogPermission16.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsAndCallLogPermission16.apk" />
+        <option name="push" value="CtsAppThatRequestsLocationPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission29.apk" />
+        <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="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" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
@@ -43,6 +55,5 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.permission.cts" />
         <option name="runtime-hint" value="13m" />
-        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.mk b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.mk
new file mode 100644
index 0000000..08b0c46
--- /dev/null
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.mk
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAppThatAccessesLocationOnCommand
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
new file mode 100644
index 0000000..e735330
--- /dev/null
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthataccesseslocation"
+    android:versionCode="1">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application android:label="CtsLocationAccess">
+        <service android:name=".AccessLocationOnCommand">
+            <intent-filter>
+                <action android:name="android.permission.cts.accesslocation" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
+
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
new file mode 100644
index 0000000..ef7aec07
--- /dev/null
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
@@ -0,0 +1,68 @@
+/*
+ * 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.permission.cts.appthataccesseslocation;
+
+import static android.location.Criteria.ACCURACY_FINE;
+
+import android.app.Service;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+
+public class AccessLocationOnCommand extends Service {
+    // Longer than the STATE_SETTLE_TIME in AppOpsManager
+    private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
+
+    private void getLocation() {
+        Criteria crit = new Criteria();
+        crit.setAccuracy(ACCURACY_FINE);
+
+        getSystemService(LocationManager.class).requestSingleUpdate(crit, new LocationListener() {
+            @Override
+            public void onLocationChanged(Location location) {
+            }
+
+            @Override
+            public void onStatusChanged(String provider, int status, Bundle extras) {
+            }
+
+            @Override
+            public void onProviderEnabled(String provider) {
+            }
+
+            @Override
+            public void onProviderDisabled(String provider) {
+            }
+        }, null);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new Binder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        return (new Handler()).postDelayed(this::getLocation, BACKGROUND_ACCESS_SETTLE_TIME);
+    }
+}
diff --git a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.mk b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.mk
new file mode 100644
index 0000000..fcc4b06
--- /dev/null
+++ b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := AppThatDoesNotHaveBgLocationAccess
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml
new file mode 100644
index 0000000..81de79b
--- /dev/null
+++ b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthataccesseslocation"
+    android:versionCode="2">
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+    <application android:label="CtsLocationAccess" />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.mk b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.mk
new file mode 100644
index 0000000..cbbb3ec
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsContactsAndCallLogPermission16
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml
new file mode 100644
index 0000000..08f0145
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="3">
+
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestContactsPermission15/Android.mk b/tests/tests/permission/AppThatRequestContactsPermission15/Android.mk
new file mode 100644
index 0000000..b77ab88
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsPermission15/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsContactsPermission15
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestContactsPermission15/AndroidManifest.xml b/tests/tests/permission/AppThatRequestContactsPermission15/AndroidManifest.xml
new file mode 100644
index 0000000..ab17c36
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsPermission15/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="2">
+
+    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestContactsPermission16/Android.mk b/tests/tests/permission/AppThatRequestContactsPermission16/Android.mk
new file mode 100644
index 0000000..de95f0f
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsPermission16/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsContactsPermission16
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestContactsPermission16/AndroidManifest.xml b/tests/tests/permission/AppThatRequestContactsPermission16/AndroidManifest.xml
new file mode 100644
index 0000000..703bb3a
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestContactsPermission16/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="1">
+
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.mk b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.mk
new file mode 100644
index 0000000..7929f54
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsLocationAndBackgroundPermission29
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml
new file mode 100644
index 0000000..f7620d6
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="3">
+
+    <!-- STOPSHIP: Set to apk level that shipped the location tristate -->
+    <!-- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> -->
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationPermission22/Android.mk b/tests/tests/permission/AppThatRequestLocationPermission22/Android.mk
new file mode 100644
index 0000000..4e88478
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission22/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsLocationPermission22
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestLocationPermission22/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationPermission22/AndroidManifest.xml
new file mode 100644
index 0000000..78251ba
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission22/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="1">
+
+    <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationPermission28/Android.mk b/tests/tests/permission/AppThatRequestLocationPermission28/Android.mk
new file mode 100644
index 0000000..d39f128
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission28/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsLocationPermission28
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestLocationPermission28/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationPermission28/AndroidManifest.xml
new file mode 100644
index 0000000..c8cf957
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission28/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+
+<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.ACCESS_COARSE_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29/Android.mk b/tests/tests/permission/AppThatRequestLocationPermission29/Android.mk
new file mode 100644
index 0000000..56f36fb
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission29/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsLocationPermission29
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationPermission29/AndroidManifest.xml
new file mode 100644
index 0000000..76e76ac
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission29/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="1">
+
+    <!-- STOPSHIP: Set to apk level that shipped the location tristate -->
+    <!-- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> -->
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.mk b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.mk
new file mode 100644
index 0000000..95168c9
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.mk
@@ -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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_PACKAGE_NAME := CtsAppThatRequestsLocationPermission29v4
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml
new file mode 100644
index 0000000..b382dcd
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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="4">
+
+    <!-- STOPSHIP: Set to apk level that shipped the location tristate -->
+    <!-- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> -->
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/jni/Android.mk b/tests/tests/permission/jni/Android.mk
index bfa8b67..ef1b903 100644
--- a/tests/tests/permission/jni/Android.mk
+++ b/tests/tests/permission/jni/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := libctspermission_jni
+LOCAL_SDK_VERSION := current
 
 # Don't include this package in any configuration by default.
 LOCAL_MODULE_TAGS := optional
@@ -28,7 +29,7 @@
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) 
 
 LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++ liblog
-LOCAL_CXX_STL := libc++_static
+LOCAL_NDK_STL_VARIANT := c++_static
 
 LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
 
diff --git a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
deleted file mode 100644
index 745d859..0000000
--- a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.permission.cts;
-
-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 static android.app.AppOpsManager.OPSTR_READ_CALENDAR;
-import static android.app.AppOpsManager.OPSTR_READ_SMS;
-import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
-import static com.android.compatibility.common.util.AppOpsUtils.allowedOperationLogged;
-import static com.android.compatibility.common.util.AppOpsUtils.rejectedOperationLogged;
-import static com.android.compatibility.common.util.AppOpsUtils.setOpMode;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.Manifest.permission;
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.Context;
-import android.os.Process;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.compatibility.common.util.AppOpsUtils;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class AppOpsTest extends InstrumentationTestCase {
-    // Notifying OnOpChangedListener callbacks is an async operation, so we define a timeout.
-    private static final int MODE_WATCHER_TIMEOUT_MS = 5000;
-
-    private AppOpsManager mAppOps;
-    private Context mContext;
-    private String mOpPackageName;
-    private int mMyUid;
-
-    // These permissions and opStrs must map to the same op codes.
-    private static Map<String, String> permissionToOpStr = new HashMap<>();
-
-    static {
-        permissionToOpStr.put(permission.ACCESS_COARSE_LOCATION,
-                AppOpsManager.OPSTR_COARSE_LOCATION);
-        permissionToOpStr.put(permission.ACCESS_FINE_LOCATION, AppOpsManager.OPSTR_FINE_LOCATION);
-        permissionToOpStr.put(permission.READ_CONTACTS, AppOpsManager.OPSTR_READ_CONTACTS);
-        permissionToOpStr.put(permission.WRITE_CONTACTS, AppOpsManager.OPSTR_WRITE_CONTACTS);
-        permissionToOpStr.put(permission.READ_CALL_LOG, AppOpsManager.OPSTR_READ_CALL_LOG);
-        permissionToOpStr.put(permission.WRITE_CALL_LOG, AppOpsManager.OPSTR_WRITE_CALL_LOG);
-        permissionToOpStr.put(permission.READ_CALENDAR, AppOpsManager.OPSTR_READ_CALENDAR);
-        permissionToOpStr.put(permission.WRITE_CALENDAR, AppOpsManager.OPSTR_WRITE_CALENDAR);
-        permissionToOpStr.put(permission.CALL_PHONE, AppOpsManager.OPSTR_CALL_PHONE);
-        permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
-        permissionToOpStr.put(permission.RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS);
-        permissionToOpStr.put(permission.RECEIVE_MMS, AppOpsManager.OPSTR_RECEIVE_MMS);
-        permissionToOpStr.put(permission.RECEIVE_WAP_PUSH, AppOpsManager.OPSTR_RECEIVE_WAP_PUSH);
-        permissionToOpStr.put(permission.SEND_SMS, AppOpsManager.OPSTR_SEND_SMS);
-        permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
-        permissionToOpStr.put(permission.WRITE_SETTINGS, AppOpsManager.OPSTR_WRITE_SETTINGS);
-        permissionToOpStr.put(permission.SYSTEM_ALERT_WINDOW,
-                AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
-        permissionToOpStr.put(permission.ACCESS_NOTIFICATIONS,
-                AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
-        permissionToOpStr.put(permission.CAMERA, AppOpsManager.OPSTR_CAMERA);
-        permissionToOpStr.put(permission.RECORD_AUDIO, AppOpsManager.OPSTR_RECORD_AUDIO);
-        permissionToOpStr.put(permission.READ_PHONE_STATE, AppOpsManager.OPSTR_READ_PHONE_STATE);
-        permissionToOpStr.put(permission.ADD_VOICEMAIL, AppOpsManager.OPSTR_ADD_VOICEMAIL);
-        permissionToOpStr.put(permission.USE_SIP, AppOpsManager.OPSTR_USE_SIP);
-        permissionToOpStr.put(permission.PROCESS_OUTGOING_CALLS,
-                AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS);
-        permissionToOpStr.put(permission.BODY_SENSORS, AppOpsManager.OPSTR_BODY_SENSORS);
-        permissionToOpStr.put(permission.READ_CELL_BROADCASTS,
-                AppOpsManager.OPSTR_READ_CELL_BROADCASTS);
-        permissionToOpStr.put(permission.READ_EXTERNAL_STORAGE,
-                AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE);
-        permissionToOpStr.put(permission.WRITE_EXTERNAL_STORAGE,
-                AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getContext();
-        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mOpPackageName = mContext.getOpPackageName();
-        mMyUid = Process.myUid();
-        assertNotNull(mAppOps);
-
-        // Reset app ops state for this test package to the system default.
-        AppOpsUtils.reset(mOpPackageName);
-    }
-
-    public void testNoteOpAndCheckOp() throws Exception {
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
-        assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_ALLOWED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_ALLOWED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_ALLOWED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED);
-        assertEquals(MODE_IGNORED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_IGNORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_IGNORED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_IGNORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT);
-        assertEquals(MODE_DEFAULT, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_DEFAULT, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_DEFAULT, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_DEFAULT, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
-        assertEquals(MODE_ERRORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_ERRORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        try {
-            mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-        try {
-            mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    public void testStartOpAndFinishOp() throws Exception {
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
-        assertEquals(MODE_ALLOWED, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        mAppOps.finishOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
-        assertEquals(MODE_ALLOWED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        mAppOps.finishOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED);
-        assertEquals(MODE_IGNORED, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_IGNORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT);
-        assertEquals(MODE_DEFAULT, mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        assertEquals(MODE_DEFAULT, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-
-        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
-        assertEquals(MODE_ERRORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
-        try {
-            mAppOps.startOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    public void testCheckPackagePassesCheck() throws Exception {
-        mAppOps.checkPackage(mMyUid, mOpPackageName);
-        mAppOps.checkPackage(Process.SYSTEM_UID, "android");
-    }
-
-    public void testCheckPackageDoesntPassCheck() throws Exception {
-        try {
-            // Package name doesn't match UID.
-            mAppOps.checkPackage(Process.SYSTEM_UID, mOpPackageName);
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-
-        try {
-            // Package name doesn't match UID.
-            mAppOps.checkPackage(mMyUid, "android");
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-
-        try {
-            // Package name missing
-            mAppOps.checkPackage(mMyUid, "");
-            fail("SecurityException expected");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    public void testWatchingMode() throws Exception {
-        OnOpChangedListener watcher = mock(OnOpChangedListener.class);
-        try {
-            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
-
-            mAppOps.startWatchingMode(OPSTR_READ_SMS, mOpPackageName, watcher);
-
-            // Make a change to the app op's mode.
-            reset(watcher);
-            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
-            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
-                    .onOpChanged(OPSTR_READ_SMS, mOpPackageName);
-
-            // Make another change to the app op's mode.
-            reset(watcher);
-            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
-            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
-                    .onOpChanged(OPSTR_READ_SMS, mOpPackageName);
-
-            // Set mode to the same value as before - expect no call to the listener.
-            reset(watcher);
-            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
-            verifyZeroInteractions(watcher);
-
-            mAppOps.stopWatchingMode(watcher);
-
-            // Make a change to the app op's mode. Since we already stopped watching the mode, the
-            // listener shouldn't be called.
-            reset(watcher);
-            setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED);
-            verifyZeroInteractions(watcher);
-        } finally {
-            // Clean up registered watcher.
-            mAppOps.stopWatchingMode(watcher);
-        }
-    }
-
-    @SmallTest
-    public void testOpCodesUnique() {
-        String[] opStrs = AppOpsManager.getOpStrs();
-        Set<Integer> opCodes = new HashSet<>();
-        for (String opStr : opStrs) {
-            opCodes.add(AppOpsManager.strOpToOp(opStr));
-        }
-        assertEquals("Not all app op codes are unique", opStrs.length, opCodes.size());
-    }
-
-    @SmallTest
-    public void testPermissionMapping() {
-        for (String permission : permissionToOpStr.keySet()) {
-            testPermissionMapping(permission, permissionToOpStr.get(permission));
-        }
-    }
-
-    private void testPermissionMapping(String permission, String opStr) {
-        // Do the public value => internal op code lookups.
-        String mappedOpStr = AppOpsManager.permissionToOp(permission);
-        assertEquals(mappedOpStr, opStr);
-        int mappedOpCode = AppOpsManager.permissionToOpCode(permission);
-        int mappedOpCode2 = AppOpsManager.strOpToOp(opStr);
-        assertEquals(mappedOpCode, mappedOpCode2);
-
-        // Do the internal op code => public value lookup (reverse lookup).
-        String permissionMappedBack = AppOpsManager.opToPermission(mappedOpCode);
-        assertEquals(permission, permissionMappedBack);
-    }
-
-    /**
-     * Test that the app can not change the app op mode for itself.
-     */
-    @SmallTest
-    public void testCantSetModeForSelf() {
-        try {
-            int writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS");
-            mAppOps.setMode(writeSmsOp, mMyUid, mOpPackageName, AppOpsManager.MODE_ALLOWED);
-            fail("Was able to set mode for self");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @SmallTest
-    public void testGetOpsForPackage_opsAreLogged() throws Exception {
-        // This test checks if operations get logged by the system. It needs to start with a clean
-        // slate, i.e. these ops can't have been logged previously for this test package. The reason
-        // is that there's no API for clearing the app op logs before a test run. However, the op
-        // logs are cleared when this test package is reinstalled between test runs. To make sure
-        // that other test methods in this class don't affect this test method, here we use
-        // operations that are not used by any other test cases.
-        String mustNotBeLogged = "Operation mustn't be logged before the test runs";
-        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
-        assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
-
-        setOpMode(mOpPackageName, OPSTR_RECORD_AUDIO, MODE_ALLOWED);
-        setOpMode(mOpPackageName, OPSTR_READ_CALENDAR, MODE_ERRORED);
-
-        // Note an op that's allowed.
-        mAppOps.noteOp(OPSTR_RECORD_AUDIO, mMyUid, mOpPackageName);
-        String mustBeLogged = "Operation must be logged";
-        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
-
-        // Note another op that's not allowed.
-        mAppOps.noteOpNoThrow(OPSTR_READ_CALENDAR, mMyUid, mOpPackageName);
-        assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO));
-        assertTrue(mustBeLogged, rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
-    }
-}
diff --git a/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java
new file mode 100644
index 0000000..8051ba1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionsTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.permission.cts.PermissionUtils.getAppOp;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AppOpsManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundPermissionsTest {
+    private static final String LOG_TAG = BackgroundPermissionsTest.class.getSimpleName();
+
+    /** The package name of all apps used in the test */
+    private static final String APP_PKG = "android.permission.cts.appthatrequestpermission";
+
+    private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+    private static final String APK_LOCATION_BACKGROUND_29 =
+            TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
+    private static final String APK_LOCATION_29v4 =
+            TMP_DIR + "CtsAppThatRequestsLocationPermission29v4.apk";
+
+    @Test
+    @AppModeFull(reason = "Instant apps cannot read properties of other packages")
+    public void verifybackgroundPermissionsProperties() throws Exception {
+        PackageInfo pkg = InstrumentationRegistry.getContext().getPackageManager().getPackageInfo(
+                "android", PackageManager.GET_PERMISSIONS);
+        ArrayMap<String, String> potentialBackgroundPermissionsToGroup = new ArrayMap<>();
+
+        int numPermissions = pkg.permissions.length;
+        for (int i = 0; i < numPermissions; i++) {
+            PermissionInfo permission = pkg.permissions[i];
+
+            // background permissions must be dangerous
+            if ((permission.getProtection() & PROTECTION_DANGEROUS) != 0) {
+                potentialBackgroundPermissionsToGroup.put(permission.name, permission.group);
+            }
+        }
+
+        for (int i = 0; i < numPermissions; i++) {
+            PermissionInfo permission = pkg.permissions[i];
+            String backgroundPermissionName = permission.backgroundPermission;
+
+            if (backgroundPermissionName != null) {
+                Log.i(LOG_TAG, permission.name + "->" + backgroundPermissionName);
+
+                // foreground permissions must be dangerous
+                assertNotEquals(0, permission.getProtection() & PROTECTION_DANGEROUS);
+
+                // All foreground permissions need an app op
+                assertNotNull(AppOpsManager.permissionToOp(permission.name));
+
+                // the background permission must exist
+                assertTrue(potentialBackgroundPermissionsToGroup
+                        .containsKey(backgroundPermissionName));
+            }
+        }
+    }
+
+    /**
+     * If a bg permission is lost during an upgrade, the app-op should downgrade to foreground
+     */
+    @Test
+    public void appOpGetsDowngradedWhenBgPermIsNotRequestedAnymore() throws Exception {
+        install(APK_LOCATION_BACKGROUND_29);
+        try {
+            grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+            install(APK_LOCATION_29v4);
+
+            assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named(
+                    "foreground app-op").isEqualTo(MODE_FOREGROUND);
+        } finally {
+            uninstallApp(APP_PKG);
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
index e40d7cb..4076ace 100644
--- a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
@@ -21,12 +21,16 @@
 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.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;
 
@@ -95,6 +99,29 @@
     }
 
     /**
+     * Check the absence of camera characteristics keys that require Permission:
+     * {@link android.Manifest.permission#CAMERA}.
+     */
+    public void testCameraCharacteristicsNeedingPermission() throws Exception {
+        for (String id : mCameraIds) {
+            CameraCharacteristics capabilities = mCameraManager.getCameraCharacteristics(id);
+            assertNotNull("Camera characteristics shouldn't be null", capabilities);
+            List<Key<?>> keysNeedingPermission = capabilities.getKeysNeedingPermission();
+            if (keysNeedingPermission == null) {
+                continue;
+            }
+            List<Key<?>> keys = capabilities.getKeys();
+            assertNotNull("Camera characteristics key list shouldn't be null", keys);
+            for (Key<?> key : keysNeedingPermission) {
+                assertEquals("Key " + key.getName() + " needing permission is part of the" +
+                        " available characteristics keys", -1, keys.indexOf(key));
+                assertNull("Key " + key.getName() + " needing permission must not present" +
+                        " in camera characteristics", capabilities.get(key));
+            }
+        }
+    }
+
+    /**
      * Add and remove availability listeners should work without permission.
      */
     @Presubmit
diff --git a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
deleted file mode 100644
index 006fb6d..0000000
--- a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
+++ /dev/null
@@ -1,50 +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.permission.cts;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-/**
- * Verify that various PowerManagement functionality requires Permission.
- */
-public class DevicePowerPermissionTest extends AndroidTestCase {
-    PowerManager mPowerManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-    }
-
-    /**
-     * Verify that going to sleep requires Permission.
-     * <p>Requires Permission:
-     *   {@link android.Manifest.permission#DEVICE_POWER}.
-     */
-    @LargeTest
-    public void testGoToSleep() {
-        try {
-            mPowerManager.goToSleep(0);
-            fail("Was able to call PowerManager.goToSleep without DEVICE_POWER Permission.");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-}
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index fb2f960..7661585 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -461,11 +461,10 @@
     }
 
     @MediumTest
-    @AppModeFull(reason = "Instant Apps cannot access proc_net labeled files")
     @Test
     public void testTcpDefaultRwndSane() throws Exception {
         File f = new File("/proc/sys/net/ipv4/tcp_default_init_rwnd");
-        assertTrue(f.canRead());
+        assertFalse(f.canRead());
         assertFalse(f.canWrite());
         assertFalse(f.canExecute());
 
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
new file mode 100644
index 0000000..50e695e
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -0,0 +1,554 @@
+/*
+ * 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.Notification.EXTRA_TITLE;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.location.Criteria.ACCURACY_FINE;
+import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS;
+import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS;
+import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+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.fail;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Privacy;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.job.nano.JobSchedulerServiceDumpProto;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+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;
+
+/**
+ * Tests the {@code LocationAccessCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocationAccessCheckTest {
+    private static final String LOG_TAG = LocationAccessCheckTest.class.getSimpleName();
+
+    private static final String TEST_APP_PKG = "android.permission.cts.appthataccesseslocation";
+    private static final String TEST_APP_LABEL = "CtsLocationAccess";
+    private static final String TEST_APP_SERVICE = TEST_APP_PKG + ".AccessLocationOnCommand";
+
+    private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
+    private static final long EXPECTED_TIMEOUT_MILLIS = 1000;
+    private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000;
+
+    // Same as in AccessLocationOnCommand
+    private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+            .getUiAutomation();
+
+    private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+            .getPermissionControllerPackageName();
+
+    /**
+     * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over
+     * again.
+     */
+    private static Boolean sCanAccessFineLocation = null;
+
+    /**
+     * Connected to {@value #TEST_APP_PKG} and make it access the location in the background
+     */
+    private static void accessLocation() {
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                sContext.unbindService(this);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                // ignore
+            }
+        };
+
+        // Connect and disconnect to service. After the service is disconnected it causes a
+        // access to the location
+        Intent testAppService = new Intent();
+        testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE));
+        sContext.bindService(testAppService, connection, BIND_AUTO_CREATE);
+    }
+
+    /**
+     * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable}
+     */
+    private interface ThrowingCallable<T> {
+        T call() throws Throwable;
+    }
+
+    /**
+     * A {@link Runnable} that can throw a {@link Throwable}
+     */
+    private interface ThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    /**
+     * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link
+     * Exception}.
+     *
+     * @param r       The {@link ThrowingRunnable} to run.
+     * @param timeout the maximum time to wait
+     */
+    public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable {
+        eventually(() -> {
+            r.run();
+            return 0;
+        }, timeout);
+    }
+
+    /**
+     * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link
+     * Exception}.
+     *
+     * @param r       The {@link ThrowingCallable} to run.
+     * @param timeout the maximum time to wait
+     *
+     * @return the return value from the callable
+     *
+     * @throws NullPointerException If the return value never becomes non-null
+     */
+    public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                T res = r.call();
+                if (res == null) {
+                    throw new NullPointerException("No result");
+                }
+
+                return res;
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < timeout) {
+                    Log.d(LOG_TAG, "Ignoring exception", e);
+
+                    Thread.sleep(100);
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    /**
+     * 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());
+        }
+    }
+
+    /**
+     * Clear all data of a package including permissions and files.
+     *
+     * @param pkg The name of the package to be cleared
+     */
+    private static void clearPackageData(@NonNull String pkg) {
+        runShellCommand("pm clear --user -2 " + pkg);
+    }
+
+    /**
+     * Force a run of the location check.
+     */
+    private static void runLocationCheck() {
+        runShellCommand("cmd jobscheduler run -f " + PERMISSION_CONTROLLER_PKG + " 0");
+    }
+
+    /**
+     * Get a notification thrown by the permission controller that is currently visible.
+     *
+     * @return The notification or {@code null} if there is none
+     */
+    private @Nullable StatusBarNotification getPermissionControllerNotification() throws Exception {
+        NotificationListenerService notificationService = NotificationListener.getInstance();
+
+        for (StatusBarNotification notification : notificationService.getActiveNotifications()) {
+            if (notification.getPackageName().equals(PERMISSION_CONTROLLER_PKG)) {
+                return notification;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get a location access notification that is currently visible.
+     *
+     * @param cancelNotification if {@code true} the notification is canceled inside this method
+     *
+     * @return The notification or {@code null} if there is none
+     */
+    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+        NotificationListenerService notificationService = NotificationListener.getInstance();
+
+        long start = System.currentTimeMillis();
+        while (true) {
+            runLocationCheck();
+
+            StatusBarNotification notification = getPermissionControllerNotification();
+            if (notification == null) {
+                // Sometimes getting a location takes some time, hence not getting a notification
+                // can be caused by not having gotten a location yet
+                if (System.currentTimeMillis() - start < LOCATION_ACCESS_TIMEOUT_MILLIS
+                        + BACKGROUND_ACCESS_SETTLE_TIME) {
+                    Thread.sleep(200);
+                    continue;
+                }
+
+                return null;
+            }
+
+            if (notification.getNotification().extras.getString(EXTRA_TITLE, "")
+                    .contains(TEST_APP_LABEL)) {
+                if (cancelNotification) {
+                    notificationService.cancelNotification(notification.getKey());
+
+                    // Wait for notification to get canceled
+                    eventually(() -> assertFalse(
+                            Arrays.asList(notificationService.getActiveNotifications()).contains(
+                                    notification)), UNEXPECTED_TIMEOUT_MILLIS);
+                }
+
+                return notification;
+            } else {
+                notificationService.cancelNotification(notification.getKey());
+
+                // Wait until new notification can be shown
+                Thread.sleep(200);
+            }
+        }
+    }
+
+    /**
+     * Grant a permission to the {@value #TEST_APP_PKG}.
+     *
+     * @param permission The permission to grant
+     */
+    private void grantPermissionToTestApp(@NonNull String permission) {
+        sUiAutomation.grantRuntimePermission(TEST_APP_PKG, permission);
+    }
+
+    /**
+     * Register {@link NotificationListener}.
+     */
+    @BeforeClass
+    public static void allowNotificationAccess() {
+        runShellCommand("cmd notification allow_listener " + (new ComponentName(sContext,
+                NotificationListener.class).flattenToString()));
+    }
+
+    /**
+     * Change settings so that permission controller can show location access notifications more
+     * often.
+     */
+    @BeforeClass
+    public static void reduceDelays() {
+        runWithShellPermissionIdentity(() -> {
+            ContentResolver cr = sContext.getContentResolver();
+
+            // New settings will be applied in when permission controller is reset
+            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
+            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
+        });
+    }
+
+    /**
+     * Reset the permission controllers state before each test
+     */
+    @Before
+    public void resetPermissionControllerBeforeEachTest() throws Throwable {
+        resetPermissionController();
+    }
+
+    /**
+     * Enable location access check
+     */
+    @Before
+    public void enableLocationAccessCheck() {
+        runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(Privacy.NAMESPACE,
+                Privacy.PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true", false));
+    }
+
+    /**
+     * Make sure fine location can be accessed at all.
+     */
+    @Before
+    public void assumeCanGetFineLocation() {
+        if (sCanAccessFineLocation == null) {
+            Criteria crit = new Criteria();
+            crit.setAccuracy(ACCURACY_FINE);
+
+            CountDownLatch locationCounter = new CountDownLatch(1);
+            sContext.getSystemService(LocationManager.class).requestSingleUpdate(crit,
+                    new LocationListener() {
+                        @Override
+                        public void onLocationChanged(Location location) {
+                            locationCounter.countDown();
+                        }
+
+                        @Override
+                        public void onStatusChanged(String provider, int status, Bundle extras) {
+                        }
+
+                        @Override
+                        public void onProviderEnabled(String provider) {
+                        }
+
+                        @Override
+                        public void onProviderDisabled(String provider) {
+                        }
+                    }, Looper.getMainLooper());
+
+
+            try {
+                sCanAccessFineLocation = locationCounter.await(LOCATION_ACCESS_TIMEOUT_MILLIS,
+                        MILLISECONDS);
+            } catch (InterruptedException ignored) {
+            }
+        }
+
+        assumeTrue(sCanAccessFineLocation);
+    }
+
+    /**
+     * Reset the permission controllers state.
+     */
+    private static void resetPermissionController() throws Throwable {
+        clearPackageData(PERMISSION_CONTROLLER_PKG);
+
+        // Wait until jobs are cleared
+        eventually(() -> {
+            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+            for (RegisteredJob job : dump.registeredJobs) {
+                assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG);
+            }
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Setup up permission controller again (simulate a reboot)
+        Intent permissionControllerSetupIntent = null;
+        for (ResolveInfo ri : sContext.getPackageManager().queryBroadcastReceivers(
+                new Intent(ACTION_BOOT_COMPLETED), 0)) {
+            String pkg = ri.activityInfo.packageName;
+
+            if (pkg.equals(PERMISSION_CONTROLLER_PKG)) {
+                permissionControllerSetupIntent = new Intent();
+                permissionControllerSetupIntent.setClassName(pkg, ri.activityInfo.name);
+            }
+        }
+
+        if (permissionControllerSetupIntent != null) {
+            sContext.sendBroadcast(permissionControllerSetupIntent);
+        }
+
+        // Wait until jobs are set up
+        eventually(() -> {
+            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+            for (RegisteredJob job : dump.registeredJobs) {
+                if (job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)) {
+                    return;
+                }
+            }
+
+            fail("Permission controller jobs not found");
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Unregister {@link NotificationListener}.
+     */
+    @AfterClass
+    public static void disallowNotificationAccess() {
+        runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext,
+                        NotificationListener.class)).flattenToString());
+    }
+
+    /**
+     * Reset settings so that permission controller runs normally.
+     */
+    @AfterClass
+    public static void resetDelays() throws Throwable {
+        runWithShellPermissionIdentity(() -> {
+            ContentResolver cr = sContext.getContentResolver();
+
+            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
+            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
+        });
+
+        resetPermissionController();
+    }
+
+    /**
+     * Reset location access check
+     */
+    @After
+    public void resetPrivacyConfig() {
+        runWithShellPermissionIdentity(
+                () -> DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, Privacy.NAMESPACE));
+    }
+
+    @Test
+    public void notificationIsShown() throws Throwable {
+        accessLocation();
+        assertNotNull(getNotification(true));
+    }
+
+    @Test
+    public void notificationIsShownOnlyOnce() throws Throwable {
+        accessLocation();
+        getNotification(true);
+
+        assertNull(getNotification(false));
+    }
+
+    @Test
+    public void notificationIsShownAgainAfterClear() throws Throwable {
+        accessLocation();
+        getNotification(true);
+
+        clearPackageData(TEST_APP_PKG);
+
+        // Wait until package is cleared and permission controller has cleared the state
+        Thread.sleep(2000);
+
+        // Clearing removed the permissions, hence grant them again
+        grantPermissionToTestApp(ACCESS_FINE_LOCATION);
+        grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION);
+
+        accessLocation();
+        assertNotNull(getNotification(false));
+    }
+
+    @Test
+    public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
+        accessLocation();
+        getNotification(true);
+
+        runShellCommand("pm uninstall " + TEST_APP_PKG);
+
+        // Wait until package permission controller has cleared the state
+        Thread.sleep(2000);
+
+        runShellCommand("pm install -g "
+                + "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk");
+
+        eventually(() -> {
+            accessLocation();
+            assertNotNull(getNotification(false));
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void removeNotificationOnUninstall() throws Throwable {
+        accessLocation();
+        getNotification(false);
+
+        runShellCommand("pm uninstall " + TEST_APP_PKG);
+
+        eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable {
+        accessLocation();
+        getNotification(true);
+
+        // Update to app to a version that does not request permission anymore
+        runShellCommand("pm install -r "
+                + "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk");
+
+        resetPermissionController();
+
+        try {
+            // We don't expect a notification, but try to trigger one anyway
+            eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
+        } catch (AssertionError expected) {
+            return;
+        }
+
+        fail("Location access notification was shown");
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
deleted file mode 100644
index 47055ec..0000000
--- a/tests/tests/permission/src/android/permission/cts/NoLocationPermissionTest.java
+++ /dev/null
@@ -1,478 +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.permission.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.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_COARSE_LOCATION.}
-     */
-    @UiThreadTest
-    public void testListenCellLocation() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        PhoneStateListener phoneStateListener = new PhoneStateListener();
-        try {
-            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
-            fail("TelephonyManager.listen(LISTEN_CELL_LOCATION) did not" +
-                    " throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            telephonyManager.getCellLocation();
-            fail("TelephonyManager.getCellLocation did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that get cell location requires permissions.
-     * <p>
-     * Requires Permission: {@link
-     * android.Manifest.permission#ACCESS_COARSE_LOCATION.}
-     */
-    @UiThreadTest
-    public void testListenCellLocation2() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        PhoneStateListener phoneStateListener = new PhoneStateListener();
-
-        try {
-            telephonyManager.getAllCellInfo();
-            fail("TelephonyManager.getAllCellInfo did not throw SecurityException as expected");
-        } 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 clearTestProviderLocation for network requires
-     * permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testClearTestProviderLocation() {
-        try {
-            mLocationManager.clearTestProviderLocation(TEST_PROVIDER_NAME);
-            fail("LocationManager.clearTestProviderLocation 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
-        }
-    }
-
-    /**
-     * Verify that checking setTestProviderStatus requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testSetTestProviderStatus() {
-        try {
-            mLocationManager.setTestProviderStatus(TEST_PROVIDER_NAME, 0, Bundle.EMPTY, 0);
-            fail("LocationManager.setTestProviderStatus did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-        }
-    }
-
-    /**
-     * Verify that checking clearTestProviderStatus requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testClearTestProviderStatus() {
-        try {
-            mLocationManager.clearTestProviderStatus(TEST_PROVIDER_NAME);
-            fail("LocationManager.setTestProviderStatus 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/permission/src/android/permission/cts/NotificationListener.java b/tests/tests/permission/src/android/permission/cts/NotificationListener.java
new file mode 100644
index 0000000..3a63524
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListener.java
@@ -0,0 +1,56 @@
+/*
+ * 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.permission.cts;
+
+import android.service.notification.NotificationListenerService;
+import android.util.Log;
+
+public class NotificationListener extends NotificationListenerService {
+    private static final String LOG_TAG = NotificationListener.class.getSimpleName();
+
+    private static final Object sLock = new Object();
+
+    private static NotificationListener sService;
+
+    @Override
+    public void onListenerConnected() {
+        Log.i(LOG_TAG, "connected");
+        synchronized (sLock) {
+            sService = this;
+            sLock.notifyAll();
+        }
+    }
+
+    public static NotificationListenerService getInstance() throws Exception {
+        synchronized (sLock) {
+            if (sService == null) {
+                sLock.wait(5000);
+            }
+
+            return sService;
+        }
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        Log.i(LOG_TAG, "disconnected");
+
+        synchronized (sLock) {
+            sService = null;
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionControllerTest.java b/tests/tests/permission/src/android/permission/cts/PermissionControllerTest.java
new file mode 100644
index 0000000..fe1b9a9
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionControllerTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.permission.PermissionControllerManager.REASON_MALWARE;
+import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.permission.PermissionControllerManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test {@link PermissionControllerManager}
+ */
+@RunWith(AndroidJUnit4.class)
+public class PermissionControllerTest {
+    private static final String APP = "android.permission.cts.appthataccesseslocation";
+
+    private static final UiAutomation sUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final PermissionControllerManager sController =
+            sContext.getSystemService(PermissionControllerManager.class);
+
+    @Before
+    public void adoptShellPermissions() {
+        sUiAutomation.adoptShellPermissionIdentity();
+    }
+
+    private @NonNull Map<String, List<String>> revoke(@NonNull Map<String, List<String>> request,
+            boolean doDryRun, int reason, @NonNull Executor executor)
+            throws Exception {
+        AtomicReference<Map<String, List<String>>> result = new AtomicReference<>();
+
+        sController.revokeRuntimePermissions(request, doDryRun, reason, executor,
+                new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+            @Override
+            public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> r) {
+                synchronized (result) {
+                    result.set(r);
+                    result.notifyAll();
+                }
+            }
+        });
+
+        synchronized (result) {
+            while (result.get() == null) {
+                result.wait();
+            }
+        }
+
+        return result.get();
+    }
+
+    private @NonNull Map<String, List<String>> revoke(@NonNull Map<String, List<String>> request,
+            boolean doDryRun) throws Exception {
+        return revoke(request, doDryRun, REASON_MALWARE, sContext.getMainExecutor());
+    }
+
+    private void setAppOp(@NonNull String pkg, @NonNull String perm, int mode) throws Exception {
+        sContext.getSystemService(AppOpsManager.class).setUidMode(permissionToOp(perm),
+                sContext.getPackageManager().getPackageUid(pkg, 0), mode);
+    }
+
+    private Map<String, List<String>> buildRequest(@NonNull String app,
+            @NonNull String permission) {
+        return Collections.singletonMap(app, Collections.singletonList(permission));
+    }
+
+    @Test
+    public void dryRunRevokeSinglePermission() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+        Map<String, List<String>> result = revoke(request, true);
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(APP)).isNotNull();
+        assertThat(result.get(APP)).containsExactly(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void revokeSinglePermission() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+        revoke(request, false);
+
+        try {
+            assertThat(sContext.getPackageManager().checkPermission(ACCESS_BACKGROUND_LOCATION,
+                    APP)).isEqualTo(PERMISSION_DENIED);
+        } finally {
+            // Restore default state
+            setAppOp(APP, ACCESS_FINE_LOCATION, MODE_ALLOWED);
+            sUiAutomation.grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+        }
+    }
+
+    @Test
+    public void doNotRevokeAlreadyRevokedPermission() throws Exception {
+        // Properly revoke the permission
+        sUiAutomation.revokeRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+        setAppOp(APP, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+
+        try {
+            Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+            Map<String, List<String>> result = revoke(request, false);
+
+            assertThat(result).isEmpty();
+        } finally {
+            // Restore default state
+            setAppOp(APP, ACCESS_FINE_LOCATION, MODE_ALLOWED);
+            sUiAutomation.grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+        }
+    }
+
+    @Test
+    public void dryRunRevokeForegroundPermission() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_FINE_LOCATION);
+
+        Map<String, List<String>> result = revoke(request, true);
+
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(APP)).isNotNull();
+        assertThat(result.get(APP)).containsExactly(ACCESS_FINE_LOCATION,
+                ACCESS_BACKGROUND_LOCATION);
+    }
+
+    @Test
+    public void revokeUnrequestedPermission() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, READ_CONTACTS);
+
+        Map<String, List<String>> result = revoke(request, false);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void revokeFromUnknownPackage() throws Exception {
+        Map<String, List<String>> request = buildRequest("invalid.app", READ_CONTACTS);
+
+        Map<String, List<String>> result = revoke(request, false);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void revokeFromUnknownPermission() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, "unknown.permission");
+
+        Map<String, List<String>> result = revoke(request, false);
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void revokePolicyViolationFromWrongPackage() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_FINE_LOCATION);
+
+        Map<String, List<String>> result = revoke(request, false,
+                REASON_INSTALLER_POLICY_VIOLATION, sContext.getMainExecutor());
+
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void useExecutorForCallback() throws Exception {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+        AtomicBoolean wasRunOnExecutor = new AtomicBoolean();
+        revoke(request, true, REASON_MALWARE, command -> {
+            wasRunOnExecutor.set(true);
+            command.run();
+        });
+
+        assertThat(wasRunOnExecutor.get()).isTrue();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPkg() throws Exception {
+        Map<String, List<String>> request = Collections.singletonMap(null,
+                Collections.singletonList(ACCESS_FINE_LOCATION));
+
+        revoke(request, true);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPermissions() throws Exception {
+        Map<String, List<String>> request = Collections.singletonMap(APP, null);
+
+        revoke(request, true);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPermission() throws Exception {
+        Map<String, List<String>> request = Collections.singletonMap(APP,
+                Collections.singletonList(null));
+
+        revoke(request, true);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullRequests() {
+        sController.revokeRuntimePermissions(null, false, REASON_MALWARE,
+                sContext.getMainExecutor(),
+                new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+            @Override
+            public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked) {
+            }
+        });
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullCallback() {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+        sController.revokeRuntimePermissions(request, false, REASON_MALWARE,
+                sContext.getMainExecutor(), null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullExecutor() {
+        Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+        sController.revokeRuntimePermissions(request, false, REASON_MALWARE, null,
+                new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+            @Override
+            public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked) {
+
+            }
+        });
+    }
+
+    @Test(expected = SecurityException.class)
+    public void tryToRevokeWithoutPermission() throws Exception {
+        sUiAutomation.dropShellPermissionIdentity();
+        try {
+            Map<String, List<String>> request = buildRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+            // This will fail as the test-app does not have the required permission
+            revoke(request, true);
+        } finally {
+            sUiAutomation.adoptShellPermissionIdentity();
+        }
+    }
+
+    @After
+    public void dropShellPermissions() {
+        sUiAutomation.dropShellPermissionIdentity();
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
index e2e8b93..3b1cd6a 100644
--- a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
+++ b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -20,6 +20,8 @@
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -37,6 +39,7 @@
 
 import com.android.compatibility.common.util.SystemUtil;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -97,7 +100,7 @@
 
     protected void clickAllowButton() throws Exception {
         mUiDevice.findObject(new UiSelector().resourceId(
-                "com.android.packageinstaller:id/permission_allow_button")).click();
+                "com.android.permissioncontroller:id/permission_allow_button")).click();
     }
 
     private void grantPermissionViaUi() throws Throwable {
@@ -141,6 +144,11 @@
         mContext.startActivity(startApp);
     }
 
+    @After
+    public void uninstallTestApp() {
+        runShellCommand("pm uninstall android.permission.cts.appthatrequestpermission");
+    }
+
     @SecurityTest
     @Test
     public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionUsageTest.java b/tests/tests/permission/src/android/permission/cts/PermissionUsageTest.java
new file mode 100644
index 0000000..39c66a6
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionUsageTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.permission.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UsesPermissionInfo;
+import android.test.InstrumentationTestCase;
+
+import org.junit.Before;
+
+public final class PermissionUsageTest extends InstrumentationTestCase {
+    private PackageManager mPm;
+    private Context mContext;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        mContext = getInstrumentation().getTargetContext();
+        mPm = mContext.getPackageManager();
+        assertNotNull(mPm);
+    }
+
+    private UsesPermissionInfo getUsesPermissionInfo(String permission) throws Exception {
+        PackageInfo info = mPm.getPackageInfo(mContext.getPackageName(),
+                PackageManager.GET_PERMISSIONS);
+        assertNotNull(info);
+        assertNotNull(info.usesPermissions);
+        for (UsesPermissionInfo upi : info.usesPermissions) {
+            if (permission.equals(upi.getPermission())) {
+                return upi;
+            }
+        }
+        return null;
+    }
+
+    public void testBasic() throws Exception {
+        UsesPermissionInfo upi = getUsesPermissionInfo(Manifest.permission.ACCESS_FINE_LOCATION);
+        assertEquals(UsesPermissionInfo.USAGE_NO, upi.getDataSentOffDevice());
+        assertEquals(UsesPermissionInfo.USAGE_NO, upi.getDataSharedWithThirdParty());
+        assertEquals(UsesPermissionInfo.USAGE_NO, upi.getDataUsedForMonetization());
+        assertEquals(UsesPermissionInfo.RETENTION_NOT_RETAINED, upi.getDataRetention());
+    }
+
+    public void testRetentionWeeks() throws Exception {
+        UsesPermissionInfo upi
+                = getUsesPermissionInfo(Manifest.permission.READ_PHONE_STATE);
+        assertEquals(UsesPermissionInfo.USAGE_YES, upi.getDataSentOffDevice());
+        assertEquals(UsesPermissionInfo.USAGE_YES, upi.getDataSharedWithThirdParty());
+        assertEquals(UsesPermissionInfo.USAGE_YES, upi.getDataUsedForMonetization());
+        assertEquals(UsesPermissionInfo.RETENTION_SPECIFIED, upi.getDataRetention());
+        assertEquals(32, upi.getDataRetentionWeeks());
+    }
+
+    public void testUserTriggered() throws Exception {
+        UsesPermissionInfo upi
+                = getUsesPermissionInfo(Manifest.permission.RECORD_AUDIO);
+        assertEquals(UsesPermissionInfo.USAGE_USER_TRIGGERED, upi.getDataSentOffDevice());
+        assertEquals(UsesPermissionInfo.USAGE_USER_TRIGGERED,
+                upi.getDataSharedWithThirdParty());
+        assertEquals(UsesPermissionInfo.USAGE_USER_TRIGGERED,
+                upi.getDataUsedForMonetization());
+        assertEquals(UsesPermissionInfo.RETENTION_UNLIMITED, upi.getDataRetention());
+    }
+
+    public void testUndefined() throws Exception {
+        UsesPermissionInfo upi
+                = getUsesPermissionInfo(Manifest.permission.READ_CALENDAR);
+        assertEquals(UsesPermissionInfo.USAGE_UNDEFINED, upi.getDataSentOffDevice());
+        assertEquals(UsesPermissionInfo.USAGE_UNDEFINED, upi.getDataSharedWithThirdParty());
+        assertEquals(UsesPermissionInfo.USAGE_UNDEFINED, upi.getDataUsedForMonetization());
+        assertEquals(UsesPermissionInfo.RETENTION_UNDEFINED, upi.getDataRetention());
+    }
+
+    public void testUsageInfoRequired() throws Exception {
+        PermissionInfo pi = mPm.getPermissionInfo("android.permission.cts.D", 0);
+        assertTrue(pi.usageInfoRequired);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionUtils.java b/tests/tests/permission/src/android/permission/cts/PermissionUtils.java
new file mode 100644
index 0000000..ad1743a
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionUtils.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+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 android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.annotation.NonNull;
+
+public class PermissionUtils {
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final UiAutomation sUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    /**
+     * Get the state of an app-op.
+     *
+     * @param packageName The package the app-op belongs to
+     * @param permission The permission the app-op belongs to
+     *
+     * @return The mode the op is on
+     */
+    static int getAppOp(@NonNull String packageName, @NonNull String permission) throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
+                        permissionToOp(permission),
+                        sContext.getPackageManager().getPackageUid(packageName, 0), packageName));
+    }
+
+    /**
+     * Install an APK.
+     *
+     * @param apkFile The apk to install
+     */
+    static void install(@NonNull String apkFile) {
+        runShellCommand("pm install -r --force-sdk " + apkFile);
+    }
+
+    /**
+     * Uninstall a package.
+     *
+     * @param packageName Name of package to be uninstalled
+     */
+    static void uninstallApp(@NonNull String packageName) {
+        runShellCommand("pm uninstall " + packageName);
+    }
+
+    /**
+     * Set a new state for an app-op
+     *
+     * @param packageName The package the app-op belongs to
+     * @param permission The permission the app-op belongs to
+     * @param mode The new mode
+     */
+    static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) {
+        runWithShellPermissionIdentity(() -> sContext.getSystemService(AppOpsManager.class)
+                .setUidMode(permissionToOp(permission),
+                        sContext.getPackageManager().getPackageUid(packageName, 0), mode));
+    }
+
+    /**
+     * Checks if a permission is granted for a package.
+     *
+     * <p>This correctly handles pre-M apps by checking the app-ops instead.
+     * <p>This also correctly handles the location background permission, but does not handle any
+     * other background permission
+     *
+     * @param packageName The package that might have the permission granted
+     * @param permission The permission that might be granted
+     *
+     * @return {@code true} iff the permission is granted
+     */
+    static boolean isGranted(@NonNull String packageName, @NonNull String permission)
+            throws Exception {
+        if (sContext.getPackageManager().checkPermission(permission, packageName)
+                == PERMISSION_DENIED) {
+            return false;
+        }
+
+        if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+            // The app-op for background location is encoded into the mode of the foreground
+            // location
+            return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED;
+        } else {
+            return getAppOp(packageName, permission) != MODE_IGNORED;
+        }
+    }
+
+    /**
+     * Grant a permission to an app.
+     *
+     * <p>This correctly handles pre-M apps by setting the app-ops.
+     * <p>This also correctly handles the location background permission, but does not handle any
+     * other background permission
+     *
+     * @param packageName The app that should have the permission granted
+     * @param permission The permission to grant
+     */
+    static void grantPermission(@NonNull String packageName, @NonNull String permission)
+            throws Exception {
+        sUiAutomation.grantRuntimePermission(packageName, permission);
+
+        if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+            // The app-op for background location is encoded into the mode of the foreground
+            // location
+            if (sContext.getPackageManager().checkPermission(ACCESS_COARSE_LOCATION, packageName)
+                    == PERMISSION_GRANTED) {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
+            } else {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+            }
+        } else if (permission.equals(ACCESS_COARSE_LOCATION)) {
+            // The app-op for location depends on the state of the bg location
+            if (sContext.getPackageManager().checkPermission(ACCESS_BACKGROUND_LOCATION,
+                    packageName) == PERMISSION_GRANTED) {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
+            } else {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+            }
+        } else {
+            setAppOp(packageName, permission, MODE_ALLOWED);
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
new file mode 100644
index 0000000..40f8f3b
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.permission.cts;
+
+import android.os.PowerManager;
+import android.test.AndroidTestCase;
+
+public class PowerManagerServicePermissionTest extends AndroidTestCase {
+
+    public void testSetBatterySaver_requiresPermissions() {
+        PowerManager manager = getContext().getSystemService(PowerManager.class);
+        boolean batterySaverOn = manager.isPowerSaveMode();
+
+        try {
+            manager.setPowerSaveMode(!batterySaverOn);
+            fail("Toggling battery saver requires POWER_SAVER or DEVICE_POWER permission");
+        } catch (SecurityException e) {
+            // Expected Exception
+        }
+    }
+
+    public void testGetPowerSaverMode_requiresPermissions() {
+        try {
+            PowerManager manager = getContext().getSystemService(PowerManager.class);
+            manager.getPowerSaveMode();
+            fail("Getting the current power saver mode requires the POWER_SAVER permission");
+        } catch (SecurityException e) {
+            // Expected Exception
+        }
+    }
+
+    public void testsetDynamicPowerSavings_requiresPermissions() {
+        try {
+            PowerManager manager = getContext().getSystemService(PowerManager.class);
+            manager.setDynamicPowerSavings(true, 0);
+            fail("Updating the dynamic power savings state requires the POWER_SAVER permission");
+        } catch (SecurityException e) {
+            // Expected Exception
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java b/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
index ec0de49..b203f14 100644
--- a/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
@@ -16,6 +16,8 @@
 
 package android.permission.cts;
 
+import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -23,12 +25,18 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import android.provider.CallLog;
 import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
 import android.provider.Settings;
+import android.provider.Telephony;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -37,18 +45,36 @@
  */
 @MediumTest
 public class ProviderPermissionTest extends AndroidTestCase {
+
+    private static final String TAG = ProviderPermissionTest.class.getSimpleName();
+
+    private static final List<Uri> CONTACT_URIS = new ArrayList<Uri>() {{
+        add(Contacts.People.CONTENT_URI); // Deprecated.
+        add(ContactsContract.Contacts.CONTENT_FILTER_URI);
+        add(ContactsContract.Contacts.CONTENT_GROUP_URI);
+        add(ContactsContract.Contacts.CONTENT_LOOKUP_URI);
+        add(ContactsContract.CommonDataKinds.Email.CONTENT_URI);
+        add(ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI);
+        add(ContactsContract.Directory.CONTENT_URI);
+        add(ContactsContract.Directory.ENTERPRISE_CONTENT_URI);
+        add(ContactsContract.Profile.CONTENT_URI);
+    }};
+
     /**
-     * Verify that read and write to contact requires permissions.
+     * Verify that reading contacts requires permissions.
      * <p>Tests Permission:
      *   {@link android.Manifest.permission#READ_CONTACTS}
      */
     public void testReadContacts() {
-        assertReadingContentUriRequiresPermission(Contacts.People.CONTENT_URI,
-                android.Manifest.permission.READ_CONTACTS);
+        for (Uri uri : CONTACT_URIS) {
+            Log.d(TAG, "Checking contacts URI " + uri);
+            assertReadingContentUriRequiresPermission(uri,
+                    android.Manifest.permission.READ_CONTACTS);
+        }
     }
 
     /**
-     * Verify that write to contact requires permissions.
+     * Verify that writing contacts requires permissions.
      * <p>Tests Permission:
      *   {@link android.Manifest.permission#WRITE_CONTACTS}
      */
@@ -57,22 +83,6 @@
                 android.Manifest.permission.WRITE_CONTACTS);
     }
 
-    public void assertWritingContentUriRequiresPermission(Uri uri, String permission,
-            boolean allowIAE) {
-        try {
-            getContext().getContentResolver().insert(uri, new ContentValues());
-            fail("expected SecurityException requiring " + permission);
-        } catch (IllegalArgumentException e) {
-            if (!allowIAE) {
-                fail("expected SecurityException requiring " + permission + " got " + e);
-            }
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-            assertTrue("error message should contain " + permission + ".",
-                    expected.getMessage().contains(permission));
-        }
-    }
-
     /**
      * Verify that reading call logs requires permissions.
      * <p>Tests Permission:
@@ -89,10 +99,66 @@
      * <p>Tests Permission:
      *   {@link android.Manifest.permission#WRITE_CALL_LOG}
      */
+    @AppModeFull
     public void testWriteCallLog() {
         assertWritingContentUriRequiresPermission(CallLog.CONTENT_URI,
-                android.Manifest.permission.WRITE_CALL_LOG,
-                getContext().getPackageManager().isInstantApp());
+                android.Manifest.permission.WRITE_CALL_LOG);
+    }
+
+    /**
+     * Verify that reading from call-log (a content provider that is not accessible to instant apps)
+     * returns null
+     */
+    @AppModeInstant
+    public void testReadCallLogInstant() {
+        assertNull(getContext().getContentResolver().query(CallLog.CONTENT_URI, null, null, null,
+                null));
+    }
+
+    /**
+     * Verify that writing to call-log (a content provider that is not accessible to instant apps)
+     * yields an IAE.
+     */
+    @AppModeInstant
+    public void testWriteCallLogInstant() {
+        try {
+            getContext().getContentResolver().insert(CallLog.CONTENT_URI, new ContentValues());
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verify that reading already received SMS messages requires permissions.
+     * <p>Tests Permission:
+     *   {@link android.Manifest.permission#READ_SMS}
+     *
+     * <p>Note: The WRITE_SMS permission has been removed.
+     */
+    @AppModeFull
+    public void testReadSms() {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        assertReadingContentUriRequiresPermission(Telephony.Sms.CONTENT_URI,
+                android.Manifest.permission.READ_SMS);
+    }
+
+    /**
+     * Verify that reading from 'sms' (a content provider that is not accessible to instant apps)
+     * returns null
+     */
+    @AppModeInstant
+    public void testReadSmsInstant() {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        assertNull(getContext().getContentResolver().query(Telephony.Sms.CONTENT_URI, null, null,
+                null, null));
     }
 
     /**
@@ -150,4 +216,36 @@
             }
         }
     }
+
+    /**
+     * The {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission is
+     * a very powerful permission that grants raw storage access to all devices,
+     * and as such it's only appropriate to be granted to the media stack. Any
+     * apps with a user-visible component (such as Camera or Gallery apps) must
+     * go through public {@link MediaStore} APIs, to ensure that users have
+     * meaningful permission controls.
+     * <p>
+     * For example, if the end user revokes the "Music" permission from an app,
+     * but that app still has raw access to music via
+     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}, that would be a
+     * privacy incident.
+     */
+    public void testMediaStackPermissions() throws Exception {
+        // The only apps holding this permission should be the internal media
+        // stack, and the best way to identify them is having no launchable UI.
+        final PackageManager pm = getContext().getPackageManager();
+        final List<PackageInfo> pkgs = pm
+                .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        for (PackageInfo pkg : pkgs) {
+            final boolean isSystem = pkg.applicationInfo.uid == android.os.Process.SYSTEM_UID;
+            final boolean hasFrontDoor = pm.getLaunchIntentForPackage(pkg.packageName) != null;
+            final boolean hasPermission = pm.checkPermission(WRITE_MEDIA_STORAGE,
+                    pkg.packageName) == PackageManager.PERMISSION_GRANTED;
+            if (!isSystem && hasFrontDoor && hasPermission) {
+                fail("Found " + pkg.packageName + " holding WRITE_MEDIA_STORAGE permission while "
+                        + "also having user-visible UI; this permission must only be held by "
+                        + "the core media stack, and must not be granted to user-launchable apps");
+            }
+        }
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/SmsManagerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SmsManagerPermissionTest.java
new file mode 100644
index 0000000..ab099ee
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SmsManagerPermissionTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.permission.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.SmsManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Test that sending SMS and MMS messages requires permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsManagerPermissionTest {
+
+    private static final String SOURCE_ADDRESS = "+15550000000";
+    private static final String DESTINATION_ADDRESS = "+15550000001";
+
+    private boolean mHasTelephony;
+    private SmsManager mSmsManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mHasTelephony = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        assumeTrue(mHasTelephony); // Don't run these tests if FEATURE_TELEPHONY is not available.
+
+        mSmsManager = SmsManager.getDefault();
+        assertNotNull(mSmsManager);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSendTextMessage() {
+        mSmsManager.sendTextMessage(
+                DESTINATION_ADDRESS, SOURCE_ADDRESS, "Message text", null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSendTextMessageWithoutPersisting() {
+        mSmsManager.sendTextMessageWithoutPersisting(
+                DESTINATION_ADDRESS, SOURCE_ADDRESS, "Message text", null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSendMultipartTextMessage() {
+        ArrayList<String> messageParts = new ArrayList<>();
+        messageParts.add("Message text");
+        mSmsManager.sendMultipartTextMessage(
+                DESTINATION_ADDRESS, SOURCE_ADDRESS, messageParts, null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSendDataMessage() {
+        mSmsManager.sendDataMessage(
+                DESTINATION_ADDRESS, SOURCE_ADDRESS, (short) 1, new byte[]{0, 0, 0}, null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSendMultimediaMessage() {
+        // Ideally we would provide an Uri to an existing resource, to make sure the
+        // SecurityException is not due to the invalid Uri.
+        Uri uri = Uri.parse("android.resource://android.permission.cts/some-image.png");
+        mSmsManager.sendMultimediaMessage(mContext, uri, "", null, null);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
new file mode 100644
index 0000000..8d39c58
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -0,0 +1,525 @@
+/*
+ * 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.app.AppOpsManager.permissionToOpCode;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.permission.cts.PermissionUtils.getAppOp;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.setAppOp;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+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.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests how split permissions behave.
+ *
+ * <ul>
+ *     <li>Default permission grant behavior</li>
+ *     <li>Changes to the grant state during upgrade of apps with split permissions</li>
+ *     <li>Special behavior of background location</li>
+ * </ul>
+ */
+@RunWith(AndroidJUnit4.class)
+public class SplitPermissionTest {
+    /** The package name of all apps used in the test */
+    private static final String APP_PKG = "android.permission.cts.appthatrequestpermission";
+
+    private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+    private static final String APK_CONTACTS_16 =
+            TMP_DIR + "CtsAppThatRequestsContactsPermission16.apk";
+    private static final String APK_CONTACTS_15 =
+            TMP_DIR + "CtsAppThatRequestsContactsPermission15.apk";
+    private static final String APK_CONTACTS_CALLLOG_16 =
+            TMP_DIR + "CtsAppThatRequestsContactsAndCallLogPermission16.apk";
+    private static final String APK_LOCATION_29 =
+            TMP_DIR + "CtsAppThatRequestsLocationPermission29.apk";
+    private static final String APK_LOCATION_28 =
+            TMP_DIR + "CtsAppThatRequestsLocationPermission28.apk";
+    private static final String APK_LOCATION_22 =
+            TMP_DIR + "CtsAppThatRequestsLocationPermission22.apk";
+    private static final String APK_LOCATION_BACKGROUND_29 =
+            TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final UiAutomation sUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private static final String LOG_TAG = SplitPermissionTest.class.getSimpleName();
+
+    /**
+     * Revoke a permission from an app.
+     *
+     * <p>This correctly handles pre-M apps by setting the app-ops.
+     * <p>This also correctly handles the location background permission, but does not handle any
+     * other background permission
+     *
+     * @param packageName The app that should have the permission revoked
+     * @param permission The permission to revoke
+     */
+    private void revokePermission(@NonNull String packageName, @NonNull String permission)
+            throws Exception {
+        sUiAutomation.revokeRuntimePermission(packageName, permission);
+
+        if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+            // The app-op for background location is encoded into the mode of the foreground
+            // location
+            if (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
+                setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+            }
+        } else {
+            setAppOp(packageName, permission, MODE_IGNORED);
+        }
+    }
+
+    /**
+     * Get all permissions that an app requests. This includes the split permissions.
+     *
+     * @param packageName The package that requests the permissions.
+     *
+     * @return The permissions requested by the app
+     */
+    private @NonNull List<String> getPermissions(@NonNull String packageName) throws Exception {
+        PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName,
+                GET_PERMISSIONS);
+
+        return Arrays.asList(appInfo.requestedPermissions);
+    }
+
+    /**
+     * Assert that {@link #APP_PKG} requests a certain permission.
+     *
+     * @param permName The permission that needs to be requested
+     */
+    private void assertRequestsPermission(@NonNull String permName) throws Exception {
+        assertThat(getPermissions(APP_PKG)).contains(permName);
+    }
+
+    /**
+     * Assert that {@link #APP_PKG} <u>does not</u> request a certain permission.
+     *
+     * @param permName The permission that needs to be not requested
+     */
+    private void assertNotRequestsPermission(@NonNull String permName) throws Exception {
+        assertThat(getPermissions(APP_PKG)).doesNotContain(permName);
+    }
+
+    /**
+     * Assert that a permission is granted to {@link #APP_PKG}.
+     *
+     * @param permName The permission that needs to be granted
+     */
+    private void assertPermissionGranted(@NonNull String permName) throws Exception {
+        assertThat(isGranted(APP_PKG, permName)).named(permName + " is granted").isTrue();
+    }
+
+    /**
+     * Assert that a permission is <u>not </u> granted to {@link #APP_PKG}.
+     *
+     * @param permName The permission that should not be granted
+     */
+    private void assertPermissionRevoked(@NonNull String permName) throws Exception {
+        assertThat(isGranted(APP_PKG, permName)).named(permName + " is granted").isFalse();
+    }
+
+    /**
+     * Install an APK.
+     *
+     * @param apkFile The apk to install
+     */
+    public void install(@NonNull String apkFile) {
+        PermissionUtils.install(apkFile);
+    }
+
+    @After
+    public void uninstallTestApp() {
+        uninstallApp(APP_PKG);
+    }
+
+    /**
+     * Apps with a targetSDK after the split should <u>not</u> have been added implicitly the new
+     * permission.
+     */
+    @Test
+    public void permissionsDoNotSplitWithHighTargetSDK() throws Exception {
+        install(APK_LOCATION_29);
+
+        assertRequestsPermission(ACCESS_COARSE_LOCATION);
+        assertNotRequestsPermission(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * Apps with a targetSDK after the split should <u>not</u> have been added implicitly the new
+     * permission.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void permissionsDoNotSplitWithHighTargetSDKPreM() throws Exception {
+        install(APK_CONTACTS_16);
+
+        assertRequestsPermission(READ_CONTACTS);
+        assertNotRequestsPermission(READ_CALL_LOG);
+    }
+
+    /**
+     * Apps with a targetSDK before the split should have been added implicitly the new permission.
+     */
+    @Test
+    public void permissionsSplitWithLowTargetSDK() throws Exception {
+        install(APK_LOCATION_28);
+
+        assertRequestsPermission(ACCESS_COARSE_LOCATION);
+        assertRequestsPermission(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * Apps with a targetSDK before the split should have been added implicitly the new permission.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void permissionsSplitWithLowTargetSDKPreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        assertRequestsPermission(READ_CONTACTS);
+        assertRequestsPermission(READ_CALL_LOG);
+    }
+
+    /**
+     * Permissions are revoked by default for post-M apps
+     */
+    @Test
+    public void nonInheritedStateHighTargetSDK() throws Exception {
+        install(APK_LOCATION_29);
+
+        assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+    }
+
+    /**
+     * Permissions are granted by default for pre-M apps
+     */
+    @Test
+    public void nonInheritedStateHighLowTargetSDKPreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        assertPermissionGranted(READ_CONTACTS);
+    }
+
+    /**
+     * Permissions are revoked by default for post-M apps. This also applies to permissions added
+     * implicitly due to splits.
+     */
+    @Test
+    public void nonInheritedStateLowTargetSDK() throws Exception {
+        install(APK_LOCATION_28);
+
+        assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * Permissions are granted by default for pre-M apps. This also applies to permissions added
+     * implicitly due to splits.
+     */
+    @Test
+    public void nonInheritedStateLowTargetSDKPreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        assertPermissionGranted(READ_CONTACTS);
+        assertPermissionGranted(READ_CALL_LOG);
+    }
+
+    /**
+     * The background location permission is never granted by default, not even for pre-M apps.
+     */
+    @Test
+    public void backgroundLocationPermissionDefaultGrantPreM() throws Exception {
+        install(APK_LOCATION_22);
+
+        assertPermissionGranted(ACCESS_COARSE_LOCATION);
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * If a permission was granted before the split happens, the new permission should inherit the
+     * granted state.
+     */
+    @Ignore("There is no post-M permission that has the default inheritence behavior")
+    @Test
+    public void inheritGrantedPermissionState() throws Exception {
+        install(APK_LOCATION_29);
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+        install(APK_LOCATION_28);
+
+        assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * Background location permissions do not inherit the state on split.
+     */
+    @Test
+    public void backgroundLocationDoesNotInheritGrantedPermissionState() throws Exception {
+        install(APK_LOCATION_29);
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+        install(APK_LOCATION_28);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * If a permission was granted before the split happens, the new permission should inherit the
+     * granted state.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void inheritGrantedPermissionStatePreM() throws Exception {
+        install(APK_CONTACTS_16);
+
+        install(APK_CONTACTS_15);
+
+        assertPermissionGranted(READ_CALL_LOG);
+    }
+
+    /**
+     * If a permission was revoked before the split happens, the new permission should inherit the
+     * revoked state.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void inheritRevokedPermissionState() throws Exception {
+        install(APK_LOCATION_29);
+
+        install(APK_LOCATION_28);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * If a permission was revoked before the split happens, the new permission should inherit the
+     * revoked state.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void inheritRevokedPermissionStatePreM() throws Exception {
+        install(APK_CONTACTS_16);
+        revokePermission(APP_PKG, READ_CONTACTS);
+
+        install(APK_CONTACTS_15);
+
+        assertPermissionRevoked(READ_CALL_LOG);
+    }
+
+    /**
+     * It should be possible to grant a permission implicitly added due to a split.
+     */
+    @Test
+    public void grantNewSplitPermissionState() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * It should be possible to grant a permission implicitly added due to a split.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void grantNewSplitPermissionStatePreM() throws Exception {
+        install(APK_CONTACTS_15);
+        revokePermission(APP_PKG, READ_CONTACTS);
+
+        grantPermission(APP_PKG, READ_CALL_LOG);
+
+        assertPermissionGranted(READ_CALL_LOG);
+    }
+
+    /**
+     * It should be possible to revoke a permission implicitly added due to a split.
+     */
+    @Test
+    public void revokeNewSplitPermissionState() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        revokePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * It should be possible to revoke a permission implicitly added due to a split.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void revokeNewSplitPermissionStatePreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        revokePermission(APP_PKG, READ_CALL_LOG);
+
+        assertPermissionRevoked(READ_CALL_LOG);
+    }
+
+    /**
+     * An implicit permission should get revoked when the app gets updated and now requests the
+     * permission.
+     */
+    @Test
+    public void newPermissionGetRevokedOnUpgrade() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        install(APK_LOCATION_BACKGROUND_29);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
+     * An implicit background permission should get revoked when the app gets updated and now
+     * requests the permission. Revoking a background permission should have changed the app-op of
+     * the foreground permission.
+     */
+    @Test
+    public void newBackgroundPermissionGetRevokedOnUpgrade() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        install(APK_LOCATION_BACKGROUND_29);
+
+        assertThat(getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).named("foreground app-op")
+                .isEqualTo(MODE_FOREGROUND);
+    }
+
+    /**
+     * An implicit permission should get revoked when the app gets updated and now requests the
+     * permission.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void newPermissionGetRevokedOnUpgradePreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        install(APK_CONTACTS_CALLLOG_16);
+
+        assertPermissionRevoked(READ_CALL_LOG);
+    }
+
+    /**
+     * When a requested permission was granted before upgrade it should still be granted.
+     */
+    @Test
+    public void oldPermissionStaysGrantedOnUpgrade() throws Exception {
+        install(APK_LOCATION_28);
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+        install(APK_LOCATION_BACKGROUND_29);
+
+        assertPermissionGranted(ACCESS_COARSE_LOCATION);
+    }
+
+    /**
+     * When a requested permission was granted before upgrade it should still be granted.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void oldPermissionStaysGrantedOnUpgradePreM() throws Exception {
+        install(APK_CONTACTS_15);
+
+        install(APK_CONTACTS_CALLLOG_16);
+
+        assertPermissionGranted(READ_CONTACTS);
+    }
+
+    /**
+     * When a requested permission was revoked before upgrade it should still be revoked.
+     */
+    @Test
+    public void oldPermissionStaysRevokedOnUpgrade() throws Exception {
+        install(APK_LOCATION_28);
+
+        install(APK_LOCATION_BACKGROUND_29);
+
+        assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+    }
+
+    /**
+     * When a requested permission was revoked before upgrade it should still be revoked.
+     *
+     * <p>(Pre-M version of test)
+     */
+    @Test
+    public void oldPermissionStaysRevokedOnUpgradePreM() throws Exception {
+        install(APK_CONTACTS_15);
+        revokePermission(APP_PKG, READ_CONTACTS);
+
+        install(APK_CONTACTS_CALLLOG_16);
+
+        assertPermissionRevoked(READ_CONTACTS);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/SuspendAppsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SuspendAppsPermissionTest.java
deleted file mode 100644
index 2346d4a..0000000
--- a/tests/tests/permission/src/android/permission/cts/SuspendAppsPermissionTest.java
+++ /dev/null
@@ -1,84 +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.permission.cts;
-
-import static android.Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
-import static android.Manifest.permission.SUSPEND_APPS;
-import static android.content.Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SuspendAppsPermissionTest {
-
-    private PackageManager mPackageManager;
-
-    @Before
-    public void setUp() {
-        mPackageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
-    }
-
-    @Test
-    public void testNumberOfAppsWithPermission() {
-        final List<PackageInfo> packagesWithPerm = mPackageManager.getPackagesHoldingPermissions(
-                new String[]{SUSPEND_APPS}, 0);
-        assertTrue("At most one app can hold the permission " + SUSPEND_APPS + ", but found more: "
-                + packagesWithPerm, packagesWithPerm.size() <= 1);
-    }
-
-    @Test
-    public void testShowSuspendedAppDetailsDeclared() {
-        final List<PackageInfo> packagesWithPerm = mPackageManager.getPackagesHoldingPermissions(
-                new String[]{SUSPEND_APPS}, 0);
-        final Intent showDetailsIntent = new Intent(ACTION_SHOW_SUSPENDED_APP_DETAILS);
-        boolean success = true;
-        StringBuilder errorString = new StringBuilder();
-        for (PackageInfo packageInfo : packagesWithPerm) {
-            showDetailsIntent.setPackage(packageInfo.packageName);
-            final ResolveInfo resolveInfo = mPackageManager.resolveActivity(showDetailsIntent, 0);
-            if (resolveInfo == null || resolveInfo.activityInfo == null) {
-                errorString.append("No activity found for " + ACTION_SHOW_SUSPENDED_APP_DETAILS
-                        + " inside package " + packageInfo.packageName);
-                success = false;
-            }
-            else if (!SEND_SHOW_SUSPENDED_APP_DETAILS.equals(resolveInfo.activityInfo.permission)) {
-                errorString.append("Activity handling " + ACTION_SHOW_SUSPENDED_APP_DETAILS
-                        + " not protected with permission " + SEND_SHOW_SUSPENDED_APP_DETAILS);
-                success = false;
-            }
-        }
-        if (!success) {
-            fail(errorString.toString());
-        }
-    }
-}
diff --git a/tests/tests/permission/src/android/permission/cts/TelephonyManagerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/TelephonyManagerPermissionTest.java
deleted file mode 100644
index d117e19..0000000
--- a/tests/tests/permission/src/android/permission/cts/TelephonyManagerPermissionTest.java
+++ /dev/null
@@ -1,323 +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.permission.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.media.AudioManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.telephony.TelephonyManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import java.util.Collections;
-
-/**
- * Test the non-location-related functionality of TelephonyManager.
- */
-@RunWith(AndroidJUnit4.class)
-public class TelephonyManagerPermissionTest {
-
-    private boolean mHasTelephony;
-    TelephonyManager mTelephonyManager = null;
-    private AudioManager mAudioManager;
-
-    @Before
-    public void setUp() throws Exception {
-        mHasTelephony = getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY);
-        mTelephonyManager =
-                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        assertNotNull(mTelephonyManager);
-        mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull(mAudioManager);
-    }
-
-    /**
-     * Verify that TelephonyManager.getDeviceId requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testGetDeviceId() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String id = mTelephonyManager.getDeviceId();
-            fail("Got device ID: " + id);
-        } catch (SecurityException e) {
-            // expected
-        }
-        try {
-            String id = mTelephonyManager.getDeviceId(0);
-            fail("Got device ID: " + id);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.getLine1Number requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testGetLine1Number() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String nmbr = mTelephonyManager.getLine1Number();
-            fail("Got line 1 number: " + nmbr);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.getSimSerialNumber requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testGetSimSerialNumber() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String nmbr = mTelephonyManager.getSimSerialNumber();
-            fail("Got SIM serial number: " + nmbr);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.getSubscriberId requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testGetSubscriberId() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String sid = mTelephonyManager.getSubscriberId();
-            fail("Got subscriber id: " + sid);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.getVoiceMailNumber requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testVoiceMailNumber() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String vmnum = mTelephonyManager.getVoiceMailNumber();
-            fail("Got voicemail number: " + vmnum);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-    /**
-     * Verify that AudioManager.setMode requires Permission.
-     * <p>
-     * Requires Permissions:
-     * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} and
-     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} for
-     * {@link AudioManager#MODE_IN_CALL}.
-     */
-    @Test
-    public void testSetMode() {
-        if (!mHasTelephony) {
-            return;
-        }
-        int audioMode = mAudioManager.getMode();
-        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
-        assertEquals(audioMode, mAudioManager.getMode());
-    }
-
-    /**
-     * Verify that TelephonyManager.setDataEnabled requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}.
-     */
-    @Test
-    public void testSetDataEnabled() {
-        if (!mHasTelephony) {
-            return;
-        }
-        try {
-            mTelephonyManager.setDataEnabled(false);
-            fail("Able to set data enabled");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that Telephony related broadcasts are protected.
-     */
-    @Test
-    public void testProtectedBroadcasts() {
-        if (!mHasTelephony) {
-            return;
-        }
-        try {
-            Intent intent = new Intent("android.intent.action.SIM_STATE_CHANGED");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent("android.intent.action.SERVICE_STATE");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent("android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent(
-                    "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent(
-                    "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent(
-                    "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent("android.intent.action.SIG_STR");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-        try {
-            Intent intent = new Intent("android.provider.Telephony.SECRET_CODE");
-            getContext().sendBroadcast(intent);
-            fail("SecurityException expected!");
-        } catch (SecurityException e) {}
-    }
-
-    /**
-     * Verify that TelephonyManager.getImei requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     */
-    @Test
-    public void testGetImei() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            String imei = mTelephonyManager.getImei();
-            fail("Got IMEI: " + imei);
-        } catch (SecurityException e) {
-            // expected
-        }
-        try {
-            String imei = mTelephonyManager.getImei(0);
-            fail("Got IMEI: " + imei);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.setAllowedCarriers requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}.
-     */
-    @Test
-    public void testSetAllowedCarriers() {
-        if (!mHasTelephony
-                || !getContext().getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)) {
-            return;
-        }
-        try {
-            mTelephonyManager.setAllowedCarriers(0, Collections.emptyList());
-            fail("Able to set allowed carriers");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that TelephonyManager.getAllowedCarriers requires Permission.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}.
-     */
-    @Test
-    public void testGetAllowedCarriers() {
-        if (!mHasTelephony
-                || !getContext().getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)) {
-            return;
-        }
-        try {
-            mTelephonyManager.getAllowedCarriers(0);
-            fail("Able to get allowed carriers");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getContext();
-    }
-}
diff --git a/tests/tests/permission/telephony/Android.mk b/tests/tests/permission/telephony/Android.mk
new file mode 100644
index 0000000..a78eb9a
--- /dev/null
+++ b/tests/tests/permission/telephony/Android.mk
@@ -0,0 +1,40 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+# Include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsPermissionTestCasesTelephony
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/telephony/AndroidManifest.xml b/tests/tests/permission/telephony/AndroidManifest.xml
new file mode 100644
index 0000000..fd82d84
--- /dev/null
+++ b/tests/tests/permission/telephony/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.telephony" android:targetSandboxVersion="2">
+
+    <!--
+        The CTS stubs package cannot be used as the target application here,
+        since that requires many permissions to be set. Instead, specify this
+        package itself as the target and include any stub activities needed.
+
+        This test package uses the default InstrumentationTestRunner, because
+        the InstrumentationCtsTestRunner is only available in the stubs
+        package. That runner cannot be added to this package either, since it
+        relies on hidden APIs.
+    -->
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.permission.cts.telephony"
+                     android:label="CTS tests of android.permission">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/permission/telephony/AndroidTest.xml b/tests/tests/permission/telephony/AndroidTest.xml
new file mode 100644
index 0000000..148419b
--- /dev/null
+++ b/tests/tests/permission/telephony/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<configuration description="Config for CTS Telephony Permission test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <!-- Install main test suite apk -->
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPermissionTestCasesTelephony.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.permission.cts.telephony" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
new file mode 100644
index 0000000..4c6b69d
--- /dev/null
+++ b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.permission.cts.telephony;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.util.Collections;
+
+/**
+ * Test the non-location-related functionality of TelephonyManager.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyManagerPermissionTest {
+
+    private boolean mHasTelephony;
+    TelephonyManager mTelephonyManager = null;
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        mTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        assertNotNull(mTelephonyManager);
+        mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull(mAudioManager);
+    }
+
+    /**
+     * Verify that TelephonyManager.getDeviceId requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testGetDeviceId() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String id = mTelephonyManager.getDeviceId();
+            fail("Got device ID: " + id);
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            String id = mTelephonyManager.getDeviceId(0);
+            fail("Got device ID: " + id);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that TelephonyManager.getLine1Number requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testGetLine1Number() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String nmbr = mTelephonyManager.getLine1Number();
+            fail("Got line 1 number: " + nmbr);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that TelephonyManager.getSimSerialNumber requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testGetSimSerialNumber() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String nmbr = mTelephonyManager.getSimSerialNumber();
+            fail("Got SIM serial number: " + nmbr);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that TelephonyManager.getSubscriberId requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testGetSubscriberId() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String sid = mTelephonyManager.getSubscriberId();
+            fail("Got subscriber id: " + sid);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that TelephonyManager.getVoiceMailNumber requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testVoiceMailNumber() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String vmnum = mTelephonyManager.getVoiceMailNumber();
+            fail("Got voicemail number: " + vmnum);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+    /**
+     * Verify that AudioManager.setMode requires Permission.
+     * <p>
+     * Requires Permissions:
+     * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} and
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} for
+     * {@link AudioManager#MODE_IN_CALL}.
+     */
+    @Test
+    public void testSetMode() {
+        if (!mHasTelephony) {
+            return;
+        }
+        int audioMode = mAudioManager.getMode();
+        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        assertEquals(audioMode, mAudioManager.getMode());
+    }
+
+    /**
+     * Verify that TelephonyManager.setDataEnabled requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}.
+     */
+    @Test
+    public void testSetDataEnabled() {
+        if (!mHasTelephony) {
+            return;
+        }
+        try {
+            mTelephonyManager.setDataEnabled(false);
+            fail("Able to set data enabled");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that Telephony related broadcasts are protected.
+     */
+    @Test
+    public void testProtectedBroadcasts() {
+        if (!mHasTelephony) {
+            return;
+        }
+        try {
+            Intent intent = new Intent("android.intent.action.SIM_STATE_CHANGED");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent("android.intent.action.SERVICE_STATE");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent("android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent(
+                    "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent(
+                    "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent(
+                    "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent("android.intent.action.SIG_STR");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+        try {
+            Intent intent = new Intent("android.provider.Telephony.SECRET_CODE");
+            getContext().sendBroadcast(intent);
+            fail("SecurityException expected!");
+        } catch (SecurityException e) {}
+    }
+
+    /**
+     * Verify that TelephonyManager.getImei requires Permission.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     */
+    @Test
+    public void testGetImei() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            String imei = mTelephonyManager.getImei();
+            fail("Got IMEI: " + imei);
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            String imei = mTelephonyManager.getImei(0);
+            fail("Got IMEI: " + imei);
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index 1614a22..c72f181 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Permission 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="instant_app" />
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index bc14d8a..0bcc91e 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -261,6 +261,7 @@
 
     <protected-broadcast android:name="android.intent.action.HEADSET_PLUG" />
     <protected-broadcast android:name="android.media.action.HDMI_AUDIO_PLUG" />
+    <protected-broadcast android:name="android.media.action.MICROPHONE_MUTE_CHANGED" />
 
     <protected-broadcast android:name="android.media.AUDIO_BECOMING_NOISY" />
     <protected-broadcast android:name="android.media.RINGER_MODE_CHANGED" />
@@ -357,6 +358,8 @@
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
+    <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
+    <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
     <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -398,6 +401,8 @@
 
     <protected-broadcast android:name="android.telecom.action.DEFAULT_DIALER_CHANGED" />
     <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED" />
+    <protected-broadcast android:name="android.provider.action.SMS_MMS_DB_CREATED" />
+    <protected-broadcast android:name="android.provider.action.SMS_MMS_DB_LOST" />
     <protected-broadcast android:name="android.intent.action.CONTENT_CHANGED" />
     <protected-broadcast android:name="android.provider.Telephony.MMS_DOWNLOADED" />
 
@@ -485,7 +490,9 @@
     <protected-broadcast android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
     <protected-broadcast android:name="android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED" />
     <protected-broadcast android:name="android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED" />
+    <protected-broadcast android:name="android.telephony.action.SECRET_CODE" />
     <protected-broadcast android:name="android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION" />
+    <protected-broadcast android:name="android.telephony.action.SUBSCRIPTION_PLANS_CHANGED" />
 
     <protected-broadcast android:name="com.android.bluetooth.btservice.action.ALARM_WAKEUP" />
     <protected-broadcast android:name="com.android.server.action.NETWORK_STATS_POLL" />
@@ -580,7 +587,7 @@
     <protected-broadcast android:name="android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED" />
     <protected-broadcast android:name="android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
     <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
-    <protected-broadcast android:name="com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
+    <protected-broadcast android:name="com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
 
     <!-- Time zone rules update intents fired by the system server -->
     <protected-broadcast android:name="com.android.intent.action.timezone.RULES_UPDATE_OPERATION" />
@@ -596,12 +603,37 @@
     <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" />
     <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
     <protected-broadcast android:name="android.app.action.STATSD_STARTED" />
+    <protected-broadcast android:name="com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET" />
+    <protected-broadcast android:name="com.android.server.biometrics.face.ACTION_LOCKOUT_RESET" />
+
+    <!-- For IdleController -->
+    <protected-broadcast android:name="android.intent.action.DOCK_IDLE" />
+    <protected-broadcast android:name="android.intent.action.DOCK_ACTIVE" />
+
+    <!-- Added in Q -->
+
+    <!-- For CarIdlenessTracker -->
+    <protected-broadcast android:name="com.android.server.jobscheduler.GARAGE_MODE_ON" />
+    <protected-broadcast android:name="com.android.server.jobscheduler.GARAGE_MODE_OFF" />
+    <protected-broadcast android:name="com.android.server.jobscheduler.FORCE_IDLE" />
+    <protected-broadcast android:name="com.android.server.jobscheduler.UNFORCE_IDLE" />
+
+    <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" />
+
+    <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
     <eat-comment />
 
+    <!-- Grouping for platform runtime permissions is not accessible to apps
+         @hide
+         @SystemApi
+    -->
+    <permission-group android:name="android.permission-group.UNDEFINED"
+        android:priority="100" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing user's contacts including personal profile   -->
     <!-- ====================================================================== -->
@@ -620,16 +652,17 @@
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CONTACTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readContacts"
         android:description="@string/permdesc_readContacts"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to write the user's contacts data.
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CONTACTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_writeContacts"
         android:description="@string/permdesc_writeContacts"
         android:protectionLevel="dangerous" />
@@ -651,16 +684,17 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CALENDAR"
-        android:permissionGroup="android.permission-group.CALENDAR"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readCalendar"
         android:description="@string/permdesc_readCalendar"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to write the user's calendar data.
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CALENDAR"
-        android:permissionGroup="android.permission-group.CALENDAR"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_writeCalendar"
         android:description="@string/permdesc_writeCalendar"
         android:protectionLevel="dangerous" />
@@ -682,7 +716,7 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.SEND_SMS"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_sendSms"
         android:description="@string/permdesc_sendSms"
         android:permissionFlags="costsMoney"
@@ -692,39 +726,43 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_SMS"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_receiveSms"
         android:description="@string/permdesc_receiveSms"
-        android:protectionLevel="dangerous"/>
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to read SMS messages.
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_SMS"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readSms"
         android:description="@string/permdesc_readSms"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to receive WAP push messages.
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_WAP_PUSH"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_receiveWapPush"
         android:description="@string/permdesc_receiveWapPush"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to monitor incoming MMS messages.
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECEIVE_MMS"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_receiveMms"
         android:description="@string/permdesc_receiveMms"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
-    <!-- Allows an application to read previously received cell broadcast
+    <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast
          messages and to register a content observer to get notifications when
          a cell broadcast has been received and added to the database. For
          emergency alerts, the database is updated immediately after the
@@ -737,17 +775,24 @@
          <p>Protection level: dangerous
          @hide Pending API council approval -->
     <permission android:name="android.permission.READ_CELL_BROADCASTS"
-        android:permissionGroup="android.permission-group.SMS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readCellBroadcasts"
         android:description="@string/permdesc_readCellBroadcasts"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
+
+    <!-- @SystemApi Allows sensor privacy to be modified.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
+                android:protectionLevel="signature" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing external storage                             -->
     <!-- ====================================================================== -->
     <eat-comment />
 
-    <!-- Used for runtime permissions related to the shared external storage. -->
+    <!-- Used for runtime permissions related to the shared external storage.
+         @deprecated replaced by new strongly-typed permission groups in Q. -->
     <permission-group android:name="android.permission-group.STORAGE"
         android:icon="@drawable/perm_group_storage"
         android:label="@string/permgrouplab_storage"
@@ -775,13 +820,14 @@
      grants your app this permission. If you don't need this permission, be sure your <a
      href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
      targetSdkVersion}</a> is 4 or higher.
-     <p>Protection level: dangerous
+     @deprecated replaced by new strongly-typed permission groups in Q.
      -->
     <permission android:name="android.permission.READ_EXTERNAL_STORAGE"
-        android:permissionGroup="android.permission-group.STORAGE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_sdcardRead"
         android:description="@string/permdesc_sdcardRead"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:permissionFlags="removed" />
 
     <!-- Allows an application to write to external storage.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
@@ -796,13 +842,70 @@
          read/write files in your application-specific directories returned by
          {@link android.content.Context#getExternalFilesDir} and
          {@link android.content.Context#getExternalCacheDir}.
-         <p>Protection level: dangerous
+         @deprecated replaced by new strongly-typed permission groups in Q.
     -->
     <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
-        android:permissionGroup="android.permission-group.STORAGE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_sdcardWrite"
         android:description="@string/permdesc_sdcardWrite"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:permissionFlags="removed" />
+
+    <!-- Runtime permission controlling access to the user's shared aural media
+         collection. -->
+    <permission-group android:name="android.permission-group.MEDIA_AURAL"
+        android:icon="@drawable/perm_group_aural"
+        android:label="@string/permgrouplab_aural"
+        android:description="@string/permgroupdesc_aural"
+        android:request="@string/permgrouprequest_aural"
+        android:priority="910" />
+
+    <!-- Allows an application to read the user's shared audio collection. -->
+    <permission android:name="android.permission.READ_MEDIA_AUDIO"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_audioRead"
+        android:description="@string/permdesc_audioRead"
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
+
+    <!-- Runtime permission controlling access to the user's shared visual media
+         collection, including images and videos. -->
+    <permission-group android:name="android.permission-group.MEDIA_VISUAL"
+        android:icon="@drawable/perm_group_visual"
+        android:label="@string/permgrouplab_visual"
+        android:description="@string/permgroupdesc_visual"
+        android:request="@string/permgrouprequest_visual"
+        android:priority="920" />
+
+    <!-- Allows an application to read the user's shared images collection. -->
+    <permission android:name="android.permission.READ_MEDIA_IMAGES"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_imagesRead"
+        android:description="@string/permdesc_imagesRead"
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
+
+    <!-- Allows an application to read the user's shared video collection. -->
+    <permission android:name="android.permission.READ_MEDIA_VIDEO"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_videoRead"
+        android:description="@string/permdesc_videoRead"
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
+
+    <!-- Allows an application to access any geographic locations persisted in the
+         user's shared collection. -->
+    <permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_mediaLocation"
+        android:description="@string/permdesc_mediaLocation"
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
+
+    <!-- @hide @SystemApi @TestApi
+         Allows an application to modify OBB files visible to other apps. -->
+    <permission android:name="android.permission.WRITE_OBB"
+        android:protectionLevel="signature|privileged" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device location                          -->
@@ -815,6 +918,9 @@
         android:label="@string/permgrouplab_location"
         android:description="@string/permgroupdesc_location"
         android:request="@string/permgrouprequest_location"
+        android:requestDetail="@string/permgrouprequestdetail_location"
+        android:backgroundRequest="@string/permgroupbackgroundrequest_location"
+        android:backgroundRequestDetail="@string/permgroupbackgroundrequestdetail_location"
         android:priority="400" />
 
     <!-- Allows an app to access precise location.
@@ -822,20 +928,37 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCESS_FINE_LOCATION"
-        android:permissionGroup="android.permission-group.LOCATION"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_accessFineLocation"
         android:description="@string/permdesc_accessFineLocation"
-        android:protectionLevel="dangerous|instant" />
+        android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an app to access approximate location.
          Alternatively, you might want {@link #ACCESS_FINE_LOCATION}.
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCESS_COARSE_LOCATION"
-        android:permissionGroup="android.permission-group.LOCATION"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_accessCoarseLocation"
         android:description="@string/permdesc_accessCoarseLocation"
-        android:protectionLevel="dangerous|instant" />
+        android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
+
+    <!-- Allows an app to access location in the background.  If you
+         are requesting this, you should also request {@link #ACCESS_FINE_LOCATION}.
+         Requesting this by itself is not sufficient to give you
+         location access.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_accessBackgroundLocation"
+        android:description="@string/permdesc_accessBackgroundLocation"
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the call log                                 -->
@@ -844,21 +967,12 @@
 
     <!-- Used for permissions that are associated telephony features. -->
     <permission-group android:name="android.permission-group.CALL_LOG"
-        android:icon="@drawable/perm_group_phone_calls"
+        android:icon="@drawable/perm_group_call_log"
         android:label="@string/permgrouplab_calllog"
         android:description="@string/permgroupdesc_calllog"
         android:request="@string/permgrouprequest_calllog"
         android:priority="450" />
 
-
-    <!-- Used for permissions that are associated telephony features. -->
-    <permission-group android:name="android.permission-group.CALL_LOG"
-                      android:icon="@drawable/perm_group_phone_calls"
-                      android:label="@string/permgrouplab_calllog"
-                      android:description="@string/permgroupdesc_calllog"
-                      android:request="@string/permgrouprequest_calllog"
-                      android:priority="450" />
-
     <!-- Allows an application to access the IMS call service: making and
          modifying a call
         <p>Protection level: signature|privileged
@@ -882,10 +996,11 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_CALL_LOG"
-        android:permissionGroup="android.permission-group.CALL_LOG"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readCallLog"
         android:description="@string/permdesc_readCallLog"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to write (but not read) the user's
          call log data.
@@ -901,7 +1016,7 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.WRITE_CALL_LOG"
-        android:permissionGroup="android.permission-group.CALL_LOG"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_writeCallLog"
         android:description="@string/permdesc_writeCallLog"
         android:protectionLevel="dangerous" />
@@ -912,10 +1027,11 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
-        android:permissionGroup="android.permission-group.CALL_LOG"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_processOutgoingCalls"
         android:description="@string/permdesc_processOutgoingCalls"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device telephony                         -->
@@ -944,26 +1060,28 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_PHONE_STATE"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readPhoneState"
         android:description="@string/permdesc_readPhoneState"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
          granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
          <p>Protection level: dangerous-->
     <permission android:name="android.permission.READ_PHONE_NUMBERS"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readPhoneNumbers"
         android:description="@string/permdesc_readPhoneNumbers"
-        android:protectionLevel="dangerous|instant" />
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an application to initiate a phone call without going through
         the Dialer user interface for the user to confirm the call.
         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.CALL_PHONE"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:permissionFlags="costsMoney"
         android:label="@string/permlab_callPhone"
         android:description="@string/permdesc_callPhone"
@@ -973,7 +1091,7 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_addVoicemail"
         android:description="@string/permdesc_addVoicemail"
         android:protectionLevel="dangerous" />
@@ -982,7 +1100,7 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.USE_SIP"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:description="@string/permdesc_use_sip"
         android:label="@string/permlab_use_sip"
         android:protectionLevel="dangerous"/>
@@ -991,14 +1109,14 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ANSWER_PHONE_CALLS"
-        android:permissionGroup="android.permission-group.PHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_answerPhoneCalls"
         android:description="@string/permdesc_answerPhoneCalls"
         android:protectionLevel="dangerous|runtime" />
 
     <!-- Allows a calling application which manages it own calls through the self-managed
          {@link android.telecom.ConnectionService} APIs.  See
-         {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED for more information on the
+         {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the
          self-managed ConnectionService APIs.
          <p>Protection level: normal
     -->
@@ -1007,6 +1125,18 @@
                 android:description="@string/permdesc_manageOwnCalls"
                 android:protectionLevel="normal" />
 
+    <!--Allows an app which implements the
+       {@link InCallService} API to be eligible to be enabled as a calling companion app. This
+       means that the Telecom framework will bind to the app's InCallService implementation when
+       there are calls active. The app can use the InCallService API to view information about
+       calls on the system and control these calls.
+       <p>Protection level: normal
+   -->
+    <permission android:name="android.permission.CALL_COMPANION_APP"
+                android:label="@string/permlab_callCompanionApp"
+                android:description="@string/permdesc_callCompanionApp"
+                android:protectionLevel="normal" />
+
     <!-- Allows a calling app to continue a call which was started in another app.  An example is a
          video calling app that wants to continue a voice call on the user's mobile network.<p>
          When the handover of a call from one app to another takes place, there are two devices
@@ -1019,7 +1149,7 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.ACCEPT_HANDOVER"
-                android:permissionGroup="android.permission-group.PHONE"
+                android:permissionGroup="android.permission-group.UNDEFINED"
                 android.label="@string/permlab_acceptHandover"
                 android:description="@string/permdesc_acceptHandovers"
                 android:protectionLevel="dangerous" />
@@ -1043,10 +1173,34 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.RECORD_AUDIO"
-        android:permissionGroup="android.permission-group.MICROPHONE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
-        android:protectionLevel="dangerous|instant"/>
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
+
+    <!-- ====================================================================== -->
+    <!-- Permissions for activity recognition                        -->
+    <!-- ====================================================================== -->
+    <eat-comment />
+
+    <!-- Used for permissions that are associated with activity recognition.
+         TODO(zezeozue). STOPSHIP: Add icon -->
+    <permission-group android:name="android.permission-group.ACTIVITY_RECOGNITION"
+        android:label="@string/permgrouplab_activityRecognition"
+        android:description="@string/permgroupdesc_activityRecognition"
+        android:request="@string/permgrouprequest_activityRecognition"
+        android:priority="1000" />
+
+    <!-- Allows an application to recognize physical activity.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.ACTIVITY_RECOGNITION"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_activityRecognition"
+        android:description="@string/permdesc_activityRecognition"
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the UCE Service                              -->
@@ -1083,19 +1237,20 @@
         android:priority="700" />
 
     <!-- Required to be able to access the camera device.
-         <p>This will automatically enforce the <a
-         href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
-         <uses-feature>}</a> manifest element for <em>all</em> camera features.
+         <p>This will automatically enforce the
+         <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+         uses-feature</a> manifest element for <em>all</em> camera features.
          If you do not require all camera features or can properly operate if a camera
          is not available, then you must modify your manifest as appropriate in order to
          install on devices that don't support all camera features.</p>
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.CAMERA"
-        android:permissionGroup="android.permission-group.CAMERA"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_camera"
         android:description="@string/permdesc_camera"
-        android:protectionLevel="dangerous|instant" />
+        android:protectionLevel="dangerous|instant"
+        android:usageInfoRequired="true" />
 
 
     <!-- ====================================================================== -->
@@ -1116,10 +1271,11 @@
          measure what is happening inside his/her body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
-        android:permissionGroup="android.permission-group.SENSORS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_bodySensors"
         android:description="@string/permdesc_bodySensors"
-        android:protectionLevel="dangerous" />
+        android:protectionLevel="dangerous"
+        android:usageInfoRequired="true" />
 
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
@@ -1322,7 +1478,7 @@
          This should only be used by HDMI-CEC service.
     -->
     <permission android:name="android.permission.HDMI_CEC"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
 
     <!-- @SystemApi Allows an application to use location features in hardware,
          such as the geofencing api.
@@ -1444,11 +1600,34 @@
 
     <!-- Allows SetupWizard to call methods in Networking services
          <p>Not for use by any other third-party or privileged applications.
+         @SystemApi
          @hide This should only be used by SetupWizard.
     -->
     <permission android:name="android.permission.NETWORK_SETUP_WIZARD"
         android:protectionLevel="signature|setup" />
 
+    <!-- Allows Managed Provisioning to call methods in Networking services
+         <p>Not for use by any other third-party or privileged applications.
+         @SystemApi
+         @hide This should only be used by ManagedProvisioning app.
+    -->
+    <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
+        android:protectionLevel="signature" />
+
+    <!-- Allows device mobility state to be set so that Wifi scan interval can be increased
+         when the device is stationary in order to save power.
+         <p>Not for use by any other third-party or privileged applications
+         @hide This should only be used by a privileged app.
+    -->
+    <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- #SystemApi @hide Allows privileged system APK to update Wifi usability stats and score.
+         <p>Not for use by third-party applications. This should only be used by a privileged app.
+    -->
+    <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCESS_LOWPAN_STATE"
@@ -1471,13 +1650,14 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- @hide Allows internal management of Wi-Fi connectivity state when on
-         permission review mode.
+         wireless consent mode.
          <p>Not for use by third-party applications. -->
-    <permission android:name="android.permission.MANAGE_WIFI_WHEN_PERMISSION_REVIEW_REQUIRED"
+    <permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
-    <!-- @hide Allows an app to bypass Private DNS.
-         <p>Not for use by third-party applications. -->
+    <!-- #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. -->
     <permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"
         android:protectionLevel="signature" />
 
@@ -1499,15 +1679,7 @@
          @hide
     -->
     <permission android:name="android.permission.SUSPEND_APPS"
-        android:protectionLevel="signature|privileged" />
-
-    <!-- @SystemApi Must be required by activities that handle the intent action
-         {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
-         hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
-         <p>Not for use by third-party applications.
-         @hide -->
-    <permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
-                android:protectionLevel="signature" />
+        android:protectionLevel="signature|wellbeing" />
 
     <!-- Allows applications to discover and pair bluetooth devices.
          <p>Protection level: normal
@@ -1579,9 +1751,9 @@
     <permission android:name="android.permission.NFC_HANDOVER_STATUS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide Allows internal management of Bluetooth state when on permission review mode.
+    <!-- @hide Allows internal management of Bluetooth state when on wireless consent mode.
          <p>Not for use by third-party applications. -->
-    <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_PERMISSION_REVIEW_REQUIRED"
+    <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
     <!-- ================================== -->
@@ -1601,10 +1773,11 @@
     <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.GET_ACCOUNTS"
-        android:permissionGroup="android.permission-group.CONTACTS"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:protectionLevel="dangerous"
         android:description="@string/permdesc_getAccounts"
-        android:label="@string/permlab_getAccounts" />
+        android:label="@string/permlab_getAccounts"
+        android:usageInfoRequired="true" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 
     <!-- @SystemApi Allows applications to call into AccountAuthenticators.
@@ -1673,6 +1846,12 @@
     <permission android:name="android.permission.MANAGE_USB"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to manage Android Debug Bridge settings.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_DEBUGGING"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to access the MTP USB kernel driver.
          For use only by the device side MTP implementation.
          @hide -->
@@ -1714,7 +1893,7 @@
          @hide This should only be used by OEM's TvInputService's.
     -->
     <permission android:name="android.permission.TV_INPUT_HARDWARE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
 
     <!-- @SystemApi Allows to capture a frame of TV input hardware such as
          built-in tuners and HDMI-in's.
@@ -1850,6 +2029,13 @@
     <permission android:name="android.permission.BIND_SCREENING_SERVICE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by a {@link android.telecom.CallRedirectionService},
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature|privileged
+    -->
+    <permission android:name="android.permission.BIND_CALL_REDIRECTION_SERVICE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a {@link android.telecom.ConnectionService},
          to ensure that only the system can bind to it.
          @deprecated {@link android.telecom.ConnectionService}s should require
@@ -1945,17 +2131,15 @@
          <p>This permission should <em>only</em> be requested by the platform
          document management app.  This permission cannot be granted to
          third-party apps.
-         <p>Protection level: signature
     -->
     <permission android:name="android.permission.MANAGE_DOCUMENTS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @hide Allows an application to cache content.
          <p>Not for use by third-party applications.
-         <p>Protection level: signature
     -->
     <permission android:name="android.permission.CACHE_CONTENT"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @SystemApi @hide
          Allows an application to aggressively allocate disk space.
@@ -1986,6 +2170,15 @@
         android:label="@string/permlab_disableKeyguard"
         android:protectionLevel="normal" />
 
+    <!-- Allows an application to get the screen lock complexity and prompt users to update the
+     screen lock to a certain complexity level.
+     <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY"
+                android:label="@string/permlab_getAndRequestScreenLockComplexity"
+                android:description="@string/permdesc_getAndRequestScreenLockComplexity"
+                android:protectionLevel="normal" />
+
     <!-- ================================== -->
     <!-- Permissions to access other installed applications  -->
     <!-- ================================== -->
@@ -2029,6 +2222,14 @@
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
         android:protectionLevel="signature|installer" />
 
+    <!-- @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
+         can start an activity on a managed profile of that user.
+         This permission is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
          third party applications. -->
@@ -2064,17 +2265,17 @@
         android:description="@string/permdesc_reorderTasks"
         android:protectionLevel="normal" />
 
-    <!-- @hide Allows an application to change to remove/kill tasks -->
+    <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
     <permission android:name="android.permission.REMOVE_TASKS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
-        android:protectionLevel="signature|privileged|development" />
+        android:protectionLevel="signature" />
 
     <!-- @SystemApi @TestApi @hide Allows an application to embed other activities -->
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
-                android:protectionLevel="signature|privileged|development" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- Allows an application to start any activity, regardless of permission
          protection or exported state.
@@ -2082,6 +2283,26 @@
     <permission android:name="android.permission.START_ANY_ACTIVITY"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to start activities from background
+         @hide -->
+    <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
+        android:protectionLevel="signature|privileged|vendorPrivileged|oem" />
+
+    <!-- @SystemApi Must be required by activities that handle the intent action
+         {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
+         hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
+                android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS" />
+
+    <!-- Allows an application to start an activity as another app, provided that app has been
+         granted a permissionToken from the ActivityManagerService.
+         @hide -->
+    <permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
+        android:protectionLevel="signature" />
+
     <!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
         API is no longer supported. -->
     <permission android:name="android.permission.RESTART_PACKAGES"
@@ -2252,7 +2473,8 @@
         android:description="@string/permdesc_install_shortcut"
         android:protectionLevel="normal"/>
 
-    <!--This permission is no longer supported.
+    <!-- <p class="caution"><strong>Don't use this permission in your app.</strong><br>This
+         permission is no longer supported.
     -->
     <permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
         android:label="@string/permlab_uninstall_shortcut"
@@ -2297,7 +2519,7 @@
     <permission android:name="android.permission.SET_SCREEN_COMPATIBILITY"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to modify the current configuration, such
+    <!-- @SystemApi @TestApi Allows an application to modify the current configuration, such
          as locale. -->
     <permission android:name="android.permission.CHANGE_CONFIGURATION"
         android:protectionLevel="signature|privileged|development" />
@@ -2324,7 +2546,17 @@
     <permission android:name="android.permission.WRITE_GSERVICES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to call
+    <!-- @SystemApi @hide Allows an application to modify config settings.
+    <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.WRITE_DEVICE_CONFIG"
+        android:protectionLevel="signature|configurator"/>
+
+    <!-- @SystemApi @hide Allows an application to read config settings.
+    <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_DEVICE_CONFIG"
+        android:protectionLevel="signature|preinstalled" />
+
+    <!-- @SystemApi @TestApi Allows an application to call
         {@link android.app.ActivityManager#forceStopPackage}.
         @hide -->
     <permission android:name="android.permission.FORCE_STOP_PACKAGES"
@@ -2359,7 +2591,7 @@
          {@link android.content.pm.PackageManager#addPackageToPreferred}
          for details. -->
     <permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
-        android:protectionLevel="signature|verifier" />
+        android:protectionLevel="signature|installer|verifier" />
 
     <!-- Allows an application to receive the
          {@link android.content.Intent#ACTION_BOOT_COMPLETED} that is
@@ -2429,7 +2661,8 @@
     <permission android:name="android.permission.ASEC_RENAME"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows applications to write the apn settings.
+    <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of
+         an existing apn settings like user and password.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_APN_SETTINGS"
         android:protectionLevel="signature|privileged" />
@@ -2567,6 +2800,20 @@
     <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- @hide @SystemApi @TestApi
+         Allow an application to approve incident and bug reports to be
+         shared off-device.  There can be only one application installed on the
+         device with this permission, and since this is a privileged permission, it
+         must be in priv-app.
+        <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.APPROVE_INCIDENT_REPORTS"
+        android:protectionLevel="signature|incidentReportApprover" />
+
+    <!-- @hide Allow an application to approve an incident or bug report approval from
+        the system. -->
+    <permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ==================================== -->
     <!-- Private permissions                  -->
     <!-- ==================================== -->
@@ -2639,6 +2886,11 @@
     <permission android:name="android.permission.MANAGE_APP_OPS_MODES"
         android:protectionLevel="signature|installer|verifier" />
 
+    <!-- @hide Permission that allows configuring appops.
+     <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_APPOPS"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to open windows that are for use by parts
          of the system user interface.
          <p>Not for use by third-party applications.
@@ -2648,7 +2900,7 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to use
-         {@link android.view.WindowManager.LayoutsParams#PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
+         {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
          to hide non-system-overlay windows.
          <p>Not for use by third-party applications.
          @hide
@@ -2817,6 +3069,12 @@
     <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by the RoleControllerService to ensure that only the system can bind to
+         it.
+         @hide -->
+    <permission android:name="android.permission.BIND_ROLE_CONTROLLER_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
          that only the system can bind to it.
          @hide -->
@@ -2830,6 +3088,14 @@
     <permission android:name="android.permission.BIND_TEXT_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Must be required by a AttentionService
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_ATTENTION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.net.VpnService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -2882,6 +3148,22 @@
     <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+                android:protectionLevel="signature" />
+
+    <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by hotword enrollment application,
          to ensure that only the system can interact with it.
          @hide <p>Not for use by third-party applications.</p> -->
@@ -2901,6 +3183,13 @@
     <permission android:name="android.permission.BIND_TV_INPUT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by an {@link android.service.sms.FinancialSmsService}
+         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_FINANCIAL_SMS_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi
          Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
          to ensure that only the system can bind to it.
@@ -2958,8 +3247,21 @@
 
     <!-- @SystemApi Required to add or remove another application as a device admin.
          <p>Not for use by third-party applications.
-         @hide -->
+         @hide
+         @removed -->
     <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an app to reset the device password.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.RESET_PASSWORD"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Allows an app to lock the device.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.LOCK_DEVICE"
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows low-level access to setting the orientation (actually
@@ -3038,6 +3340,15 @@
     <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to install existing system packages. This is a limited
+         version of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+         <p>Not for use by third-party applications.
+         TODO(b/80204953): remove this permission once we have a long-term solution.
+         @hide
+    -->
+    <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
@@ -3126,10 +3437,25 @@
     <permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
          android:protectionLevel="signature|installer|verifier" />
 
+    <!-- @SystemApi Allows the system to read runtime permission state.
+        @hide -->
+    <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to observe permission changes. -->
     <permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to manage the holders of a role.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
+                android:protectionLevel="signature|installer" />
+
+    <!-- @SystemApi Allows an application to observe role holder changes.
+         @hide -->
+    <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
+                android:protectionLevel="signature|installer" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
@@ -3137,11 +3463,13 @@
     <permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to take screen shots and more generally
+    <!-- Allows an application to take screen shots and more generally
          get access to the frame buffer data.
-         <p>Not for use by third-party applications. -->
+         <p>Not for use by third-party applications.
+          @hide
+          @removed -->
     <permission android:name="android.permission.READ_FRAME_BUFFER"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature" />
 
     <!-- Allows an application to use InputFlinger's low level features.
          @hide -->
@@ -3179,6 +3507,13 @@
     <permission android:name="android.permission.CONTROL_DISPLAY_SATURATION"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to control display color transformations.
+         <p>Not for use by third-party applications.</p>
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to collect usage infomation about brightness slider changes.
          <p>Not for use by third-party applications.</p>
          @hide
@@ -3236,16 +3571,33 @@
     <permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to capture video output.
-         <p>Not for use by third-party applications.</p> -->
-    <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
+    <!-- Allows an application to modify what effects are applied to all audio
+         (matching certain criteria) from any application.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to capture secure video output.
-         <p>Not for use by third-party applications.</p> -->
-    <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+    <!-- @SystemApi Allows an application to provide remote displays.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.REMOTE_DISPLAY_PROVIDER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to capture video output.
+         <p>Not for use by third-party applications.</p>
+          @hide
+          @removed -->
+    <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an application to capture secure video output.
+         <p>Not for use by third-party applications.</p>
+          @hide
+          @removed -->
+    <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to know what content is playing and control its playback.
          <p>Not for use by third-party applications due to privacy of media consumption</p>  -->
     <permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
@@ -3285,6 +3637,12 @@
    <permission android:name="android.permission.DEVICE_POWER"
         android:protectionLevel="signature" />
 
+    <!-- Allows toggling battery saver on the system.
+         Superseded by DEVICE_POWER permission. @hide @SystemApi
+    -->
+    <permission android:name="android.permission.POWER_SAVER"
+        android:protectionLevel="signature|privileged" />
+
    <!-- Allows access to the PowerManager.userActivity function.
    <p>Not for use by third-party applications. @hide @SystemApi -->
     <permission android:name="android.permission.USER_ACTIVITY"
@@ -3372,7 +3730,7 @@
     <permission android:name="android.permission.OBSERVE_APP_USAGE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide @SystemApi Allows an application to change the app idle state of an app.
+    <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
         android:protectionLevel="signature|privileged" />
@@ -3488,6 +3846,10 @@
     <permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- @hide Internal permission to allows an application to access card content provider. -->
+    <permission android:name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows applications to set a live wallpaper.
          @hide XXX Change to signature once the picker is moved to its
          own apk as Ghod Intended. -->
@@ -3561,6 +3923,16 @@
     <permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
         android:protectionLevel="signature" />
 
+    <!-- @hide Rollback manager needs to have this permission before the PackageManager will
+         trust it to enable rollback.
+    -->
+    <permission android:name="android.permission.PACKAGE_ROLLBACK_AGENT"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi @hide Allows managing apk level rollbacks. -->
+    <permission android:name="android.permission.MANAGE_ROLLBACKS"
+        android:protectionLevel="signature|verifier" />
+
     <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
     <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
         android:protectionLevel="signature|verifier" />
@@ -3598,7 +3970,7 @@
     <permission android:name="android.permission.UPDATE_LOCK"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to read the current set of notifications, including
+    <!-- @SystemApi @TestApi Allows an application to read the current set of notifications, including
          any metadata and intents attached.
          @hide -->
     <permission android:name="android.permission.ACCESS_NOTIFICATIONS"
@@ -3636,11 +4008,32 @@
     <permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows direct access to the <Biometric>Service interfaces. Reserved for the system. @hide -->
+    <permission android:name="android.permission.MANAGE_BIOMETRIC"
+        android:protectionLevel="signature" />
+
+    <!-- Allows direct access to the <Biometric>Service authentication methods. Reserved for the system. @hide -->
+    <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
+        android:protectionLevel="signature" />
+
+    <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
+    <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
+        android:protectionLevel="signature" />
+
+    <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
+    <permission android:name="android.permission.RESET_FACE_LOCKOUT"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.CONTROL_KEYGUARD"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to control keyguard features like secure notifications.
+         @hide -->
+    <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to listen to trust changes.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.TRUST_LISTENER"
@@ -3827,10 +4220,10 @@
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
 
-    <!-- Allows the holder to access and manage instant applications on the device.
-    @hide -->
+    <!-- @SystemApi Allows the holder to access and manage instant applications on the device.
+         @hide -->
     <permission android:name="android.permission.ACCESS_INSTANT_APPS"
-            android:protectionLevel="signature|installer|verifier" />
+            android:protectionLevel="signature|installer|verifier|wellbeing" />
     <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
 
     <!-- Allows the holder to view the instant applications on the device.
@@ -3926,6 +4319,21 @@
     <permission android:name="android.permission.MANAGE_AUTO_FILL"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the content capture service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an application to manage the content suggestions service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
+         android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an application to manage the app predictions service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
+         android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -3988,7 +4396,7 @@
     <!-- Allows an application to directly open the "Open by default" page inside a package's
          Details screen.
          @hide <p>Not for use by third-party applications. -->
-    <permission android:name="android.permission.OPEN_APPLICATION_DETAILS_OPEN_BY_DEFAULT_PAGE"
+    <permission android:name="android.permission.OPEN_APP_OPEN_BY_DEFAULT_SETTINGS"
                 android:protectionLevel="signature" />
 
     <!-- Allows hidden API checks to be disabled when starting a process.
@@ -3996,6 +4404,63 @@
     <permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
                 android:protectionLevel="signature" />
 
+    <!-- @hide Permission that protects the
+        {@link android.provider.Telephony.Intents#ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL}
+        broadcast -->
+    <permission android:name="android.permission.MONITOR_DEFAULT_SMS_PACKAGE"
+        android:protectionLevel="signature" />
+
+    <!-- A subclass of {@link android.app.SmsAppService} must be protected with this permission. -->
+    <permission android:name="android.permission.BIND_SMS_APP_SERVICE"
+        android:protectionLevel="signature" />
+
+    <!-- @hide Permission that allows background clipboard access.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows modifying accessibility state.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_ACCESSIBILITY"
+        android:protectionLevel="signature|setup" />
+
+    <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
+        android:protectionLevel="signature" />
+
+    <!-- Allows financial apps to read filtered sms messages. -->
+    <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
+        android:protectionLevel="signature|appop" />
+
+    <!-- Required for apps targeting {@link android.os.Build.VERSION_CODES#P} that want to use
+         {@link android.app.Notification.Builder#setFullScreenIntent notification full screen
+         intents}.  -->
+    <permission android:name="android.permission.USE_FULL_SCREEN_INTENT"
+                android:protectionLevel="normal" />
+
+    <!-- @SystemApi Allows requesting the framework broadcast the
+         {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
+         @hide -->
+    <permission android:name="android.permission.SEND_DEVICE_CUSTOMIZATION_READY"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Permission that protects the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY}
+         intent.
+         @hide -->
+    <permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"
+        android:protectionLevel="signature|preinstalled" />
+    <!-- @SystemApi Allows wallpaper to be rendered in ambient mode.
+         @hide -->
+    <permission android:name="android.permission.AMBIENT_WALLPAPER"
+                android:protectionLevel="signature|preinstalled" />
+
+    <!-- Allows the device to be reset, clearing all data and enables Test Harness Mode.
+         @hide-->
+    <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+        android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -4151,7 +4616,7 @@
         </activity>
 
         <activity android:name="com.android.internal.app.NetInitiatedActivity"
-                android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+                android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
                 android:process=":ui">
         </activity>
@@ -4172,26 +4637,32 @@
         <activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
                 android:excludeFromRecents="true"
                 android:process=":ui"
-                android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert">
+                android:theme="@style/Theme.Dialog.Confirmation">
             <intent-filter android:priority="1000">
                 <action android:name="android.os.action.CREATE_USER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
 
+        <activity android:name="com.android.internal.app.SuspendedAppActivity"
+                  android:theme="@style/Theme.Dialog.Confirmation"
+                  android:excludeFromRecents="true"
+                  android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.internal.app.UnlaunchableAppActivity"
-                android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+                android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
                 android:process=":ui">
         </activity>
 
         <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
-                  android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+                  android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true">
         </activity>
 
         <activity android:name="com.android.internal.app.HarmfulAppWarningActivity"
-                  android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+                  android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true"
                   android:process=":ui"
                   android:label="@string/harmful_app_warning_title"
@@ -4277,6 +4748,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
+                  android:permission="android.permission.UPDATE_CONFIG">
+            <intent-filter>
+                <action android:name="android.intent.action.ACTION_UPDATE_CONVERSATION_ACTIONS" />
+                <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
@@ -4301,23 +4780,10 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.server.stats.StatsCompanionService$AnomalyAlarmReceiver"
-                  android:permission="android.permission.STATSCOMPANION"
-                  android:exported="false">
-        </receiver>
-
-        <receiver android:name="com.android.server.stats.StatsCompanionService$PullingAlarmReceiver"
-                  android:permission="android.permission.STATSCOMPANION"
-                  android:exported="false">
-        </receiver>
-
-        <receiver android:name="com.android.server.am.BatteryStatsService$UsbConnectionReceiver"
-                  android:exported="false">
+        <receiver android:name="com.android.server.WallpaperUpdateReceiver"
+                  android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.hardware.usb.action.USB_STATE" />
+                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
             </intent-filter>
         </receiver>
 
@@ -4325,14 +4791,6 @@
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
 
-        <service android:name="com.android.internal.backup.LocalTransportService"
-                android:permission="android.permission.CONFIRM_FULL_BACKUP"
-                android:exported="false">
-            <intent-filter>
-                <action android:name="android.backup.TRANSPORT_HOST" />
-            </intent-filter>
-        </service>
-
         <service android:name="com.android.server.MountServiceIdler"
                  android:exported="true"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
new file mode 100644
index 0000000..d1d42bc
--- /dev/null
+++ b/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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.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_COARSE_LOCATION.}
+     */
+    @UiThreadTest
+    public void testListenCellLocation() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        PhoneStateListener phoneStateListener = new PhoneStateListener();
+        try {
+            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+            fail("TelephonyManager.listen(LISTEN_CELL_LOCATION) did not" +
+                    " throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        try {
+            telephonyManager.getCellLocation();
+            fail("TelephonyManager.getCellLocation did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that get cell location requires permissions.
+     * <p>
+     * Requires Permission: {@link
+     * android.Manifest.permission#ACCESS_COARSE_LOCATION.}
+     */
+    @UiThreadTest
+    public void testListenCellLocation2() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        PhoneStateListener phoneStateListener = new PhoneStateListener();
+
+        try {
+            telephonyManager.getAllCellInfo();
+            fail("TelephonyManager.getAllCellInfo did not throw SecurityException as expected");
+        } 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 2888a71..e83d6f5 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.os.storage.StorageManager;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
@@ -39,8 +40,10 @@
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -61,6 +64,7 @@
     private static final String AUTOMOTIVE_SERVICE_PACKAGE_NAME = "com.android.car";
 
     private static final String TAG_PERMISSION = "permission";
+    private static final String TAG_PERMISSION_GROUP = "permission-group";
 
     private static final String ATTR_NAME = "name";
     private static final String ATTR_PERMISSION_GROUP = "permissionGroup";
@@ -79,7 +83,8 @@
             declaredGroupsSet.add(declaredGroup.name);
         }
 
-        Set<String> expectedPermissionGroups = new ArraySet<>();
+        Set<String> expectedPermissionGroups = loadExpectedPermissionGroupNames(
+                R.raw.android_manifest);
         List<PermissionInfo> expectedPermissions = loadExpectedPermissions(R.raw.android_manifest);
 
         if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
@@ -103,8 +108,6 @@
 
             // We want to end up with OEM defined permissions and groups to check their namespace
             declaredPermissionsMap.remove(expectedPermissionName);
-            // Collect expected groups to check if OEM defined groups aren't in platform namespace
-            expectedPermissionGroups.add(expectedPermission.group);
 
             // OEMs cannot change permission protection
             final int expectedProtection = expectedPermission.protectionLevel
@@ -132,14 +135,15 @@
 
             // OEMs cannot change permission grouping
             if ((declaredPermission.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
-                if (!expectedPermission.group.equals(declaredPermission.group)) {
+                if (!Objects.equals(expectedPermission.group, declaredPermission.group)) {
                     offendingList.add(
                             "Permission " + expectedPermissionName + " not in correct group "
                             + "(expected=" + expectedPermission.group + " actual="
                                     + declaredPermission.group);
                 }
 
-                if (!declaredGroupsSet.contains(declaredPermission.group)) {
+                if (declaredPermission.group != null
+                        && !declaredGroupsSet.contains(declaredPermission.group)) {
                     offendingList.add(
                             "Permission group " + expectedPermission.group + " must be defined");
                 }
@@ -213,9 +217,50 @@
                 }
             }
         }
+
+        // STOPSHIP: remove this once isolated storage is always enabled
+        if (!StorageManager.hasIsolatedStorage()) {
+            Iterator<PermissionInfo> it = permissions.iterator();
+            while (it.hasNext()) {
+                final PermissionInfo pi = it.next();
+                switch (pi.name) {
+                    case android.Manifest.permission.READ_MEDIA_AUDIO:
+                    case android.Manifest.permission.READ_MEDIA_VIDEO:
+                    case android.Manifest.permission.READ_MEDIA_IMAGES:
+                    case android.Manifest.permission.ACCESS_MEDIA_LOCATION:
+                    case android.Manifest.permission.WRITE_OBB:
+                        it.remove();
+                        break;
+                }
+            }
+        }
+
         return permissions;
     }
 
+    private Set<String> loadExpectedPermissionGroupNames(int resourceId) throws Exception {
+        ArraySet<String> permissionGroups = new ArraySet<>();
+        try (InputStream in = getContext().getResources().openRawResource(resourceId)) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+
+            final int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                if (TAG_PERMISSION_GROUP.equals(parser.getName())) {
+                    permissionGroups.add(parser.getAttributeValue(null, ATTR_NAME));
+                } else {
+                    Log.e(LOG_TAG, "Unknown tag " + parser.getName());
+                }
+            }
+        }
+        return permissionGroups;
+    }
+
     private static int parseProtectionLevel(String protectionLevelString) {
         int protectionLevel = 0;
         String[] fragments = protectionLevelString.split("\\|");
@@ -258,6 +303,9 @@
                 case "privileged": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
                 } break;
+                case "oem": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_OEM;
+                } break;
                 case "vendorPrivileged": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
                 } break;
@@ -267,6 +315,18 @@
                 case "textClassifier": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
                 } break;
+                case "wellbeing": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_WELLBEING;
+                } break;
+                case "configurator": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_CONFIGURATOR;
+                } break;
+                case "incidentReportApprover": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER;
+                } break;
+                case "documenter": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_DOCUMENTER;
+                } break;
                 case "instant": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTANT;
                 } break;
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index ea3e6f6..39e874f 100644
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -69,7 +69,7 @@
         "android.net.conn.TETHER_STATE_CHANGED",
         "android.net.conn.INET_CONDITION_ACTION",
         "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
-        "com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER"
+        "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER"
     };
 
     private static final String BROADCASTS_TELEPHONY[] = new String[] {
diff --git a/tests/tests/preference/AndroidTest.xml b/tests/tests/preference/AndroidTest.xml
index 584bbc8..4287b76 100644
--- a/tests/tests/preference/AndroidTest.xml
+++ b/tests/tests/preference/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Preference test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/preference2/AndroidTest.xml b/tests/tests/preference2/AndroidTest.xml
index b13eba5..a3fb234 100644
--- a/tests/tests/preference2/AndroidTest.xml
+++ b/tests/tests/preference2/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Preference test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsPreference2TestCases.apk" />
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowLandscapeTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowLandscapeTest.java
index e0f387b..8e042d7 100644
--- a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowLandscapeTest.java
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowLandscapeTest.java
@@ -101,14 +101,6 @@
     }
 
     /**
-     * Landscape setup of {@link #startWithFragmentAndInitTitleMultiWindowInner}.
-     */
-    @Test
-    public void startWithFragmentAndInitTitleMultiWindowLandscapeTest() {
-        startWithFragmentAndInitTitleMultiWindowInner();
-    }
-
-    /**
      * Landscape setup of {@link #startWithFragmentNoHeadersInner}.
      */
     @Test
@@ -125,14 +117,6 @@
     }
 
     /**
-     * Landscape setup of {@link #startWithFragmentNoHeadersMultiWindowTest}.
-     */
-    @Test
-    public void startWithFragmentNoHeadersMultiWindowLandscapeTest() {
-        startWithFragmentNoHeadersMultiWindowTest();
-    }
-
-    /**
      * Landscape setup of {@link #listDialogTest}.
      */
     @Test
@@ -156,38 +140,6 @@
         recreateInnerFragmentTest();
     }
 
-    /**
-     * Landscape setup of {@link #multiWindowInOutTest}.
-     */
-    @Test
-    public void multiWindowInOutLandscapeTest() {
-        multiWindowInOutTest();
-    }
-
-    /**
-     * Landscape setup of {@link #multiWindowInnerFragmentInOutTest}.
-     */
-    @Test
-    public void multiWindowInnerFragmentInOutLandscapeTest() {
-        multiWindowInnerFragmentInOutTest();
-    }
-
-    /**
-     * Landscape setup of {@link #multiWindowInitialHeaderOnBackTest}.
-     */
-    @Test
-    public void multiWindowInitialHeaderOnBackLandscapeTest() {
-        multiWindowInitialHeaderOnBackTest();
-    }
-
-    /**
-     * Landscape setup of {@link #multiWindowHistoryPreserveTest}.
-     */
-    @Test
-    public void multiWindowHistoryPreserveLandscapeTest() {
-        multiWindowHistoryPreserveTest();
-    }
-
     @Override
     protected PreferenceWithHeaders launchActivity(Intent intent) {
         if (intent != null) {
@@ -200,9 +152,7 @@
     @Override
     protected void runOnUiThread(final Runnable runnable) {
         try {
-            mActivityRule.runOnUiThread(() -> {
-                runnable.run();
-            });
+            mActivityRule.runOnUiThread(runnable);
         } catch (Throwable ex) {
             throw new RuntimeException("Failure on the UI thread", ex);
         }
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowPortraitTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowPortraitTest.java
index 6b484bd..48e7ef7 100644
--- a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowPortraitTest.java
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowPortraitTest.java
@@ -108,14 +108,6 @@
     }
 
     /**
-     * Portrait setup of {@link #startWithFragmentAndInitTitleMultiWindowInner}.
-     */
-    @Test
-    public void startWithFragmentAndInitTitleMultiWindowPortraitTest() {
-        startWithFragmentAndInitTitleMultiWindowInner();
-    }
-
-    /**
      * Portrait setup of {@link #startWithFragmentNoHeadersInner}.
      */
     @Test
@@ -132,14 +124,6 @@
     }
 
     /**
-     * Portrait setup of {@link #startWithFragmentNoHeadersMultiWindowTest}.
-     */
-    @Test
-    public void startWithFragmentNoHeadersMultiWindowPortraitTest() {
-        startWithFragmentNoHeadersMultiWindowTest();
-    }
-
-    /**
      * Portrait setup of {@link #listDialogTest}.
      */
     @Test
@@ -163,38 +147,6 @@
         recreateInnerFragmentTest();
     }
 
-    /**
-     * Portrait setup of {@link #multiWindowInOutTest}.
-     */
-    @Test
-    public void multiWindowInOutPortraitTest() {
-        multiWindowInOutTest();
-    }
-
-    /**
-     * Portrait setup of {@link #multiWindowInnerFragmentInOutTest}.
-     */
-    @Test
-    public void multiWindowInnerFragmentInOutPortraitTest() {
-        multiWindowInnerFragmentInOutTest();
-    }
-
-    /**
-     * Portrait setup of {@link #multiWindowInitialHeaderOnBackTest}.
-     */
-    @Test
-    public void multiWindowInitialHeaderOnBackPortraitTest() {
-        multiWindowInitialHeaderOnBackTest();
-    }
-
-    /**
-     * Portrait setup of {@link #multiWindowHistoryPreserveTest}.
-     */
-    @Test
-    public void multiWindowHistoryPreservePortraitTest() {
-        multiWindowHistoryPreserveTest();
-    }
-
     @Override
     protected PreferenceWithHeaders launchActivity(Intent intent) {
         if (intent != null) {
@@ -207,9 +159,7 @@
     @Override
     protected void runOnUiThread(final Runnable runnable) {
         try {
-            mActivityRule.runOnUiThread(() -> {
-                runnable.run();
-            });
+            mActivityRule.runOnUiThread(runnable);
         } catch (Throwable ex) {
             throw new RuntimeException("Failure on the UI thread", ex);
         }
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowTest.java
index 253f8f6..28071ac 100644
--- a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowTest.java
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityFlowTest.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.SystemClock;
-import android.preference2.cts.R;
 import android.util.Log;
 
 import com.android.compatibility.common.util.BitmapUtils;
@@ -325,36 +324,6 @@
     }
 
     /**
-     * For: Large screen (multi-pane).
-     * Scenario: Tests that initial title is displayed or hidden properly when transitioning in and
-     * out of the multi-window mode.
-     */
-    void startWithFragmentAndInitTitleMultiWindowInner() {
-        launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
-                false /* noHeaders */, INITIAL_TITLE_RES_ID);
-        if (!shouldRunLargeDeviceTest()) {
-            return;
-        }
-
-        assertInitialStateForFragment();
-        String testTitle = mActivity.getResources().getString(INITIAL_TITLE_RES_ID);
-
-        // Title should not be shown (we are in multi-pane).
-        assertFalse(mTestUtils.isTextShown(testTitle));
-
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.getMultiWindowFocus(mActivity);
-
-        // Title should be shown (we are in single-pane).
-        assertTextShown(testTitle);
-
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Title should not be shown (we are back in multi-pane).
-        assertTextHidden(testTitle);
-    }
-
-    /**
      * For: Any screen (single or multi-pane).
      * Scenario: Tests that EXTRA_NO_HEADERS intent arg that prevents showing headers in multi-pane
      * is applied correctly.
@@ -390,34 +359,6 @@
 
     /**
      * For: Any screen (single or multi-pane).
-     * Scenario: Tests that EXTRA_NO_HEADERS intent arg that prevents showing headers survives
-     * correctly multi-window changes. Tested via screenshots.
-     */
-    void startWithFragmentNoHeadersMultiWindowTest() {
-        launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
-                true /* noHeaders */, -1 /* initialTitle */);
-
-        assertInitialStateForFragment();
-
-        // Workaround for some focus bug in the framework
-        mTestUtils.tapOnViewWithText(PREFS2_PANEL_TITLE);
-
-        // Take screenshot
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        // Enter and leave multi-window.
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        assertInitialStateForFragment();
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
-    /**
-     * For: Any screen (single or multi-pane).
      * Scenario: Tests that list preference opens correctly and that back press correctly closes it.
      */
     void listDialogTest()  {
@@ -515,163 +456,6 @@
         assertScreenshotsAreEqual(before, after);
     }
 
-    /**
-     * For: Any screen (single or multi-pane).
-     * Scenario: Tests that the PreferenceActivity properly restores its state after going to
-     * multi-window and back. Test done via screenshots.
-     */
-    void multiWindowInOutTest() {
-        launchActivity();
-
-        assertInitialState();
-        // Tap on Prefs2 header.
-        tapOnPrefs2Header();
-
-        assertPanelPrefs2Shown();
-
-        // Take screenshot
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        // Enter and leave multi-window.
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        assertPanelPrefs2Shown();
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
-    /**
-     * For: Any screen (single or multi-pane).
-     * Scenario: Tests that the PreferenceActivity properly restores its state after going to
-     * multi-window and back while an inner fragment is shown. Test done via screenshots.
-     */
-    void multiWindowInnerFragmentInOutTest() {
-        launchActivity();
-
-        assertInitialState();
-        if (!mIsMultiPane) {
-            tapOnPrefs1Header();
-        }
-
-        // Go to preferences inner fragment.
-        mTestUtils.tapOnViewWithText(INNER_FRAGMENT_PREF_BUTTON);
-
-        // We don't need to check that correct panel is displayed that is already covered by
-        // smallScreenGoToFragmentInner and largeScreenGoToFragmentInner
-
-        // Take screenshot
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        // Enter and leave multi-window.
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
-    /**
-     * For: Large screen (single or multi-pane).
-     * Scenario: Goes to single-pane by entering multi-window and tests that back press ends up with
-     * a list of headers and nothing else. Then leaves multi-window back to single-pane and tests if
-     * the proper default header was opened (screenshot test).
-     */
-    void multiWindowInitialHeaderOnBackTest() {
-        launchActivity();
-        if (!shouldRunLargeDeviceTest()) {
-            return;
-        }
-
-        assertInitialState();
-
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        // Enter multi-window.
-        mTestUtils.enterMultiWindow(mActivity);
-
-        // Get window focus (otherwise back press would close multi-window instead of firing to the
-        // Activity.
-        mTestUtils.getMultiWindowFocus(mActivity);
-
-        pressBack();
-
-        // Only headers should be shown (also checks that we have correct focus).
-        assertHeadersShown();
-        assertPanelPrefs1Hidden();
-        assertPanelPrefs2Hidden();
-
-        // Leave multi-window
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Headers and Prefs1 should be shown.
-        assertHeadersShown();
-        assertPanelPrefs1Shown();
-        assertPanelPrefs2Hidden();
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
-    /**
-     * For: Large screen (multi-pane).
-     * Scenario: Tests that history is preserved correctly while transitioning to multi-window.
-     * Navigates to Prefs2 pane and then goes to single-pane mode via multi-window. Test that back
-     * press navigates to the headers list. Then tests that restoring multi-pane by leaving
-     * multi-window opens the same screen with which was the activity started before (screenshot
-     * test).
-     */
-    void multiWindowHistoryPreserveTest() {
-        launchActivity();
-        if (!shouldRunLargeDeviceTest()) {
-            return;
-        }
-
-        assertInitialState();
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        tapOnPrefs2Header();
-
-        // Enter multi-window.
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.getMultiWindowFocus(mActivity);
-
-        // Only Prefs2 should be shown (also checks that we have correct focus).
-        assertHeadersHidden();
-        assertPanelPrefs1Hidden();
-        assertPanelPrefs2Shown();
-
-        pressBack();
-
-        // Only headers should be shown.
-        assertHeadersShown();
-        assertPanelPrefs1Hidden();
-        assertPanelPrefs2Hidden();
-
-        tapOnPrefs1Header();
-
-        // Only Prefs1 should be shown.
-        assertHeadersHidden();
-        assertPanelPrefs1Shown();
-        assertPanelPrefs2Hidden();
-
-        // Leave multi-window
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Headers and Prefs1 should be shown.
-        assertHeadersShown();
-        assertPanelPrefs1Shown();
-        assertPanelPrefs2Hidden();
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
     private void assertScreenshotsAreEqual(Bitmap before, Bitmap after) {
         assertTrue("Screenshots do not match!", BitmapUtils.compareBitmaps(before, after));
     }
@@ -690,7 +474,6 @@
             assertPanelPrefs1Hidden();
             assertPanelPrefs2Hidden();
         }
-
         assertHeadersAreLoaded();
     }
 
@@ -708,8 +491,6 @@
             assertPanelPrefs1Hidden();
             assertPanelPrefs2Shown();
         }
-
-
     }
 
     public boolean shouldRunLargeDeviceTest() {
@@ -739,12 +520,10 @@
     }
 
     private void assertHeadersAreLoaded() {
-        runOnUiThread(() -> {
-            assertEquals(EXPECTED_HEADERS_COUNT,
-                    mActivity.loadedHeaders == null
-                            ? 0
-                            : mActivity.loadedHeaders.size());
-        });
+        runOnUiThread(() -> assertEquals(EXPECTED_HEADERS_COUNT,
+                mActivity.loadedHeaders == null
+                        ? 0
+                        : mActivity.loadedHeaders.size()));
     }
 
     private void assertHeadersShown() {
@@ -822,9 +601,7 @@
     private void launchActivity() {
         mActivity = launchActivity(null);
         mTestUtils.device.waitForIdle();
-        runOnUiThread(() -> {
-            mIsMultiPane = mActivity.isMultiPane();
-        });
+        runOnUiThread(() -> mIsMultiPane = mActivity.isMultiPane());
     }
 
     private void launchActivityWithExtras(Class extraFragment, boolean noHeaders,
@@ -843,9 +620,7 @@
 
         mActivity = launchActivity(intent);
         mTestUtils.device.waitForIdle();
-        runOnUiThread(() -> {
-            mIsMultiPane = mActivity.isMultiPane();
-        });
+        runOnUiThread(() -> mIsMultiPane = mActivity.isMultiPane());
     }
 
     protected abstract PreferenceWithHeaders launchActivity(Intent intent);
diff --git a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityLegacyFlowTest.java b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityLegacyFlowTest.java
index b3d8b8d..46863cb 100644
--- a/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityLegacyFlowTest.java
+++ b/tests/tests/preference2/src/android/preference2/cts/PreferenceActivityLegacyFlowTest.java
@@ -84,44 +84,6 @@
         assertScreenshotsAreEqual(before, after);
     }
 
-    /**
-     * Scenario: Tests that the activity still shows the preference screen even after multi-window
-     * is entered.
-     */
-    @Test
-    public void legacyActivityMultiWindowTest() {
-        waitForIdle();
-
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.getMultiWindowFocus(mActivity);
-
-        // Prefs list should be shown.
-        assertTextShown(LEGACY_SCREEN_TEXT);
-
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Prefs list should be shown.
-        assertTextShown(LEGACY_SCREEN_TEXT);
-    }
-
-    /**
-     * Scenario: Tests that the activity correctly restores its state after multi-window changes
-     * in legacy mode.
-     */
-    @Test
-    public void legacyActivityMultiWindowToggleTest() {
-        waitForIdle();
-
-        Bitmap before = mTestUtils.takeScreenshot();
-
-        mTestUtils.enterMultiWindow(mActivity);
-        mTestUtils.leaveMultiWindow(mActivity);
-
-        // Compare screenshots
-        Bitmap after = mTestUtils.takeScreenshot();
-        assertScreenshotsAreEqual(before, after);
-    }
-
     private void recreate() {
         runOnUiThread(() -> mActivity.recreate());
         SystemClock.sleep(1000);
@@ -130,9 +92,7 @@
 
     private void runOnUiThread(final Runnable runnable) {
         try {
-            mActivityRule.runOnUiThread(() -> {
-                runnable.run();
-            });
+            mActivityRule.runOnUiThread(() -> runnable.run());
         } catch (Throwable ex) {
             throw new RuntimeException("Failure on the UI thread", ex);
         }
diff --git a/tests/tests/preference2/src/android/preference2/cts/TestUtils.java b/tests/tests/preference2/src/android/preference2/cts/TestUtils.java
index e00e658..5eafca1 100644
--- a/tests/tests/preference2/src/android/preference2/cts/TestUtils.java
+++ b/tests/tests/preference2/src/android/preference2/cts/TestUtils.java
@@ -16,26 +16,21 @@
 
 package android.preference2.cts;
 
-import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.app.UiModeManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
+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 com.android.compatibility.common.util.SystemUtil;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import android.support.test.uiautomator.Until;
 
 /**
  * Collection of helper utils for testing preferences.
@@ -43,19 +38,24 @@
 public class TestUtils {
 
     final UiDevice device;
+
+    private final Context mContext;
     private final Instrumentation mInstrumentation;
+    private final String mPackageName;
     private final UiAutomation mAutomation;
     private int mStatusBarHeight = -1;
     private int mNavigationBarHeight = -1;
 
     TestUtils() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+        mPackageName = mContext.getPackageName();
         device = UiDevice.getInstance(mInstrumentation);
         mAutomation = mInstrumentation.getUiAutomation();
     }
 
     Bitmap takeScreenshot() {
-        // Only take screenshot once the screen is stable enough.
+        // Only take a screenshot once the screen is stable enough.
         device.waitForIdle();
 
         Bitmap bt = mAutomation.takeScreenshot();
@@ -66,8 +66,8 @@
         // Crop-out the navigation bar to avoid flakiness with button animations.
         int navigationBarHeight = getNavigationBarHeight();
 
-        // Crop the right side for scrollbar which might or might not be visible. On wearable
-        // devices the scroll bar is a curve and occupies 20% of the right side.
+        // Crop-out the right side for the scrollbar which may or may not be visible.
+        // On wearable devices the scroll bar is a curve and occupies 20% of the right side.
         int xToCut = isOnWatchUiMode() ? bt.getWidth() / 5 : bt.getWidth() / 20;
 
         bt = Bitmap.createBitmap(
@@ -77,142 +77,65 @@
         return bt;
     }
 
-    void tapOnViewWithText(String searchText) {
-        if (searchText == null) {
-            return;
+    void tapOnViewWithText(String text) {
+        UiObject2 object2 = getTextObject(text);
+        if (object2 != null) {
+          object2.click();
+          return;
         }
 
+        // If the view is a part of a scrollable, it might be offscreen
         try {
-            // If the current UI has shown text, just click on it.
-            UiObject text = new UiObject(new UiSelector().text(searchText));
-            if (text.exists() || text.waitForExists(1000)) {
-                text.click();
-                return;
-            }
-
-            // Otherwise, if it is scrollable, scroll to where the text is and tap.
             UiScrollable textScroll = new UiScrollable(new UiSelector().scrollable(true));
 
-            textScroll.scrollIntoView(new UiSelector().text(searchText));
-            text = new UiObject(new UiSelector().text(searchText));
-            text.click();
+            textScroll.scrollIntoView(new UiSelector().text(text));
+            UiObject object = new UiObject(new UiSelector().text(text));
+            object.click();
         } catch (UiObjectNotFoundException e) {
-            throw new AssertionError("View with text '" + searchText + "' was not found!", e);
+            throw new AssertionError("View with text '" + text + "' was not found!", e);
         }
     }
 
-    boolean isTextShown(String searchText) {
-        if (searchText == null) {
-            return false;
-        }
-
-        UiObject text = new UiObject(new UiSelector().text(searchText));
-        if (text.exists() || text.waitForExists(1000)) {
-            return true;
+    boolean isTextShown(String text) {
+        if (getTextObject(text) != null) {
+          return true;
         }
 
         UiScrollable textScroll = new UiScrollable(new UiSelector().scrollable(true));
         try {
-            return textScroll.scrollIntoView(new UiSelector().text(searchText));
+            return textScroll.scrollIntoView(new UiSelector().text(text));
         } catch (UiObjectNotFoundException e) {
             return false;
         }
     }
 
     boolean isTextHidden(String text) {
-        UiObject obj = device.findObject(new UiSelector().textMatches(text));
-        if (!obj.exists()) {
-            return true;
-        }
-        return obj.waitUntilGone(1000);
+        return getTextObject(text) == null;
     }
 
     boolean isTextFocused(String text) {
-        UiObject obj = device.findObject(new UiSelector().textMatches(text));
-        try {
-            return obj.isFocused();
-        } catch (UiObjectNotFoundException e) {
-            return false;
-        }
+        UiObject2 object = getTextObject(text);
+        return object != null && object.isFocused();
     }
 
     boolean isOnWatchUiMode() {
-        Context context = mInstrumentation.getTargetContext();
-        UiModeManager uiModeManager = context.getSystemService(UiModeManager.class);
+        UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
         return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_WATCH;
     }
 
-    void getMultiWindowFocus(Context context) {
-        // Get window focus (otherwise back press would close multi-window instead of firing to the
-        // Activity and also the automator would fail to find objects on the screen.
-        // We want to click slightly below status bar in the 1/3 of width of the screen.
-        int x = device.getDisplayWidth() / 3;
-        int resourceId =
-                context.getResources().getIdentifier("status_bar_height", "dimen", "android");
-        int statusBarHeight =
-                (resourceId > 0) ? context.getResources().getDimensionPixelSize(resourceId) : 0;
-        device.click(x, 2 * statusBarHeight);
-    }
-
-    // Multi-window helpers taken from ActivityManagerDockedStackTests.java
-
-    void enterMultiWindow(Activity activity) {
-        try {
-            int id = getActivityTaskId(activity);
-            runShellCommand("am stack move-task " + id + " 3 true");
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to get activity task id!", e);
-        }
-        SystemClock.sleep(5000);
-    }
-
-    void leaveMultiWindow(Activity activity) {
-        try {
-            int id = getActivityTaskId(activity);
-            runShellCommand("am stack move-task " + id + " 1 true");
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to get activity task id!", e);
-        }
-        SystemClock.sleep(5000);
-    }
-
-    private int getActivityTaskId(Activity activity) throws IOException {
-        // Taken from ActivityManagerTestBase.java
-        final String output = runShellCommand("am stack list");
-        final Pattern activityPattern =
-                Pattern.compile("(.*) " + getWindowName(activity) + " (.*)");
-        for (String line : output.split("\\n")) {
-            Matcher matcher = activityPattern.matcher(line);
-            if (matcher.matches()) {
-                for (String word : line.split("\\s+")) {
-                    if (word.startsWith("taskId")) {
-                        final String withColon = word.split("=")[1];
-                        return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
-                    }
-                }
-            }
-        }
-        return -1;
-    }
-
-    private String getWindowName(Activity activity) {
-        String componentName = activity.getPackageName();
-        String baseWindowName = componentName + "/" + componentName + ".";
-        return baseWindowName + activity.getClass().getSimpleName();
-    }
-
     private int getStatusBarHeight() {
         // Cache the result to keep it fast.
         if (mStatusBarHeight >= 0) {
             return mStatusBarHeight;
         }
 
-        mStatusBarHeight = 0;
         int resourceId = mInstrumentation.getTargetContext().getResources()
                 .getIdentifier("status_bar_height", "dimen", "android");
         if (resourceId > 0) {
             mStatusBarHeight = mInstrumentation.getTargetContext().getResources()
                     .getDimensionPixelSize(resourceId);
+        } else {
+            mStatusBarHeight = 0;
         }
         return mStatusBarHeight;
     }
@@ -223,21 +146,19 @@
             return mNavigationBarHeight;
         }
 
-        mNavigationBarHeight = 0;
         int resourceId = mInstrumentation.getTargetContext().getResources()
                 .getIdentifier("navigation_bar_height", "dimen", "android");
         if (resourceId > 0) {
             mNavigationBarHeight = mInstrumentation.getTargetContext().getResources()
                     .getDimensionPixelSize(resourceId);
+        } else {
+            mNavigationBarHeight = 0;
         }
         return mNavigationBarHeight;
     }
 
-    private String runShellCommand(String cmd) {
-        try {
-            return SystemUtil.runShellCommand(mInstrumentation, cmd);
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to run command: " + cmd, e);
-        }
+    private UiObject2 getTextObject(String text) {
+        // Wait for up to 1 second to find the object. Returns null if the object cannot be found.
+        return device.wait(Until.findObject(By.text(text).pkg(mPackageName)), 1000);
     }
 }
diff --git a/tests/tests/print/AndroidTest.xml b/tests/tests/print/AndroidTest.xml
index ffcaf65..8572398 100644
--- a/tests/tests/print/AndroidTest.xml
+++ b/tests/tests/print/AndroidTest.xml
@@ -17,12 +17,24 @@
     <option name="test-suite-tag" value="cts" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="component" value="print" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="cmd print set-bind-instant-service-allowed true" />
         <option name="teardown-command" value="cmd print set-bind-instant-service-allowed false" />
     </target_preparer>
 
+    <!-- Create place to store apks -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/print" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <!-- Load external print service APK onto device -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="CtsExternalPrintService.apk->/data/local/tmp/cts/print/CtsExternalPrintService.apk" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPrintTestCases.apk" />
diff --git a/tests/tests/print/ExternalPrintService/Android.mk b/tests/tests/print/ExternalPrintService/Android.mk
new file mode 100644
index 0000000..4cee3b7
--- /dev/null
+++ b/tests/tests/print/ExternalPrintService/Android.mk
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_PACKAGE_NAME := CtsExternalPrintService
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/print/ExternalPrintService/AndroidManifest.xml b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
new file mode 100644
index 0000000..ff480c1
--- /dev/null
+++ b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.print.cts.externalservice" >
+
+    <application android:allowBackup="false" >
+        <service
+            android:name=".ExternalService"
+            android:permission="android.permission.BIND_PRINT_SERVICE">
+          <intent-filter>
+              <action android:name="android.printservice.PrintService" />
+          </intent-filter>
+
+          <meta-data
+              android:name="android.printservice"
+              android:resource="@xml/printservice">
+          </meta-data>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/print/ExternalPrintService/res/xml/printservice.xml b/tests/tests/print/ExternalPrintService/res/xml/printservice.xml
new file mode 100644
index 0000000..a470cc7
--- /dev/null
+++ b/tests/tests/print/ExternalPrintService/res/xml/printservice.xml
@@ -0,0 +1,17 @@
+<!--
+  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.
+  -->
+
+<print-service />
diff --git a/tests/tests/print/ExternalPrintService/src/android/print/cts/externalservice/ExternalService.java b/tests/tests/print/ExternalPrintService/src/android/print/cts/externalservice/ExternalService.java
new file mode 100644
index 0000000..d50f15e
--- /dev/null
+++ b/tests/tests/print/ExternalPrintService/src/android/print/cts/externalservice/ExternalService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.print.cts.externalservice;
+
+import static android.print.PrinterInfo.STATUS_IDLE;
+
+import static org.junit.Assert.fail;
+
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ExternalService extends PrintService {
+    private static final String PRINTER_NAME = "ExternalServicePrinter";
+
+    @Override
+    protected PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+        return new PrinterDiscoverySession() {
+            @Override
+            public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
+                addPrinters(Collections.singletonList(
+                        (new PrinterInfo.Builder(generatePrinterId(PRINTER_NAME), PRINTER_NAME,
+                                STATUS_IDLE)).build()));
+            }
+
+            @Override
+            public void onStopPrinterDiscovery() {
+                // empty
+            }
+
+            @Override
+            public void onValidatePrinters(List<PrinterId> printerIds) {
+                // empty
+            }
+
+            @Override
+            public void onStartPrinterStateTracking(PrinterId printerId) {
+                // empty
+            }
+
+            @Override
+            public void onStopPrinterStateTracking(PrinterId printerId) {
+                // empty
+            }
+
+            @Override
+            public void onDestroy() {
+                // empty
+            }
+        };
+    }
+
+    @Override
+    protected void onRequestCancelPrintJob(PrintJob printJob) {
+        fail("This service does not support printing");
+    }
+
+    @Override
+    protected void onPrintJobQueued(PrintJob printJob) {
+        fail("This service does not support printing");
+    }
+}
diff --git a/tests/tests/print/printTestUtilLib/Android.bp b/tests/tests/print/printTestUtilLib/Android.bp
index b661a91..ae67c66 100644
--- a/tests/tests/print/printTestUtilLib/Android.bp
+++ b/tests/tests/print/printTestUtilLib/Android.bp
@@ -23,6 +23,7 @@
         "ub-uiautomator",
         "compatibility-device-util",
         "android-support-test",
+        "platformprotosnano",
     ],
 
     sdk_version: "test_current",
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
index 5c40dfc..ffe8ddb 100755
--- a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.GET_META_DATA;
 import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.print.test.Utils.eventually;
 import static android.print.test.Utils.getPrintManager;
 
 import static org.junit.Assert.assertFalse;
@@ -64,8 +65,10 @@
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -99,6 +102,7 @@
 import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -108,7 +112,7 @@
 public abstract class BasePrintTest {
     private static final String LOG_TAG = "BasePrintTest";
 
-    protected static final long OPERATION_TIMEOUT_MILLIS = 60000;
+    protected static final long OPERATION_TIMEOUT_MILLIS = 20000;
     protected static final String PRINT_JOB_NAME = "Test";
     static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
 
@@ -189,7 +193,7 @@
         sEnabledImes = null;
     }
 
-    protected static Instrumentation getInstrumentation() {
+    public static Instrumentation getInstrumentation() {
         return InstrumentationRegistry.getInstrumentation();
     }
 
@@ -205,7 +209,8 @@
         Log.d(LOG_TAG, "disableImes()");
         disableImes();
         Log.d(LOG_TAG, "disablePrintServices()");
-        disablePrintServices(instrumentation.getTargetContext().getPackageName());
+        sDisabledPrintServicesBefore = disablePrintServices(instrumentation.getTargetContext()
+                .getPackageName());
 
         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
         // Dexmaker is used by mockito.
@@ -219,12 +224,14 @@
      * Disable all print services beside the ones we want to leave enabled.
      *
      * @param packageToLeaveEnabled The package of the services to leave enabled.
+     *
+     * @return Services that were enabled before this method was called
      */
-    private static void disablePrintServices(@NonNull String packageToLeaveEnabled)
+    protected static @NonNull String disablePrintServices(@Nullable String packageToLeaveEnabled)
             throws IOException {
         Instrumentation instrumentation = getInstrumentation();
 
-        sDisabledPrintServicesBefore = SystemUtil.runShellCommand(instrumentation,
+        String previousEnabledServices = SystemUtil.runShellCommand(instrumentation,
                 "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
 
         Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
@@ -233,7 +240,8 @@
 
         StringBuilder builder = new StringBuilder();
         for (ResolveInfo service : installedServices) {
-            if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
+            if (packageToLeaveEnabled != null
+                    && packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
                 continue;
             }
             if (builder.length() > 0) {
@@ -245,15 +253,19 @@
 
         SystemUtil.runShellCommand(instrumentation, "settings put secure "
                 + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
+
+        return previousEnabledServices;
     }
 
     /**
      * Revert {@link #disablePrintServices(String)}
+     *
+     * @param servicesToEnable Services that should be enabled
      */
-    private static  void enablePrintServices() throws IOException {
+    protected static void enablePrintServices(@NonNull String servicesToEnable) throws IOException {
         SystemUtil.runShellCommand(getInstrumentation(),
                 "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
-                        + sDisabledPrintServicesBefore);
+                        + servicesToEnable);
     }
 
     @Before
@@ -315,7 +327,7 @@
         Instrumentation instrumentation = getInstrumentation();
 
         Log.d(LOG_TAG, "enablePrintServices()");
-        enablePrintServices();
+        enablePrintServices(sDisabledPrintServicesBefore);
 
         Log.d(LOG_TAG, "enableImes()");
         enableImes();
@@ -551,68 +563,62 @@
     protected void waitForPrinterUnavailable() throws Exception {
         final String printerUnavailableMessage = "This printer isn\'t available right now.";
 
-        UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/message"));
+        UiObject2 message = getUiDevice().wait(Until.findObject(
+                By.res("com.android.printspooler:id/message")), OPERATION_TIMEOUT_MILLIS);
+
+        if (message == null) {
+            dumpWindowHierarchy();
+            throw new UiObjectNotFoundException("Cannot find " + printerUnavailableMessage);
+        }
         if (!message.getText().equals(printerUnavailableMessage)) {
             throw new Exception("Wrong message: " + message.getText() + " instead of "
                     + printerUnavailableMessage);
         }
     }
 
-    protected void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
+    protected void selectPrinter(String printerName) throws IOException, UiObjectNotFoundException {
+        selectPrinter(printerName, OPERATION_TIMEOUT_MILLIS);
+    }
+
+    protected void selectPrinter(String printerName, long timeout) throws IOException,
+            UiObjectNotFoundException {
         try {
-            long delay = 1;
-            while (true) {
-                try {
-                    UiDevice uiDevice = getUiDevice();
-                    UiObject destinationSpinner = uiDevice.findObject(new UiSelector()
-                            .resourceId("com.android.printspooler:id/destination_spinner"));
+            UiDevice uiDevice = getUiDevice();
+            UiObject2 destinationSpinner = uiDevice.wait(Until.findObject(
+                    By.res("com.android.printspooler:id/destination_spinner")), timeout);
 
-                    destinationSpinner.click();
-                    getUiDevice().waitForIdle();
-
-                    // Give spinner some time to expand
-                    try {
-                        Thread.sleep(delay);
-                    } catch (InterruptedException e) {
-                        // ignore
-                    }
-
-                    // try to select printer
-                    UiObject printerOption = uiDevice.findObject(
-                            new UiSelector().text(printerName));
-                    printerOption.click();
-                } catch (UiObjectNotFoundException e) {
-                    Log.e(LOG_TAG, "Could not select printer " + printerName, e);
-                }
-
+            if (destinationSpinner != null) {
+                destinationSpinner.click();
                 getUiDevice().waitForIdle();
-
-                if (!printerName.equals("All printers…")) {
-                    // Make sure printer is selected
-                    if (getUiDevice().hasObject(By.text(printerName))) {
-                        break;
-                    } else {
-                        if (delay <= OPERATION_TIMEOUT_MILLIS) {
-                            Log.w(LOG_TAG, "Cannot find printer " + printerName + ", retrying.");
-                            delay *= 2;
-                        } else {
-                            throw new UiObjectNotFoundException(
-                                    "Could find printer " + printerName
-                                            + " even though we retried");
-                        }
-                    }
-                } else {
-                    break;
-                }
             }
-        } catch (UiObjectNotFoundException e) {
+
+            selectPrinterSpinnerOpen(printerName, timeout);
+        } catch (Exception e) {
+            dumpWindowHierarchy();
+            throw e;
+        }
+    }
+
+    protected void selectPrinterSpinnerOpen(String printerName, long timeout)
+            throws IOException, UiObjectNotFoundException {
+        try {
+            UiDevice uiDevice = getUiDevice();
+            UiObject2 printerOption = uiDevice.wait(Until.findObject(By.text(printerName)),
+                    timeout);
+            if (printerOption == null) {
+                throw new UiObjectNotFoundException(printerName + " not found");
+            }
+
+            printerOption.click();
+            getUiDevice().waitForIdle();
+        } catch (Exception e) {
             dumpWindowHierarchy();
             throw e;
         }
     }
 
     protected void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
+        getUiDevice().waitForIdle();
         UiObject button;
         if (confirm) {
             button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button1"));
@@ -728,14 +734,15 @@
     }
 
     public void clickPrintButton() throws UiObjectNotFoundException, IOException {
-        try {
-            UiObject printButton = getUiDevice().findObject(new UiSelector().resourceId(
-                    "com.android.printspooler:id/print_button"));
-            printButton.click();
-        } catch (UiObjectNotFoundException e) {
+        getUiDevice().waitForIdle();
+
+        UiObject2 printButton = getUiDevice().wait(Until.findObject(By.res(
+                "com.android.printspooler:id/print_button")), OPERATION_TIMEOUT_MILLIS);
+        if (printButton == null) {
             dumpWindowHierarchy();
-            throw e;
+            throw new UiObjectNotFoundException("print button not found");
         }
+        printButton.click();
     }
 
     protected void clickRetryButton() throws UiObjectNotFoundException, IOException {
@@ -864,9 +871,7 @@
 
                     callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
                             .setPageCount(numPages).build(),
-                            !oldAttributes.equals(printAttributes[0]));
-
-                    oldAttributes = printAttributes[0];
+                            !Objects.equals(oldAttributes, printAttributes[0]));
 
                     onLayoutCalled();
                     return null;
@@ -969,18 +974,22 @@
     }
 
     protected void selectPages(String pages, int totalPages) throws Exception {
+        getUiDevice().waitForIdle();
         UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
                 "com.android.printspooler:id/range_options_spinner"));
         pagesSpinner.click();
 
+        getUiDevice().waitForIdle();
         UiObject rangeOption = getUiDevice().findObject(new UiSelector().textContains("Range of "
                 + totalPages));
         rangeOption.click();
 
+        getUiDevice().waitForIdle();
         UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
                 "com.android.printspooler:id/page_range_edittext"));
         pagesEditText.setText(pages);
 
+        getUiDevice().waitForIdle();
         // Hide the keyboard.
         getUiDevice().pressBack();
     }
@@ -1037,7 +1046,7 @@
      * @throws Exception If the printer could not be made default
      */
     public void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
-            throws Exception {
+            throws Throwable {
         // Perform a full print operation on the printer
         Log.d(LOG_TAG, "print");
         print(adapter);
@@ -1047,10 +1056,13 @@
         selectPrinter(printerName);
         Log.d(LOG_TAG, "clickPrintButton");
         clickPrintButton();
-        Log.d(LOG_TAG, "answerPrintServicesWarning");
-        answerPrintServicesWarning(true);
-        Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+        eventually(() -> {
+            Log.d(LOG_TAG, "answerPrintServicesWarning");
+            answerPrintServicesWarning(true);
+            Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Switch to new activity, which should now use the default printer
         Log.d(LOG_TAG, "getActivity().finish()");
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
index 7e40bad..b7f9fe6 100644
--- a/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
@@ -16,14 +16,23 @@
 
 package android.print.test;
 
+import static android.print.test.BasePrintTest.getInstrumentation;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ParcelFileDescriptor;
 import android.print.PrintJob;
 import android.print.PrintManager;
-import androidx.annotation.NonNull;
+import android.service.print.nano.PrintServiceDumpProto;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Utilities for print tests
  */
@@ -97,16 +106,18 @@
      * Make sure that a {@link Invokable} eventually finishes without throwing a {@link Throwable}.
      *
      * @param r The {@link Invokable} to run.
+     * @param timeout the maximum time to wait
      */
-    public static void eventually(@NonNull Invokable r) throws Throwable {
-        long start = System.currentTimeMillis();
+    public static void eventually(@NonNull Invokable r, long timeout) throws Throwable {
+        long start = System.nanoTime();
 
         while (true) {
             try {
                 r.run();
                 break;
             } catch (Throwable e) {
-                if (System.currentTimeMillis() - start < BasePrintTest.OPERATION_TIMEOUT_MILLIS) {
+                if (System.nanoTime() - start < TimeUnit.NANOSECONDS.convert(timeout,
+                        TimeUnit.MILLISECONDS)) {
                     Log.e(LOG_TAG, "Ignoring exception", e);
 
                     try {
@@ -122,6 +133,15 @@
     }
 
     /**
+     * Make sure that a {@link Invokable} eventually finishes without throwing a {@link Throwable}.
+     *
+     * @param r The {@link Invokable} to run.
+     */
+    public static void eventually(@NonNull Invokable r) throws Throwable {
+        eventually(r, BasePrintTest.OPERATION_TIMEOUT_MILLIS);
+    }
+
+    /**
      * @param name Name of print job
      *
      * @return The print job for the name
@@ -145,4 +165,30 @@
     public static @NonNull PrintManager getPrintManager(@NonNull Context context) {
         return (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
     }
+
+    /**
+     * Get the {@link PrintServiceDumpProto}
+     */
+    public static PrintServiceDumpProto getProtoDump() throws Exception {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand("dumpsys print --proto");
+
+        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+            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 PrintServiceDumpProto.parseFrom(os.toByteArray());
+        }
+    }
 }
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
index 09d1f78..cc82d04 100644
--- a/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
@@ -17,13 +17,16 @@
 package android.print.test.services;
 
 import android.content.Context;
+import android.print.PrinterId;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
 import android.printservice.PrinterDiscoverySession;
+import android.util.Log;
 
 import java.util.List;
 
 public abstract class StubbablePrintService extends PrintService {
+    private static final String LOG_TAG = StubbablePrintService.class.getSimpleName();
 
     @Override
     public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
@@ -32,7 +35,39 @@
             return new StubbablePrinterDiscoverySession(this,
                     getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
         }
-        return null;
+
+        Log.w(LOG_TAG, "onCreatePrinterDiscoverySession called but no callbacks are set up");
+        return new PrinterDiscoverySession() {
+            @Override
+            public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
+                // empty
+            }
+
+            @Override
+            public void onStopPrinterDiscovery() {
+                // empty
+            }
+
+            @Override
+            public void onValidatePrinters(List<PrinterId> printerIds) {
+                // empty
+            }
+
+            @Override
+            public void onStartPrinterStateTracking(PrinterId printerId) {
+                // empty
+            }
+
+            @Override
+            public void onStopPrinterStateTracking(PrinterId printerId) {
+                // empty
+            }
+
+            @Override
+            public void onDestroy() {
+                // empty
+            }
+        };
     }
 
     @Override
diff --git a/tests/tests/print/src/android/print/cts/InstallBehavior.java b/tests/tests/print/src/android/print/cts/InstallBehavior.java
new file mode 100644
index 0000000..ea0f399
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/InstallBehavior.java
@@ -0,0 +1,91 @@
+/*
+ * 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.print.cts;
+
+import static org.junit.Assert.fail;
+
+import android.print.PrintManager;
+import android.print.test.BasePrintTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+
+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.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Behavior of Android when a print service is installed
+ */
+@RunWith(AndroidJUnit4.class)
+public class InstallBehavior extends BasePrintTest {
+    private static String sPreviousEnabledServices;
+
+    @BeforeClass
+    public static void disableAllPrintServices() throws Exception {
+        sPreviousEnabledServices = disablePrintServices(null);
+    }
+
+    @Before
+    @After
+    public void uninstallExternalPrintService() throws Exception {
+        SystemUtil.runShellCommand(getInstrumentation(),
+                "pm uninstall android.print.cts.externalservice");
+    }
+
+    @AfterClass
+    public static void reenablePrintServices() throws Exception {
+        enablePrintServices(sPreviousEnabledServices);
+    }
+
+    /**
+     * Printers from a newly installed print service should show up immediately.
+     *
+     * <p>This tests:
+     * <ul>
+     *     <li>Print services are enabled by default</li>
+     *     <li>Print services get added to already running printer discovery sessions</li>
+     * </ul>
+     */
+    @Test
+    public void installedServiceIsEnabled() throws Exception {
+        getActivity().getSystemService(PrintManager.class).print("printjob",
+                createDefaultPrintDocumentAdapter(1), null);
+
+        waitForWriteAdapterCallback(1);
+
+        // Printer should not be available
+        try {
+            selectPrinter("ExternalServicePrinter", 500);
+            fail();
+        } catch (UiObjectNotFoundException expected) {
+            // expected
+        }
+
+        SystemUtil.runShellCommand(getInstrumentation(),
+                "pm install /data/local/tmp/cts/print/CtsExternalPrintService.apk");
+
+        selectPrinterSpinnerOpen("ExternalServicePrinter", OPERATION_TIMEOUT_MILLIS);
+
+        // Exit print preview and thereby end printing
+        getUiDevice().pressBack();
+    }
+}
diff --git a/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java b/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java
new file mode 100644
index 0000000..897c524
--- /dev/null
+++ b/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java
@@ -0,0 +1,188 @@
+/*
+ * 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.print.cts;
+
+import static android.print.PrintAttributes.COLOR_MODE_COLOR;
+import static android.print.PrintAttributes.MediaSize.ISO_A5;
+import static android.print.PrinterInfo.STATUS_IDLE;
+import static android.print.PrinterInfo.STATUS_UNAVAILABLE;
+import static android.print.test.Utils.runOnMainThread;
+
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests interaction between printer discovery and print document lifecycle
+ */
+@RunWith(AndroidJUnit4.class)
+public class InteractionBetweenPrintDocumentAndPrinterDiscovery extends BasePrintTest {
+    static final String PRINTER_NAME = "Test printer";
+
+    @Before
+    public void clearPrintSpoolerState() throws Exception {
+        clearPrintSpoolerData();
+    }
+
+    /**
+     * Add or update the test printer.
+     *
+     * @param session The printer discovery session the printer belongs to
+     * @param status  the status of the printer (idle, unavailable, busy)
+     */
+    private static void addPrinter(StubbablePrinterDiscoverySession session, int status) {
+        List<PrinterInfo> printers = new ArrayList<>();
+
+        PrinterId testId = session.getService().generatePrinterId(PRINTER_NAME);
+        PrinterCapabilitiesInfo testCap =
+                new PrinterCapabilitiesInfo.Builder(testId)
+                        .setMinMargins(new Margins(0, 0, 0, 0))
+                        .addMediaSize(ISO_A5, true)
+                        .addResolution(new Resolution("r", "r", 1, 1), true)
+                        .setColorModes(COLOR_MODE_COLOR, COLOR_MODE_COLOR)
+                        .build();
+        PrinterInfo testPrinter = new PrinterInfo.Builder(testId, PRINTER_NAME, status)
+                .setCapabilities(testCap).build();
+        printers.add(testPrinter);
+
+        session.addPrinters(printers);
+    }
+
+    /**
+     * While a print document adapter write task is getting canceled, make the printer unavailable
+     * and available again.
+     */
+    @Test
+    public void printerReappearsWhileCanceling() throws Throwable {
+        // The session once initialized
+        StubbablePrinterDiscoverySession[] session = new StubbablePrinterDiscoverySession[1];
+
+        // Set up discovery session
+        final PrinterDiscoverySessionCallbacks callbacks =
+                createMockPrinterDiscoverySessionCallbacks(inv -> {
+                    session[0] = ((PrinterDiscoverySessionCallbacks) inv.getMock()).getSession();
+
+                    addPrinter(session[0], STATUS_IDLE);
+                    return null;
+                }, null, null, null, null, null, invocation -> {
+                    onPrinterDiscoverySessionDestroyCalled();
+                    return null;
+                });
+
+        // Set up print services
+        FirstPrintService.setCallbacks(createMockPrintServiceCallbacks((inv) -> callbacks, null,
+                null));
+        SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
+
+        // The print attributes set in the layout task
+        PrintAttributes[] printAttributes = new PrintAttributes[1];
+
+        // The callback for the last write task
+        WriteResultCallback[] writeCallBack = new WriteResultCallback[1];
+
+        // The adapter that will handle the print tasks
+        final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
+                invocation -> {
+                    Object[] args = invocation.getArguments();
+
+                    printAttributes[0] = (PrintAttributes) args[1];
+                    LayoutResultCallback callback = (LayoutResultCallback) args[3];
+                    PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME).build();
+                    callback.onLayoutFinished(info, true);
+
+                    onLayoutCalled();
+                    return null;
+                }, invocation -> {
+                    Object[] args = invocation.getArguments();
+
+                    ((CancellationSignal) args[2]).setOnCancelListener(this::onWriteCancelCalled);
+
+                    // Write single page document
+                    ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+                    writeBlankPages(printAttributes[0], fd, 0, 1);
+                    fd.close();
+
+                    writeCallBack[0] = (WriteResultCallback) args[3];
+
+                    onWriteCalled();
+                    return null;
+                }, null);
+
+        // Start printing
+        print(adapter);
+
+        // Handle write for default printer
+        waitForWriteAdapterCallback(1);
+        writeCallBack[0].onWriteFinished(new PageRange[]{new PageRange(0, 1)});
+
+        // Select test printer
+        selectPrinter(PRINTER_NAME);
+
+        // Selecting the printer changes the document size to A5 which causes a new layout + write
+        // Do _not_ call back from the write immediately
+        waitForLayoutAdapterCallbackCount(2);
+        waitForWriteAdapterCallback(2);
+
+        // While write task is in progress, make the printer unavailable
+        runOnMainThread(() -> addPrinter(session[0], STATUS_UNAVAILABLE));
+
+        // This should cancel the write task
+        waitForWriteCancelCallback(1);
+
+        // Make the printer available again, this should add a new task that re-tries the canceled
+        // task
+        runOnMainThread(() -> addPrinter(session[0], STATUS_IDLE));
+
+        // Give print preview UI some time to schedule new write task
+        Thread.sleep(500);
+
+        // Report write from above as canceled
+        writeCallBack[0].onWriteCancelled();
+
+        // Now the write that was canceled before should have been retried
+        waitForWriteAdapterCallback(3);
+
+        // Finish test: Fail pending write, exit print activity and wait for print subsystem to shut
+        // down
+        writeCallBack[0].onWriteFailed(null);
+        getUiDevice().pressBack();
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+    }
+}
diff --git a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
index 8ec44fe..4431644 100644
--- a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
+++ b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
@@ -183,7 +183,7 @@
     }
 
     @Before
-    public void setPrinter() throws Exception {
+    public void setPrinter() throws Throwable {
         if (!sHasBeenSetUp) {
             resetCounters();
             PrintDocumentAdapter adapter = setupPrint(PrintJobInfo.STATE_COMPLETED);
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
index 6ad2896..d697930 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
@@ -45,9 +45,10 @@
 import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
-import androidx.annotation.NonNull;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -79,7 +80,7 @@
     private final boolean mWriteAllPages;
 
     @Before
-    public void setDefaultPrinter() throws Exception {
+    public void setDefaultPrinter() throws Throwable {
         if (!sIsDefaultPrinterSet) {
             // Create a callback for the target print service.
             PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index c1abafc..a5e20f3 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -69,7 +69,7 @@
     private static boolean sIsDefaultPrinterSet;
 
     @Before
-    public void setDefaultPrinter() throws Exception {
+    public void setDefaultPrinter() throws Throwable {
         if (!sIsDefaultPrinterSet) {
             // Create a callback for the target print service.
             PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
diff --git a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
index 7661ff9..6a95e23 100644
--- a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
@@ -321,7 +321,7 @@
     }
 
     @Before
-    public void setUpServicesAndAdapter() throws Exception {
+    public void setUpServicesAndAdapter() throws Throwable {
         if (!sHasBeenSetup) {
             // Set up printer with supported and default attributes
             PrintDocumentAdapter adapter =
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 706f971..44c529d 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -16,6 +16,8 @@
 
 package android.print.cts;
 
+import static android.print.test.Utils.eventually;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.inOrder;
@@ -78,7 +80,7 @@
     }
 
     @Test
-    public void noPrintOptionsOrPrinterChange() throws Exception {
+    public void noPrintOptionsOrPrinterChange() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -126,11 +128,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -309,7 +313,7 @@
     }
 
     @Test
-    public void nonChangePrinterWhileNotWritten() throws Exception {
+    public void nonChangePrinterWhileNotWritten() throws Throwable {
         final PrintAttributes[] lastLayoutPrintAttributes = new PrintAttributes[1];
         final boolean[] isWriteBroken = new boolean[1];
 
@@ -374,14 +378,16 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
     }
 
     @Test
-    public void printOptionsChangeAndNoPrinterChange() throws Exception {
+    public void printOptionsChangeAndNoPrinterChange() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -457,11 +463,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -570,7 +578,7 @@
     }
 
     /* Disabled @Test, as this will intentionally kill the activity that started the test */
-    public void printCorruptedFile() throws Exception {
+    public void printCorruptedFile() throws Throwable {
         final boolean[] writeCorruptedFile = new boolean[1];
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
@@ -640,13 +648,15 @@
         // Click the print button
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Printing will abort automatically
+            // Printing will abort automatically
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -654,7 +664,7 @@
 
 
     @Test
-    public void printOptionsChangeAndPrinterChange() throws Exception {
+    public void printOptionsChangeAndPrinterChange() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -719,11 +729,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -804,7 +816,7 @@
 
     @Test
     public void printOptionsChangeAndNoPrinterChangeAndContentChange()
-            throws Exception {
+            throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -856,11 +868,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -922,7 +936,7 @@
     }
 
     @Test
-    public void newPrinterSupportsSelectedPrintOptions() throws Exception {
+    public void newPrinterSupportsSelectedPrintOptions() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -969,11 +983,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -1016,7 +1032,7 @@
     }
 
     @Test
-    public void nothingChangesAllPagesWrittenFirstTime() throws Exception {
+    public void nothingChangesAllPagesWrittenFirstTime() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         // Create a mock print adapter.
@@ -1067,11 +1083,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for a finish.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for a finish.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -1405,7 +1423,7 @@
     }
 
     @Test
-    public void unexpectedLayoutCancel() throws Exception {
+    public void unexpectedLayoutCancel() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
         final int[] numLayoutCalls = new int[1];
 
@@ -1462,15 +1480,17 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for the session to be destroyed to isolate tests.
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+            // Wait for the session to be destroyed to isolate tests.
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
     }
 
     @Test
-    public void unexpectedWriteCancel() throws Exception {
+    public void unexpectedWriteCancel() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
         final int[] numWriteCalls = new int[1];
 
@@ -1529,11 +1549,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for the session to be destroyed to isolate tests.
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+            // Wait for the session to be destroyed to isolate tests.
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
     }
 
     @Test
@@ -1757,7 +1779,13 @@
                     // Mark layout called.
                     onLayoutCalled();
                     return null;
-                }, null, invocation -> {
+                }, invocation -> {
+                    WriteResultCallback callback = (WriteResultCallback) invocation
+                            .getArguments()[3];
+                    callback.onWriteFailed(null);
+                    onWriteCalled();
+                    return null;
+                }, invocation -> {
                     // Mark finish was called.
                     onFinishCalled();
                     return null;
@@ -1768,6 +1796,7 @@
 
         // Wait for layout.
         waitForLayoutAdapterCallbackCount(1);
+        waitForWriteAdapterCallback(1);
 
         // Cancel printing.
         getUiDevice().pressBack(); // wakes up the device.
@@ -1855,6 +1884,7 @@
 
         // Wait for layout.
         waitForLayoutAdapterCallbackCount(1);
+        waitForWriteAdapterCallback(1);
 
         // Cancel printing.
         getUiDevice().pressBack(); // wakes up the device.
@@ -1900,7 +1930,7 @@
      * @throws Exception If anything is unexpected
      */
     @Test
-    public void notEnoughPages() throws Exception {
+    public void notEnoughPages() throws Throwable {
         final PrintAttributes[] printAttributes = new PrintAttributes[1];
 
         final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
@@ -1940,10 +1970,12 @@
         selectPrinter("First printer");
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        waitForAdapterFinishCallbackCalled();
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
index 63e83e3..b57664a 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
@@ -61,7 +61,7 @@
     private static boolean sIsDefaultPrinterSet;
 
     @Before
-    public void setDefaultPrinter() throws Exception {
+    public void setDefaultPrinter() throws Throwable {
         if (!sIsDefaultPrinterSet) {
             // Create a callback for the target print service.
             FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
@@ -107,9 +107,12 @@
                 invocation -> printerDiscoverySessionCallbacks,
                 invocation -> {
                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
-                    queuedInfo[0] = printJob.getDocument().getInfo();
-                    queuedData[0] = printJob.getDocument().getData();
+                    synchronized (queuedInfo) {
+                        queuedInfo[0] = printJob.getDocument().getInfo();
+                        queuedData[0] = printJob.getDocument().getData();
+                    }
                     printJob.complete();
+                    onPrintJobQueuedCalled();
                     return null;
                 }, null);
 
@@ -127,12 +130,15 @@
                     return null;
                 }, invocation -> {
                     Object[] args = invocation.getArguments();
-                    PageRange[] pages = (PageRange[]) args[0];
                     ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
                     WriteResultCallback callback = (WriteResultCallback) args[3];
                     writeBlankPages(printAttributes[0], fd, 0, 1);
                     fd.close();
-                    callback.onWriteFinished(pages);
+                    if (pageCount != null && pageCount > 0) {
+                        callback.onWriteFinished(new PageRange[]{new PageRange(0, pageCount - 1)});
+                    } else {
+                        callback.onWriteFinished(new PageRange[]{new PageRange(0, 1)});
+                    }
                     onWriteCalled();
                     return null;
                 }, invocation -> null);
@@ -148,9 +154,14 @@
 
         // Wait for the session to be destroyed to isolate tests.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        waitForServiceOnPrintJobQueuedCallbackCalled(1);
 
         // Check that the document name was carried over 1:1
-        eventually(() -> assertEquals(name, queuedInfo[0].getName()));
+        eventually(() -> {
+            synchronized (queuedInfo) {
+                assertEquals(name, queuedInfo[0].getName());
+            }
+        });
 
         // Content type is set to document by default, but is otherwise unrestricted
         if (contentType != null) {
@@ -191,7 +202,7 @@
      */
     @Test
     public void documentInfoNothingSet() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, null, null);
+        printDocumentBaseTest("documentInfoNothingSet", null, null);
     }
 
     /**
@@ -201,7 +212,8 @@
      */
     @Test
     public void documentInfoUnknownPageCount() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, null, PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
+        printDocumentBaseTest("documentInfoUnknownPageCount", null,
+                PrintDocumentInfo.PAGE_COUNT_UNKNOWN);
     }
 
     /**
@@ -211,7 +223,7 @@
      */
     @Test
     public void documentInfoZeroPageCount() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, null, 0);
+        printDocumentBaseTest("documentInfoZeroPageCount", null, 0);
     }
 
     /**
@@ -221,7 +233,7 @@
      */
     @Test
     public void documentInfoOnePageCount() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, null, 1);
+        printDocumentBaseTest("documentInfoOnePageCount", null, 1);
     }
 
     /**
@@ -231,7 +243,7 @@
      */
     @Test
     public void documentInfoThreePageCount() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, null, 3);
+        printDocumentBaseTest("documentInfoThreePageCount", null, 3);
     }
 
     /**
@@ -241,7 +253,8 @@
      */
     @Test
     public void documentInfoContentTypePhoto() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, PrintDocumentInfo.CONTENT_TYPE_PHOTO, null);
+        printDocumentBaseTest("documentInfoContentTypePhoto", PrintDocumentInfo.CONTENT_TYPE_PHOTO,
+                null);
     }
 
     /**
@@ -251,7 +264,8 @@
      */
     @Test
     public void documentInfoContentTypeUnknown() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, PrintDocumentInfo.CONTENT_TYPE_UNKNOWN, null);
+        printDocumentBaseTest("documentInfoContentTypeUnknown",
+                PrintDocumentInfo.CONTENT_TYPE_UNKNOWN, null);
     }
 
     /**
@@ -261,7 +275,7 @@
      */
     @Test
     public void documentInfoContentTypeNonDefined() throws Throwable {
-        printDocumentBaseTest(PRINT_JOB_NAME, -23, null);
+        printDocumentBaseTest("documentInfoContentTypeNonDefined", -23, null);
     }
 
     private PrinterDiscoverySessionCallbacks createFirstMockDiscoverySessionCallbacks() {
diff --git a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
index 75a7847..6eba1ff 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
@@ -265,7 +265,7 @@
     }
 
     @Before
-    public void setPrinter() throws Exception {
+    public void setPrinter() throws Throwable {
         if (!sHasBeenSetUp) {
             createActivity();
 
diff --git a/tests/tests/print/src/android/print/cts/PrintJobTest.java b/tests/tests/print/src/android/print/cts/PrintJobTest.java
index 2ab52b3..2c2de48 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobTest.java
@@ -147,8 +147,7 @@
      *
      * @throws Exception If anything is unexpected.
      */
-    private void baseTest(PrintJobTestFn testFn)
-            throws Exception {
+    private void baseTest(PrintJobTestFn testFn) throws Throwable {
         testSuccess[0] = false;
 
         // Create the session of the printers that we will be checking.
@@ -168,12 +167,17 @@
         // Create a print adapter that respects the print contract.
         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
 
+
         // Start printing.
         print(adapter);
-        clickPrintButton();
+        waitForWriteAdapterCallback(1);
 
-        // Wait for print job to be queued
-        waitForServiceOnPrintJobQueuedCallbackCalled(1);
+        eventually(() -> {
+            clickPrintButton();
+
+            // Wait for print job to be queued
+            waitForServiceOnPrintJobQueuedCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Wait for discovery session to be destroyed to isolate tests from each other
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -211,7 +215,7 @@
     }
 
     @Before
-    public void setPrinter() throws Exception {
+    public void setPrinter() throws Throwable {
         if (!sHasBeenSetUp) {
             resetCounters();
             PrinterDiscoverySessionCallbacks sessionCallbacks
@@ -239,7 +243,7 @@
     }
 
     @Test
-    public void blockWithReason() throws Exception {
+    public void blockWithReason() throws Throwable {
         baseTest(printJob -> {
             printJob.start();
             checkState(printJob, PrintJobInfo.STATE_STARTED);
@@ -267,7 +271,7 @@
     }
 
     @Test
-    public void failWithReason() throws Exception {
+    public void failWithReason() throws Throwable {
         baseTest(printJob -> {
             printJob.start();
             checkState(printJob, PrintJobInfo.STATE_STARTED);
@@ -287,7 +291,7 @@
     }
 
     @Test
-    public void tag() throws Exception {
+    public void tag() throws Throwable {
         baseTest(printJob -> {
             // Default value should be null
             assertNull(printJob.getTag());
@@ -451,7 +455,7 @@
     }
 
     @Test
-    public void other() throws Exception {
+    public void other() throws Throwable {
         baseTest(printJob -> {
             assertNotNull(printJob.getDocument());
             assertNotNull(printJob.getId());
@@ -459,7 +463,7 @@
     }
 
     @Test
-    public void setStatus() throws Exception {
+    public void setStatus() throws Throwable {
         baseTest(printJob -> {
             printJob.start();
 
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index b5a23e2..9da3ad3 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -60,9 +60,13 @@
 import android.printservice.PrintJob;
 import android.printservice.PrintService;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+import androidx.annotation.NonNull;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -86,6 +90,16 @@
     /** The custom printer icon to use */
     private Icon mIcon;
 
+    private @NonNull PrinterCapabilitiesInfo getDefaultOptionPrinterCapabilites(
+            @NonNull PrinterId printerId) {
+        return new PrinterCapabilitiesInfo.Builder(printerId)
+                .setMinMargins(new Margins(200, 200, 200, 200))
+                .addMediaSize(MediaSize.ISO_A4, true)
+                .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+                .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+                        PrintAttributes.COLOR_MODE_COLOR).build();
+    }
+
     /**
      * Create a mock {@link PrinterDiscoverySessionCallbacks} that discovers a single printer with
      * minimal capabilities.
@@ -93,7 +107,7 @@
      * @return The mock session callbacks
      */
     private PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
-            String printerName, ArrayList<String> trackedPrinters) {
+            String printerName) {
         return createMockPrinterDiscoverySessionCallbacks(invocation -> {
             // Get the session.
             StubbablePrinterDiscoverySession session =
@@ -106,25 +120,16 @@
                 PrinterId printerId = session.getService()
                         .generatePrinterId(printerName);
 
-                PrinterCapabilitiesInfo capabilities = new PrinterCapabilitiesInfo.Builder(
-                        printerId)
-                        .setMinMargins(new Margins(200, 200, 200, 200))
-                        .addMediaSize(MediaSize.ISO_A4, true)
-                        .addResolution(new Resolution("300x300", "300x300", 300, 300),
-                                true)
-                        .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
-                                PrintAttributes.COLOR_MODE_COLOR)
-                        .build();
-
                 Intent infoIntent = new Intent(getActivity(), InfoActivity.class);
                 infoIntent.putExtra("PRINTER_NAME", PRINTER_NAME);
 
-                PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0,
+                PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(),
+                        0,
                         infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
                 sPrinter = new PrinterInfo.Builder(printerId, printerName,
                         PrinterInfo.STATUS_IDLE)
-                        .setCapabilities(capabilities)
+                        .setCapabilities(getDefaultOptionPrinterCapabilites(printerId))
                         .setDescription("Minimal capabilities")
                         .setInfoIntent(infoPendingIntent)
                         .build();
@@ -136,16 +141,7 @@
             onPrinterDiscoverySessionCreateCalled();
 
             return null;
-        }, null, null, invocation -> {
-            if (trackedPrinters != null) {
-                synchronized (trackedPrinters) {
-                    trackedPrinters
-                            .add(((PrinterId) invocation.getArguments()[0]).getLocalId());
-                    trackedPrinters.notifyAll();
-                }
-            }
-            return null;
-        }, invocation -> {
+        }, null, null, null, invocation -> {
             CustomPrinterIconCallback callback = (CustomPrinterIconCallback) invocation
                     .getArguments()[2];
 
@@ -153,16 +149,7 @@
                 callback.onCustomPrinterIconLoaded(mIcon);
             }
             return null;
-        }, invocation -> {
-            if (trackedPrinters != null) {
-                synchronized (trackedPrinters) {
-                    trackedPrinters.remove(((PrinterId) invocation.getArguments()[0]).getLocalId());
-                    trackedPrinters.notifyAll();
-                }
-            }
-
-            return null;
-        }, invocation -> {
+        }, null, invocation -> {
             // Take a note onDestroy was called.
             onPrinterDiscoverySessionDestroyCalled();
             return null;
@@ -276,8 +263,8 @@
     @Test
     public void progress() throws Throwable {
         // Create the session callbacks that we will be checking.
-        PrinterDiscoverySessionCallbacks sessionCallbacks
-                = createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME, null);
+        PrinterDiscoverySessionCallbacks sessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks(
@@ -304,11 +291,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait until the print job is queued and #sPrintJob is set
-        waitForServiceOnPrintJobQueuedCallbackCalled(1);
+            // Wait until the print job is queued and #sPrintJob is set
+            waitForServiceOnPrintJobQueuedCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Progress print job and check for appropriate notifications
         progress(0, "printed 0");
@@ -382,8 +371,8 @@
     @Test
     public void updateIcon() throws Throwable {
         // Create the session callbacks that we will be checking.
-        final PrinterDiscoverySessionCallbacks sessionCallbacks
-                = createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME, null);
+        final PrinterDiscoverySessionCallbacks sessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks(
@@ -446,8 +435,8 @@
     @Test
     public void cannotUseAttachBaseContext() throws Throwable {
         // Create the session callbacks that we will be checking.
-        final PrinterDiscoverySessionCallbacks sessionCallbacks
-                = createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME, null);
+        final PrinterDiscoverySessionCallbacks sessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks(
@@ -490,15 +479,15 @@
             PrintManager pm = (PrintManager) getActivity().getSystemService(Context.PRINT_SERVICE);
 
             // Configure first print service
-            PrinterDiscoverySessionCallbacks sessionCallbacks1
-                    = createMockPrinterDiscoverySessionCallbacks("Printer1", null);
+            PrinterDiscoverySessionCallbacks sessionCallbacks1 =
+                    createMockPrinterDiscoverySessionCallbacks("Printer1");
             PrintServiceCallbacks serviceCallbacks1 = createMockPrinterServiceCallbacks(
                     sessionCallbacks1);
             FirstPrintService.setCallbacks(serviceCallbacks1);
 
             // Configure second print service
-            PrinterDiscoverySessionCallbacks sessionCallbacks2
-                    = createMockPrinterDiscoverySessionCallbacks("Printer2", null);
+            PrinterDiscoverySessionCallbacks sessionCallbacks2 =
+                    createMockPrinterDiscoverySessionCallbacks("Printer2");
             PrintServiceCallbacks serviceCallbacks2 = createMockPrinterServiceCallbacks(
                     sessionCallbacks2);
             SecondPrintService.setCallbacks(serviceCallbacks2);
@@ -510,17 +499,20 @@
 
             // Init services
             waitForPrinterDiscoverySessionCreateCallbackCalled();
-            StubbablePrintService firstService = serviceCallbacks1.getService();
 
             waitForWriteAdapterCallback(1);
             selectPrinter("Printer1");
 
+            StubbablePrintService firstService = serviceCallbacks1.getService();
             // Job is not yet confirmed, hence it is not yet "active"
             runOnMainThread(() -> assertEquals(0, firstService.callGetActivePrintJobs().size()));
 
             clickPrintButton();
-            answerPrintServicesWarning(true);
-            onPrintJobQueuedCalled();
+
+            eventually(() -> {
+                answerPrintServicesWarning(true);
+                waitForServiceOnPrintJobQueuedCallbackCalled(1);
+            }, OPERATION_TIMEOUT_MILLIS * 2);
 
             eventually(() -> runOnMainThread(
                     () -> assertEquals(1, firstService.callGetActivePrintJobs().size())));
@@ -530,7 +522,7 @@
             runOnMainThread(() -> pm.print("job2", adapter, null));
             waitForWriteAdapterCallback(1);
             clickPrintButton();
-            onPrintJobQueuedCalled();
+            waitForServiceOnPrintJobQueuedCallbackCalled(1);
 
             eventually(() -> runOnMainThread(
                     () -> assertEquals(2, firstService.callGetActivePrintJobs().size())));
@@ -541,14 +533,17 @@
 
             waitForPrinterDiscoverySessionCreateCallbackCalled();
 
-            StubbablePrintService secondService = serviceCallbacks2.getService();
-            runOnMainThread(() -> assertEquals(0, secondService.callGetActivePrintJobs().size()));
-
             waitForWriteAdapterCallback(1);
             selectPrinter("Printer2");
             clickPrintButton();
-            answerPrintServicesWarning(true);
-            onPrintJobQueuedCalled();
+
+            StubbablePrintService secondService = serviceCallbacks2.getService();
+            runOnMainThread(() -> assertEquals(0, secondService.callGetActivePrintJobs().size()));
+
+            eventually(() -> {
+                answerPrintServicesWarning(true);
+                waitForServiceOnPrintJobQueuedCallbackCalled(1);
+            }, OPERATION_TIMEOUT_MILLIS * 2);
 
             eventually(() -> runOnMainThread(
                     () -> assertEquals(1, secondService.callGetActivePrintJobs().size())));
@@ -587,8 +582,60 @@
         ArrayList<String> trackedPrinters = new ArrayList<>();
 
         // Create the session callbacks that we will be checking.
-        final PrinterDiscoverySessionCallbacks sessionCallbacks
-                = createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME, trackedPrinters);
+        final PrinterDiscoverySessionCallbacks sessionCallbacks =
+                createMockPrinterDiscoverySessionCallbacks(invocation -> {
+                    // Get the session.
+                    StubbablePrinterDiscoverySession session =
+                            ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
+
+                    PrinterId printer1Id = session.getService().generatePrinterId("Printer1");
+
+                    PrinterInfo printer1 = new PrinterInfo.Builder(printer1Id, "Printer1",
+                            PrinterInfo.STATUS_IDLE).setCapabilities(
+                            getDefaultOptionPrinterCapabilites(printer1Id)).build();
+
+                    PrinterId printer2Id = session.getService().generatePrinterId("Printer2");
+
+                    Intent infoIntent = new Intent(getActivity(), InfoActivity.class);
+                    infoIntent.putExtra("PRINTER_NAME", "Printer2");
+
+                    PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0,
+                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+                    PrinterInfo printer2 = new PrinterInfo.Builder(printer2Id, "Printer2",
+                            PrinterInfo.STATUS_IDLE)
+                            .setInfoIntent(infoPendingIntent)
+                            .setCapabilities(getDefaultOptionPrinterCapabilites(printer2Id))
+                            .build();
+
+                    List<PrinterInfo> printers = new ArrayList<>();
+                    printers.add(printer1);
+                    printers.add(printer2);
+                    session.addPrinters(printers);
+
+                    onPrinterDiscoverySessionCreateCalled();
+
+                    return null;
+                }, null, null, invocation -> {
+                    synchronized (trackedPrinters) {
+                        trackedPrinters.add(
+                                ((PrinterId) invocation.getArguments()[0]).getLocalId());
+                        trackedPrinters.notifyAll();
+                    }
+
+                    return null;
+                }, null, invocation -> {
+                    synchronized (trackedPrinters) {
+                        trackedPrinters.remove(
+                                ((PrinterId) invocation.getArguments()[0]).getLocalId());
+                        trackedPrinters.notifyAll();
+                    }
+
+                    return null;
+                }, invocation -> {
+                    onPrinterDiscoverySessionDestroyCalled();
+                    return null;
+                });
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks(
@@ -606,34 +653,52 @@
         // Start printing.
         print(adapter);
 
+        selectPrinter("Printer1");
+
+        eventually(() -> {
+            synchronized (trackedPrinters) {
+                assertFalse(trackedPrinters.contains("Printer2"));
+            }
+        });
+
         // Enter select printer activity
         selectPrinter("All printers…");
 
-        assertFalse(trackedPrinters.contains(PRINTER_NAME));
+        try {
+            InfoActivity.addObserver(activity -> {
+                Intent intent = activity.getIntent();
 
-        InfoActivity.addObserver(activity -> {
-            Intent intent = activity.getIntent();
+                assertEquals("Printer2", intent.getStringExtra("PRINTER_NAME"));
+                assertTrue(intent.getBooleanExtra(PrintService.EXTRA_CAN_SELECT_PRINTER,
+                        false));
 
-            assertEquals(PRINTER_NAME, intent.getStringExtra("PRINTER_NAME"));
-            assertTrue(intent.getBooleanExtra(PrintService.EXTRA_CAN_SELECT_PRINTER,
-                            false));
+                activity.setResult(Activity.RESULT_OK,
+                        (new Intent()).putExtra(PrintService.EXTRA_SELECT_PRINTER, true));
+                activity.finish();
+            });
 
-            activity.setResult(Activity.RESULT_OK,
-                    (new Intent()).putExtra(PrintService.EXTRA_SELECT_PRINTER, true));
-            activity.finish();
-        });
+            try {
+                // Wait until printer is selected and thereby tracked
+                eventually(() -> {
+                    getUiDevice().waitForIdle();
+                    // Open info activity which executes the code above
+                    getUiDevice().wait(
+                            Until.findObject(By.res("com.android.printspooler:id/more_info")),
+                            OPERATION_TIMEOUT_MILLIS).click();
 
-        // Open info activity which executed the code above
-        UiObject moreInfoButton = getUiDevice().findObject(new UiSelector().resourceId(
-                "com.android.printspooler:id/more_info"));
-        moreInfoButton.click();
+                    eventually(() -> {
+                        synchronized (trackedPrinters) {
+                            assertTrue(trackedPrinters.contains("Printer2"));
+                        }
+                    }, OPERATION_TIMEOUT_MILLIS  / 2);
+                }, OPERATION_TIMEOUT_MILLIS * 2);
+            } finally {
+                InfoActivity.clearObservers();
+            }
+        } finally {
+            getUiDevice().pressBack();
+        }
 
-        // Wait until printer is selected and thereby tracked
-        eventually(() -> assertTrue(trackedPrinters.contains(PRINTER_NAME)));
-
-        InfoActivity.clearObservers();
-
-        getUiDevice().pressBack();
         getUiDevice().pressBack();
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
index 251b371..f321d84 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
@@ -155,7 +155,7 @@
     }
 
     @Before
-    public void setUpPrinting() throws Exception {
+    public void setUpPrinting() throws Throwable {
         // Create the mSession[0] callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
                 createMockPrinterDiscoverySessionCallbacks(invocation -> {
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index 2531f80..4943450 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -41,11 +41,12 @@
 import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.PrintJob;
 import android.printservice.PrinterDiscoverySession;
-import androidx.annotation.NonNull;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiSelector;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -117,9 +118,13 @@
         waitForWriteAdapterCallback(2);
 
         clickPrintButton();
-        answerPrintServicesWarning(true);
 
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        eventually(() -> {
+            answerPrintServicesWarning(true);
+
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
+
         resetCounters();
     }
 
@@ -185,8 +190,11 @@
 
         // Wait for preview to load and finish print
         waitForWriteAdapterCallback(1);
-        clickPrintButton();
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+        eventually(() -> {
+            clickPrintButton();
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
     }
 
     @Test
@@ -234,8 +242,11 @@
 
         // Wait for preview to load and finish print
         waitForWriteAdapterCallback(1);
-        clickPrintButton();
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+        eventually(() -> {
+            clickPrintButton();
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
     }
 
     @Test
@@ -303,11 +314,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for all print jobs to be handled after which the session destroyed.
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+            // Wait for all print jobs to be handled after which the session destroyed.
+            waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         runOnMainThread(() -> assertTrue(sSession.isDestroyed()));
         runOnMainThread(() -> assertFalse(sSession.isPrinterDiscoveryStarted()));
@@ -488,11 +501,13 @@
         // Click the print button.
         clickPrintButton();
 
-        // Answer the dialog for the print service cloud warning
-        answerPrintServicesWarning(true);
+        eventually(() -> {
+            // Answer the dialog for the print service cloud warning
+            answerPrintServicesWarning(true);
 
-        // Wait for the print to complete.
-        waitForAdapterFinishCallbackCalled();
+            // Wait for the print to complete.
+            waitForAdapterFinishCallbackCalled();
+        }, OPERATION_TIMEOUT_MILLIS * 2);
 
         // Now print again as we want to confirm that the start
         // printer discovery passes in the priority list.
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBoolTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBoolTest.java
new file mode 100644
index 0000000..32f0382
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBoolTest.java
@@ -0,0 +1,502 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class ProtoInputStreamBoolTest extends TestCase {
+
+    /**
+     * Test reading single bool field
+     */
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    /**
+     * Implementation of testRead with a given chunkSize.
+     */
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 3 -> 1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[] results = new boolean[2];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readBoolean(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0]);
+        assertEquals(true, results[1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(false);
+        testReadCompat(true);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(boolean val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+        final long FIELD_ID = fieldFlags | ((long) 130 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean result = false; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readBoolean(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolField, result);
+    }
+
+
+    /**
+     * Test reading repeated bool field
+     */
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    /**
+     * Implementation of testRepeated with a given chunkSize.
+     */
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+
+                // 4 -> 0
+                (byte) 0x20,
+                (byte) 0x00,
+                // 4 -> 1
+                (byte) 0x20,
+                (byte) 0x01,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+
+                // 3 -> 0
+                (byte) 0x18,
+                (byte) 0x00,
+                // 3 -> 1
+                (byte) 0x18,
+                (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[][] results = new boolean[3][2];
+        int[] indices = new int[3];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readBoolean(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readBoolean(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readBoolean(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0][0]);
+        assertEquals(false, results[0][1]);
+        assertEquals(true, results[1][0]);
+        assertEquals(true, results[1][1]);
+        assertEquals(false, results[2][0]);
+        assertEquals(true, results[2][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new boolean[0]);
+        testRepeatedCompat(new boolean[]{false, true});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(boolean[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL;
+        final long FIELD_ID = fieldFlags | ((long) 131 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean[] result = new boolean[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readBoolean(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.boolFieldRepeated[i], result[i]);
+        }
+    }
+
+    /**
+     * Test reading packed bool field
+     */
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    /**
+     * Implementation of testPacked with a given chunkSize.
+     */
+    public void testPacked(int chunkSize) throws IOException {
+
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 4 -> 0,1
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x01,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 3 -> 0,1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x01,
+        };
+
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[][] results = new boolean[3][2];
+        int[] indices = new int[3];
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readBoolean(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readBoolean(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readBoolean(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0][0]);
+        assertEquals(false, results[0][1]);
+        assertEquals(true, results[1][0]);
+        assertEquals(true, results[1][1]);
+        assertEquals(false, results[2][0]);
+        assertEquals(true, results[2][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new boolean[0]);
+        testPackedCompat(new boolean[]{false, true});
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(boolean[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL;
+        final long FIELD_ID = fieldFlags | ((long) 132 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean[] result = new boolean[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readBoolean(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.boolFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readBoolean(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readBoolean(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readBoolean(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed booleans)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readBoolean(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBytesTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBytesTest.java
new file mode 100644
index 0000000..6a47be8
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamBytesTest.java
@@ -0,0 +1,422 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class ProtoInputStreamBytesTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 5 -> { 0, 1, 2, 3, 4 }
+                (byte) 0x2a,
+                (byte) 0x05,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        byte[][] results = new byte[4][];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0] = pi.readBytes(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readBytes(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readBytes(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readBytes(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertTrue("Expected: []  Actual: " + Arrays.toString(results[0]),
+                Arrays.equals(new byte[]{}, results[0]));
+        assertTrue("Expected: []  Actual: " + Arrays.toString(results[1]),
+                Arrays.equals(new byte[]{}, results[1]));
+        assertTrue("Expected: [0, 1, 2, 3, 4, 5]  Actual: " + Arrays.toString(results[2]),
+                Arrays.equals(new byte[]{0, 1, 2, 3, 4, 5}, results[2]));
+        assertTrue("Expected: [-1, -2, -3, -4]  Actual: " + Arrays.toString(results[3]),
+                Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                        results[3]));
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(new byte[0]);
+        testReadCompat(new byte[]{1, 2, 3, 4});
+        testReadCompat(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc});
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(byte[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+        final long FIELD_ID = fieldFlags | ((long) 150 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.bytesField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        byte[] result = new byte[val.length];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readBytes(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.bytesField.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.bytesField[i], result[i]);
+        }
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+
+                // 5 -> { 0, 1, 2, 3, 4}
+                (byte) 0x2a,
+                (byte) 0x05,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
+
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        byte[][][] results = new byte[4][2][];
+        int[] indices = new int[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readBytes(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readBytes(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readBytes(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readBytes(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assert (Arrays.equals(new byte[]{}, results[0][0]));
+        assert (Arrays.equals(new byte[]{}, results[0][1]));
+        assert (Arrays.equals(new byte[]{}, results[1][0]));
+        assert (Arrays.equals(new byte[]{}, results[1][1]));
+        assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][0]));
+        assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][1]));
+        assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                results[3][0]));
+        assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                results[3][1]));
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new byte[0][]);
+        testRepeatedCompat(new byte[][]{
+                new byte[0],
+                new byte[]{1, 2, 3, 4},
+                new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(byte[][] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
+        final long FIELD_ID = fieldFlags | ((long) 151 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.bytesFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        byte[][] result = new byte[val.length][]; // start off with default value
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readBytes(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.bytesFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.bytesFieldRepeated[i].length, result[i].length);
+            for (int j = 0; j < result[i].length; j++) {
+                assertEquals(readback.bytesFieldRepeated[i][j], result[i][j]);
+            }
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readBytes(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readBytes(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readBytes(FIELD_ID_3);
+                        // don't fail, length delimited is ok
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readBytes(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamDoubleTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamDoubleTest.java
new file mode 100644
index 0000000..1efaf32
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamDoubleTest.java
@@ -0,0 +1,727 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class ProtoInputStreamDoubleTest extends TestCase {
+
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x51,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[] results = new double[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readDouble(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readDouble(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readDouble(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readDouble(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readDouble(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readDouble(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7] = pi.readDouble(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8] = pi.readDouble(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0]);
+        assertEquals(1.0, results[1]);
+        assertEquals(-1234.432, results[2]);
+        assertEquals(42.42, results[3]);
+        assertEquals(Double.MIN_NORMAL, results[4]);
+        assertEquals(Double.MIN_VALUE, results[5]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6]);
+        assertEquals(Double.NaN, results[7]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1234.432);
+        testReadCompat(42.42);
+        testReadCompat(Double.MIN_NORMAL);
+        testReadCompat(Double.MIN_VALUE);
+        testReadCompat(Double.NEGATIVE_INFINITY);
+        testReadCompat(Double.NaN);
+        testReadCompat(Double.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(double val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long FIELD_ID = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double result = 0.0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readDouble(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+                // 10 -> 1
+                (byte) 0x51,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[][] results = new double[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readDouble(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readDouble(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readDouble(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readDouble(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readDouble(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readDouble(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readDouble(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7][indices[7]++] = pi.readDouble(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8][indices[8]++] = pi.readDouble(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0][0]);
+        assertEquals(0.0, results[0][1]);
+        assertEquals(1.0, results[1][0]);
+        assertEquals(1.0, results[1][1]);
+        assertEquals(-1234.432, results[2][0]);
+        assertEquals(-1234.432, results[2][1]);
+        assertEquals(42.42, results[3][0]);
+        assertEquals(42.42, results[3][1]);
+        assertEquals(Double.MIN_NORMAL, results[4][0]);
+        assertEquals(Double.MIN_NORMAL, results[4][1]);
+        assertEquals(Double.MIN_VALUE, results[5][0]);
+        assertEquals(Double.MIN_VALUE, results[5][1]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Double.NaN, results[7][0]);
+        assertEquals(Double.NaN, results[7][1]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new double[0]);
+        testRepeatedCompat(new double[]{0, 1, -1234.432, 42.42,
+                Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN,
+                Double.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(double[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long FIELD_ID = fieldFlags | ((long) 11 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double[] result = new double[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readDouble(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.doubleFieldRepeated[i], result[i]);
+        }
+    }
+
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x52,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x4a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[][] results = new double[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readDouble(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readDouble(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readDouble(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readDouble(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readDouble(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readDouble(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readDouble(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7][indices[7]++] = pi.readDouble(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8][indices[8]++] = pi.readDouble(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0][0]);
+        assertEquals(0.0, results[0][1]);
+        assertEquals(1.0, results[1][0]);
+        assertEquals(1.0, results[1][1]);
+        assertEquals(-1234.432, results[2][0]);
+        assertEquals(-1234.432, results[2][1]);
+        assertEquals(42.42, results[3][0]);
+        assertEquals(42.42, results[3][1]);
+        assertEquals(Double.MIN_NORMAL, results[4][0]);
+        assertEquals(Double.MIN_NORMAL, results[4][1]);
+        assertEquals(Double.MIN_VALUE, results[5][0]);
+        assertEquals(Double.MIN_VALUE, results[5][1]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Double.NaN, results[7][0]);
+        assertEquals(Double.NaN, results[7][1]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new double[0]);
+        testPackedCompat(new double[]{0, 1, -1234.432, 42.42,
+                Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN,
+                Double.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(double[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long FIELD_ID = fieldFlags | ((long) 12 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double[] result = new double[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readDouble(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.doubleFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x09,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readDouble(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readDouble(FIELD_ID_2);
+                        // don't fail, fixed64 is ok
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readDouble(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed doubles)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readDouble(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamEnumTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamEnumTest.java
new file mode 100644
index 0000000..6a0a7b9
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamEnumTest.java
@@ -0,0 +1,569 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamEnumTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+        final long FIELD_ID = fieldFlags | ((long) 160 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[]{});
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM;
+        final long FIELD_ID = fieldFlags | ((long) 161 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(val[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[]{});
+        testPackedCompat(new int[]{0, 1});
+
+        // Nano proto has a bug.  It gets the size with computeInt32SizeNoTag (correctly)
+        // but incorrectly uses writeRawVarint32 to write the value for negative numbers.
+        //testPackedCompat(new int[] { 0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM;
+        final long FIELD_ID = fieldFlags | ((long) 162 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(val[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed enums)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed32Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed32Test.java
new file mode 100644
index 0000000..935a3ad
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed32Test.java
@@ -0,0 +1,546 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamFixed32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 90 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 91 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 92 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x0d,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed fixed32)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed64Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed64Test.java
new file mode 100644
index 0000000..8aa5cb3
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFixed64Test.java
@@ -0,0 +1,648 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamFixed64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 100 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 101 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 102 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x09,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readLong(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readLong(FIELD_ID_2);
+                        // don't fail, fixed64 is ok
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readLong(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed fixed64)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readLong(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFloatTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFloatTest.java
new file mode 100644
index 0000000..336fa57
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamFloatTest.java
@@ -0,0 +1,678 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class ProtoInputStreamFloatTest extends TestCase {
+
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x55,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[] results = new float[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readFloat(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readFloat(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readFloat(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readFloat(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readFloat(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readFloat(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7] = pi.readFloat(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8] = pi.readFloat(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0]);
+        assertEquals(1.0f, results[1]);
+        assertEquals(-1234.432f, results[2]);
+        assertEquals(42.42f, results[3]);
+        assertEquals(Float.MIN_NORMAL, results[4]);
+        assertEquals(Float.MIN_VALUE, results[5]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6]);
+        assertEquals(Float.NaN, results[7]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1234.432f);
+        testReadCompat(42.42f);
+        testReadCompat(Float.MIN_NORMAL);
+        testReadCompat(Float.MIN_VALUE);
+        testReadCompat(Float.NEGATIVE_INFINITY);
+        testReadCompat(Float.NaN);
+        testReadCompat(Float.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(float val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+        final long FIELD_ID = fieldFlags | ((long) 20 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float result = 0.0f; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readFloat(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+
+                // 10 -> 1
+                (byte) 0x55,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[][] results = new float[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readFloat(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readFloat(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readFloat(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readFloat(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readFloat(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readFloat(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readFloat(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7][indices[7]++] = pi.readFloat(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8][indices[8]++] = pi.readFloat(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0][0]);
+        assertEquals(0.0f, results[0][1]);
+        assertEquals(1.0f, results[1][0]);
+        assertEquals(1.0f, results[1][1]);
+        assertEquals(-1234.432f, results[2][0]);
+        assertEquals(-1234.432f, results[2][1]);
+        assertEquals(42.42f, results[3][0]);
+        assertEquals(42.42f, results[3][1]);
+        assertEquals(Float.MIN_NORMAL, results[4][0]);
+        assertEquals(Float.MIN_NORMAL, results[4][1]);
+        assertEquals(Float.MIN_VALUE, results[5][0]);
+        assertEquals(Float.MIN_VALUE, results[5][1]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Float.NaN, results[7][0]);
+        assertEquals(Float.NaN, results[7][1]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new float[0]);
+        testRepeatedCompat(new float[]{0, 1, -1234.432f, 42.42f,
+                Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN,
+                Float.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(float[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT;
+        final long FIELD_ID = fieldFlags | ((long) 21 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float[] result = new float[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readFloat(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.floatFieldRepeated[i], result[i]);
+        }
+    }
+
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long FIELD_ID_9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long FIELD_ID_10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x52,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x42,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[][] results = new float[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readFloat(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readFloat(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readFloat(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readFloat(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readFloat(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readFloat(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readFloat(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    results[7][indices[7]++] = pi.readFloat(FIELD_ID_8);
+                    break;
+                case (int) FIELD_ID_9:
+                    results[8][indices[8]++] = pi.readFloat(FIELD_ID_9);
+                    break;
+                case (int) FIELD_ID_10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0][0]);
+        assertEquals(0.0f, results[0][1]);
+        assertEquals(1.0f, results[1][0]);
+        assertEquals(1.0f, results[1][1]);
+        assertEquals(-1234.432f, results[2][0]);
+        assertEquals(-1234.432f, results[2][1]);
+        assertEquals(42.42f, results[3][0]);
+        assertEquals(42.42f, results[3][1]);
+        assertEquals(Float.MIN_NORMAL, results[4][0]);
+        assertEquals(Float.MIN_NORMAL, results[4][1]);
+        assertEquals(Float.MIN_VALUE, results[5][0]);
+        assertEquals(Float.MIN_VALUE, results[5][1]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Float.NaN, results[7][0]);
+        assertEquals(Float.NaN, results[7][1]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new float[0]);
+        testPackedCompat(new float[]{0, 1, -1234.432f, 42.42f,
+                Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN,
+                Float.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(float[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT;
+        final long FIELD_ID = fieldFlags | ((long) 22 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float[] result = new float[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readFloat(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.floatFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x0d,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readFloat(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readFloat(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readFloat(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed floats)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readFloat(FIELD_ID_6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt32Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt32Test.java
new file mode 100644
index 0000000..5e86e3c
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt32Test.java
@@ -0,0 +1,564 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+        final long FIELD_ID = fieldFlags | ((long) 30 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+        final long FIELD_ID = fieldFlags | ((long) 31 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+        final long FIELD_ID = fieldFlags | ((long) 32 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed int32)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt64Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt64Test.java
new file mode 100644
index 0000000..caa8b2af
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamInt64Test.java
@@ -0,0 +1,644 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+        final long FIELD_ID = fieldFlags | ((long) 40 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+        final long FIELD_ID = fieldFlags | ((long) 41 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+        final long FIELD_ID = fieldFlags | ((long) 42 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readLong(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readLong(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readLong(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed int64)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readLong(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamObjectTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamObjectTest.java
new file mode 100644
index 0000000..24e0948
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamObjectTest.java
@@ -0,0 +1,507 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamObjectTest extends TestCase {
+
+
+    class SimpleObject {
+        public char mChar;
+        public char mLargeChar;
+        public String mString;
+        public SimpleObject mNested;
+
+        void parseProto(ProtoInputStream pi) throws IOException {
+            final long uintFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+            final long stringFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+            final long messageFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+            final long CHAR_ID = uintFieldFlags | ((long) 2 & 0x0ffffffffL);
+            final long LARGE_CHAR_ID = uintFieldFlags | ((long) 5000 & 0x0ffffffffL);
+            final long STRING_ID = stringFieldFlags | ((long) 4 & 0x0ffffffffL);
+            final long NESTED_ID = messageFieldFlags | ((long) 5 & 0x0ffffffffL);
+
+            while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (pi.getFieldNumber()) {
+                    case (int) CHAR_ID:
+                        mChar = (char) pi.readInt(CHAR_ID);
+                        break;
+                    case (int) LARGE_CHAR_ID:
+                        mLargeChar = (char) pi.readInt(LARGE_CHAR_ID);
+                        break;
+                    case (int) STRING_ID:
+                        mString = pi.readString(STRING_ID);
+                        break;
+                    case (int) NESTED_ID:
+                        long token = pi.start(NESTED_ID);
+                        mNested = new SimpleObject();
+                        mNested.parseProto(pi);
+                        pi.end(token);
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Test reading an object with one char in it.
+     */
+    public void testObjectOneChar() throws IOException {
+        testObjectOneChar(0);
+        testObjectOneChar(1);
+        testObjectOneChar(5);
+    }
+
+    /**
+     * Implementation of testObjectOneChar for a given chunkSize.
+     */
+    private void testObjectOneChar(int chunkSize) throws IOException {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long MESSAGE_ID_1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long MESSAGE_ID_2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // Message 2 : { char 2 : 'c' }
+                (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x63,
+                // Message 1 : { char 2 : 'b' }
+                (byte) 0x0a, (byte) 0x02, (byte) 0x10, (byte) 0x62,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject result = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) MESSAGE_ID_1:
+                    final long token = pi.start(MESSAGE_ID_1);
+                    result = new SimpleObject();
+                    result.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) MESSAGE_ID_2:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(result);
+        assertEquals('b', result.mChar);
+    }
+
+    /**
+     * Test reading an object with one multibyte unicode char in it.
+     */
+    public void testObjectOneLargeChar() throws IOException {
+        testObjectOneLargeChar(0);
+        testObjectOneLargeChar(1);
+        testObjectOneLargeChar(5);
+    }
+
+    /**
+     * Implementation of testObjectOneLargeChar for a given chunkSize.
+     */
+    private void testObjectOneLargeChar(int chunkSize) throws IOException {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long MESSAGE_ID_1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long MESSAGE_ID_2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // Message 2 : { char 5000 : '\u3110' }
+                (byte) 0x12, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
+                (byte) 0x02, (byte) 0x90, (byte) 0x62,
+                // Message 1 : { char 5000 : '\u3110' }
+                (byte) 0x0a, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
+                (byte) 0x02, (byte) 0x90, (byte) 0x62,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject result = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) MESSAGE_ID_1:
+                    final long token = pi.start(MESSAGE_ID_1);
+                    result = new SimpleObject();
+                    result.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) MESSAGE_ID_2:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(result);
+        assertEquals('\u3110', result.mLargeChar);
+    }
+
+    /**
+     * Test reading a char, then an object, then a char.
+     */
+    public void testObjectAndTwoChars() throws IOException {
+        testObjectAndTwoChars(0);
+        testObjectAndTwoChars(1);
+        testObjectAndTwoChars(5);
+    }
+
+    /**
+     * Implementation of testObjectAndTwoChars for a given chunkSize.
+     */
+    private void testObjectAndTwoChars(int chunkSize) throws IOException  {
+        final long uintFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long CHAR_ID_1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long MESSAGE_ID_2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long CHAR_ID_4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 'a'
+                (byte)0x08, (byte)0x61,
+                // Message 1 : { char 2 : 'b' }
+                (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x62,
+                // 4 -> 'c'
+                (byte)0x20, (byte)0x63,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+        char char1 = '\0';
+        char char4 = '\0';
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) CHAR_ID_1:
+                    char1 = (char) pi.readInt(CHAR_ID_1);
+                    break;
+                case (int) MESSAGE_ID_2:
+                    final long token = pi.start(MESSAGE_ID_2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) CHAR_ID_4:
+                    char4 = (char) pi.readInt(CHAR_ID_4);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals('a', char1);
+        assertNotNull(obj);
+        assertEquals('b', obj.mChar);
+        assertEquals('c', char4);
+    }
+
+    /**
+     * Test reading a char, then an object with an int and a string in it, then a char.
+     */
+    public void testComplexObject() throws IOException {
+        testComplexObject(0);
+        testComplexObject(1);
+        testComplexObject(5);
+    }
+
+    /**
+     * Implementation of testComplexObject for a given chunkSize.
+     */
+    private void testComplexObject(int chunkSize) throws IOException  {
+        final long uintFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long CHAR_ID_1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long MESSAGE_ID_2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long CHAR_ID_4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 'x'
+                (byte)0x08, (byte)0x78,
+                // begin object 2
+                (byte)0x12, (byte)0x10,
+                    // 2 -> 'y'
+                    (byte)0x10, (byte)0x79,
+                    // 4 -> "abcdefghijkl"
+                    (byte)0x22, (byte)0x0c,
+                        (byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64, (byte)0x65, (byte)0x66,
+                        (byte)0x67, (byte)0x68, (byte)0x69, (byte)0x6a, (byte)0x6b, (byte)0x6c,
+                // 4 -> 'z'
+                (byte)0x20, (byte)0x7a,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+        char char1 = '\0';
+        char char4 = '\0';
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) CHAR_ID_1:
+                    char1 = (char) pi.readInt(CHAR_ID_1);
+                    break;
+                case (int) MESSAGE_ID_2:
+                    final long token = pi.start(MESSAGE_ID_2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) CHAR_ID_4:
+                    char4 = (char) pi.readInt(CHAR_ID_4);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals('x', char1);
+        assertNotNull(obj);
+        assertEquals('y', obj.mChar);
+        assertEquals("abcdefghijkl", obj.mString);
+        assertEquals('z', char4);
+    }
+
+    /**
+     * Test reading 3 levels deep of objects.
+     */
+    public void testDeepObjects() throws IOException {
+        testDeepObjects(0);
+        testDeepObjects(1);
+        testDeepObjects(5);
+    }
+
+    /**
+     * Implementation of testDeepObjects for a given chunkSize.
+     */
+    private void testDeepObjects(int chunkSize) throws IOException  {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+        final long MESSAGE_ID_2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // begin object id 2
+                (byte)0x12, (byte)0x1a,
+                    // 2 -> 'a'
+                    (byte)0x10, (byte)0x61,
+                    // begin nested object id 5
+                    (byte)0x2a, (byte)0x15,
+                        // 5000 -> '\u3110'
+                        (byte) 0xc0, (byte) 0xb8,
+                        (byte) 0x02, (byte) 0x90, (byte) 0x62,
+                        // begin nested object id 5
+                        (byte)0x2a, (byte)0x0e,
+                            // 4 -> "abcdefghijkl"
+                            (byte)0x22, (byte)0x0c,
+                            (byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64, (byte)0x65, (byte)0x66,
+                            (byte)0x67, (byte)0x68, (byte)0x69, (byte)0x6a, (byte)0x6b, (byte)0x6c,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) MESSAGE_ID_2:
+                    final long token = pi.start(MESSAGE_ID_2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(obj);
+        assertEquals('a', obj.mChar);
+        assertNotNull(obj.mNested);
+        assertEquals('\u3110', obj.mNested.mLargeChar);
+        assertNotNull(obj.mNested.mNested);
+        assertEquals("abcdefghijkl", obj.mNested.mNested.mString);
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readBytes(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readBytes(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readBytes(FIELD_ID_3);
+                        // don't fail, length delimited is ok
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readBytes(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed32Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed32Test.java
new file mode 100644
index 0000000..37be34a
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed32Test.java
@@ -0,0 +1,546 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSFixed32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 110 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 111 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long FIELD_ID = fieldFlags | ((long) 112 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed sfixed32)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed64Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed64Test.java
new file mode 100644
index 0000000..e2a3b19
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSFixed64Test.java
@@ -0,0 +1,647 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSFixed64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 120 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 121 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long FIELD_ID = fieldFlags | ((long) 122 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readLong(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readLong(FIELD_ID_2);
+                        // don't fail, fixed32 is ok
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readLong(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed sfixed64)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readLong(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt32Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt32Test.java
new file mode 100644
index 0000000..f6b254b
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt32Test.java
@@ -0,0 +1,546 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+        final long FIELD_ID = fieldFlags | ((long) 70 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+        final long FIELD_ID = fieldFlags | ((long) 71 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x02,
+                (byte) 0x02,
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+        final long FIELD_ID = fieldFlags | ((long) 72 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed sint32)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt64Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt64Test.java
new file mode 100644
index 0000000..5504230
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamSInt64Test.java
@@ -0,0 +1,621 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+        final long FIELD_ID = fieldFlags | ((long) 80 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+        final long FIELD_ID = fieldFlags | ((long) 81 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x02,
+                (byte) 0x02,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x14,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+        final long FIELD_ID = fieldFlags | ((long) 82 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readLong(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readLong(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readLong(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed sint64)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readLong(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamStringTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamStringTest.java
new file mode 100644
index 0000000..2a0250a
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamStringTest.java
@@ -0,0 +1,404 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class ProtoInputStreamStringTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, not written
+                // 2 -> "" - default value, not written
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 5 -> "Hi"
+                (byte) 0x2a,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        String[] results = new String[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0] = pi.readString(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readString(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readString(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readString(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNull(results[0]);
+        assertNull(results[1]);
+        assertEquals("abcd\u3110!", results[2]);
+        assertEquals("Hi", results[3]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat("");
+        testReadCompat("abcd\u3110!");
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(String val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+        final long FIELD_ID = fieldFlags | ((long) 140 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.stringField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        String result = "";
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readString(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.stringField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> "" - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+
+                // 5 -> "Hi"
+                (byte) 0x2a,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> "" - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        String[][] results = new String[4][2];
+        int[] indices = new int[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readString(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readString(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readString(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readString(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals("", results[0][0]);
+        assertEquals("", results[0][1]);
+        assertEquals("", results[1][0]);
+        assertEquals("", results[1][1]);
+        assertEquals("abcd\u3110!", results[2][0]);
+        assertEquals("abcd\u3110!", results[2][1]);
+        assertEquals("Hi", results[3][0]);
+        assertEquals("Hi", results[3][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new String[0]);
+        testRepeatedCompat(new String[]{"", "abcd\u3110!", "Hi",});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(String[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
+        final long FIELD_ID = fieldFlags | ((long) 141 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.stringFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        String[] result = new String[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readString(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.stringFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.stringFieldRepeated[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readString(FIELD_ID_1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readString(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readString(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed booleans)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readString(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt32Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt32Test.java
new file mode 100644
index 0000000..eeb6105
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt32Test.java
@@ -0,0 +1,563 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamUInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long FIELD_ID = fieldFlags | ((long) 50 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+        final long FIELD_ID = fieldFlags | ((long) 51 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readInt(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readInt(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readInt(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readInt(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readInt(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+        final long FIELD_ID = fieldFlags | ((long) 52 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readInt(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readLong(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readInt(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readInt(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readInt(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed uint32)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readInt(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt64Test.java b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt64Test.java
new file mode 100644
index 0000000..f8766b5
--- /dev/null
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoInputStreamUInt64Test.java
@@ -0,0 +1,640 @@
+/*
+ * 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.util.proto.cts;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+import android.util.proto.cts.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamUInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    fail("Should never reach this");
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+        final long FIELD_ID = fieldFlags | ((long) 60 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+        final long FIELD_ID = fieldFlags | ((long) 61 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long FIELD_ID_5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long FIELD_ID_7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long FIELD_ID_8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID_1:
+                    results[0][indices[0]++] = pi.readLong(FIELD_ID_1);
+                    break;
+                case (int) FIELD_ID_2:
+                    results[1][indices[1]++] = pi.readLong(FIELD_ID_2);
+                    break;
+                case (int) FIELD_ID_3:
+                    results[2][indices[2]++] = pi.readLong(FIELD_ID_3);
+                    break;
+                case (int) FIELD_ID_4:
+                    results[3][indices[3]++] = pi.readLong(FIELD_ID_4);
+                    break;
+                case (int) FIELD_ID_5:
+                    results[4][indices[4]++] = pi.readLong(FIELD_ID_5);
+                    break;
+                case (int) FIELD_ID_6:
+                    results[5][indices[5]++] = pi.readLong(FIELD_ID_6);
+                    break;
+                case (int) FIELD_ID_7:
+                    results[6][indices[6]++] = pi.readLong(FIELD_ID_7);
+                    break;
+                case (int) FIELD_ID_8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE,});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+        final long FIELD_ID = fieldFlags | ((long) 62 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) FIELD_ID:
+                    result[index++] = pi.readLong(FIELD_ID);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readFloat(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readDouble(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readInt(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBoolean(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readBytes(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(FIELD_ID_1);
+        try {
+            pi.readString(FIELD_ID_1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long FIELD_ID_1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long FIELD_ID_2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long FIELD_ID_3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long FIELD_ID_6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) FIELD_ID_1:
+                        pi.readLong(FIELD_ID_1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) FIELD_ID_2:
+                        pi.readLong(FIELD_ID_2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) FIELD_ID_3:
+                        pi.readLong(FIELD_ID_3);
+                        // don't fail, length delimited is ok (represents packed uint64)
+                        break;
+                    case (int) FIELD_ID_6:
+                        pi.readLong(FIELD_ID_6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
index 8178b46..f8bd54b 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
@@ -17,6 +17,7 @@
 package android.util.proto.cts;
 
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoStream;
 import android.util.proto.cts.nano.Test;
 
 import com.google.protobuf.nano.MessageNano;
@@ -36,31 +37,31 @@
      * Test making the tokens for startObject.
      */
     public void testMakeToken() throws Exception {
-        assertEquals(0xe000000000000000L, ProtoOutputStream.makeToken(0xffffffff, false, 0, 0, 0));
-        assertEquals(0x1000000000000000L, ProtoOutputStream.makeToken(0, true, 0, 0, 0));
-        assertEquals(0x0ff8000000000000L, ProtoOutputStream.makeToken(0, false, 0xffffffff, 0, 0));
-        assertEquals(0x0007ffff00000000L, ProtoOutputStream.makeToken(0, false, 0, 0xffffffff, 0));
-        assertEquals(0x00000000ffffffffL, ProtoOutputStream.makeToken(0, false, 0, 0, 0xffffffff));
+        assertEquals(0xe000000000000000L, ProtoStream.makeToken(0xffffffff, false, 0, 0, 0));
+        assertEquals(0x1000000000000000L, ProtoStream.makeToken(0, true, 0, 0, 0));
+        assertEquals(0x0ff8000000000000L, ProtoStream.makeToken(0, false, 0xffffffff, 0, 0));
+        assertEquals(0x0007ffff00000000L, ProtoStream.makeToken(0, false, 0, 0xffffffff, 0));
+        assertEquals(0x00000000ffffffffL, ProtoStream.makeToken(0, false, 0, 0, 0xffffffff));
     }
 
     /**
      * Test decoding the tokens.
      */
     public void testDecodeToken() throws Exception {
-        assertEquals(0x07, ProtoOutputStream.getTagSizeFromToken(0xffffffffffffffffL));
-        assertEquals(0, ProtoOutputStream.getTagSizeFromToken(0x1fffffffffffffffL));
+        assertEquals(0x07, ProtoStream.getTagSizeFromToken(0xffffffffffffffffL));
+        assertEquals(0, ProtoStream.getTagSizeFromToken(0x1fffffffffffffffL));
 
-        assertEquals(true, ProtoOutputStream.getRepeatedFromToken(0xffffffffffffffffL));
-        assertEquals(false, ProtoOutputStream.getRepeatedFromToken(0xefffffffffffffffL));
+        assertEquals(true, ProtoStream.getRepeatedFromToken(0xffffffffffffffffL));
+        assertEquals(false, ProtoStream.getRepeatedFromToken(0xefffffffffffffffL));
 
-        assertEquals(0x01ff, ProtoOutputStream.getDepthFromToken(0xffffffffffffffffL));
-        assertEquals(0, ProtoOutputStream.getDepthFromToken(0xf005ffffffffffffL));
+        assertEquals(0x01ff, ProtoStream.getDepthFromToken(0xffffffffffffffffL));
+        assertEquals(0, ProtoStream.getDepthFromToken(0xf005ffffffffffffL));
 
-        assertEquals(0x07ffff, ProtoOutputStream.getObjectIdFromToken(0xffffffffffffffffL));
-        assertEquals(0, ProtoOutputStream.getObjectIdFromToken(0xfff80000ffffffffL));
+        assertEquals(0x07ffff, ProtoStream.getObjectIdFromToken(0xffffffffffffffffL));
+        assertEquals(0, ProtoStream.getObjectIdFromToken(0xfff80000ffffffffL));
 
-        assertEquals(0xffffffff, ProtoOutputStream.getSizePosFromToken(0xffffffffffffffffL));
-        assertEquals(0, ProtoOutputStream.getSizePosFromToken(0xffffffff00000000L));
+        assertEquals(0xffffffff, ProtoStream.getOffsetFromToken(0xffffffffffffffffL));
+        assertEquals(0, ProtoStream.getOffsetFromToken(0xffffffff00000000L));
     }
 
     /**
@@ -617,9 +618,9 @@
             // Check this, because it's really useful, and if we lose the message it'll be
             // harder to debug typos.
             assertEquals("Mismatched startObject/endObject calls. Current depth 2"
-                    + " token=Token(val=0x2017fffd0000000a depth=2 object=2 tagSize=1 sizePos=10)"
+                    + " token=Token(val=0x2017fffd0000000a depth=2 object=2 tagSize=1 offset=10)"
                     + " expectedToken=Token(val=0x2017fffc0000000a depth=2 object=3 tagSize=1"
-                    + " sizePos=10)", ex.getMessage());
+                    + " offset=10)", ex.getMessage());
         }
     }
 
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoTests.java b/tests/tests/proto/src/android/util/proto/cts/ProtoTests.java
index 1802fd0..97b25d6 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoTests.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoTests.java
@@ -42,6 +42,24 @@
         suite.addTestSuite(ProtoOutputStreamEnumTest.class);
         suite.addTestSuite(ProtoOutputStreamObjectTest.class);
 
+        suite.addTestSuite(ProtoInputStreamDoubleTest.class);
+        suite.addTestSuite(ProtoInputStreamFloatTest.class);
+        suite.addTestSuite(ProtoInputStreamInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamUInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamUInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamSInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamSInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamFixed32Test.class);
+        suite.addTestSuite(ProtoInputStreamFixed64Test.class);
+        suite.addTestSuite(ProtoInputStreamSFixed32Test.class);
+        suite.addTestSuite(ProtoInputStreamSFixed64Test.class);
+        suite.addTestSuite(ProtoInputStreamBoolTest.class);
+        suite.addTestSuite(ProtoInputStreamStringTest.class);
+        suite.addTestSuite(ProtoInputStreamBytesTest.class);
+        suite.addTestSuite(ProtoInputStreamEnumTest.class);
+        suite.addTestSuite(ProtoInputStreamObjectTest.class);
+
         return suite;
     }
 }
diff --git a/tests/tests/provider/Android.bp b/tests/tests/provider/Android.bp
new file mode 100644
index 0000000..1079cf8
--- /dev/null
+++ b/tests/tests/provider/Android.bp
@@ -0,0 +1,37 @@
+
+android_test {
+    name: "CtsProviderTestCases",
+    defaults: ["cts_defaults"],
+
+    compile_multilib: "both",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base.stubs",
+        "android.test.runner.stubs",
+        "telephony-common",
+    ],
+
+    static_libs: [
+        "compatibility-device-util",
+        "ctstestrunner",
+        "junit",
+        "truth-prebuilt",
+    ],
+
+    jni_libs: [
+        "libcts_jni",
+        "libnativehelper_compat_libc++",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+    min_sdk_version: "21",
+}
diff --git a/tests/tests/provider/Android.mk b/tests/tests/provider/Android.mk
index 400f0d2..6361f9b 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -1,51 +1,2 @@
-# 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-
-# Include both the 32 and 64 bit versions of libs
-LOCAL_MULTILIB := both
-
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_JAVA_LIBRARIES := android.test.mock android.test.base.stubs android.test.runner.stubs telephony-common
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util \
-    ctstestrunner \
-    ub-uiautomator \
-    junit
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    androidx.legacy_legacy-support-v4 
-
-LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libnativehelper_compat_libc++
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsProviderTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_CTS_PACKAGE)
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 571fe6b..cfe0c6f 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -18,9 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.provider.cts">
 
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
+    <uses-sdk android:targetSdkVersion="28" />
 
-    <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
@@ -53,8 +52,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.provider.cts.GetResultActivity" />
-
         <service android:name="android.provider.cts.contacts.account.MockAccountService"
                  process="android.provider.cts"
                  android:exported="true">
@@ -84,18 +81,15 @@
             <meta-data android:name="android.content.ContactDirectory" android:value="true" />
         </provider>
 
-        <provider android:name="androidx.core.content.FileProvider"
-            android:authorities="android.provider.cts.fileprovider"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
-        </provider>
         <provider android:name="android.provider.cts.MockFontProvider"
                   android:authorities="android.provider.fonts.cts.font"
                   android:exported="false"
                   android:multiprocess="true" />
 
+        <provider android:name="android.provider.cts.TestSRSProvider"
+                  android:authorities="android.provider.cts.TestSRSProvider"
+                  android:exported="false" />
+
         <service
             android:name="android.provider.cts.contacts.StubInCallService"
             android:permission="android.permission.BIND_INCALL_SERVICE">
diff --git a/tests/tests/provider/AndroidTest.xml b/tests/tests/provider/AndroidTest.xml
index 123931c..cafbdaa 100644
--- a/tests/tests/provider/AndroidTest.xml
+++ b/tests/tests/provider/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Provider 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="instant_app" />
+    <target_preparer class="android.provider.cts.preconditions.ExternalStoragePreparer" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsProviderTestCases.apk" />
diff --git a/tests/tests/provider/preconditions/Android.mk b/tests/tests/provider/preconditions/Android.mk
new file mode 100644
index 0000000..f4063c4
--- /dev/null
+++ b/tests/tests/provider/preconditions/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed tradefed
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-host-provider-preconditions
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java b/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java
new file mode 100644
index 0000000..5007cea
--- /dev/null
+++ b/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.preconditions;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetCleaner;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * Creates secondary external storage for use during a test suite.
+ */
+public class ExternalStoragePreparer implements ITargetPreparer, ITargetCleaner {
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        if (!hasIsolatedStorage(device)) return;
+
+        device.executeShellCommand("sm set-virtual-disk false");
+        device.executeShellCommand("sm set-virtual-disk true");
+
+        // Partition disks to make sure they're usable by tests
+        final String diskId = getVirtualDisk(device);
+        device.executeShellCommand("sm partition " + diskId + " public");
+    }
+
+    @Override
+    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable throwable)
+            throws DeviceNotAvailableException {
+        if (!hasIsolatedStorage(device)) return;
+
+        device.executeShellCommand("sm set-virtual-disk false");
+    }
+
+    private boolean hasIsolatedStorage(ITestDevice device) throws DeviceNotAvailableException {
+        return device.executeShellCommand("getprop sys.isolated_storage_snapshot")
+                .contains("true");
+    }
+
+    private String getVirtualDisk(ITestDevice device) throws DeviceNotAvailableException {
+        int attempt = 0;
+        String disks = device.executeShellCommand("sm list-disks");
+        while ((disks == null || disks.isEmpty()) && attempt++ < 15) {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ignored) {
+            }
+            disks = device.executeShellCommand("sm list-disks");
+        }
+
+        if (disks == null || disks.isEmpty()) {
+            throw new AssertionError("Device must support virtual disk to verify behavior");
+        }
+        return disks.split("\n")[0].trim();
+    }
+}
diff --git a/tests/tests/provider/res/raw/volantis.jpg b/tests/tests/provider/res/raw/volantis.jpg
new file mode 100644
index 0000000..cfe300f
--- /dev/null
+++ b/tests/tests/provider/res/raw/volantis.jpg
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java b/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java
index adf558b..cb8eeda 100644
--- a/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/BlockedNumberBackupRestoreTest.java
@@ -22,7 +22,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.provider.BlockedNumberContract;
-import android.telecom.Log;
+import android.util.Log;
 
 /**
  * CTS tests for backup and restore of blocked numbers using local transport.
@@ -34,7 +34,7 @@
 public class BlockedNumberBackupRestoreTest extends TestCaseThatRunsIfTelephonyIsEnabled {
     private static final String TAG = "BlockedNumberBackupRestoreTest";
     private static final String LOCAL_BACKUP_COMPONENT =
-            "android/com.android.internal.backup.LocalTransport";
+            "com.android.localtransport/.LocalTransport";
     private static final String BLOCKED_NUMBERS_PROVIDER_PACKAGE =
             "com.android.providers.blockednumber";
 
diff --git a/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java b/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
index 30fdf24..39aa0bb 100644
--- a/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/BlockedNumberContractTest.java
@@ -16,7 +16,6 @@
 
 package android.provider.cts;
 
-import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -323,7 +322,7 @@
     }
 
     private Uri assertInsertBlockedNumberSucceeds(
-            String originalNumber, @Nullable String e164Number) {
+            String originalNumber, String e164Number) {
         ContentValues cv = new ContentValues();
         cv.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, originalNumber);
         if (e164Number != null) {
diff --git a/tests/tests/provider/src/android/provider/cts/DocumentsContractTest.java b/tests/tests/provider/src/android/provider/cts/DocumentsContractTest.java
new file mode 100644
index 0000000..6d2f7cb
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/DocumentsContractTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.assertTrue;
+
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DocumentsContractTest {
+    @Test
+    public void testDocumentUri() {
+        final String auth = "com.example";
+        final String docId = "doc:12";
+
+        final Uri uri = DocumentsContract.buildDocumentUri(auth, docId);
+        assertEquals(auth, uri.getAuthority());
+        assertEquals(docId, DocumentsContract.getDocumentId(uri));
+        assertFalse(DocumentsContract.isTreeUri(uri));
+    }
+
+    @Test
+    public void testTreeDocumentUri() {
+        final String auth = "com.example";
+        final String treeId = "doc:12";
+        final String leafId = "doc:24";
+
+        final Uri treeUri = DocumentsContract.buildTreeDocumentUri(auth, treeId);
+        assertEquals(auth, treeUri.getAuthority());
+        assertEquals(treeId, DocumentsContract.getTreeDocumentId(treeUri));
+        assertTrue(DocumentsContract.isTreeUri(treeUri));
+
+        final Uri leafUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, leafId);
+        assertEquals(auth, leafUri.getAuthority());
+        assertEquals(treeId, DocumentsContract.getTreeDocumentId(leafUri));
+        assertEquals(leafId, DocumentsContract.getDocumentId(leafUri));
+        assertTrue(DocumentsContract.isTreeUri(leafUri));
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/FontsContractTest.java b/tests/tests/provider/src/android/provider/cts/FontsContractTest.java
index ea88369..631401a 100644
--- a/tests/tests/provider/src/android/provider/cts/FontsContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/FontsContractTest.java
@@ -40,8 +40,6 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.internal.annotations.GuardedBy;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -99,13 +97,13 @@
 
     private static class TestCallback extends FontsContract.FontRequestCallback {
         private final Object mLock = new Object();
-        @GuardedBy("mLock")
+        // @GuardedBy("mLock")
         private Typeface mTypeface;
-        @GuardedBy("mLock")
+        // @GuardedBy("mLock")
         private int mFailedReason;
-        @GuardedBy("mLock")
+        // @GuardedBy("mLock")
         private int mSuccessCallCount;
-        @GuardedBy("mLock")
+        // @GuardedBy("mLock")
         private int mFailedCallCount;
 
         public void onTypefaceRetrieved(Typeface typeface) {
diff --git a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java b/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
deleted file mode 100644
index 5a55eec..0000000
--- a/tests/tests/provider/src/android/provider/cts/GetResultActivity.java
+++ /dev/null
@@ -1,84 +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.provider.cts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class GetResultActivity extends Activity {
-    private static LinkedBlockingQueue<Result> sResult;
-
-    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 onActivityResult(int requestCode, int resultCode, Intent data) {
-        try {
-            sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public void clearResult() {
-        sResult = new LinkedBlockingQueue<>();
-    }
-
-    public Result getResult() {
-        final Result result;
-        try {
-            result = sResult.poll(30, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-        if (result == null) {
-            throw new IllegalStateException("Activity didn't receive a Result in 30 seconds");
-        }
-        return result;
-    }
-
-    public Result getResult(long timeout, TimeUnit unit) {
-        try {
-            return sResult.poll(timeout, unit);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
index 56d74f3..a2b62f0 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
@@ -19,12 +19,18 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
-import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Media;
+import android.support.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}
@@ -45,20 +51,21 @@
  * @see MediaStore_Audio_Artists_AlbumsTest
  * @see MediaStore_Audio_AlbumsTest
  */
+@RunWith(AndroidJUnit4.class)
 public class MediaStoreAudioTestHelper {
     public static abstract class MockAudioMediaInfo {
-        public abstract ContentValues getContentValues(boolean isInternal);
+        public abstract ContentValues getContentValues(String volumeName);
 
-        public Uri insertToInternal(ContentResolver contentResolver) {
-            Uri uri = contentResolver.insert(Media.INTERNAL_CONTENT_URI, getContentValues(true));
-            Assert.assertNotNull(uri);
-            return uri;
-        }
+        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)
+            });
 
-        public Uri insertToExternal(ContentResolver contentResolver) {
-            Uri uri = contentResolver.insert(Media.EXTERNAL_CONTENT_URI, getContentValues(false));
-            Assert.assertNotNull(uri);
-            return uri;
+            final Uri itemUri = contentResolver.insert(dirUri, values);
+            Assert.assertNotNull(itemUri);
+            return itemUri;
         }
 
         public int delete(ContentResolver contentResolver, Uri uri) {
@@ -77,50 +84,37 @@
         }
 
         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 IS_DRM = 0;
-
         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 DISPLAY_NAME = "Jam -Michael Jackson";
-
-        public static final String INTERNAL_DATA =
-            "/data/data/android.provider.cts/files/Jam.mp3";
-
         public static final String FILE_NAME = "Jam.mp3";
-
-        public static final String EXTERNAL_DATA = Environment.getExternalStorageDirectory() +
-                "/" + FILE_NAME;
-
         public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
-
         public static final String GENRE = "POP";
+
         @Override
-        public ContentValues getContentValues(boolean isInternal) {
+        public ContentValues getContentValues(String volumeName) {
             ContentValues values = new ContentValues();
-            values.put(Media.DATA, isInternal ? INTERNAL_DATA : EXTERNAL_DATA);
+            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);
@@ -136,8 +130,6 @@
             values.put(Media.IS_ALARM, IS_ALARM);
             values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
             values.put(Media.IS_RINGTONE, IS_RINGTONE);
-            values.put(Media.IS_DRM, IS_DRM);
-
             return values;
         }
     }
@@ -153,53 +145,38 @@
         }
 
         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 IS_DRM = 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 DISPLAY_NAME = "Jam(Live)-Michael Jackson";
-
         public static final String FILE_NAME = "Jam_live.mp3";
-
-        public static final String EXTERNAL_DATA =
-            Environment.getExternalStorageDirectory().getPath() + "/" + FILE_NAME;
-
-        public static final String INTERNAL_DATA =
-            "/data/data/android.provider.cts/files/Jam_live.mp3";
-
-
-
-        public static final long DATE_MODIFIED = System.currentTimeMillis();
+        public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
 
         @Override
-        public ContentValues getContentValues(boolean isInternal) {
+        public ContentValues getContentValues(String volumeName) {
             ContentValues values = new ContentValues();
-            values.put(Media.DATA, isInternal ? INTERNAL_DATA : EXTERNAL_DATA);
+            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);
@@ -215,8 +192,6 @@
             values.put(Media.IS_ALARM, IS_ALARM);
             values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
             values.put(Media.IS_RINGTONE, IS_RINGTONE);
-            values.put(Media.IS_DRM, IS_DRM);
-
             return values;
         }
     }
@@ -232,8 +207,8 @@
         }
 
         @Override
-        public ContentValues getContentValues(boolean isInternal) {
-            ContentValues values = super.getContentValues(isInternal);
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
             values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
             return values;
         }
@@ -250,8 +225,8 @@
         }
 
         @Override
-        public ContentValues getContentValues(boolean isInternal) {
-            ContentValues values = super.getContentValues(isInternal);
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
             values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
             return values;
         }
@@ -268,13 +243,18 @@
         }
 
         @Override
-        public ContentValues getContentValues(boolean isInternal) {
-            ContentValues values = super.getContentValues(isInternal);
+        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
index 21d4727..c027004 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
@@ -16,11 +16,24 @@
 
 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.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+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;
 
@@ -28,45 +41,72 @@
  * Tests to verify that common actions on {@link MediaStore} content are
  * available.
  */
-public class MediaStoreIntentsTest extends AndroidTestCase {
+@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 = getContext()
+        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(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        intent.setData(mExternalImages);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testPickVideoDir() {
         Intent intent = new Intent(Intent.ACTION_PICK);
-        intent.setData(MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
+        intent.setData(mExternalVideo);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testPickAudioDir() {
         Intent intent = new Intent(Intent.ACTION_PICK);
-        intent.setData(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
+        intent.setData(mExternalAudio);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewImageDir() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        intent.setData(mExternalImages);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewVideoDir() {
         Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
+        intent.setData(mExternalVideo);
         assertCanBeHandled(intent);
     }
 
+    @Test
     public void testViewImageFile() {
         final String[] schemes = new String[] {
                 "file", "http", "https", "content" };
@@ -87,6 +127,7 @@
         }
     }
 
+    @Test
     public void testViewVideoFile() {
         final String[] schemes = new String[] {
                 "file", "http", "https", "content" };
@@ -105,6 +146,7 @@
         }
     }
 
+    @Test
     public void testViewAudioFile() {
         final String[] schemes = new String[] {
                 "file", "http", "content" };
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
new file mode 100644
index 0000000..2d15765
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
@@ -0,0 +1,451 @@
+/*
+ * 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.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.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.MediaStore.PendingParams;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+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;
+import java.util.regex.Pattern;
+
+@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 MediaStore.PendingParams params = new MediaStore.PendingParams(
+                insertUri, displayName, "image/png");
+
+        final Uri pendingUri = MediaStore.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 (MediaStore.PendingSession session = MediaStore.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 MediaStore.PendingParams params = new MediaStore.PendingParams(
+                insertUri, displayName, "image/png");
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final File pendingFile;
+
+        try (MediaStore.PendingSession session = MediaStore.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 MediaStore.PendingParams params1 = new MediaStore.PendingParams(
+                insertUri, displayName, "audio/mpeg");
+        final MediaStore.PendingParams params2 = new MediaStore.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.setPrimaryDirectory(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.setPrimaryDirectory(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.setPrimaryDirectory(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.setPrimaryDirectory(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.setPrimaryDirectory(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.setPrimaryDirectory("foo/bar");
+        assertNotCreatePending(params);
+    }
+
+    @Test
+    public void testDirectories_Secondary() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
+        params.setSecondaryDirectory("Kittens");
+
+        final Uri uri = execPending(params, R.raw.scenery);
+        final File rawFile = getRawFile(uri);
+        assertEquals("Kittens", rawFile.getParentFile().getName());
+        assertEquals(Environment.DIRECTORY_PICTURES,
+                rawFile.getParentFile().getParentFile().getName());
+
+        // Verify that shady paths don't work
+        params.setSecondaryDirectory("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.setPrimaryDirectory(Environment.DIRECTORY_DCIM);
+        params.setSecondaryDirectory("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());
+    }
+
+    private void assertCreatePending(PendingParams params) {
+        MediaStore.createPending(mContext, params);
+    }
+
+    private void assertNotCreatePending(PendingParams params) {
+        assertNotCreatePending(null, params);
+    }
+
+    private void assertNotCreatePending(String message, PendingParams params) {
+        try {
+            MediaStore.createPending(mContext, params);
+            fail(message);
+        } catch (Exception expected) {
+        }
+    }
+
+    private Uri execPending(PendingParams params, int resId) throws Exception {
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(resId);
+                    OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            return session.publish();
+        }
+    }
+
+    private static File getRawFile(Uri uri) throws Exception {
+        final String res = ProviderTestUtils.executeShellCommand(
+                "content query --uri " + uri + " --projection _data",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+        final int i = res.indexOf("_data=");
+        if (i >= 0) {
+            return new File(res.substring(i + 6));
+        } else {
+            throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
+        }
+    }
+
+    private static String getRawFileHash(File file) throws Exception {
+        final String res = ProviderTestUtils.executeShellCommand(
+                "sha1sum " + file.getAbsolutePath(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+        if (Pattern.matches("[0-9a-fA-F]{40}.+", res)) {
+            return res.substring(0, 40);
+        } else {
+            throw new FileNotFoundException("Failed to find hash for " + file + "; found " + res);
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
index 7d78bd6..6104f61 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
@@ -16,77 +16,247 @@
 
 package android.provider.cts;
 
+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.Assert.fail;
+
+import android.app.usage.StorageStatsManager;
 import android.content.ContentResolver;
-import android.content.ContentValues;
+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.provider.MediaStore;
-import android.test.InstrumentationTestCase;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStoreTest extends InstrumentationTestCase {
-    private static final String TEST_VOLUME_NAME = "volume_for_cts";
+import libcore.util.HexEncoding;
 
-    private static final String[] PROJECTION = new String[] { MediaStore.MEDIA_SCANNER_VOLUME };
+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;
 
-    private Uri mScannerUri;
+import java.io.File;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
 
-    private String mVolumnBackup;
+@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;
 
-    @Override
-    protected void setUp() throws Exception {
-        mScannerUri = MediaStore.getMediaScannerUri();
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
-        Cursor c = mContentResolver.query(mScannerUri, PROJECTION, null, null, null);
-        if (c != null) {
-            c.moveToFirst();
-            mVolumnBackup = c.getString(0);
-            c.close();
-        }
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        // restore initial values
-        if (mVolumnBackup != null) {
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.MEDIA_SCANNER_VOLUME, mVolumnBackup);
-            mContentResolver.insert(mScannerUri, values);
-        }
-        super.tearDown();
+    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() {
-        ContentValues values = new ContentValues();
-        String selection = MediaStore.MEDIA_SCANNER_VOLUME + "=?";
-        String[] selectionArgs = new String[] { TEST_VOLUME_NAME };
-
-        // assert there is no item with name TEST_VOLUME_NAME
-        assertNull(mContentResolver.query(mScannerUri, PROJECTION,
-                selection, selectionArgs, null));
-
-        // insert
-        values.put(MediaStore.MEDIA_SCANNER_VOLUME, TEST_VOLUME_NAME);
-        assertEquals(MediaStore.getMediaScannerUri(),
-                mContentResolver.insert(mScannerUri, values));
-
         // query
-        Cursor c = mContentResolver.query(mScannerUri, PROJECTION,
-                selection, selectionArgs, null);
+        Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
+                null, null, null);
         assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(TEST_VOLUME_NAME, c.getString(0));
         c.close();
-
-        // delete
-        assertEquals(1, mContentResolver.delete(mScannerUri, null, null));
-        assertNull(mContentResolver.query(mScannerUri, PROJECTION, null, null, null));
     }
 
+    @Test
     public void testGetVersion() {
         // Could be a version string or null...just check it doesn't blow up.
-        MediaStore.getVersion(getInstrumentation().getTargetContext());
+        MediaStore.getVersion(getContext());
+    }
+
+    @Test
+    public void testGetAllVolumeNames() {
+        Set<String> volumeNames = MediaStore.getAllVolumeNames(getContext());
+
+        // At very least should contain these two volumes
+        assertTrue(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
+        assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
+    }
+
+    @Test
+    public void testContributedMedia() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.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.scanFile(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);
+        }
+    }
+
+    @Test
+    public void testHash() throws Exception {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
+        SystemClock.sleep(500);
+
+        final String expected = Arrays
+                .toString(HexEncoding.decode("dd41258ce8d306163f3b727603cb064be81973db"));
+
+        // We can force hash to be generated by requesting canonicalization
+        resolver.canonicalize(uri);
+        try (Cursor c = resolver.query(uri, new String[] { MediaColumns.HASH }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(expected, Arrays.toString(c.getBlob(0)));
+        }
+
+        // Make sure that editing image results in a different hash
+        try (OutputStream out = resolver.openOutputStream(uri, "wa")) {
+            out.write(42);
+        }
+        SystemClock.sleep(500);
+        try (Cursor c = resolver.query(uri, new String[] { MediaColumns.HASH }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertNotEquals(expected, Arrays.toString(c.getBlob(0)));
+        }
+    }
+
+    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/MediaStoreTrashTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreTrashTest.java
new file mode 100644
index 0000000..97cd1ff
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreTrashTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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 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.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+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 MediaStoreTrashTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    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();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testTrashUntrash() throws Exception {
+        final Uri insertUri = mExternalImages;
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, insertUri);
+        final long id = ContentUris.parseId(uri);
+
+        // Default is not trashed, and no expiration date
+        try (Cursor c = mResolver.query(uri,
+                new String[] { MediaColumns.IS_TRASHED, MediaColumns.DATE_EXPIRES }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(0, c.getInt(0));
+            assertTrue(c.isNull(1));
+        }
+        assertTrue(containsId(insertUri, id));
+        assertTrue(containsId(MediaStore.setIncludeTrashed(insertUri), id));
+
+        // Trash should expire roughly 2 days from now
+        MediaStore.trash(mContext, uri);
+        try (Cursor c = mResolver.query(uri,
+                new String[] { MediaColumns.IS_TRASHED, MediaColumns.DATE_EXPIRES }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(1, c.getInt(0));
+            assertMostlyEquals((System.currentTimeMillis() + (2 * DateUtils.DAY_IN_MILLIS)) / 1000,
+                    c.getLong(1), 30);
+        }
+        assertFalse(containsId(insertUri, id));
+        assertTrue(containsId(MediaStore.setIncludeTrashed(insertUri), id));
+
+        // Untrash should bring us back
+        MediaStore.untrash(mContext, uri);
+        try (Cursor c = mResolver.query(uri,
+                new String[] { MediaColumns.IS_TRASHED, MediaColumns.DATE_EXPIRES }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(0, c.getInt(0));
+            assertTrue(c.isNull(1));
+        }
+        assertTrue(containsId(insertUri, id));
+        assertTrue(containsId(MediaStore.setIncludeTrashed(insertUri), id));
+    }
+
+    @Test
+    public void testTrashExecutes() throws Exception {
+        final Uri insertUri = mExternalImages;
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, insertUri);
+
+        MediaStore.trash(mContext, uri, 1);
+
+        // Force idle maintenance to run
+        ProviderTestUtils.executeShellCommand(
+                "cmd jobscheduler run -f com.android.providers.media -200",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+        // Wait around for item to be deleted
+        final long timeout = SystemClock.elapsedRealtime() + DateUtils.MINUTE_IN_MILLIS;
+        while (SystemClock.elapsedRealtime() < timeout) {
+            try (Cursor c = mResolver.query(uri, null, null, null)) {
+                Log.v(TAG, "Count " + c.getCount());
+                if (c.getCount() == 0) {
+                    return;
+                }
+            }
+            SystemClock.sleep(500);
+        }
+
+        fail("Timed out waiting for job to delete trashed item");
+    }
+
+    private boolean containsId(Uri uri, long id) {
+        try (Cursor c = mResolver.query(uri,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                if (c.getLong(0) == id) return true;
+            }
+        }
+        return false;
+    }
+
+    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/MediaStoreUiTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
deleted file mode 100644
index 6ce81a0..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreUiTest.java
+++ /dev/null
@@ -1,334 +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.provider.cts;
-
-import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.Manifest.permission.CAMERA;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.RECORD_AUDIO;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriPermission;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.media.ExifInterface;
-import android.media.MediaScannerConnection;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
-import android.provider.MediaStore;
-import android.provider.cts.GetResultActivity.Result;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import androidx.core.content.FileProvider;
-
-public class MediaStoreUiTest extends InstrumentationTestCase {
-    private static final String TAG = "MediaStoreUiTest";
-
-    private static final int REQUEST_CODE = 42;
-    private static final String CONTENT = "Test";
-
-    private UiDevice mDevice;
-    private GetResultActivity mActivity;
-
-    private File mFile;
-    private Uri mMediaStoreUri;
-
-    @Override
-    public void setUp() throws Exception {
-        mDevice = UiDevice.getInstance(getInstrumentation());
-
-        final Context context = getInstrumentation().getContext();
-        mActivity = launchActivity(context.getPackageName(), GetResultActivity.class, null);
-        mActivity.clearResult();
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (mFile != null) {
-            mFile.delete();
-        }
-
-        final ContentResolver resolver = mActivity.getContentResolver();
-        for (UriPermission permission : resolver.getPersistedUriPermissions()) {
-            mActivity.revokeUriPermission(
-                    permission.getUri(),
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION
-                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        }
-
-        mActivity.finish();
-    }
-
-    public void testGetDocumentUri() throws Exception {
-        if (!supportsHardware()) return;
-
-        prepareFile();
-
-        final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
-        assertNotNull(treeUri);
-
-        final Uri docUri = MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
-        assertNotNull(docUri);
-
-        final ContentResolver resolver = mActivity.getContentResolver();
-        try (ParcelFileDescriptor fd = resolver.openFileDescriptor(docUri, "rw")) {
-            // Test reading
-            try (final BufferedReader reader =
-                         new BufferedReader(new FileReader(fd.getFileDescriptor()))) {
-                assertEquals(CONTENT, reader.readLine());
-            }
-
-            // Test writing
-            try (final OutputStream out = new FileOutputStream(fd.getFileDescriptor())) {
-                out.write(CONTENT.getBytes());
-            }
-        }
-    }
-
-    public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception {
-        if (!supportsHardware()) return;
-
-        prepareFile();
-
-        try {
-            MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
-            fail("Expecting SecurityException.");
-        } catch (SecurityException e) {
-            // Expected
-        }
-    }
-
-    private void maybeClick(UiSelector sel) {
-        try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
-    }
-
-    private void maybeClick(BySelector sel) {
-        try { mDevice.findObject(sel).click(); } catch (Throwable ignored) { }
-    }
-
-    private void maybeGrantRuntimePermission(String pkg, Set<String> requested, String permission) {
-        if (requested.contains(permission)) {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                    .grantRuntimePermission(pkg, permission);
-        }
-    }
-
-    /**
-     * Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
-     * correctly write the contents into a passed {@code content://} Uri.
-     */
-    public void testImageCapture() throws Exception {
-        final Context context = getInstrumentation().getContext();
-        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
-            Log.d(TAG, "Skipping due to lack of camera");
-            return;
-        }
-
-        final File targetDir = new File(context.getFilesDir(), "debug");
-        final File target = new File(targetDir, "capture.jpg");
-
-        targetDir.mkdirs();
-        assertFalse(target.exists());
-
-        final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-        intent.putExtra(MediaStore.EXTRA_OUTPUT,
-                FileProvider.getUriForFile(context, "android.provider.cts.fileprovider", target));
-
-        // Figure out who is going to answer the phone
-        final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
-        final String pkg = ri.activityInfo.packageName;
-        Log.d(TAG, "We're probably launching " + ri);
-
-        final PackageInfo pi = context.getPackageManager().getPackageInfo(pkg,
-                PackageManager.GET_PERMISSIONS);
-        final Set<String> req = new HashSet<>();
-        req.addAll(Arrays.asList(pi.requestedPermissions));
-
-        // Grant them all the permissions they might want
-        maybeGrantRuntimePermission(pkg, req, CAMERA);
-        maybeGrantRuntimePermission(pkg, req, ACCESS_COARSE_LOCATION);
-        maybeGrantRuntimePermission(pkg, req, ACCESS_FINE_LOCATION);
-        maybeGrantRuntimePermission(pkg, req, RECORD_AUDIO);
-        maybeGrantRuntimePermission(pkg, req, READ_EXTERNAL_STORAGE);
-        maybeGrantRuntimePermission(pkg, req, WRITE_EXTERNAL_STORAGE);
-        SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-        mDevice.waitForIdle();
-
-        // To ensure camera app is launched
-        SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
-
-        // Try a couple different strategies for taking a photo: first take a
-        // photo and confirm using hardware keys
-        mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
-        mDevice.waitForIdle();
-        SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
-        mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
-        mDevice.waitForIdle();
-
-        // Maybe that gave us a result?
-        Result result = mActivity.getResult(15, TimeUnit.SECONDS);
-        Log.d(TAG, "First pass result was " + result);
-
-        // Hrm, that didn't work; let's try an alternative approach of digging
-        // around for a shutter button
-        if (result == null) {
-            maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
-            mDevice.waitForIdle();
-            SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
-            maybeClick(new UiSelector().resourceId(pkg + ":id/shutter_button"));
-            mDevice.waitForIdle();
-            maybeClick(new UiSelector().resourceId(pkg + ":id/done_button"));
-            mDevice.waitForIdle();
-
-            result = mActivity.getResult(15, TimeUnit.SECONDS);
-            Log.d(TAG, "Second pass result was " + result);
-        }
-
-        // Grr, let's try hunting around even more
-        if (result == null) {
-            maybeClick(By.pkg(pkg).descContains("Capture"));
-            mDevice.waitForIdle();
-            SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
-            maybeClick(By.pkg(pkg).descContains("Done"));
-            mDevice.waitForIdle();
-
-            result = mActivity.getResult(15, TimeUnit.SECONDS);
-            Log.d(TAG, "Third pass result was " + result);
-        }
-
-        assertNotNull("Expected to get a IMAGE_CAPTURE result; your camera app should "
-                + "respond to the CAMERA and DPAD_CENTER keycodes", result);
-
-        assertTrue("exists", target.exists());
-        assertTrue("has data", target.length() > 65536);
-
-        // At the very least we expect photos generated by the device to have
-        // sane baseline EXIF data
-        final ExifInterface exif = new ExifInterface(new FileInputStream(target));
-        assertAttribute(exif, ExifInterface.TAG_MAKE);
-        assertAttribute(exif, ExifInterface.TAG_MODEL);
-        assertAttribute(exif, ExifInterface.TAG_DATETIME);
-    }
-
-    private static void assertAttribute(ExifInterface exif, String tag) {
-        final String res = exif.getAttribute(tag);
-        if (res == null || res.length() == 0) {
-            Log.d(TAG, "Expected valid EXIF tag for tag " + tag);
-        }
-    }
-
-    private boolean supportsHardware() {
-        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        return !pm.hasSystemFeature("android.hardware.type.television")
-                && !pm.hasSystemFeature("android.hardware.type.watch");
-    }
-
-    private void prepareFile() throws Exception {
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
-
-        final File documents =
-                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
-        documents.mkdirs();
-        assertTrue(documents.isDirectory());
-
-        mFile = new File(documents, "test.jpg");
-        try (OutputStream os = new FileOutputStream(mFile)) {
-            os.write(CONTENT.getBytes());
-        }
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaScannerConnection.scanFile(
-                mActivity,
-                new String[]{ mFile.getAbsolutePath() },
-                new String[]{ "image/jpeg" },
-                (String path, Uri uri) -> onScanCompleted(uri, latch)
-        );
-        assertTrue(
-                "MediaScanner didn't finish scanning in 30s.", latch.await(30, TimeUnit.SECONDS));
-    }
-
-    private void onScanCompleted(Uri uri, CountDownLatch latch) {
-        mMediaStoreUri = uri;
-        latch.countDown();
-    }
-
-    private Uri acquireAccess(File file, String directoryName) {
-        StorageManager storageManager =
-                (StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
-
-        // Request access from DocumentsUI
-        final StorageVolume volume = storageManager.getStorageVolume(file);
-        final Intent intent = volume.createAccessIntent(directoryName);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-
-        // Granting the access
-        BySelector buttonPanelSelector = By.pkg("com.android.documentsui")
-                .res("android:id/buttonPanel");
-        mDevice.wait(Until.hasObject(buttonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
-        final UiObject2 buttonPanel = mDevice.findObject(buttonPanelSelector);
-        final UiObject2 allowButton = buttonPanel.findObject(By.res("android:id/button1"));
-        allowButton.click();
-
-        mDevice.waitForIdle();
-
-        // Check granting result and take persistent permission
-        final Result result = mActivity.getResult();
-        assertEquals(Activity.RESULT_OK, result.resultCode);
-
-        final Intent resultIntent = result.data;
-        final Uri resultUri = resultIntent.getData();
-        final int flags = resultIntent.getFlags()
-                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        mActivity.getContentResolver().takePersistableUriPermission(resultUri, flags);
-        return resultUri;
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
index 79118dd..155e314 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
@@ -16,20 +16,26 @@
 
 package android.provider.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.provider.MediaStore.Audio;
+import android.support.test.runner.AndroidJUnit4;
 
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class MediaStore_AudioTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_AudioTest {
     private String mKeyForBeatles;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mKeyForBeatles = Audio.keyFor("beatles");
     }
 
+    @Test
     public void testKeyFor() {
         assertEquals(mKeyForBeatles, Audio.keyFor("[beatles]"));
         assertEquals(mKeyForBeatles, Audio.keyFor("(beatles)"));
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
index 9c9d9ca..079fec4 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
@@ -16,63 +16,74 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.res.AssetFileDescriptor;
+import android.content.Context;
 import android.database.Cursor;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
-import android.os.Environment;
 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.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+import android.util.Size;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+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;
 
-public class MediaStore_Audio_AlbumsTest extends AndroidTestCase {
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_AlbumsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = mContext.getContentResolver();
+    @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(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
+                Albums.getContentUri(mVolumeName), null, null,
                 null, null));
         c.close();
-        assertNotNull(c = mContentResolver.query(
-                Albums.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
-                null, null));
-        c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Albums.getContentUri(volume), null, null, null, null));
     }
 
-    public void testStoreAudioAlbumsInternal() {
-        testStoreAudioAlbums(true);
-    }
-
-    public void testStoreAudioAlbumsExternal() {
-        testStoreAudioAlbums(false);
-    }
-
-    private void testStoreAudioAlbums(boolean isInternal) {
+    @Test
+    public void testStoreAudioAlbums() {
         // do not support direct insert operation of the albums
-        Uri audioAlbumsUri = isInternal? Albums.INTERNAL_CONTENT_URI : Albums.EXTERNAL_CONTENT_URI;
+        Uri audioAlbumsUri = Albums.getContentUri(mVolumeName);
         try {
             mContentResolver.insert(audioAlbumsUri, new ContentValues());
             fail("Should throw UnsupportedOperationException!");
@@ -82,8 +93,7 @@
 
         // the album item is inserted when inserting audio media
         Audio1 audio1 = Audio1.getInstance();
-        Uri audioMediaUri = isInternal ? audio1.insertToInternal(mContentResolver)
-                : audio1.insertToExternal(mContentResolver);
+        Uri audioMediaUri = audio1.insert(mContentResolver, mVolumeName);
 
         String selection = Albums.ALBUM +"=?";
         String[] selectionArgs = new String[] { Audio1.ALBUM };
@@ -161,58 +171,62 @@
         c.close();
     }
 
-    public void testAlbumArt() {
-        File path = new File(Environment.getExternalStorageDirectory()
-                + "/test" + System.currentTimeMillis() + ".mp3");
-        Uri uri = null;
+    @Test
+    public void testAlbumArt() throws Exception {
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File path = new File(dir, "test" + System.currentTimeMillis() + ".mp3");
         try {
-            FileCopyHelper copier = new FileCopyHelper(mContext);
-            File dir = path.getParentFile();
-            dir.mkdirs();
-            copier.copyToExternalStorage(R.raw.testmp3, path);
+            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());
-            uri = mContentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, v);
-            AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(
-                    uri.buildUpon().appendPath("albumart").build(), "r");
-            assertNotNull(afd);
-            afd.close();
 
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            c.moveToFirst();
-            long aid = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
-            c.close();
+            final Uri mediaUri = mContentResolver
+                    .insert(MediaStore.Audio.Media.getContentUri(mVolumeName), v);
+            final long mediaId = ContentUris.parseId(mediaUri);
 
-            Uri albumart = Uri.parse("content://media/external/audio/albumart/" + aid);
-            try {
-                mContentResolver.delete(albumart, null, null);
-                afd = mContentResolver.openAssetFileDescriptor(albumart, "r");
-            } catch (FileNotFoundException e) {
-                fail("no album art");
+            final long albumId;
+            try (Cursor c = mContentResolver.query(mediaUri, null, null, null, null)) {
+                assertTrue(c.moveToFirst());
+                albumId = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
             }
 
-            c = mContentResolver.query(albumart, null, null, null, null);
-            c.moveToFirst();
-            String albumartfile = c.getString(c.getColumnIndex("_data"));
-            c.close();
-            new File(albumartfile).delete();
+            // Verify that normal thumbnails work
+            assertNotNull(mContentResolver.loadThumbnail(mediaUri, 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 {
-                afd = mContentResolver.openAssetFileDescriptor(albumart, "r");
-            } catch (FileNotFoundException e) {
-                fail("no album art");
+                mContentResolver.loadThumbnail(mediaUri, 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) {
             }
 
-        } catch (Exception e) {
-            fail("album art failed " + e);
         } finally {
             path.delete();
-            if (uri != null) {
-                mContentResolver.delete(uri, null, null);
-            }
         }
     }
-
 }
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
index 9fe31c5..60950d1 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
@@ -16,52 +16,65 @@
 
 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.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore.Audio.Artists;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStore_Audio_ArtistsTest extends InstrumentationTestCase {
+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;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @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(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
-                    null, null));
-        c.close();
-        assertNotNull(c = mContentResolver.query(
-                Artists.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
+                Artists.getContentUri(mVolumeName), null, null,
                 null, null));
         c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Artists.getContentUri(volume), null, null, null, null));
     }
 
-    public void testStoreAudioArtistsInternal() {
-        testStoreAudioArtists(true);
-    }
-
-    public void testStoreAudioArtistsExternal() {
-        testStoreAudioArtists(false);
-    }
-
-    private void testStoreAudioArtists(boolean isInternal) {
-        Uri artistsUri = isInternal ? Artists.INTERNAL_CONTENT_URI : Artists.EXTERNAL_CONTENT_URI;
+    @Test
+    public void testStoreAudioArtists() {
+        Uri artistsUri = Artists.getContentUri(mVolumeName);
         // do not support insert operation of the artists
         try {
             mContentResolver.insert(artistsUri, new ContentValues());
@@ -70,8 +83,7 @@
             // expected
         }
         // the artist items are inserted when inserting audio media
-        Uri uri = isInternal ? Audio1.getInstance().insertToInternal(mContentResolver)
-                : Audio1.getInstance().insertToExternal(mContentResolver);
+        Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
 
         String selection = Artists.ARTIST + "=?";
         String[] selectionArgs = new String[] { Audio1.ARTIST };
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
index 72d9067..bba1e1a 100644
--- 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
@@ -16,67 +16,73 @@
 
 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.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.Media;
 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.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStore_Audio_Artists_AlbumsTest extends InstrumentationTestCase {
+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;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @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(
-                MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME, 1);
+        Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, 1);
         assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
         c.close();
-
-        contentUri = MediaStore.Audio.Artists.Albums.getContentUri(
-                MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1);
-        assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
-        c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(MediaStore.Audio.Artists.Albums.getContentUri(volume, 1),
-                null, null, null, null));
     }
 
-    public void testStoreAudioArtistsAlbumsInternal() {
-        testStoreAudioArtistsAlbums(true);
-    }
-
-    public void testStoreAudioArtistsAlbumsExternal() {
-        testStoreAudioArtistsAlbums(false);
-    }
-
-    private void testStoreAudioArtistsAlbums(boolean isInternal) {
+    @Test
+    public void testStoreAudioArtistsAlbums() {
         // the album item is inserted when inserting audio media
-        Uri audioMediaUri = isInternal ? Audio1.getInstance().insertToInternal(mContentResolver)
-                : Audio1.getInstance().insertToExternal(mContentResolver);
+        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(isInternal ?
-                MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME :
-                    MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, artistId);
+        Uri artistsAlbumsUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, artistId);
         // do not support insert operation of the albums
         try {
             mContentResolver.insert(artistsAlbumsUri, new ContentValues());
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
index 8d41b38..c675059 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
@@ -16,32 +16,61 @@
 
 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.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Media;
 import android.provider.MediaStore.Audio.Genres.Members;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStore_Audio_GenresTest extends InstrumentationTestCase {
+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_GenresTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @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(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
+                Genres.getContentUri(mVolumeName), null, null,
                     null, null));
         c.close();
         try {
@@ -53,17 +82,14 @@
         } catch (SQLException e) {
             // expected
         }
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Genres.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testStoreAudioGenresExternal() {
         // insert
         ContentValues values = new ContentValues();
         values.put(Genres.NAME, "POP");
-        Uri uri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
         assertNotNull(uri);
 
         try {
@@ -74,20 +100,12 @@
             assertEquals("POP", c.getString(c.getColumnIndex(Genres.NAME)));
             assertTrue(c.getLong(c.getColumnIndex(Genres._ID)) > 0);
             c.close();
-
-            // update
-            values.clear();
-            values.put(Genres.NAME, "ROCK");
-            assertEquals(1, mContentResolver.update(uri, values, null, null));
-            c = mContentResolver.query(uri, null, null, null, null);
-            c.moveToFirst();
-            assertEquals("ROCK", c.getString(c.getColumnIndex(Genres.NAME)));
-            c.close();
         } finally {
             assertEquals(1, mContentResolver.delete(uri, null, null));
         }
     }
 
+    @Test
     public void testStoreAudioGenresInternal() {
         // the internal database does not have genres
         ContentValues values = new ContentValues();
@@ -96,35 +114,33 @@
         assertNull(uri);
     }
 
+    @Test
     public void testGetContentUriForAudioId() {
         // Insert an audio file into the content provider.
-        ContentValues values = Audio1.getInstance().getContentValues(true);
-        Uri audioUri = mContentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+        Uri audioUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
         assertNotNull(audioUri);
         long audioId = ContentUris.parseId(audioUri);
         assertTrue(audioId != -1);
 
         // Insert a genre into the content provider.
-        values.clear();
+        ContentValues values = new ContentValues();
         values.put(Genres.NAME, "Soda Pop");
-        Uri genreUri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+        Uri genreUri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
         assertNotNull(genreUri);
         long genreId = ContentUris.parseId(genreUri);
         assertTrue(genreId != -1);
 
         Cursor cursor = null;
         try {
-            String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
-
             // Check that the audio file has no genres yet.
-            Uri audioGenresUri = Genres.getContentUriForAudioId(volumeName, (int) audioId);
+            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(volumeName, genreId);
+            Uri membersUri = Members.getContentUri(mVolumeName, genreId);
             assertNotNull(mContentResolver.insert(membersUri, values));
 
             // Check that the audio file has the genre it was linked to.
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
index 7310fa1..9d704cb 100644
--- 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
@@ -16,58 +16,90 @@
 
 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.provider.MediaStore;
 import android.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Media;
 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.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStore_Audio_Genres_MembersTest extends InstrumentationTestCase {
+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_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;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
-        Uri uri = Audio1.getInstance().insertToExternal(mContentResolver);
+        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().insertToExternal(mContentResolver);
+        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();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @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.EXTERNAL_CONTENT_URI, Media._ID + "=" + mAudioIdOfJam, null);
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mAudioIdOfJamLive,
-                null);
-        super.tearDown();
+        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(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME, 1), null,
+                Members.getContentUri(mVolumeName, 1), null,
                     null, null, null));
         c.close();
 
@@ -80,17 +112,13 @@
         } catch (SQLException e) {
             // expected
         }
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Members.getContentUri(volume, 1), null, null, null,
-                null));
     }
 
+    @Test
     public void testStoreAudioGenresMembersExternal() {
         ContentValues values = new ContentValues();
         values.put(Genres.NAME, Audio1.GENRE);
-        Uri uri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
         Cursor c = mContentResolver.query(uri, null, null, null, null);
         c.moveToFirst();
 
@@ -99,13 +127,13 @@
         c.close();
 
         // verify that the Uri has the correct format and genre value
-        assertEquals(uri.toString(), "content://media/external/audio/genres/" + genreId);
+        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(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                genreId);
+        Uri membersUri = Members.getContentUri(mVolumeName, genreId);
         assertNotNull(mContentResolver.insert(membersUri, values));
 
         try {
@@ -118,15 +146,16 @@
             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)));
-            assertEquals(Audio1.EXTERNAL_DATA, c.getString(c.getColumnIndex(Members.DATA)));
+            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.FILE_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            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)));
-            assertEquals(Audio1.IS_DRM, c.getInt(c.getColumnIndex(Members.IS_DRM)));
             String albumKey = c.getString(c.getColumnIndex(Members.ALBUM_KEY));
             assertNotNull(albumKey);
             long albumId = c.getLong(c.getColumnIndex(Members.ALBUM_ID));
@@ -170,7 +199,9 @@
             c.close();
 
             // Query members across all genres
-            Uri allMembersUri = Uri.parse("content://media/external/audio/genres/all/members");
+            // 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;
@@ -196,7 +227,7 @@
             // create another genre
             values.clear();
             values.put(Genres.NAME, Audio1.GENRE + "-2");
-            uri = mContentResolver.insert(Genres.EXTERNAL_CONTENT_URI, values);
+            uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
             c = mContentResolver.query(uri, null, null, null, null);
             c.moveToFirst();
             genre2Id = c.getLong(c.getColumnIndex(Genres._ID));
@@ -205,8 +236,7 @@
             // insert the song into the second genre
             values.clear();
             values.put(Members.AUDIO_ID, mAudioIdOfJam);
-            Uri members2Uri = Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                genre2Id);
+            Uri members2Uri = Members.getContentUri(mVolumeName, genre2Id);
             assertNotNull(mContentResolver.insert(members2Uri, values));
 
             // Query members across all genres again
@@ -231,18 +261,6 @@
             assertEquals(1, jamcnt2);
             c.close();
 
-
-            // update the member
-            values.clear();
-            values.put(Members.AUDIO_ID, mAudioIdOfJamLive);
-            try {
-                mContentResolver.update(membersUri, values, null, null);
-                fail("Should throw SQLException because there is no column with name "
-                        + "\"Members.AUDIO_ID\" in the table");
-            } catch (SQLException e) {
-                // expected
-            }
-
             // Delete the members, note that this does not delete the genre itself
             assertEquals(1, mContentResolver.delete(membersUri, null, null)); // check number of rows deleted
 
@@ -260,8 +278,7 @@
             // 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(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                    genreId);
+            membersUri = Members.getContentUri(mVolumeName, genreId);
             assertNotNull(mContentResolver.insert(membersUri, values));
             // Query members across all genres
             c = mContentResolver.query(allMembersUri,
@@ -277,7 +294,7 @@
             }
             assertEquals(1, jamcnt);
             c.close();
-            mContentResolver.delete(Media.EXTERNAL_CONTENT_URI,
+            mContentResolver.delete(Media.getContentUri(mVolumeName),
                     Media._ID + "=" + mAudioIdOfJam, null);
             // Query members across all genres
             c = mContentResolver.query(allMembersUri,
@@ -294,9 +311,11 @@
             c.close();
         } finally {
             // the members are deleted when deleting the genre which they belong to
-            mContentResolver.delete(Genres.EXTERNAL_CONTENT_URI, Genres._ID + "=" + genreId, null);
+            mContentResolver.delete(Genres.getContentUri(mVolumeName),
+                    Genres._ID + "=" + genreId, null);
             if (genre2Id >= 0) {
-                mContentResolver.delete(Genres.EXTERNAL_CONTENT_URI, Genres._ID + "=" + genre2Id, null);
+                mContentResolver.delete(Genres.getContentUri(mVolumeName),
+                        Genres._ID + "=" + genre2Id, null);
             }
             c = mContentResolver.query(membersUri, null, null, null, null);
             assertEquals(0, c.getCount());
@@ -304,6 +323,7 @@
         }
     }
 
+    @Test
     public void testStoreAudioGenresMembersInternal() {
         // the internal database can not have genres
         ContentValues values = new ContentValues();
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
index 82c2342..22f91f3 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
@@ -16,43 +16,62 @@
 
 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.provider.MediaStore.Audio.Media;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-public class MediaStore_Audio_MediaTest extends InstrumentationTestCase {
+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_MediaTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @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(
-                Media.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME), null, null,
+                Media.getContentUri(mVolumeName), null, null,
                     null, null));
         c.close();
-        assertNotNull(c = mContentResolver.query(
-                Media.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
-                    null, null));
-        c.close();
-
-        // can not accept any other volume names
-        String volume = "faveVolume";
-        assertNull(mContentResolver.query(Media.getContentUri(volume), null, null, null, null));
     }
 
+    @Test
     public void testGetContentUriForPath() {
         Cursor c = null;
         String externalPath = Environment.getExternalStorageDirectory().getPath();
@@ -60,26 +79,18 @@
                 null, null));
         c.close();
 
-        String internalPath =
-            getInstrumentation().getTargetContext().getFilesDir().getAbsolutePath();
+        String internalPath = mContext.getFilesDir().getAbsolutePath();
         assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
                 null, null));
         c.close();
     }
 
-    public void testStoreAudioMediaInternal() {
-        testStoreAudioMedia(true);
-    }
-
-    public void testStoreAudioMediaExternal() {
-        testStoreAudioMedia(false);
-    }
-
-    private void testStoreAudioMedia(boolean isInternal) {
+    @Test
+    public void testStoreAudioMedia() {
         Audio1 audio1 = Audio1.getInstance();
-        ContentValues values = audio1.getContentValues(isInternal);
+        ContentValues values = audio1.getContentValues(mVolumeName);
         //insert
-        Uri mediaUri = isInternal ? Media.INTERNAL_CONTENT_URI : Media.EXTERNAL_CONTENT_URI;
+        Uri mediaUri = Media.getContentUri(mVolumeName);
         Uri uri = mContentResolver.insert(mediaUri, values);
         assertNotNull(uri);
 
@@ -93,12 +104,11 @@
             c.moveToFirst();
             long id = c.getLong(c.getColumnIndex(Media._ID));
             assertTrue(id > 0);
-            String expected = isInternal ? Audio1.INTERNAL_DATA : Audio1.EXTERNAL_DATA;
+            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.IS_DRM, c.getInt(c.getColumnIndex(Media.IS_DRM)));
-            assertEquals(Audio1.FILE_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+            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)));
@@ -124,48 +134,10 @@
             assertNotNull(titleKey);
             c.close();
 
-            // update
-            // the column DISPLAY_NAME will not be ignored when updating
-            Audio2 audio2 = Audio2.getInstance();
-            values = audio2.getContentValues(isInternal);
-
-            int result = mContentResolver.update(uri, values, null, null);
-            assertEquals(1, result);
-            c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long id2 = c.getLong(c.getColumnIndex(Media._ID));
-            assertTrue(id == id2);
-            expected = isInternal ? Audio2.INTERNAL_DATA : Audio2.EXTERNAL_DATA;
-            assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
-            assertEquals(Audio2.DATE_MODIFIED, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
-            assertEquals(Audio2.IS_DRM, c.getInt(c.getColumnIndex(Media.IS_DRM)));
-            assertEquals(Audio2.DISPLAY_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals(Audio2.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals(Audio2.SIZE, c.getInt(c.getColumnIndex(Media.SIZE)));
-            assertEquals(Audio2.TITLE, c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(Audio2.ALBUM, c.getString(c.getColumnIndex(Media.ALBUM)));
-            assertFalse(albumKey.equals(c.getString(c.getColumnIndex(Media.ALBUM_KEY))));
-            assertTrue(albumId !=  c.getLong(c.getColumnIndex(Media.ALBUM_ID)));
-            assertEquals(Audio2.ARTIST, c.getString(c.getColumnIndex(Media.ARTIST)));
-            assertFalse(artistKey.equals(c.getString(c.getColumnIndex(Media.ARTIST_KEY))));
-            assertTrue(artistId !=  c.getLong(c.getColumnIndex(Media.ARTIST_ID)));
-            assertEquals(Audio2.COMPOSER, c.getString(c.getColumnIndex(Media.COMPOSER)));
-            assertEquals(Audio2.DURATION, c.getLong(c.getColumnIndex(Media.DURATION)));
-            assertEquals(Audio2.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
-            assertEquals(Audio2.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
-            assertEquals(Audio2.IS_NOTIFICATION,
-                    c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
-            assertEquals(Audio2.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
-            assertEquals(Audio2.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
-            assertEquals(Audio2.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
-            assertTrue(titleKey.equals(c.getString(c.getColumnIndex(Media.TITLE_KEY))));
-            c.close();
-
             // test filtering
-            Uri baseUri = isInternal ? Media.INTERNAL_CONTENT_URI : Media.EXTERNAL_CONTENT_URI;
+            Uri baseUri = Media.getContentUri(mVolumeName);
             Uri filterUri = baseUri.buildUpon()
-                .appendQueryParameter("filter", Audio2.ARTIST).build();
+                .appendQueryParameter("filter", Audio1.ARTIST).build();
             c = mContentResolver.query(filterUri, null, null, null, null);
             assertEquals(1, c.getCount());
             c.moveToFirst();
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
index 463dfcb..a06e93c 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
@@ -16,32 +16,59 @@
 
 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.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.net.Uri;
-import android.os.Environment;
 import android.provider.MediaStore.Audio.Playlists;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-import java.util.regex.Pattern;
+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;
 
-public class MediaStore_Audio_PlaylistsTest extends InstrumentationTestCase {
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_PlaylistsTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Parameter(0)
+    public String mVolumeName;
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+    @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(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME), null, null,
+                Playlists.getContentUri(mVolumeName), null, null,
                 null, null));
         c.close();
 
@@ -55,15 +82,12 @@
         } catch (SQLException e) {
             // expected
         }
-
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Playlists.getContentUri(volume), null, null, null,
-                null));
     }
 
-    public void testStoreAudioPlaylistsExternal() {
-        final String externalPlaylistPath = Environment.getExternalStorageDirectory().getPath() +
-            "/my_favorites.pl";
+    @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);
@@ -71,7 +95,7 @@
         long dateModified = System.currentTimeMillis() / 1000;
         values.put(Playlists.DATE_MODIFIED, dateModified);
         // insert
-        Uri uri = mContentResolver.insert(Playlists.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
         assertNotNull(uri);
 
         try {
@@ -88,45 +112,8 @@
             assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
             assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0);
             c.close();
-
-            // update
-            values.clear();
-            values.put(Playlists.NAME, "xxx");
-            dateModified = System.currentTimeMillis();
-            values.put(Playlists.DATE_MODIFIED, dateModified);
-            assertEquals(1, mContentResolver.update(uri, values, null, null));
-            c = mContentResolver.query(uri, null, null, null, null);
-            c.moveToFirst();
-            assertEquals("xxx", c.getString(c.getColumnIndex(Playlists.NAME)));
-            assertEquals(externalPlaylistPath,
-                    c.getString(c.getColumnIndex(Playlists.DATA)));
-
-            assertEquals(realDateAdded, c.getLong(c.getColumnIndex(Playlists.DATE_ADDED)));
-            assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
-            c.close();
         } finally {
             assertEquals(1, mContentResolver.delete(uri, null, null));
         }
     }
-
-    public void testStoreAudioPlaylistsInternal() {
-        ContentValues values = new ContentValues();
-        values.put(Playlists.NAME, "My favourites");
-        values.put(Playlists.DATA, "/data/data/android.provider.cts/files/my_favorites.pl");
-        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.INTERNAL_CONTENT_URI, values);
-        assertNotNull(uri);
-
-        try {
-            assertTrue(Pattern.matches("content://media/internal/audio/playlists/\\d+",
-                    uri.toString()));
-        } finally {
-            // delete the playlists
-            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
index cc69942..89b7bc5 100644
--- 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
@@ -16,9 +16,17 @@
 
 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.provider.MediaStore;
@@ -31,11 +39,19 @@
 import android.provider.cts.MediaStoreAudioTestHelper.Audio4;
 import android.provider.cts.MediaStoreAudioTestHelper.Audio5;
 import android.provider.cts.MediaStoreAudioTestHelper.MockAudioMediaInfo;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-import java.util.regex.Pattern;
+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;
 
-public class MediaStore_Audio_Playlists_MembersTest extends InstrumentationTestCase {
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Playlists_MembersTest {
     private String[] mAudioProjection = {
             Members._ID,
             Members.ALBUM,
@@ -51,7 +67,6 @@
             Members.DISPLAY_NAME,
             Members.DURATION,
             Members.IS_ALARM,
-            Members.IS_DRM,
             Members.IS_MUSIC,
             Members.IS_NOTIFICATION,
             Members.IS_RINGTONE,
@@ -81,7 +96,6 @@
             Members.DISPLAY_NAME,
             Members.DURATION,
             Members.IS_ALARM,
-            Members.IS_DRM,
             Members.IS_MUSIC,
             Members.IS_NOTIFICATION,
             Members.IS_RINGTONE,
@@ -93,6 +107,7 @@
             Members.YEAR,
     };
 
+    private Context mContext;
     private ContentResolver mContentResolver;
 
     private long mIdOfAudio1;
@@ -101,8 +116,16 @@
     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.insertToExternal(mContentResolver);
+        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));
@@ -110,11 +133,13 @@
         return id;
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
 
-        mContentResolver = getInstrumentation().getContext().getContentResolver();
+        Log.d(TAG, "Using volume " + mVolumeName);
+
         mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
         mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
         mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
@@ -122,16 +147,17 @@
         mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio1, null);
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio2, null);
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio3, null);
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio4, null);
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio5, null);
-        super.tearDown();
+    @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());
@@ -182,7 +208,11 @@
         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, "");
@@ -191,7 +221,7 @@
         long dateModified = System.currentTimeMillis();
         values.put(Playlists.DATE_MODIFIED, dateModified);
         // insert
-        Uri uri = mContentResolver.insert(Playlists.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
         assertNotNull(uri);
         Cursor c = mContentResolver.query(uri, null, null, null, null);
         c.moveToFirst();
@@ -199,11 +229,11 @@
         long playlist2Id = -1; // used later
 
         // verify that the Uri has the correct format and playlist value
-        assertEquals(uri.toString(), "content://media/external/audio/playlists/" + playlistId);
+        assertEquals(ContentUris.withAppendedId(Playlists.getContentUri(mVolumeName), playlistId),
+                uri);
 
         // insert audio as the member of the playlist
-        Uri membersUri = Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                playlistId);
+        Uri membersUri = Members.getContentUri(mVolumeName, playlistId);
         Uri audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
 
         assertNotNull(audioUri);
@@ -216,11 +246,12 @@
             c.moveToFirst();
             long memberId = c.getLong(c.getColumnIndex(Members._ID));
             assertEquals(memberId, Long.parseLong(audioUri.getPathSegments().get(5)));
-            assertEquals(Audio1.EXTERNAL_DATA, c.getString(c.getColumnIndex(Members.DATA)));
+            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.IS_DRM, c.getInt(c.getColumnIndex(Members.IS_DRM)));
-            assertEquals(Audio1.FILE_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            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)));
@@ -260,11 +291,12 @@
             c.moveToFirst();
             assertEquals(2, c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
             assertEquals(memberId, c.getLong(c.getColumnIndex(Members._ID)));
-            assertEquals(Audio2.EXTERNAL_DATA, c.getString(c.getColumnIndex(Members.DATA)));
+            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.IS_DRM, c.getInt(c.getColumnIndex(Members.IS_DRM)));
-            assertEquals(Audio2.FILE_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            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)));
@@ -319,7 +351,8 @@
                     new long [] {mIdOfAudio1, mIdOfAudio2, mIdOfAudio3}, new int [] {1,2,3});
 
             // delete the middle item
-            mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, Media._ID + "=" + mIdOfAudio2, null);
+            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
@@ -353,7 +386,7 @@
             values.put(Playlists.DATE_ADDED, dateAdded);
             values.put(Playlists.DATE_MODIFIED, dateModified);
             // insert
-            uri = mContentResolver.insert(Playlists.EXTERNAL_CONTENT_URI, values);
+            uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
             assertNotNull(uri);
             c = mContentResolver.query(uri, null, null, null, null);
             c.moveToFirst();
@@ -361,8 +394,7 @@
             c.close();
 
             // insert audio into 2nd playlist
-            Uri members2Uri = Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                    playlist2Id);
+            Uri members2Uri = Members.getContentUri(mVolumeName, playlist2Id);
             Uri audio2Uri = insertPlaylistItem(members2Uri, mIdOfAudio1, 1);
 
             c = mContentResolver.query(membersUri, null, null, null, null);
@@ -406,8 +438,7 @@
 
             // insert again, then verify that deleting the audio entry cleans up its playlist member
             // entry as well
-            membersUri = Members.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME,
-                    playlistId);
+            membersUri = Members.getContentUri(mVolumeName, playlistId);
             audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
             assertNotNull(audioUri);
             // Query members of the playlist
@@ -424,7 +455,7 @@
             }
             assertEquals(1, cnt);
             c.close();
-            mContentResolver.delete(Media.EXTERNAL_CONTENT_URI,
+            mContentResolver.delete(Media.getContentUri(mVolumeName),
                     Media._ID + "=" + mIdOfAudio1, null);
             // Query members of the playlist
             c = mContentResolver.query(membersUri,
@@ -442,33 +473,12 @@
 
         } finally {
             // delete the playlists
-            mContentResolver.delete(Playlists.EXTERNAL_CONTENT_URI,
+            mContentResolver.delete(Playlists.getContentUri(mVolumeName),
                     Playlists._ID + "=" + playlistId, null);
             if (playlist2Id >= 0) {
-                mContentResolver.delete(Playlists.EXTERNAL_CONTENT_URI,
+                mContentResolver.delete(Playlists.getContentUri(mVolumeName),
                         Playlists._ID + "=" + playlist2Id, null);
             }
         }
     }
-
-    public void testStoreAudioPlaylistsMembersInternal() {
-        ContentValues values = new ContentValues();
-        values.put(Playlists.NAME, "My favourites");
-        values.put(Playlists.DATA, "/data/data/android.provider.cts/files/my_favorites.pl");
-        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.INTERNAL_CONTENT_URI, values);
-        assertNotNull(uri);
-
-        try {
-            assertTrue(Pattern.matches("content://media/internal/audio/playlists/\\d+",
-                    uri.toString()));
-        } finally {
-            // delete the playlists
-            assertEquals(1, mContentResolver.delete(uri, null, 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
new file mode 100644
index 0000000..f9a3372
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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 org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+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.ContentObserver;
+import android.database.Cursor;
+import android.media.MediaScannerConnection;
+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.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_DownloadsTest {
+    private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
+    private static final long SCAN_TIMEOUT_MILLIS = 4000;
+    private static final long NOTIFY_TIMEOUT_MILLIS = 4000;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private File mDownloadsDir;
+    private File mPicturesDir;
+    private ArrayList<Uri> mAddedUris;
+    private final Uri mExternalDownloads = Downloads.EXTERNAL_CONTENT_URI;
+    private CountDownLatch mCountDownLatch;
+    private int mInitialDownloadsCount;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+        mDownloadsDir = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOWNLOADS);
+        mPicturesDir = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_PICTURES);
+        mDownloadsDir.mkdir();
+        mPicturesDir.mkdir();
+        mAddedUris = new ArrayList<>();
+        mInitialDownloadsCount = getInitialDownloadsCount();
+    }
+
+    @After
+    public void tearDown() {
+        for (Uri uri : mAddedUris) {
+            mContentResolver.delete(uri, null, null);
+        }
+    }
+
+    @Test
+    public void testScannedDownload() throws Exception {
+        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 {
+        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(Downloads.INTERNAL_CONTENT_URI,
+                null, null, null, null));
+        c.close();
+        assertNotNull(c = mContentResolver.query(Downloads.EXTERNAL_CONTENT_URI,
+                null, null, null, null));
+        c.close();
+    }
+
+    @Test
+    public void testMediaInDownloadsDir() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final Uri insertUri = insertImage(displayName, "test image",
+                new File(mDownloadsDir, "scenery.jpg"), "image/jpeg", R.raw.scenery);
+        final String displayName2 = "cts" + System.nanoTime();
+        final Uri insertUri2 = insertImage(displayName2, "test image2",
+                new File(mPicturesDir, "volantis.jpg"), "image/jpeg", R.raw.volantis);
+
+        try (Cursor cursor = mContentResolver.query(Downloads.EXTERNAL_CONTENT_URI,
+                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));
+        mAddedUris.remove(insertUri);
+        try (Cursor cursor = mContentResolver.query(Downloads.EXTERNAL_CONTENT_URI,
+                null, null, null, null)) {
+            assertEquals(mInitialDownloadsCount, cursor.getCount());
+        }
+    }
+
+    @Test
+    public void testUpdateDownload() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        mAddedUris.add(pendingUri);
+        final Uri publishUri;
+        try (MediaStore.PendingSession session = MediaStore.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/3gp");
+        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/3gp",
+                    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 MediaStore.PendingParams params = new MediaStore.PendingParams(
+                Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        mAddedUris.add(pendingUri);
+        final Uri publishUri;
+        try (MediaStore.PendingSession session = MediaStore.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(Downloads.EXTERNAL_CONTENT_URI,
+                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(Downloads.EXTERNAL_CONTENT_URI, true, observer);
+        mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer);
+        final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon()
+                .appendPath(MediaStore.getVolumeName(Downloads.EXTERNAL_CONTENT_URI))
+                .build();
+        mContentResolver.registerContentObserver(volumeUri, false, observer);
+
+        mCountDownLatch = new CountDownLatch(1);
+        final String displayName = "cts" + System.nanoTime();
+        final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        mAddedUris.add(pendingUri);
+        final Uri publishUri;
+        try (MediaStore.PendingSession session = MediaStore.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(Downloads.EXTERNAL_CONTENT_URI,
+                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(R.raw.scenery);
+             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(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+        assertNotNull(insertUri);
+        mAddedUris.add(insertUri);
+        return insertUri;
+    }
+
+    private void verifyScannedDownload(File file) throws Exception {
+        final Uri mediaStoreUri = scanFile(file);
+        Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri);
+        mAddedUris.add(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());
+    }
+
+    private Uri scanFile(File file) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Uri[] mediaStoreUris = new Uri[1];
+        MediaScannerConnection.scanFile(mContext,
+                new String[] {file.getAbsolutePath()},
+                null /* mimeType */,
+                (String path, Uri uri) -> {
+                    mediaStoreUris[0] = uri;
+                    latch.countDown();
+                });
+
+        if (!latch.await(SCAN_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for scanFile: " + file.getAbsolutePath());
+        }
+        assertNotNull("Failed to scan " + file.getAbsolutePath(), mediaStoreUris[0]);
+        return mediaStoreUris[0];
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index 621d973..ffc6b49 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -16,6 +16,16 @@
 
 package android.provider.cts;
 
+import static android.provider.cts.MediaStoreTest.TAG;
+import static android.provider.cts.ProviderTestUtils.containsId;
+
+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;
@@ -24,109 +34,76 @@
 import android.net.Uri;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
 import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
 import android.provider.MediaStore.Files.FileColumns;
-import android.test.AndroidTestCase;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+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.FileOutputStream;
-import java.io.FilenameFilter;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
-public class MediaStore_FilesTest extends AndroidTestCase {
-
+@RunWith(Parameterized.class)
+public class MediaStore_FilesTest {
+    private Context mContext;
     private ContentResolver mResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    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();
-        cleanup();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+        mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        cleanup();
-    }
+    @Test
+    public void testGetContentUri() throws Exception {
+        Uri allFilesUri = mExternalFiles;
 
-    void cleanup() {
-        final String testName = getClass().getCanonicalName();
-        mResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                "_data LIKE ?1", new String[] {"%" + testName + "%"});
-
-        mResolver.delete(MediaStore.Files.getContentUri("external"),
-                "_data LIKE ?1", new String[] {"%" + testName + "%"});
-
-        File ext = Environment.getExternalStorageDirectory();
-        File[] junk = ext.listFiles(new FilenameFilter() {
-
-            @Override
-            public boolean accept(File dir, String filename) {
-                return filename.contains(testName);
-            }
-        });
-        for (File f: junk) {
-            deleteAll(f);
-        }
-    }
-
-    void deleteAll(File f) {
-        if (f.isDirectory()) {
-            File [] sub = f.listFiles();
-            for (File s: sub) {
-                deleteAll(s);
-            }
-        }
-        f.delete();
-    }
-
-    public void testGetContentUri() {
-        String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
-        Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
-
-        // Get the current file count. We will check if this increases after
-        // adding a file to the provider.
-        int fileCount = getFileCount(allFilesUri);
-
-        // Check that inserting empty values causes an exception.
         ContentValues values = new ContentValues();
-        try {
-            mResolver.insert(allFilesUri, values);
-            fail("Should throw an exception");
-        } catch (IllegalArgumentException e) {
-            // Expecting an exception
-        }
 
         // Add a path for a file and check that the returned uri appends a
         // path properly.
-        String dataPath = "does_not_really_exist.txt";
+        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 = MediaStore.Files.getContentUri(volumeName, fileId);
+        Uri rowUri = ContentUris.withAppendedId(mExternalFiles, fileId);
         assertEquals(fileUri, rowUri);
 
         // Check that the file count has increased.
-        int newFileCount = getFileCount(allFilesUri);
-        assertEquals(fileCount + 1, newFileCount);
+        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 = "still_does_not_exist.txt";
+        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);
@@ -137,7 +114,7 @@
 
         // Delete the file and observe that the file count decreased.
         assertEquals(1, mResolver.delete(fileUri, null, null));
-        assertEquals(fileCount, getFileCount(allFilesUri));
+        assertFalse(containsId(allFilesUri, fileId));
 
         // Make sure the deleted file is not returned by the cursor.
         Cursor cursor = mResolver.query(fileUri, null, null, null, null);
@@ -150,7 +127,10 @@
         // insert file and check its parent
         values.clear();
         try {
-            String b = mContext.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getCanonicalPath();
+            File stageDir = new File(ProviderTestUtils.stageDir(mVolumeName),
+                    Environment.DIRECTORY_MUSIC);
+            stageDir.mkdirs();
+            String b = stageDir.getAbsolutePath();
             values.put(MediaColumns.DATA, b + "/testing");
             fileUri = mResolver.insert(allFilesUri, values);
             cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
@@ -176,327 +156,115 @@
         }
     }
 
+    @Test
     public void testCaseSensitivity() throws IOException {
-        String fileDir = Environment.getExternalStorageDirectory() +
-                "/" + getClass().getCanonicalName();
-        String fileName = fileDir + "/Test.Mp3";
-        writeFile(R.raw.testmp3, fileName);
+        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);
 
-        String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
-        Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
+        Uri allFilesUri = mExternalFiles;
         ContentValues values = new ContentValues();
-        values.put(MediaColumns.DATA, fileDir + "/test.mp3");
+        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);
-            new File(fileName).delete();
-            new File(fileDir).delete();
         }
     }
 
-    String realPathFor(ParcelFileDescriptor pfd) {
-        File real = new File("/proc/self/fd/" + pfd.getFd());
-        try {
-            return real.getCanonicalPath();
-        } catch (IOException e) {
-            return 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("Failed to insert " + 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);
+
+            try {
+                mResolver.insert(internalFiles, values);
+                fail("Able to insert " + invalid);
+            } catch (SecurityException | IllegalArgumentException expected) {
+            }
         }
     }
 
-    public void testAccess() throws IOException {
-        // clean up from previous run
-        mResolver.delete(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
-                "_data NOT LIKE ?", new String[] { "/system/%" } );
+    @Test
+    public void testAccess() throws Exception {
+        final String path = MediaStore.getVolumePath(mVolumeName).getAbsolutePath();
+        final Uri updateUri = ContentUris.withAppendedId(mExternalFiles,
+                ContentUris.parseId(ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages)));
 
-        // insert some dummy starter data into the provider
-        ContentValues values = new ContentValues();
-        values.put(MediaStore.Images.Media.DISPLAY_NAME, "My Bitmap");
-        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
-        values.put(MediaStore.Images.Media.DATA, "/foo/bar/dummy.jpg");
-        Uri uri = mResolver.insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
+        for (String valid : new String[] {
+                path + "/" + System.nanoTime() + ".jpg",
+                path + "/DCIM/" + System.nanoTime() + ".jpg",
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.DATA, valid);
 
-        // point _data at directory and try to get an fd for it
-        values = new ContentValues();
-        values.put("_data", "/data/media");
-        mResolver.update(uri, values, null, null);
-        ParcelFileDescriptor pfd = null;
-        try {
-            pfd = mResolver.openFileDescriptor(uri, "r");
-            pfd.close();
-            fail("shouldn't be here");
-        } catch (FileNotFoundException e) {
-            // expected
+            final Uri uri = mResolver.insert(mExternalFiles, values);
+            assertNotNull("Failed to insert " + valid, uri);
+            mResolver.delete(uri, null, null);
+
+            final int count = mResolver.update(updateUri, values, null, null);
+            assertEquals("Failed to update", 1, count);
         }
 
-        // try to create a file in a place we don't have access to
-        values = new ContentValues();
-        values.put("_data", "/data/media/test.dat");
-        mResolver.update(uri, values, null, null);
-        try {
-            pfd = mResolver.openFileDescriptor(uri, "w");
-            pfd.close();
-            fail("shouldn't be here");
-        } catch (FileNotFoundException e) {
-            // expected
-        }
-        // read file back
-        try {
-            pfd = mResolver.openFileDescriptor(uri, "r");
-            pfd.close();
-            fail("shouldn't be here");
-        } catch (FileNotFoundException e) {
-            // expected
-        }
+        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);
 
-        // point _data at media database and read it
-        values = new ContentValues();
-        values.put("_data", "/data/data/com.android.providers.media/databases/internal.db");
-        mResolver.update(uri, values, null, null);
-        try {
-            pfd = mResolver.openFileDescriptor(uri, "r");
-            pfd.close();
-            fail("shouldn't be here");
-        } catch (FileNotFoundException e) {
-            // expected
-        }
-
-        // Insert a private file into the database. Since it's private, the media provider won't
-        // be able to open it
-        FileOutputStream fos = mContext.openFileOutput("dummy.dat", Context.MODE_PRIVATE);
-        fos.write(0);
-        fos.close();
-        File path = mContext.getFileStreamPath("dummy.dat");
-        values = new ContentValues();
-        values.put("_data", path.getAbsolutePath());
-
-        mResolver.update(uri, values, null, null);
-        try {
-            pfd = mResolver.openFileDescriptor(uri, "r");
-            pfd.close();
-            fail("shouldn't be here");
-        } catch (FileNotFoundException e) {
-            // expected
-        }
-        path.delete();
-
-        File sdfile = null;
-        if (Environment.isExternalStorageEmulated()) {
-            // create file on sdcard and check access via real path
-            String fileDir = Environment.getExternalStorageDirectory() +
-                    "/" + getClass().getCanonicalName() + "/test.mp3";
-            sdfile = new File(fileDir);
-            writeFile(R.raw.testmp3, sdfile.getCanonicalPath());
-            assertTrue(sdfile.exists());
-            values = new ContentValues();
-            values.put("_data", sdfile.getCanonicalPath());
-            mResolver.update(uri, values, null, null);
             try {
-                pfd = mResolver.openFileDescriptor(uri, "r");
-
-                // get the real path from the file descriptor (this relies on the media provider
-                // having opened the path via the real path instead of the emulated path).
-                String realPath = realPathFor(pfd);
-                pfd.close();
-                if (realPath.equals(sdfile.getCanonicalPath())) {
-                    // provider did not use real/translated path
-                    sdfile = null;
-                } else {
-                    values = new ContentValues();
-                    values.put("_data", realPath);
-                    mResolver.update(uri, values, null, null);
-
-                    // we shouldn't be able to access this
-                    try {
-                        pfd = mResolver.openFileDescriptor(uri, "r");
-                        fail("shouldn't have fd for " + realPath);
-                    } catch (FileNotFoundException e) {
-                        // expected
-                    } finally {
-                        pfd.close();
-                    }
-                }
-            } catch (FileNotFoundException e) {
-                fail("couldn't open file");
+                mResolver.insert(mExternalFiles, values);
+                fail("Able to insert " + invalid);
+            } catch (SecurityException | IllegalArgumentException expected) {
             }
-        }
 
-        // clean up
-        assertEquals(1, mResolver.delete(uri, null, null));
-        if (sdfile != null) {
-            assertEquals("couldn't delete " + sdfile.getCanonicalPath(), true, sdfile.delete());
-        }
-
-        // test secondary storage if present
-        List<File> allpaths = getSecondaryPackageSpecificPaths(mContext);
-        List<String> trimmedPaths = new ArrayList<String>();
-
-        for (File extpath: allpaths) {
-            assertNotNull("Valid media must be inserted during CTS", extpath);
-            assertEquals("Valid media must be inserted for " + extpath
-                    + " during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(extpath));
-
-            File child = extpath;
-            while (true) {
-                File parent = child.getParentFile();
-                if (parent == null) {
-                    fail("didn't expect to be here");
-                }
-                if (!Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(parent))) {
-                    // we went past the root
-                    String abspath = child.getAbsolutePath();
-                    if (!trimmedPaths.contains(abspath)) {
-                        trimmedPaths.add(abspath);
-                    }
-                    break;
-                }
-                child = parent;
-            }
-        }
-
-        String fileDir = Environment.getExternalStorageDirectory() +
-                "/" + getClass().getCanonicalName() + "-" + SystemClock.elapsedRealtime();
-        String fileName = fileDir + "/TestSecondary.Mp3";
-        writeFile(R.raw.testmp3_2, fileName); // file without album art
-
-
-        // insert temp file
-        values = new ContentValues();
-        values.put(MediaStore.Audio.Media.DATA, fileName);
-        values.put(MediaStore.Audio.Media.ARTIST, "Artist-" + SystemClock.elapsedRealtime());
-        values.put(MediaStore.Audio.Media.ALBUM, "Album-" + SystemClock.elapsedRealtime());
-        values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
-        Uri fileUri = mResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
-        // give media provider some time to realize there's no album art
-        SystemClock.sleep(1000);
-        // get its album id
-        Cursor c = mResolver.query(fileUri, new String[] { MediaStore.Audio.Media.ALBUM_ID},
-                null, null, null);
-        assertTrue(c.moveToFirst());
-        int albumid = c.getInt(0);
-        Uri albumArtUriBase = Uri.parse("content://media/external/audio/albumart");
-        Uri albumArtUri = ContentUris.withAppendedId(albumArtUriBase, albumid);
-        try {
-            pfd = mResolver.openFileDescriptor(albumArtUri, "r");
-            fail("no album art, shouldn't be here. Got: " + realPathFor(pfd));
-        } catch (Exception e) {
-            // expected
-        }
-
-        // replace file with one that has album art
-        writeFile(R.raw.testmp3, fileName); // file with album art
-
-        for (String s: trimmedPaths) {
-            File dir = new File(s + "/foobardir-" + SystemClock.elapsedRealtime());
-            assertFalse("please remove " + dir.getAbsolutePath()
-                    + " before running", dir.exists());
-            File file = new File(dir, "foobar");
-            values = new ContentValues();
-            values.put(MediaStore.Audio.Media.ALBUM_ID, albumid);
-            values.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
-            mResolver.insert(albumArtUriBase, values);
             try {
-                pfd = mResolver.openFileDescriptor(albumArtUri, "r");
-                fail("shouldn't have fd for album " + albumid + ", got " + realPathFor(pfd));
-            } catch (Exception e) {
-                // expected
-            } finally {
-                pfd.close();
+                mResolver.update(updateUri, values, null, null);
+                fail("Able to update " + invalid);
+            } catch (SecurityException | IllegalArgumentException expected) {
             }
-            assertFalse(dir.getAbsolutePath() + " was created", dir.exists());
-        }
-        mResolver.delete(fileUri, null, null);
-        new File(fileName).delete();
-
-        // try creating files in root
-        for (String s: trimmedPaths) {
-            File dir = new File(s);
-            File file = new File(dir, "foobar.jpg");
-
-            values = new ContentValues();
-            values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
-            fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
-            assertNotNull(fileUri);
-
-            // check that adding the file doesn't cause it to be created
-            assertFalse(file.exists());
-
-            // check if opening the file for write works
-            try {
-                mResolver.openOutputStream(fileUri).close();
-                fail("shouldn't have been able to create output stream");
-            } catch (SecurityException e) {
-                // expected
-            }
-            // check that deleting the file doesn't cause it to be created
-            mResolver.delete(fileUri, null, null);
-            assertFalse(file.exists());
-        }
-
-        // try creating files in new subdir
-        for (String s: trimmedPaths) {
-            File dir = new File(s + "/foobardir");
-            File file = new File(dir, "foobar.jpg");
-
-            values = new ContentValues();
-            values.put(MediaStore.Files.FileColumns.DATA, dir.getAbsolutePath());
-
-            Uri dirUri = mResolver.insert(MediaStore.Files.getContentUri("external"), values);
-            assertNotNull(dirUri);
-
-            values = new ContentValues();
-            values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
-            fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
-            assertNotNull(fileUri);
-
-            // check that adding the file or its folder didn't cause either one to be created
-            assertFalse(dir.exists());
-            assertFalse(file.exists());
-
-            // check if opening the file for write works
-            try {
-                mResolver.openOutputStream(fileUri).close();
-                fail("shouldn't have been able to create output stream");
-            } catch (SecurityException e) {
-                // expected
-            }
-            // check that deleting the file or its folder doesn't cause either one to be created
-            mResolver.delete(fileUri, null, null);
-            assertFalse(dir.exists());
-            assertFalse(file.exists());
-            mResolver.delete(dirUri, null, null);
-            assertFalse(dir.exists());
-            assertFalse(file.exists());
         }
     }
 
-    public static List<File> getSecondaryPackageSpecificPaths(Context context) {
-        final List<File> paths = new ArrayList<File>();
-        Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
-        Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
-        Collections.addAll(
-                paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
-        Collections.addAll(paths, dropFirst(context.getObbDirs()));
-        return paths;
-    }
-
+    @Test
     public void testUpdateMediaType() throws Exception {
-        String fileDir = Environment.getExternalStorageDirectory() +
-                "/" + getClass().getCanonicalName();
-        String fileName = fileDir + "/test.mp3";
-        writeFile(R.raw.testmp3, fileName);
+        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "test" + System.nanoTime() + ".mp3");
+        ProviderTestUtils.stageFile(R.raw.testmp3, file);
 
-        String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
-        Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
+        Uri allFilesUri = mExternalFiles;
         ContentValues values = new ContentValues();
-        values.put(MediaColumns.DATA, fileName);
+        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
@@ -513,29 +281,6 @@
         }
     }
 
-    private static File[] dropFirst(File[] before) {
-        final File[] after = new File[before.length - 1];
-        System.arraycopy(before, 1, after, 0, after.length);
-        return after;
-    }
-
-    private void writeFile(int resid, String path) throws IOException {
-        File out = new File(path);
-        File dir = out.getParentFile();
-        dir.mkdirs();
-        FileCopyHelper copier = new FileCopyHelper(mContext);
-        copier.copyToExternalStorage(resid, out);
-    }
-
-    private int getFileCount(Uri uri) {
-        Cursor cursor = mResolver.query(uri, null, null, null, null);
-        try {
-            return cursor.getCount();
-        } finally {
-            cursor.close();
-        }
-    }
-
     private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
         Cursor cursor = mResolver.query(fileUri, null, null, null, null);
         try {
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
index 7a66c8c..d930560 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
@@ -16,7 +16,12 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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;
@@ -24,86 +29,83 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.media.ExifInterface;
 import android.net.Uri;
-import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+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.MediaStore.Images.Thumbnails;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
+import android.util.Size;
 
-import com.android.compatibility.common.util.FileCopyHelper;
 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.lang.Math;
-import java.util.ArrayList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 
-public class MediaStore_Images_MediaTest extends InstrumentationTestCase {
+@RunWith(Parameterized.class)
+public class MediaStore_Images_MediaTest {
     private static final String MIME_TYPE_JPEG = "image/jpeg";
 
-    private static final String TEST_TITLE1 = "test title1";
-
-    private static final String TEST_DESCRIPTION1 = "test description1";
-
-    private static final String TEST_TITLE2 = "test title2";
-
-    private static final String TEST_DESCRIPTION2 = "test description2";
-
-    private static final String TEST_TITLE3 = "test title3";
-
-    private static final String TEST_DESCRIPTION3 = "test description3";
-
-    private static final String LOG_TAG = "MediaStore_Images_MediaTest";
-    
-    private ArrayList<Uri> mRowsAdded;
-
     private Context mContext;
-
     private ContentResolver mContentResolver;
 
-    private FileCopyHelper mHelper;
+    private Uri mExternalImages;
 
-    @Override
-    protected void tearDown() throws Exception {
-        for (Uri row : mRowsAdded) {
-            mContentResolver.delete(row, null, null);
-        }
+    @Parameter(0)
+    public String mVolumeName;
 
-        mHelper.clear();
-        super.tearDown();
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContext = getInstrumentation().getTargetContext();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mContentResolver = mContext.getContentResolver();
 
-        mHelper = new FileCopyHelper(mContext);
-        mRowsAdded = new ArrayList<Uri>();
-
-        File pics = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
-        if (!pics.exists()) {
-            Log.i(LOG_TAG, "Nonstandard test-environment: Pictures directory does not exist!");
-            pics.mkdirs();
-            if (!pics.exists()) {
-                Log.i(LOG_TAG, "Couldn't create Pictures directory, some tests may fail!");
-            }
-        }
-
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
     }
 
+    @Test
     public void testInsertImageWithImagePath() throws Exception {
-        Cursor c = Media.query(mContentResolver, Media.EXTERNAL_CONTENT_URI, null, null,
+        // 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 String TEST_DESCRIPTION1 = "Description " + unique1;
+
+        final long unique2 = System.nanoTime();
+        final String TEST_TITLE2 = "Title " + unique2;
+        final String TEST_DESCRIPTION2 = "Description " + unique2;
+
+        Cursor c = Media.query(mContentResolver, mExternalImages, null, null,
                 "_id ASC");
         int previousCount = c.getCount();
         c.close();
 
         // insert an image by path
-        String path = mHelper.copy(R.raw.scenery, "mediaStoreTest1.jpg");
+        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, TEST_DESCRIPTION1);
@@ -114,10 +116,12 @@
             fail("There is no sdcard attached! " + e.getMessage());
         }
         assertInsertionSuccess(stringUrl);
-        mRowsAdded.add(Uri.parse(stringUrl));
 
         // insert another image by path
-        path = mHelper.copy(R.raw.scenery, "mediaStoreTest2.jpg");
+        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, TEST_DESCRIPTION2);
@@ -128,7 +132,6 @@
             fail("There is no sdcard attached! " + e.getMessage());
         }
         assertInsertionSuccess(stringUrl);
-        mRowsAdded.add(Uri.parse(stringUrl));
 
         // query the newly added image
         c = Media.query(mContentResolver, Uri.parse(stringUrl),
@@ -142,7 +145,7 @@
 
         // 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, Media.EXTERNAL_CONTENT_URI,
+        c = Media.query(mContentResolver, mExternalImages,
                 new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE }, null,
                 "_id DESC");
         assertEquals(previousCount + 2, c.getCount());
@@ -167,7 +170,12 @@
         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;
@@ -178,7 +186,6 @@
             fail("There is no sdcard attached! " + e.getMessage());
         }
         assertInsertionSuccess(stringUrl);
-        mRowsAdded.add(Uri.parse(stringUrl));
 
         Cursor c = Media.query(mContentResolver, Uri.parse(stringUrl), new String[] { Media.DATA },
                 null, "_id ASC");
@@ -194,30 +201,28 @@
     }
 
     @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("external"), null, null, null,
+        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
                 null));
         c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Media.getContentUri(volume), null, null, null, null));
     }
 
     private void cleanExternalMediaFile(String path) {
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, "_data=?", new String[] { path });
+        mContentResolver.delete(mExternalImages, "_data=?", new String[] { path });
         new File(path).delete();
     }
 
+    @Test
     public void testStoreImagesMediaExternal() throws Exception {
-        final String externalPath = Environment.getExternalStorageDirectory().getPath() +
-                "/testimage.jpg";
-        final String externalPath2 = Environment.getExternalStorageDirectory().getPath() +
-                "/testimage1.jpg";
+        final String externalPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "testimage.jpg").getAbsolutePath();
+        final String externalPath2 = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "testimage1.jpg").getAbsolutePath();
 
         // clean up any potential left over entries from a previous aborted run
         cleanExternalMediaFile(externalPath);
@@ -232,8 +237,6 @@
         long dateTaken = System.currentTimeMillis();
         values.put(Media.DATE_TAKEN, dateTaken);
         values.put(Media.DESCRIPTION, "This is a image");
-        values.put(Media.LATITUDE, 40.689060d);
-        values.put(Media.LONGITUDE, -74.044636d);
         values.put(Media.IS_PRIVATE, 1);
         values.put(Media.MINI_THUMB_MAGIC, 0);
         values.put(Media.DATA, externalPath);
@@ -247,7 +250,7 @@
         values.put(Media.DATE_MODIFIED, dateModified);
 
         // insert
-        Uri uri = mContentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(mExternalImages, values);
         assertNotNull(uri);
 
         try {
@@ -262,8 +265,6 @@
             assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
             assertEquals("This is a image",
                     c.getString(c.getColumnIndex(Media.DESCRIPTION)));
-            assertEquals(40.689060d, c.getDouble(c.getColumnIndex(Media.LATITUDE)), 0d);
-            assertEquals(-74.044636d, c.getDouble(c.getColumnIndex(Media.LONGITUDE)), 0d);
             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)));
@@ -277,49 +278,6 @@
             assertTrue(Math.abs(dateModified - c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)))
                        < 5);
             c.close();
-
-            // update
-            values.clear();
-            values.put(Media.ORIENTATION, 90);
-            values.put(Media.PICASA_ID, 10);
-            dateTaken = System.currentTimeMillis();
-            values.put(Media.DATE_TAKEN, dateTaken);
-            values.put(Media.DESCRIPTION, "This is another image");
-            values.put(Media.LATITUDE, 41.689060d);
-            values.put(Media.LONGITUDE, -75.044636d);
-            values.put(Media.IS_PRIVATE, 0);
-            values.put(Media.MINI_THUMB_MAGIC, 2);
-            values.put(Media.DATA, externalPath2);
-            values.put(Media.DISPLAY_NAME, "testimage1");
-            values.put(Media.MIME_TYPE, "image/jpeg");
-            values.put(Media.SIZE, 86854);
-            values.put(Media.TITLE, "testimage1");
-            dateModified = System.currentTimeMillis() / 1000;
-            values.put(Media.DATE_MODIFIED, dateModified);
-            assertEquals(1, mContentResolver.update(uri, values, null, null));
-
-            c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(id, c.getLong(c.getColumnIndex(Media._ID)));
-            assertEquals(90, c.getInt(c.getColumnIndex(Media.ORIENTATION)));
-            assertEquals(10, c.getInt(c.getColumnIndex(Media.PICASA_ID)));
-            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
-            assertEquals("This is another image",
-                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
-            assertEquals(41.689060d, c.getDouble(c.getColumnIndex(Media.LATITUDE)), 0d);
-            assertEquals(-75.044636d, c.getDouble(c.getColumnIndex(Media.LONGITUDE)), 0d);
-            assertEquals(0, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
-            assertEquals(2, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
-            assertEquals(externalPath2,
-                    c.getString(c.getColumnIndex(Media.DATA)));
-            assertEquals("testimage1", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals("testimage1", c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(86854, c.getInt(c.getColumnIndex(Media.SIZE)));
-            assertEquals(realDateAdded, c.getLong(c.getColumnIndex(Media.DATE_ADDED)));
-            assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
-            c.close();
         } finally {
             // delete
             assertEquals(1, mContentResolver.delete(uri, null, null));
@@ -327,6 +285,7 @@
         }
     }
 
+    @Test
     public void testStoreImagesMediaInternal() {
         // can not insert any data, so other operations can not be tested
         try {
@@ -337,21 +296,110 @@
         }
     }
 
-    private void assertInsertionSuccess(String stringUrl) {
-        assertNotNull(stringUrl);
+    private void assertInsertionSuccess(String stringUrl) throws IOException {
+        final Uri uri = Uri.parse(stringUrl);
+
         // check whether the thumbnails are generated
-        Cursor c = mContentResolver.query(Uri.parse(stringUrl), new String[]{ Media._ID }, null,
-                null, null);
-        assertTrue(c.moveToFirst());
-        long imageId = c.getLong(c.getColumnIndex(Media._ID));
-        c.close();
-        assertNotNull(Thumbnails.getThumbnail(mContentResolver, imageId,
-                Thumbnails.MINI_KIND, null));
-        assertNotNull(Thumbnails.getThumbnail(mContentResolver, imageId,
-                Thumbnails.MICRO_KIND, null));
-        c = mContentResolver.query(Thumbnails.EXTERNAL_CONTENT_URI, null,
-                Thumbnails.IMAGE_ID + "=" + imageId, null, null);
-        assertEquals(2, c.getCount());
-        c.close();
+        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 MediaStore.PendingParams params = new MediaStore.PendingParams(
+                mExternalImages, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final Uri publishUri;
+        try (MediaStore.PendingSession session = MediaStore.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();
+        }
+
+        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(37.42303, latLong[0], 0.001);
+            assertEquals(-122.162025, latLong[1], 0.001);
+        }
+        // As owner, we should be able to request the original bytes
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+        }
+
+        // Now remove ownership, which means that Exif should be redacted
+        ProviderTestUtils.executeShellCommand(
+                "content update --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);
+        }
+        // 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 MediaStore.PendingParams params = new MediaStore.PendingParams(
+                mExternalImages, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final Uri publishUri;
+        try (MediaStore.PendingSession session = MediaStore.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));
+        }
     }
 }
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
index f57af32..19cba84 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
@@ -16,40 +16,80 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.os.SystemClock;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Images.Media;
 import android.provider.MediaStore.Images.Thumbnails;
 import android.provider.MediaStore.MediaColumns;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+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;
-import android.util.DisplayMetrics;
 
-public class MediaStore_Images_ThumbnailsTest extends InstrumentationTestCase {
+@RunWith(Parameterized.class)
+public class MediaStore_Images_ThumbnailsTest {
     private ArrayList<Uri> mRowsAdded;
 
     private Context mContext;
-
     private ContentResolver mContentResolver;
 
-    private FileCopyHelper mHelper;
+    private Uri mExternalImages;
 
-    @Override
-    protected void tearDown() throws Exception {
+    @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);
@@ -58,34 +98,57 @@
                 // ignores the exception and make the loop goes on
             }
         }
-
-        mHelper.clear();
-        super.tearDown();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mContext = getInstrumentation().getTargetContext();
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
         mContentResolver = mContext.getContentResolver();
 
-        mHelper = new FileCopyHelper(mContext);
         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);
     }
 
-    public void testQueryInternalThumbnails() throws Exception {
+    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.INTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
+                Thumbnails.EXTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
         int previousMicroKindCount = c.getCount();
         c.close();
 
         // add a thumbnail
-        String path = mHelper.copy(R.raw.scenery, "testThumbnails.jpg");
+        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);
-        Uri uri = mContentResolver.insert(Thumbnails.INTERNAL_CONTENT_URI, values);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
+        Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
         if (uri != null) {
             mRowsAdded.add(uri);
         }
@@ -98,7 +161,7 @@
         assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
 
         // query all thumbnails with other kind
-        c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.INTERNAL_CONTENT_URI,
+        c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.EXTERNAL_CONTENT_URI,
                 Thumbnails.MICRO_KIND, null);
         assertEquals(previousMicroKindCount, c.getCount());
         c.close();
@@ -112,7 +175,11 @@
         c.close();
     }
 
-    public void testQueryExternalMiniThumbnails() {
+    @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;
@@ -136,32 +203,9 @@
         String imagePath = c.getString(c.getColumnIndex(Media.DATA));
         c.close();
 
-        assertTrue("image file does not exist", new File(imagePath).exists());
-
-        String[] sizeProjection = new String[] { Thumbnails.WIDTH, Thumbnails.HEIGHT };
-        c = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MINI_KIND,
-                sizeProjection);
-        assertEquals(1, c.getCount());
-        assertTrue(c.moveToFirst());
-        assertTrue(c.getLong(c.getColumnIndex(Thumbnails.WIDTH)) >= Math.min(src.getWidth(), 240));
-        assertTrue(c.getLong(c.getColumnIndex(Thumbnails.HEIGHT)) >= Math.min(src.getHeight(), 240));
-        c.close();
-        c = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MICRO_KIND,
-                sizeProjection);
-        assertEquals(1, c.getCount());
-        assertTrue(c.moveToFirst());
-        assertEquals(50, c.getLong(c.getColumnIndex(Thumbnails.WIDTH)));
-        assertEquals(50, c.getLong(c.getColumnIndex(Thumbnails.HEIGHT)));
-        c.close();
-
-        c = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MINI_KIND,
-                new String[] { Thumbnails._ID, Thumbnails.DATA, Thumbnails.IMAGE_ID});
-
-        c.moveToNext();
-        long img = c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID));
-        assertEquals(imageId, img);
-        String thumbPath = c.getString(c.getColumnIndex(Thumbnails.DATA));
-        assertTrue("thumbnail file does not exist", new File(thumbPath).exists());
+        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
@@ -169,15 +213,9 @@
         mContentResolver.delete(stringUri, null, null);
         mRowsAdded.remove(stringUri);
 
-        assertFalse("image file should no longer exist", new File(imagePath).exists());
-
-        Cursor c2 = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MINI_KIND,
-                new String[] { Thumbnails._ID, Thumbnails.DATA, Thumbnails.IMAGE_ID});
-        assertEquals(0, c2.getCount());
-        c2.close();
-
-        assertFalse("thumbnail file should no longer exist", new File(thumbPath).exists());
-        c.close();
+        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, null, null);
@@ -187,59 +225,35 @@
         imageId = c.getLong(c.getColumnIndex(Media._ID));
         imagePath = c.getString(c.getColumnIndex(Media.DATA));
         c.close();
-        assertTrue("image file does not exist", new File(imagePath).exists());
+        assertExists("image file does not exist", imagePath);
         Uri fileuri = MediaStore.Files.getContentUri("external", imageId);
         mContentResolver.delete(fileuri, null, null);
-        assertFalse("image file should no longer exist", new File(imagePath).exists());
-
-
-        // insert image, then delete its thumbnail
-        stringUrl = Media.insertImage(mContentResolver, src, null, 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();
-        c2 = Thumbnails.queryMiniThumbnail(mContentResolver, imageId, Thumbnails.MINI_KIND,
-                new String[] { Thumbnails._ID, Thumbnails.DATA, Thumbnails.IMAGE_ID});
-        c2.moveToFirst();
-        thumbPath = c2.getString(c2.getColumnIndex(Thumbnails.DATA));
-        assertTrue("thumbnail file does not exist", new File(thumbPath).exists());
-
-        Uri imguri = Uri.parse(stringUrl);
-        long imgid = ContentUris.parseId(imguri);
-        assertEquals(imgid, imageId);
-        mRowsAdded.add(imguri);
-        mContentResolver.delete(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
-                MediaStore.Images.Thumbnails.IMAGE_ID + "=?", new String[]{ "" + imgid});
-        assertFalse("thumbnail file should no longer exist", new File(thumbPath).exists());
-        assertTrue("image file should still exist", new File(imagePath).exists());
+        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("external"), null, null,
+        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri(mVolumeName), null, null,
                 null, null));
         c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Thumbnails.getContentUri(volume), null, null, null,
-                null));
     }
 
-    public void testStoreImagesMediaExternal() {
+    @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, 0);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
         values.put(Thumbnails.HEIGHT, 480);
         values.put(Thumbnails.WIDTH, 320);
         values.put(Thumbnails.DATA, externalImgPath);
@@ -255,7 +269,7 @@
         long id = c.getLong(c.getColumnIndex(Thumbnails._ID));
         assertTrue(id > 0);
         assertEquals(Thumbnails.FULL_SCREEN_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
-        assertEquals(0, c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID)));
+        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)));
@@ -264,7 +278,7 @@
         // update
         values.clear();
         values.put(Thumbnails.KIND, Thumbnails.MICRO_KIND);
-        values.put(Thumbnails.IMAGE_ID, 1);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mBlue));
         values.put(Thumbnails.HEIGHT, 50);
         values.put(Thumbnails.WIDTH, 50);
         values.put(Thumbnails.DATA, externalImgPath2);
@@ -274,43 +288,32 @@
         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, "test", "test description"));
+        long imageId = ContentUris.parseId(uri);
 
-        // query its thumbnail
-        Cursor c = mContentResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "image_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sort */
-                );
-        assertTrue("couldn't find thumbnail", c.moveToNext());
-        String path = c.getString(0);
-        c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        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 */);
-        assertFalse("thumbnail still exists after source file delete", new File(path).exists());
+
+        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, "test", "test description"));
+        imageId = ContentUris.parseId(uri);
 
         // query its thumbnail again
-        c = mContentResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "image_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sortOrder */
-                );
-        assertTrue("couldn't find thumbnail", c.moveToNext());
-        path = c.getString(0);
-        c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        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();
@@ -319,23 +322,11 @@
                 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
-
-        // query its thumbnail again
-        c = mContentResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "image_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sort */
-                );
-        if (c != null) {
-            assertFalse("thumbnail entry exists for non-thumbnail file", c.moveToNext());
-            c.close();
-        }
-        assertFalse("thumbnail remains after source file type change", new File(path).exists());
+        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
-        c = mContentResolver.query(uri,
+        Cursor c = mContentResolver.query(uri,
                 null /* projection */, null /* where */, null /* where args */, null /* sort */);
         assertFalse("source entry should be gone", c.moveToNext());
         c.close();
@@ -351,10 +342,11 @@
         c.close();
 
         // clean up
-        mContentResolver.delete(uri, null /* where */, null /* where args */);
+        mContentResolver.delete(fileUri, null /* where */, null /* where args */);
         new File(sourcePath).delete();
     }
 
+    @Test
     public void testStoreImagesMediaInternal() {
         // can not insert any data, so other operations can not be tested
         try {
@@ -366,7 +358,10 @@
         }
     }
 
+    @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{
@@ -378,35 +373,12 @@
                 assertNotNull(foo);
             }
 
-            // remove one of the images, its thumbnail, and the thumbnail cache
-            Cursor c = mContentResolver.query(url[1], null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            String path = c.getString(c.getColumnIndex(MediaColumns.DATA));
-            long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
-            c.close();
-            assertTrue(new File(path).delete());
+            // Remove one of the images, which will also delete any thumbnails
+            mContentResolver.delete(url[1], null, null);
 
             long removedId = Long.parseLong(url[1].getLastPathSegment());
             long remainingId1 = Long.parseLong(url[0].getLastPathSegment());
             long remainingId2 = Long.parseLong(url[2].getLastPathSegment());
-            c = mContentResolver.query(
-                    MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, null,
-                    Thumbnails.IMAGE_ID + "=?", new String[] { Long.toString(removedId) }, null);
-            assertTrue(c.getCount() > 0);  // one or more thumbnail kinds
-            while (c.moveToNext()) {
-                path = c.getString(c.getColumnIndex(MediaColumns.DATA));
-                assertTrue(new File(path).delete());
-            }
-            c.close();
-
-            File thumbdir = new File(path).getParentFile();
-            File[] list = thumbdir.listFiles();
-            for (int i = 0; i < list.length; i++) {
-                if (list[i].getName().startsWith(".thumbdata")) {
-                    assertTrue(list[i].delete());
-                }
-            }
 
             // check if a thumbnail is still being returned for the image that was removed
             Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
@@ -414,11 +386,11 @@
             assertNull(foo);
 
             for (String order: new String[] { " ASC", " DESC" }) {
-                c = mContentResolver.query(
+                Cursor c = mContentResolver.query(
                         MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
                         MediaColumns._ID + order);
                 while (c.moveToNext()) {
-                    id = c.getLong(c.getColumnIndex(MediaColumns._ID));
+                    long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
                     foo = MediaStore.Images.Thumbnails.getThumbnail(
                             mContentResolver, id,
                             MediaStore.Images.Thumbnails.MICRO_KIND, null);
@@ -435,4 +407,93 @@
             fail("There is no sdcard attached! " + e.getMessage());
         }
     }
+
+    @Test
+    public void testInsertUpdateDelete() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                mExternalImages, displayName, "image/png");
+        final Uri pendingUri = MediaStore.createPending(mContext, params);
+        final Uri finalUri;
+        try (MediaStore.PendingSession session = MediaStore.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
+            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
+                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
+        SystemClock.sleep(1000);
+
+        {
+            // Thumbnail should match updated contents
+            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 {
+            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
index 8ba8c17..612e2b4 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
@@ -16,61 +16,70 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.provider.MediaStore;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Video.VideoColumns;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-import com.android.compatibility.common.util.FileCopyHelper;
+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.ArrayList;
+import java.io.File;
 
-public class MediaStore_VideoTest extends InstrumentationTestCase {
-    private static final String TEST_VIDEO_3GP = "testVideo.3gp";
-
-    private ArrayList<Uri> mRowsAdded;
-
+@RunWith(Parameterized.class)
+public class MediaStore_VideoTest {
     private Context mContext;
+    private ContentResolver mResolver;
 
-    private ContentResolver mContentResolver;
+    private Uri mExternalVideo;
 
-    private FileCopyHelper mHelper;
+    @Parameter(0)
+    public String mVolumeName;
 
-    @Override
-    protected void tearDown() throws Exception {
-        for (Uri row : mRowsAdded) {
-            mContentResolver.delete(row, null, null);
-        }
-        mHelper.clear();
-        super.tearDown();
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-        mHelper = new FileCopyHelper(mContext);
-        mRowsAdded = new ArrayList<Uri>();
+    @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();
-        String valueOfData = mHelper.copy(R.raw.testvideo, TEST_VIDEO_3GP);
+
+        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 = mContentResolver.insert(Video.Media.INTERNAL_CONTENT_URI, values);
-        if (!Video.Media.INTERNAL_CONTENT_URI.equals(newUri)) {
-            mRowsAdded.add(newUri);
-        }
+        Uri newUri = mResolver.insert(mExternalVideo, values);
+        assertNotNull(newUri);
 
-        Cursor c = Video.query(mContentResolver, newUri, new String[] { VideoColumns.DATA });
+        Cursor c = Video.query(mResolver, newUri, new String[] { VideoColumns.DATA });
         assertEquals(1, c.getCount());
         c.moveToFirst();
         assertEquals(valueOfData, c.getString(c.getColumnIndex(VideoColumns.DATA)));
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
index 14e76bb..c4577b6 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
@@ -16,60 +16,84 @@
 
 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 android.provider.cts.R;
+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.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Video.Media;
 import android.provider.MediaStore.Video.VideoColumns;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
 
-import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.FileUtils;
 
+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;
 
-public class MediaStore_Video_MediaTest extends AndroidTestCase {
+@RunWith(Parameterized.class)
+public class MediaStore_Video_MediaTest {
+    private Context mContext;
     private ContentResolver mContentResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    private Uri mExternalVideo;
 
-        mContentResolver = getContext().getContentResolver();
+    @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("external"), null, null, null,
+        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
                 null));
         c.close();
-
-        // can not accept any other volume names
-        String volume = "fakeVolume";
-        assertNull(mContentResolver.query(Media.getContentUri(volume), null, null, null, null));
     }
 
     private void cleanExternalMediaFile(String path) {
-        mContentResolver.delete(Media.EXTERNAL_CONTENT_URI, "_data=?", new String[] { path });
+        mContentResolver.delete(mExternalVideo, "_data=?", new String[] { path });
         new File(path).delete();
     }
 
+    @Test
     public void testStoreVideoMediaExternal() throws Exception {
-        final String externalVideoPath = Environment.getExternalStorageDirectory().getPath() +
-                 "/video/testvideo.3gp";
-        final String externalVideoPath2 = Environment.getExternalStorageDirectory().getPath() +
-                "/video/testvideo1.3gp";
+        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);
@@ -88,8 +112,6 @@
         values.put(Media.DESCRIPTION, "This is a video");
         values.put(Media.DURATION, 8480);
         values.put(Media.LANGUAGE, "en");
-        values.put(Media.LATITUDE, 40.689060d);
-        values.put(Media.LONGITUDE, -74.044636d);
         values.put(Media.IS_PRIVATE, 1);
         values.put(Media.MINI_THUMB_MAGIC, 0);
         values.put(Media.RESOLUTION, "176x144");
@@ -105,7 +127,7 @@
         values.put(Media.DATE_MODIFIED, dateModified);
 
         // insert
-        Uri uri = mContentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+        Uri uri = mContentResolver.insert(mExternalVideo, values);
         assertNotNull(uri);
 
         try {
@@ -123,14 +145,12 @@
             assertEquals("This is a video",
                     c.getString(c.getColumnIndex(Media.DESCRIPTION)));
             assertEquals("en", c.getString(c.getColumnIndex(Media.LANGUAGE)));
-            assertEquals(40.689060d, c.getDouble(c.getColumnIndex(Media.LATITUDE)), 0d);
-            assertEquals(-74.044636d, c.getDouble(c.getColumnIndex(Media.LONGITUDE)), 0d);
             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("testvideo", 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)));
@@ -138,59 +158,6 @@
             assertTrue(realDateAdded >= dateAdded);
             assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
             c.close();
-
-            // update
-            values.clear();
-            values.put(Media.ALBUM, "cts1");
-            values.put(Media.ARTIST, "cts team1");
-            values.put(Media.CATEGORY, "test1");
-            dateTaken = System.currentTimeMillis();
-            values.put(Media.DATE_TAKEN, dateTaken);
-            values.put(Media.DESCRIPTION, "This is another video");
-            values.put(Media.DURATION, 8481);
-            values.put(Media.LANGUAGE, "cn");
-            values.put(Media.LATITUDE, 41.689060d);
-            values.put(Media.LONGITUDE, -75.044636d);
-            values.put(Media.IS_PRIVATE, 0);
-            values.put(Media.MINI_THUMB_MAGIC, 2);
-            values.put(Media.RESOLUTION, "320x240");
-            values.put(Media.TAGS, "cts1, test1");
-            values.put(Media.DATA, externalVideoPath2);
-            values.put(Media.DISPLAY_NAME, "testvideo1");
-            values.put(Media.MIME_TYPE, "video/3gpp");
-            values.put(Media.SIZE, 86854);
-            values.put(Media.TITLE, "testvideo1");
-            dateModified = System.currentTimeMillis();
-            values.put(Media.DATE_MODIFIED, dateModified);
-            assertEquals(1, mContentResolver.update(uri, values, null, null));
-
-            c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(id, c.getLong(c.getColumnIndex(Media._ID)));
-            assertEquals("cts1", c.getString(c.getColumnIndex(Media.ALBUM)));
-            assertEquals("cts team1", c.getString(c.getColumnIndex(Media.ARTIST)));
-            assertEquals("test1", c.getString(c.getColumnIndex(Media.CATEGORY)));
-            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
-            assertEquals(8481, c.getInt(c.getColumnIndex(Media.DURATION)));
-            assertEquals("This is another video",
-                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
-            assertEquals("cn", c.getString(c.getColumnIndex(Media.LANGUAGE)));
-            assertEquals(41.689060d, c.getDouble(c.getColumnIndex(Media.LATITUDE)), 0d);
-            assertEquals(-75.044636d, c.getDouble(c.getColumnIndex(Media.LONGITUDE)), 0d);
-            assertEquals(0, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
-            assertEquals(2, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
-            assertEquals("320x240", c.getString(c.getColumnIndex(Media.RESOLUTION)));
-            assertEquals("cts1, test1", c.getString(c.getColumnIndex(Media.TAGS)));
-            assertEquals(externalVideoPath2,
-                    c.getString(c.getColumnIndex(Media.DATA)));
-            assertEquals("testvideo1", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals("video/3gpp", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals("testvideo1", c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(86854, c.getInt(c.getColumnIndex(Media.SIZE)));
-            assertEquals(realDateAdded, c.getLong(c.getColumnIndex(Media.DATE_ADDED)));
-            assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
-            c.close();
         } finally {
             // delete
             assertEquals(1, mContentResolver.delete(uri, null, null));
@@ -198,24 +165,15 @@
         }
 
         // check that the video file is removed when deleting the database entry
-        Context context = getContext();
+        Context context = mContext;
         Uri videoUri = insertVideo(context);
-        File videofile = new File(Environment.getExternalStorageDirectory(), "testVideo.3gp");
-        assertTrue(videofile.exists());
+        File videofile = new File(ProviderTestUtils.stageDir(mVolumeName), "testVideo.3gp");
+        assertExists(videofile);
         mContentResolver.delete(videoUri, null, null);
-        assertFalse(videofile.exists());
-
-        // insert again, then delete with the "delete data" parameter set to false
-        videoUri = insertVideo(context);
-        assertTrue(videofile.exists());
-        Uri.Builder builder = videoUri.buildUpon();
-        builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
-        mContentResolver.delete(builder.build(), null, null);
-        assertTrue(videofile.exists());
-        videofile.delete();
-
+        assertNotExists(videofile);
     }
 
+    @Test
     public void testStoreVideoMediaInternal() {
         // can not insert any data, so other operations can not be tested
         try {
@@ -228,14 +186,15 @@
     }
 
     private Uri insertVideo(Context context) throws IOException {
-        File file = new File(Environment.getExternalStorageDirectory(), "testVideo.3gp");
+        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());
 
-        new FileCopyHelper(context).copyToExternalStorage(R.raw.testvideo, file);
+        ProviderTestUtils.stageFile(R.raw.testvideo, file);
 
         ContentValues values = new ContentValues();
         values.put(VideoColumns.DATA, file.getAbsolutePath());
-        return context.getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
+        return context.getContentResolver().insert(mExternalVideo, values);
     }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
index c256722..e265206 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
@@ -16,54 +16,83 @@
 
 package android.provider.cts;
 
-import android.provider.cts.R;
+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.media.MediaCodecInfo;
-import android.media.MediaCodecList;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
 import android.net.Uri;
-import android.os.Environment;
+import android.os.FileUtils;
+import android.os.SystemClock;
+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.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
+import android.util.Size;
 
-import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.MediaUtils;
 
-import java.io.File;
-import java.io.IOException;
+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;
 
-public class MediaStore_Video_ThumbnailsTest extends AndroidTestCase {
+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 FileCopyHelper mFileHelper;
-
     private boolean hasCodec() {
         return MediaUtils.hasCodecForResourceAndDomain(
                 mContext, R.raw.testthumbvideo, "video/");
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    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();
-        mFileHelper = new FileCopyHelper(mContext);
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        mFileHelper.clear();
-        super.tearDown();
-    }
-
+    @Test
     public void testGetContentUri() {
         Uri internalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME);
         Uri externalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME);
@@ -71,7 +100,10 @@
         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);
@@ -79,9 +111,6 @@
         assertEquals(ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, videoId),
                 videoUri);
 
-        // Get the current thumbnail count for future comparison.
-        int count = getThumbnailCount(Thumbnails.EXTERNAL_CONTENT_URI);
-
         // Don't run the test if the codec isn't supported.
         if (!hasCodec()) {
             // Calling getThumbnail should not generate a new thumbnail.
@@ -94,37 +123,12 @@
         assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
         assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MICRO_KIND, null));
 
-        try {
-            Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.FULL_SCREEN_KIND, null);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Full screen thumbnails not supported by getThumbnail...
-        }
-
-        // Check that an additional thumbnails have been registered.
-        int count2 = getThumbnailCount(Thumbnails.EXTERNAL_CONTENT_URI);
-        assertTrue(count2 > count);
-
-        Cursor c = mResolver.query(Thumbnails.EXTERNAL_CONTENT_URI,
-                new String[] { Thumbnails._ID, Thumbnails.DATA, Thumbnails.VIDEO_ID },
-                null, null, null);
-
-        if (c.moveToLast()) {
-            long vid = c.getLong(2);
-            assertEquals(videoId, vid);
-            String path = c.getString(1);
-            assertTrue("thumbnail file does not exist", new File(path).exists());
-            long id = c.getLong(0);
-            mResolver.delete(ContentUris.withAppendedId(Thumbnails.EXTERNAL_CONTENT_URI, id),
-                    null, null);
-            assertFalse("thumbnail file should no longer exist", new File(path).exists());
-        }
-        c.close();
-
         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
@@ -136,45 +140,20 @@
         Uri uri = insertVideo();
 
         // request thumbnail creation
-        Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null /* options */);
-
-        // query the thumbnail
-        Cursor c = mResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "video_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sort */
-                );
-        assertTrue("couldn't find thumbnail", c.moveToNext());
-        String path = c.getString(0);
-        c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        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 */);
-        assertFalse("thumbnail still exists after source file delete", new File(path).exists());
+        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null /* options */));
 
         // insert again
         uri = insertVideo();
 
         // request thumbnail creation
-        Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null);
-
-        // query its thumbnail again
-        c = mResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "video_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sort */
-                );
-        assertTrue("couldn't find thumbnail", c.moveToNext());
-        path = c.getString(0);
-        c.close();
-        assertTrue("thumbnail does not exist", new File(path).exists());
+        assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null));
 
         // update the media type
         ContentValues values = new ContentValues();
@@ -183,23 +162,11 @@
                 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
-
-        // query its thumbnail again
-        c = mResolver.query(
-                Thumbnails.EXTERNAL_CONTENT_URI,
-                new String [] {Thumbnails.DATA},
-                "video_id=?",
-                new String[] {uri.getLastPathSegment()},
-                null /* sort */
-                );
-        if (c != null) {
-            assertFalse("thumbnail entry exists for non-thumbnail file", c.moveToNext());
-            c.close();
-        }
-        assertFalse("thumbnail remains after source file type change", new File(path).exists());
+        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null /* options */));
 
         // check source no longer exists as video
-        c = mResolver.query(uri,
+        Cursor c = mResolver.query(uri,
                 null /* projection */, null /* where */, null /* where args */, null /* sort */);
         assertFalse("source entry should be gone", c.moveToNext());
         c.close();
@@ -215,29 +182,88 @@
         c.close();
 
         // clean up
-        mResolver.delete(uri, null /* where */, null /* where args */);
+        mResolver.delete(fileUri, null /* where */, null /* where args */);
         new File(sourcePath).delete();
     }
 
     private Uri insertVideo() throws IOException {
-        File file = new File(Environment.getExternalStorageDirectory(), "testVideo.3gp");
+        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();
-        mFileHelper.copyToExternalStorage(R.raw.testthumbvideo, file);
+
+        ProviderTestUtils.stageFile(R.raw.testthumbvideo, file);
 
         ContentValues values = new ContentValues();
         values.put(VideoColumns.DATA, file.getAbsolutePath());
         return mResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
     }
 
-    private int getThumbnailCount(Uri uri) {
-        Cursor cursor = mResolver.query(uri, null, null, null, null);
-        try {
-            return cursor.getCount();
-        } finally {
-            cursor.close();
+    @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
+        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
+            }) {
+                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);
+        }
+
+        // Wait a few moments for events to settle
+        SystemClock.sleep(1000);
+
+        // Thumbnail should match updated contents
+        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 {
+            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/MockFontProvider.java b/tests/tests/provider/src/android/provider/cts/MockFontProvider.java
index 3a64173..e74db44 100644
--- a/tests/tests/provider/src/android/provider/cts/MockFontProvider.java
+++ b/tests/tests/provider/src/android/provider/cts/MockFontProvider.java
@@ -125,8 +125,8 @@
 
         map.put(MULTIPLE_FAMILY_QUERY, new Font[] {
             new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK, true),
-            new Font(id++, SAMPLE_FONT_FILE_1_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK, true),
-            new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 1, Columns.RESULT_CODE_OK, true),
+            new Font(id++, SAMPLE_FONT_FILE_1_ID, 0, null, 400, 1, Columns.RESULT_CODE_OK, true),
+            new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, true),
             new Font(id++, SAMPLE_FONT_FILE_1_ID, 0, null, 700, 1, Columns.RESULT_CODE_OK, true),
         });
 
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index e2ed68d..038291a 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -16,16 +16,46 @@
 
 package android.provider.cts;
 
+import static android.provider.cts.MediaStoreTest.TAG;
+
+import static org.junit.Assert.fail;
+
 import android.app.UiAutomation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.support.test.InstrumentationRegistry;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -38,15 +68,33 @@
     private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile(
             "^Backup Manager currently (enabled|disabled)$");
 
+    static Iterable<String> getSharedVolumeNames() {
+        if (StorageManager.hasIsolatedStorage()) {
+            final Set<String> volumeNames = MediaStore
+                    .getAllVolumeNames(InstrumentationRegistry.getTargetContext());
+            volumeNames.remove(MediaStore.VOLUME_INTERNAL);
+            return volumeNames;
+        } else {
+            return Arrays.asList(MediaStore.VOLUME_EXTERNAL);
+        }
+    }
+
     static void setDefaultSmsApp(boolean setToSmsApp, String packageName, UiAutomation uiAutomation)
             throws Exception {
-        String command = String.format(
-                "appops set %s WRITE_SMS %s", packageName, setToSmsApp ? "allow" : "default");
-        executeShellCommand(command, uiAutomation);
+        String mode = setToSmsApp ? "allow" : "default";
+        String cmd = "appops set %s %s %s";
+        executeShellCommand(String.format(cmd, packageName, "WRITE_SMS", mode), uiAutomation);
+        executeShellCommand(String.format(cmd, packageName, "READ_SMS", mode), uiAutomation);
+    }
+
+    static String executeShellCommand(String command) throws IOException {
+        return executeShellCommand(command,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
     }
 
     static String executeShellCommand(String command, UiAutomation uiAutomation)
             throws IOException {
+        Log.v(TAG, "$ " + command);
         ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
         BufferedReader br = null;
         try (InputStream in = new FileInputStream(pfd.getFileDescriptor());) {
@@ -54,6 +102,7 @@
             String str = null;
             StringBuilder out = new StringBuilder();
             while ((str = br.readLine()) != null) {
+                Log.v(TAG, "> " + str);
                 out.append(str);
             }
             return out.toString();
@@ -119,4 +168,151 @@
             throws Exception {
         executeShellCommand("bmgr wipe " + backupTransport + " " + packageName, uiAutomation);
     }
+
+    static File stageDir(String volumeName) throws IOException {
+        return Environment.buildPath(MediaStore.getVolumePath(volumeName), "Android", "media",
+                "android.provider.cts");
+    }
+
+    static void 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
+        if (FileUtils.contains(Environment.getStorageDirectory(), file)) {
+            executeShellCommand("mkdir -p " + file.getParent());
+
+            final Context context = InstrumentationRegistry.getTargetContext();
+            try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
+                final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor());
+                final long skip = afd.getStartOffset();
+                final long count = afd.getLength();
+
+                executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s",
+                        source.getAbsolutePath(), skip, count, file.getAbsolutePath()));
+
+                // Force sync to try updating other views
+                executeShellCommand("sync");
+            }
+        } else {
+            final File dir = file.getParentFile();
+            dir.mkdirs();
+            if (!dir.exists()) {
+                throw new FileNotFoundException("Failed to create parent for " + file);
+            }
+            final Context context = InstrumentationRegistry.getTargetContext();
+            try (InputStream source = context.getResources().openRawResource(resId);
+                    OutputStream target = new FileOutputStream(file)) {
+                FileUtils.copy(source, target);
+            }
+        }
+    }
+
+    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 {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String displayName = "cts" + System.nanoTime();
+        final MediaStore.PendingParams params = new MediaStore.PendingParams(
+                collectionUri, displayName, mimeType);
+        final Uri pendingUri = MediaStore.createPending(context, params);
+        try (MediaStore.PendingSession session = MediaStore.openPending(context, pendingUri)) {
+            try (InputStream source = context.getResources().openRawResource(resId);
+                    OutputStream target = session.openOutputStream()) {
+                FileUtils.copy(source, target);
+            }
+            return session.publish();
+        }
+    }
+
+    static Uri scanFile(File file) throws Exception {
+        final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+                .getContentResolver();
+        try (ContentProviderClient cpc = resolver
+                .acquireContentProviderClient(MediaStore.AUTHORITY)) {
+            final Bundle in = new Bundle();
+            in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
+            in.putBoolean(Intent.EXTRA_LOCAL_ONLY, true);
+            final Bundle out = cpc.call(MediaStore.AUTHORITY, MediaStore.SCAN_FILE_CALL, null, in);
+            return out.getParcelable(Intent.EXTRA_STREAM);
+        }
+    }
+
+    static void scanVolume(File file) throws Exception {
+        final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+                .getContentResolver();
+        try (ContentProviderClient cpc = resolver
+                .acquireContentProviderClient(MediaStore.AUTHORITY)) {
+            final Bundle in = new Bundle();
+            in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
+            in.putBoolean(Intent.EXTRA_LOCAL_ONLY, true);
+            cpc.call(MediaStore.AUTHORITY, MediaStore.SCAN_VOLUME_CALL, null, in);
+        }
+    }
+
+    public static byte[] hash(InputStream in) throws Exception {
+        try (DigestInputStream digestIn = new DigestInputStream(in,
+                MessageDigest.getInstance("SHA-1"));
+                OutputStream out = new FileOutputStream(new File("/dev/null"))) {
+            FileUtils.copy(digestIn, out);
+            return digestIn.getMessageDigest().digest();
+        }
+    }
+
+    public static void assertExists(String path) throws IOException {
+        assertExists(null, path);
+    }
+
+    public static void assertExists(File file) throws IOException {
+        assertExists(null, file.getAbsolutePath());
+    }
+
+    public static void assertExists(String msg, String path) throws IOException {
+        if (!access(path)) {
+            fail(msg);
+        }
+    }
+
+    public static void assertNotExists(String path) throws IOException {
+        assertNotExists(null, path);
+    }
+
+    public static void assertNotExists(File file) throws IOException {
+        assertNotExists(null, file.getAbsolutePath());
+    }
+
+    public static void assertNotExists(String msg, String path) throws IOException {
+        if (access(path)) {
+            fail(msg);
+        }
+    }
+
+    private static boolean access(String path) 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
+        if (FileUtils.contains(Environment.getStorageDirectory(), new File(path))) {
+            return executeShellCommand("ls -la " + path).contains(path);
+        } else {
+            try {
+                Os.access(path, OsConstants.F_OK);
+                return true;
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.ENOENT) {
+                    return false;
+                } else {
+                    throw new IOException(e.getMessage());
+                }
+            }
+        }
+    }
+
+    public static boolean containsId(Uri uri, long id) {
+        try (Cursor c = InstrumentationRegistry.getTargetContext().getContentResolver().query(uri,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                if (c.getLong(0) == id) return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
new file mode 100644
index 0000000..917b1bc
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests related SettingsPanels:
+ *
+ * atest SettingsPanelTest
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SettingsPanelTest {
+
+    private static final int TIMEOUT = 8000;
+
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+    private static final String RESOURCE_DONE = "done";
+    private static final String RESOURCE_SEE_MORE = "see_more";
+    private static final String RESOURCE_TITLE = "panel_title";
+
+    private String mLauncherPackage;
+
+    private Context mContext;
+
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        PackageManager packageManager = mContext.getPackageManager();
+        Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+        launcherIntent.addCategory(Intent.CATEGORY_HOME);
+        mLauncherPackage = packageManager.resolveActivity(launcherIntent,
+                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+    }
+
+    @After
+    public void cleanUp() {
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+    }
+
+    // Check correct package is opened
+
+    @Test
+    public void internetPanel_correctPackage() {
+        launchInternetPanel();
+
+        String currentPackage = mDevice.getCurrentPackageName();
+
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void volumePanel_correctPackage() {
+        launchVolumePanel();
+
+        String currentPackage = mDevice.getCurrentPackageName();
+
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void nfcPanel_correctPackage() {
+        launchNfcPanel();
+
+        String currentPackage = mDevice.getCurrentPackageName();
+
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void internetPanel_correctTitle() {
+        launchInternetPanel();
+
+        final UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+
+        assertThat(titleView.getText()).isEqualTo("Internet Connectivity");
+    }
+
+    @Test
+    public void volumePanel_correctTitle() {
+        launchVolumePanel();
+
+        final UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+
+        assertThat(titleView.getText()).isEqualTo("Volume");
+    }
+
+    @Test
+    public void nfcPanel_correctTitle() {
+        launchNfcPanel();
+
+        final UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+
+        assertThat(titleView.getText()).isEqualTo("NFC");
+    }
+
+    @Test
+    public void internetPanel_doneClosesPanel() {
+        // Launch panel
+        launchInternetPanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the done button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_DONE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+        // Assert that we have left the panel
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isNotEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void volumePanel_doneClosesPanel() {
+        // Launch panel
+        launchVolumePanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the done button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_DONE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+        // Assert that we have left the panel
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isNotEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void nfcPanel_doneClosesPanel() {
+        // Launch panel
+        launchNfcPanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the done button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_DONE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+        // Assert that we have left the panel
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isNotEqualTo(SETTINGS_PACKAGE);
+    }
+
+    @Test
+    public void internetPanel_seeMoreButton_launchesIntoSettings() {
+        // Launch panel
+        launchInternetPanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the see more button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_SEE_MORE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+
+        // Assert that we're still in Settings, on a different page.
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+        UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+        assertThat(titleView).isNull();
+    }
+
+    @Test
+    public void volumePanel_seeMoreButton_launchesIntoSettings() {
+        // Launch panel
+        launchVolumePanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the see more button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_SEE_MORE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+
+        // Assert that we're still in Settings, on a different page.
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+        UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+        assertThat(titleView).isNull();
+    }
+
+    @Test
+    public void nfcPanel_seeMoreButton_launchesIntoSettings() {
+        // Launch panel
+        launchNfcPanel();
+        String currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+
+        // Click the see more button
+        mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_SEE_MORE)).click();
+        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+
+        // Assert that we're still in Settings, on a different page.
+        currentPackage = mDevice.getCurrentPackageName();
+        assertThat(currentPackage).isEqualTo(SETTINGS_PACKAGE);
+        UiObject2 titleView = mDevice.findObject(By.res(SETTINGS_PACKAGE, RESOURCE_TITLE));
+        assertThat(titleView).isNull();
+    }
+
+    private void launchVolumePanel() {
+        launchPanel(Settings.Panel.ACTION_VOLUME);
+    }
+
+    private void launchInternetPanel() {
+        launchPanel(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+    }
+
+    private void launchNfcPanel() {
+        launchPanel(Settings.Panel.ACTION_NFC);
+    }
+
+    private void launchPanel(String action) {
+        // Start from the home screen
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+        Intent intent = new Intent(action);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK);    // Clear out any previous instances
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
index 71de3bd..a18a0eb 100644
--- a/tests/tests/provider/src/android/provider/cts/SettingsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
@@ -16,8 +16,14 @@
 
 package android.provider.cts;
 
-import com.android.compatibility.common.util.SystemUtil;
+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;
@@ -32,13 +38,41 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.provider.Settings;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.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;
 
-public class SettingsTest extends InstrumentationTestCase {
+@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
@@ -46,7 +80,7 @@
         final int NAME_INDEX = 1;
         final int VALUE_INDEX = 2;
 
-        String name = "name";
+        String name = Settings.System.NEXT_ALARM_FORMATTED;
         String insertValue = "value_insert";
         String updateValue = "value_update";
 
@@ -89,16 +123,6 @@
             assertEquals(updateValue, cursor.getString(VALUE_INDEX));
             cursor.close();
             cursor = null;
-
-            // Test: delete
-            provider.delete(Settings.System.CONTENT_URI,
-                    Settings.System.NAME + "=\"" + name + "\"", null);
-            cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
-                    Settings.System.NAME + "=\"" + name + "\"", null, null, null);
-            assertNotNull(cursor);
-            assertEquals(0, cursor.getCount());
-            cursor.close();
-            cursor = null;
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -106,6 +130,7 @@
         }
     }
 
+    @Test
     public void testSecureTable() throws Exception {
         final String[] SECURE_PROJECTION = new String[] {
                 Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
@@ -196,6 +221,7 @@
         }
     }
 
+    @Test
     public void testAccessNonTable() {
         tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
         tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
@@ -204,6 +230,7 @@
         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(
@@ -211,6 +238,7 @@
         assertTrue(ri != null);
     }
 
+    @Test
     public void testNoStaleValueModifiedFromSameProcess() throws Exception {
         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
                 Settings.System.VIBRATE_WHEN_RINGING);
@@ -229,6 +257,7 @@
         }
     }
 
+    @Test
     public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
                 Settings.System.VIBRATE_WHEN_RINGING);
@@ -247,6 +276,7 @@
         }
     }
 
+    @Test
     public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
                 Settings.System.VIBRATE_WHEN_RINGING);
@@ -270,6 +300,7 @@
         }
     }
 
+    @Test
     public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
                 Settings.System.VIBRATE_WHEN_RINGING);
@@ -312,7 +343,11 @@
         }
     }
 
+    private Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
     private Context getContext() {
-        return getInstrumentation().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
index 72923cf..f9ddf7d 100644
--- a/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
+++ b/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
@@ -16,32 +16,59 @@
 
 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 android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-public class Settings_NameValueTableTest extends AndroidTestCase {
+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() {
-        ContentResolver cr = mContext.getContentResolver();
+        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
         Uri uri = Settings.System.CONTENT_URI;
-        String name = "name1";
+        String name = Settings.System.NEXT_ALARM_FORMATTED;
         String value = "value1";
 
         // before putString
         Cursor c = cr.query(uri, null, null, null, null);
         try {
             assertNotNull(c);
-            int origCount = c.getCount();
             c.close();
 
             MyNameValueTable.putString(cr, uri, name, value);
             c = cr.query(uri, null, null, null, null);
             assertNotNull(c);
-            assertEquals(origCount + 1, c.getCount());
             c.close();
 
             // query this row
@@ -50,21 +77,16 @@
             assertNotNull(c);
             assertEquals(1, c.getCount());
             c.moveToFirst();
-            assertEquals("name1", c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
-            assertEquals("value1", c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
+            assertEquals(name, c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
+            assertEquals(value, c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
             c.close();
-
-            // delete this row
-            cr.delete(uri, selection, null);
-            c = cr.query(uri, null, null, null, null);
-            assertNotNull(c);
-            assertEquals(origCount, c.getCount());
         } finally {
             // TODO should clean up more better
             c.close();
         }
     }
 
+    @Test
     public void testGetUriFor() {
         Uri uri = Uri.parse("content://authority/path");
         String name = "table";
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
index 7bb5908..042e847 100644
--- a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
+++ b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
@@ -16,6 +16,10 @@
 
 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;
@@ -23,9 +27,15 @@
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SettingNotFoundException;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-public class Settings_SecureTest extends AndroidTestCase {
+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";
 
@@ -37,11 +47,9 @@
 
     private ContentResolver cr;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        cr = mContext.getContentResolver();
+    @Before
+    public void setUp() throws Exception {
+        cr = InstrumentationRegistry.getTargetContext().getContentResolver();
         assertNotNull(cr);
         assertSettingsForTests();
     }
@@ -59,12 +67,14 @@
         }
     }
 
+    @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));
 
@@ -87,6 +97,7 @@
         }
     }
 
+    @Test
     public void testGetPutFloat() throws SettingNotFoundException {
         assertNull(Secure.getString(cr, NO_SUCH_SETTING));
 
@@ -109,6 +120,7 @@
         }
     }
 
+    @Test
     public void testGetPutLong() {
         assertNull(Secure.getString(cr, NO_SUCH_SETTING));
 
@@ -131,6 +143,7 @@
         }
     }
 
+    @Test
     public void testGetPutString() {
         assertNull(Secure.getString(cr, NO_SUCH_SETTING));
 
@@ -145,6 +158,7 @@
         assertNull(Secure.getString(cr, NO_SUCH_SETTING));
     }
 
+    @Test
     public void testGetUriFor() {
         String name = "table";
 
@@ -153,6 +167,7 @@
         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));
@@ -165,6 +180,7 @@
      * 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));
 
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
index e187a57..f2e5fd3 100644
--- a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
+++ b/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
@@ -16,11 +16,15 @@
 
 package android.provider.cts;
 
-
 import android.provider.Settings.SettingNotFoundException;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 
-public class Settings_SettingNotFoundExceptionTest extends AndroidTestCase {
+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
index bbdf714..f325af9 100644
--- a/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
+++ b/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
@@ -16,86 +16,59 @@
 
 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.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Settings.System;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.Scanner;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class Settings_SystemTest extends InstrumentationTestCase {
-    private ContentResolver cr;
+@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;
 
-    private static final String INT_FIELD = "IntField";
-    private static final String LONG_FIELD = "LongField";
-    private static final String FLOAT_FIELD = "FloatField";
-    private static final String STRING_FIELD = "StringField";
+    @BeforeClass
+    public static void setUp() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings allow");
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        cr = getInstrumentation().getContext().getContentResolver();
-        assertNotNull(cr);
+        // Wait a beat to persist the change
+        SystemClock.sleep(500);
     }
 
-    private void deleteTestedRows() {
-        String selection = System.NAME + "=\"" + INT_FIELD + "\"";
-        cr.delete(System.CONTENT_URI, selection, null);
-
-        selection = System.NAME + "=\"" + LONG_FIELD + "\"";
-        cr.delete(System.CONTENT_URI, selection, null);
-
-        selection = System.NAME + "=\"" + FLOAT_FIELD + "\"";
-        cr.delete(System.CONTENT_URI, selection, null);
-
-        selection = System.NAME + "=\"" + STRING_FIELD + "\"";
-        cr.delete(System.CONTENT_URI, selection, null);
+    @AfterClass
+    public static void tearDown() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings default");
     }
 
-    private void enableAppOps() {
-        StringBuilder cmd = new StringBuilder();
-        cmd.append("appops set ");
-        cmd.append(getInstrumentation().getContext().getPackageName());
-        cmd.append(" android:write_settings allow");
-        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
-
-        StringBuilder query = new StringBuilder();
-        query.append("appops get ");
-        query.append(getInstrumentation().getContext().getPackageName());
-        query.append(" android:write_settings");
-        String queryStr = query.toString();
-
-        String result = "No operations.";
-        while (result.contains("No operations")) {
-            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
-                    queryStr);
-            InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
-            result = convertStreamToString(inputStream);
-        }
-    }
-
-    private String convertStreamToString(InputStream is) {
-        try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
-            return scanner.hasNext() ? scanner.next() : "";
-        }
-    }
-
+    @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.
          */
-        // Precondition: these rows must not exist in the db when we begin
-        deleteTestedRows();
 
         // first query exist rows
         Cursor c = cr.query(System.CONTENT_URI, null, null, null, null);
@@ -107,7 +80,6 @@
 
         try {
             assertNotNull(c);
-            int origCount = c.getCount();
             c.close();
 
             String stringValue = "cts";
@@ -120,7 +92,6 @@
 
             c = cr.query(System.CONTENT_URI, null, null, null, null);
             assertNotNull(c);
-            assertEquals(origCount + 4, c.getCount());
             c.close();
 
             // get these rows to assert
@@ -130,12 +101,8 @@
 
             assertEquals(stringValue, System.getString(cr, STRING_FIELD));
 
-            // delete the tested rows again
-            deleteTestedRows();
-
             c = cr.query(System.CONTENT_URI, null, null, null, null);
             assertNotNull(c);
-            assertEquals(origCount, c.getCount());
 
             // update fontScale row
             cfg = new Configuration();
@@ -143,7 +110,7 @@
             assertTrue(System.putConfiguration(cr, cfg));
 
             System.getConfiguration(cr, cfg);
-            assertEquals(1.2f, cfg.fontScale);
+            assertEquals(1.2f, cfg.fontScale, 0.001);
         } finally {
             // TODO should clean up more better
             c.close();
@@ -159,12 +126,16 @@
         }
     }
 
+    @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";
 
diff --git a/tests/tests/provider/src/android/provider/cts/SmsBackupRestoreTest.java b/tests/tests/provider/src/android/provider/cts/SmsBackupRestoreTest.java
index b11fad2..97cc62a 100644
--- a/tests/tests/provider/src/android/provider/cts/SmsBackupRestoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SmsBackupRestoreTest.java
@@ -41,7 +41,7 @@
 public class SmsBackupRestoreTest extends TestCaseThatRunsIfTelephonyIsEnabled {
     private static final String TAG = "SmsBackupRestoreTest";
     private static final String LOCAL_BACKUP_COMPONENT =
-            "android/com.android.internal.backup.LocalTransport";
+            "com.android.localtransport/.LocalTransport";
     private static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
 
     private static final String[] smsAddressBody1 = new String[] {"+123" , "sms CTS text"};
diff --git a/tests/tests/provider/src/android/provider/cts/TestSRSProvider.java b/tests/tests/provider/src/android/provider/cts/TestSRSProvider.java
new file mode 100644
index 0000000..34029ad
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/TestSRSProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class TestSRSProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
index ae3e41a..bc7f341 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
@@ -33,10 +33,10 @@
 
     private static final Uri URI = ContactsContract.Contacts.CONTENT_URI;
 
-    public static void update(ContentResolver resolver, long contactId,
+    public static int update(ContentResolver resolver, long contactId,
             ContentValues values) {
         Uri uri = ContentUris.withAppendedId(URI, contactId);
-        resolver.update(uri, values, null, null);
+        return resolver.update(uri, values, null, null);
     }
 
     public static void delete(ContentResolver resolver, long contactId) {
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
index c0ff4c0..be9d1d2 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
@@ -32,7 +32,7 @@
         List<ResolveInfo> resolveInfoList = getContext()
                 .getPackageManager().queryIntentActivities(intent, 0);
         assertNotNull("Missing ResolveInfo", resolveInfoList);
-        assertTrue("No ResolveInfo found for " + intent.toInsecureString(),
+        assertTrue("No ResolveInfo found for " + intent.toString(),
                 resolveInfoList.size() > 0);
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
index c19e571..7b3a93a 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
@@ -59,7 +59,7 @@
         long [] contactIds = setupThreeContacts();
 
         // Setup: create query with first and last name reversed.
-        Uri uri = AggregationSuggestions.builder()
+        Uri uri = new AggregationSuggestions.Builder()
                 .addNameParameter("last1 first1")
                 .build();
 
@@ -81,7 +81,7 @@
         long [] contactIds = setupThreeContacts();
 
         // Setup: create query with first and last name in same order as display name.
-        Uri uri = AggregationSuggestions.builder()
+        Uri uri = new AggregationSuggestions.Builder()
                 .addNameParameter("first1 last1")
                 .build();
 
@@ -102,7 +102,7 @@
         setupThreeContacts();
 
         // Setup: query with name that is completely different than all the contacts.
-        Uri uri = AggregationSuggestions.builder()
+        Uri uri = new AggregationSuggestions.Builder()
                 .addNameParameter("unmatched name")
                 .build();
 
@@ -118,7 +118,7 @@
         long [] contactIds = setupThreeContacts();
 
         // Setup: query with two names. The first name is completely unlike all the contacts.
-        Uri uri = AggregationSuggestions.builder()
+        Uri uri = new AggregationSuggestions.Builder()
                 .addNameParameter("unmatched name")
                 .addNameParameter("first2 last2")
                 .build();
@@ -139,7 +139,7 @@
         long [] contactIds = setupThreeContacts();
 
         // Setup: query with two names. The second name is completely unlike all the contacts.
-        Uri uri = AggregationSuggestions.builder()
+        Uri uri = new AggregationSuggestions.Builder()
                 .addNameParameter("first2 last2")
                 .addNameParameter("unmatched name")
                 .build();
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
index c5fdc1a..c9d1371 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
@@ -71,46 +71,19 @@
         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(System.currentTimeMillis() / 86400 * 86400,
-                    contact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, i, contact.getLong(Contacts.TIMES_CONTACTED));
+            assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+            assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
 
-            assertEquals(System.currentTimeMillis() / 86400 * 86400,
-                    rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, i, rawContact.getLong(Contacts.TIMES_CONTACTED));
-        }
-
-        for (int i = 0; i < 10; i++) {
-            Contacts.markAsContacted(mResolver, contact.getId());
-            contact.load();
-            rawContact.load();
-
-            assertEquals(System.currentTimeMillis() / 86400 * 86400,
-                    contact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 10, contact.getLong(Contacts.TIMES_CONTACTED));
-
-            assertEquals(System.currentTimeMillis() / 86400 * 86400,
-                    rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 10, rawContact.getLong(Contacts.TIMES_CONTACTED));
-        }
-
-        for (int i = 0; i < 10; i++) {
-            Contacts.markAsContacted(mResolver, contact.getId());
-            contact.load();
-            rawContact.load();
-
-            assertEquals(System.currentTimeMillis() / 86400 * 86400,
-                    contact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 20, contact.getLong(Contacts.TIMES_CONTACTED));
-
-            assertEquals(System.currentTimeMillis() / 86400 * 86400,
-                    rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 20, rawContact.getLong(Contacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+            assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
         }
     }
 
@@ -201,11 +174,17 @@
         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();
 
@@ -216,6 +195,8 @@
         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();
@@ -223,6 +204,8 @@
         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);
@@ -230,17 +213,27 @@
         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);
         }
@@ -248,16 +241,11 @@
         contact.load();
         rawContact.load();
 
-        final ContentValues cv = new ContentValues();
-            cv.put(Contacts.STARRED, 1);
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
 
-        final String where =
-                (Contacts.LAST_TIME_CONTACTED + "=P1 AND " + Contacts.TIMES_CONTACTED + "=P2")
-                        .replaceAll("P1", String.valueOf(now / 86400 * 86400))
-                        .replaceAll("P2", "30");
-
-        rawContact.setAlreadyDeleted();
-        contact.setAlreadyDeleted();
+        assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
     }
 
     public void testProjection() throws Exception {
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
index befbeb8..f1a6409 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
@@ -18,25 +18,40 @@
 
 import static android.provider.ContactsContract.DataUsageFeedback;
 
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
-import android.provider.cts.contacts.DataUtil;
-import android.provider.cts.contacts.DatabaseAsserts;
-import android.provider.cts.contacts.RawContactUtil;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 
+/**
+ * Note we no longer support contact affinity as of Q, so times_contacted and
+ * last_time_contacted are always 0.
+ */
 public class ContactsContract_DataUsageTest 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 {
+        mBuilder.cleanup();
+        super.tearDown();
     }
 
     public void testSingleDataUsageFeedback_incrementsCorrectDataItems() {
@@ -44,45 +59,14 @@
 
         long[] dataIds = setupRawContactDataItems(ids.mRawContactId);
 
-        // Update just 1 data item at a time.
-        updateDataUsageAndAssert(dataIds[1], 1);
-        updateDataUsageAndAssert(dataIds[1], 2);
-        updateDataUsageAndAssert(dataIds[1], 3);
-        updateDataUsageAndAssert(dataIds[1], 4);
-        updateDataUsageAndAssert(dataIds[1], 5);
-        updateDataUsageAndAssert(dataIds[1], 6);
-        updateDataUsageAndAssert(dataIds[1], 7);
-        updateDataUsageAndAssert(dataIds[1], 8);
-        updateDataUsageAndAssert(dataIds[1], 9);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 10);
+        updateDataUsageAndAssert(dataIds[1], 0);
+        updateDataUsageAndAssert(dataIds[1], 0);
 
-        updateDataUsageAndAssert(dataIds[2], 1);
-        updateDataUsageAndAssert(dataIds[2], 2);
-        updateDataUsageAndAssert(dataIds[2], 3);
+        updateDataUsageAndAssert(dataIds[2], 0);
+        updateDataUsageAndAssert(dataIds[2], 0);
 
-        // Go back and update the previous data item again.
-        updateDataUsageAndAssert(dataIds[1], 10);
-        updateDataUsageAndAssert(dataIds[1], 20);
-
-        updateDataUsageAndAssert(dataIds[2], 4);
-        updateDataUsageAndAssert(dataIds[2], 5);
-        updateDataUsageAndAssert(dataIds[2], 6);
-        updateDataUsageAndAssert(dataIds[2], 7);
-        updateDataUsageAndAssert(dataIds[2], 8);
-        updateDataUsageAndAssert(dataIds[2], 9);
-        updateDataUsageAndAssert(dataIds[2], 10);
-
-        updateDataUsageAndAssert(dataIds[1], 20);
-        updateDataUsageAndAssert(dataIds[1], 20);
-        updateDataUsageAndAssert(dataIds[1], 20);
+        updateDataUsageAndAssert(dataIds[1], 0);
+        updateDataUsageAndAssert(dataIds[1], 0);
 
         deleteDataUsage();
         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
@@ -93,20 +77,10 @@
 
         long[] dataIds = setupRawContactDataItems(ids.mRawContactId);
 
+        assertDataUsageEquals(dataIds, 0, 0, 0, 0);
+
         updateMultipleAndAssertUpdateSuccess(new long[] {dataIds[1], dataIds[2]});
-
-        updateMultipleAndAssertUpdateSuccess(new long[]{dataIds[1], dataIds[2]});
-
-        for (int i = 3; i <= 10; i++) {
-            updateMultipleAndAssertUpdateSuccess(new long[]{dataIds[1]});
-        }
-
-        updateMultipleAndAssertUpdateSuccess(new long[]{dataIds[0], dataIds[1]});
-
-        for (int i = 12; i <= 19; i++) {
-            updateMultipleAndAssertUpdateSuccess(new long[]{dataIds[1]});
-        }
-        updateMultipleAndAssertUpdateSuccess(new long[]{dataIds[1]});
+        assertDataUsageEquals(dataIds, 0, 0, 0, 0);
 
         deleteDataUsage();
         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
@@ -122,9 +96,6 @@
         return dataIds;
     }
 
-    /**
-     * Updates multiple data ids at once.  And asserts the update returned success.
-     */
     private void updateMultipleAndAssertUpdateSuccess(long[] dataIds) {
         String[] ids = new String[dataIds.length];
         for (int i = 0; i < dataIds.length; i++) {
@@ -134,20 +105,94 @@
                 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
                         DataUsageFeedback.USAGE_TYPE_CALL).build();
         int result = mResolver.update(uri, new ContentValues(), null, null);
+        assertEquals(0, result); // always 0
     }
 
-    /**
-     * Updates a single data item usage.  Asserts the update was successful.  Asserts the usage
-     * number is equal to expected value.
-     */
     private void updateDataUsageAndAssert(long dataId, int assertValue) {
         Uri uri = DataUsageFeedback.FEEDBACK_URI.buildUpon().appendPath(String.valueOf(dataId))
                 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
                         DataUsageFeedback.USAGE_TYPE_CALL).build();
         int result = mResolver.update(uri, new ContentValues(), null, null);
+        assertEquals(0, result); // always 0
+
+        assertDataUsageEquals(dataId, assertValue);
+    }
+
+    private void assertDataUsageEquals(long[] dataIds, int... expectedValues) {
+        if (dataIds.length != expectedValues.length) {
+            throw new IllegalArgumentException("dataIds and expectedValues must be the same size");
+        }
+
+        for (int i = 0; i < dataIds.length; i++) {
+            assertDataUsageEquals(dataIds[i], expectedValues[i]);
+        }
+    }
+
+    /**
+     * Assert a single data item has a specific usage value.
+     */
+    private void assertDataUsageEquals(long dataId, int expectedValue) {
+        // Query and assert value is expected.
+        String[] projection = new String[]{ContactsContract.Data.TIMES_USED};
+        String[] record = DataUtil.queryById(mResolver, dataId, projection);
+        assertNotNull(record);
+        long actual = 0;
+        // Tread null as 0
+        if (record[0] != null) {
+            actual = Long.parseLong(record[0]);
+        }
+        assertEquals(expectedValue, actual);
+
+        // Also make sure the rounded value is used in 'where' too.
+        assertEquals("Query should match", 1, DataUtil.queryById(mResolver, dataId, projection,
+                "ifnull(" + Data.TIMES_USED + ",0)=" + expectedValue, null).length);
     }
 
     private void deleteDataUsage() {
         mResolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null);
     }
+
+
+    public void testUsageUpdate() throws Exception {
+        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        final TestContact contact = rawContact.getContact().load();
+
+        ContentValues values;
+
+        values = new ContentValues();
+        values.put(Contacts.LAST_TIME_CONTACTED, 123);
+        assertEquals(1, ContactUtil.update(mResolver, contact.getId(), values));
+
+        values = new ContentValues();
+        values.put(Contacts.LAST_TIME_CONTACTED, 123);
+        assertEquals(1, RawContactUtil.update(mResolver, rawContact.getId(), values));
+
+        values = new ContentValues();
+        values.put(Contacts.TIMES_CONTACTED, 456);
+        assertEquals(1, ContactUtil.update(mResolver, contact.getId(), values));
+
+        values = new ContentValues();
+        values.put(Contacts.TIMES_CONTACTED, 456);
+        assertEquals(1, RawContactUtil.update(mResolver, rawContact.getId(), values));
+
+
+        values = new ContentValues();
+        values.put(Contacts.LAST_TIME_CONTACTED, 123);
+        values.put(Contacts.TIMES_CONTACTED, 456);
+        assertEquals(1, ContactUtil.update(mResolver, contact.getId(), values));
+
+        values = new ContentValues();
+        values.put(Contacts.LAST_TIME_CONTACTED, 123);
+        values.put(Contacts.TIMES_CONTACTED, 456);
+        assertEquals(1, RawContactUtil.update(mResolver, rawContact.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));
+    }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
index f06c859..3fb6720 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
@@ -37,7 +37,10 @@
 /**
  * CTS tests for {@link android.provider.ContactsContract.Contacts#CONTENT_FREQUENT_URI},
  * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} and
- * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} apis.
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} APIs.
+ *
+ * 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.
  */
 public class ContactsContract_FrequentsStrequentsTest extends InstrumentationTestCase {
     private ContentResolver mResolver;
@@ -180,11 +183,6 @@
                 false, sContentValues[1], sContentValues[0]);
     }
 
-    /**
-     * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
-     * frequent contacts in the correct order if there are only frequent contacts in the user's
-     * contacts.
-     */
     public void testStrequents_frequentsOnlyInCorrectOrder() throws Exception {
         long[] ids = setupTestData();
 
@@ -196,13 +194,13 @@
 
         // Contact the third contact twice.
         markDataAsUsed(mDataIds[2], 2);
+
+        // The strequents uri should now return contact 2, 3, 1 in order due to ranking by
+        // data usage.
+        assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
+                false);
     }
 
-    /**
-     * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
-     * first starred, then frequent contacts in their respective correct orders if there are both
-     * starred and frequent contacts in the user's contacts.
-     */
     public void testStrequents_starredAndFrequentsInCorrectOrder() throws Exception {
         long[] ids = setupTestData();
 
@@ -215,6 +213,13 @@
         // Contact the third contact twice, and mark it as used
         markDataAsUsed(mDataIds[2], 2);
         starContact(ids[2]);
+
+        // The strequents uri should now return contact 3, 2, 1 in order. Contact 3 is ranked first
+        // because it is starred, followed by contacts 2 and 1 due to their data usage ranking.
+        // Note that contact 3 is only returned once (as a starred contact) even though it is also
+        // a frequently contacted contact.
+        assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
+                false, sContentValues[2]);
     }
 
     /**
@@ -231,18 +236,23 @@
 
         // Construct a uri that filters for the query string "ta".
         Uri uri = Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon().appendEncodedPath("ta").build();
+
+        // Only contact 1 and 2 should be returned (sorted in alphabetical order) due to the
+        // filtered query.
+        assertCursorStoredValuesWithContactsFilter(uri, ids, false,
+                sContentValues[1], sContentValues[0]);
     }
 
     public void testStrequents_projection() throws Exception {
         long[] ids = setupTestData();
 
-        // Start contact 0 and mark contact 2 as frequent
+        // Star contact 0 and mark contact 2 as frequent
         starContact(ids[0]);
         markDataAsUsed(mDataIds[2], 1);
 
         DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_STREQUENT_URI,
                 STREQUENT_PROJECTION,
-                new long[]{ids[0], ids[2]}
+                new long[]{ids[0]}
         );
 
         // Strequent filter.
@@ -287,6 +297,9 @@
         // Construct a uri for phone only favorites.
         Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
                 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
+
+        // Only the contacts with phone numbers are returned, in frequency ranking order.
+        assertCursorStoredValuesWithContactsFilter(uri, mDataIds, false);
     }
 
     public void testStrequents_phoneOnly_projection() throws Exception {
@@ -302,7 +315,7 @@
 
         DatabaseAsserts.checkProjection(mResolver, uri,
                 STREQUENT_PHONE_ONLY_PROJECTION,
-                new long[]{mDataIds[0], mDataIds[2]} // Note _id from phone_only is data._id
+                new long[]{mDataIds[0]}
         );
     }
 
@@ -322,6 +335,11 @@
 
         // Contact the third contact twice.
         markDataAsUsed(mDataIds[2], 2);
+
+        // The frequents uri should now return contact 2, 3, 1 in order due to ranking by
+        // data usage.
+        assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_FREQUENT_URI, ids,
+                true);
     }
 
     public void testFrequent_projection() throws Exception {
@@ -331,7 +349,7 @@
 
         DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_FREQUENT_URI,
                 STREQUENT_PROJECTION,
-                new long[]{ids[0]}
+                new long[]{}
         );
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
index 5e27e21..89960cc 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
@@ -50,12 +50,10 @@
                 testCallback(intent);
             }
 
-            @Override
             public void startActivityAsUser(Intent intent, UserHandle user) {
                 testCallback(intent);
             }
 
-            @Override
             public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
                 testCallback(intent);
             }
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
index d937005..89889f7 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
@@ -233,6 +233,9 @@
     }
 
     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()
@@ -243,6 +246,8 @@
                     .insert();
 
             rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
         }
 
         {
@@ -253,6 +258,8 @@
                     .insert();
 
             rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
         }
         {
             TestRawContact rawContact = mBuilder.newRawContact()
@@ -262,6 +269,8 @@
                     .insert();
 
             rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
         }
     }
 
@@ -277,17 +286,23 @@
                 .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_StreamItemPhotosTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StreamItemPhotosTest.java
deleted file mode 100644
index 24cc122..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StreamItemPhotosTest.java
+++ /dev/null
@@ -1,76 +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.contacts;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.ContactsContract.StreamItemPhotos;
-import android.provider.ContactsContract.StreamItems;
-import android.provider.cts.PhotoUtil;
-import android.test.AndroidTestCase;
-
-public class ContactsContract_StreamItemPhotosTest extends AndroidTestCase {
-
-    private ContentResolver mResolver;
-
-    private Uri mStreamItemUri;
-
-    private long mStreamItemId;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResolver = mContext.getContentResolver();
-
-        long rawContactId = ContactsContract_StreamItemsTest.insertRawContact(mResolver);
-        mStreamItemUri = ContactsContract_StreamItemsTest.insertViaContentDirectoryUri(mResolver,
-                rawContactId);
-        mStreamItemId = ContentUris.parseId(mStreamItemUri);
-        assertTrue(mStreamItemId != -1);
-    }
-
-    public void testContentDirectoryUri() {
-        byte[] photoData = PhotoUtil.getTestPhotoData(mContext);
-        ContentValues values = new ContentValues();
-        values.put(StreamItemPhotos.SORT_INDEX, 1);
-        values.put(StreamItemPhotos.PHOTO, photoData);
-
-        Uri insertUri = Uri.withAppendedPath(
-                ContentUris.withAppendedId(StreamItems.CONTENT_URI, mStreamItemId),
-                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY);
-        Uri uri = mResolver.insert(insertUri, values);
-        long photoId = ContentUris.parseId(uri);
-        assertTrue(photoId != -1);
-        assertEquals(Uri.withAppendedPath(insertUri, Long.toString(photoId)), uri);
-    }
-
-    public void testContentPhotoUri() {
-        byte[] photoData = PhotoUtil.getTestPhotoData(mContext);
-        ContentValues values = new ContentValues();
-        values.put(StreamItemPhotos.STREAM_ITEM_ID, mStreamItemId);
-        values.put(StreamItemPhotos.SORT_INDEX, 1);
-        values.put(StreamItemPhotos.PHOTO, photoData);
-
-        Uri uri = mResolver.insert(StreamItems.CONTENT_PHOTO_URI, values);
-        long photoId = ContentUris.parseId(uri);
-        assertTrue(photoId != -1);
-        assertEquals(Uri.withAppendedPath(StreamItems.CONTENT_PHOTO_URI,
-                Long.toString(photoId)), uri);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StreamItemsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StreamItemsTest.java
deleted file mode 100644
index a69b97c..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StreamItemsTest.java
+++ /dev/null
@@ -1,182 +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.contacts;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-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 android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StreamItems;
-import android.test.AndroidTestCase;
-
-import java.util.ArrayList;
-
-public class ContactsContract_StreamItemsTest extends AndroidTestCase {
-
-    private static final String ACCOUNT_TYPE = "com.android.cts";
-    private static final String ACCOUNT_NAME = "ContactsContract_StreamItemsTest";
-
-    private static final String INSERT_TEXT = "Wrote a test for the StreamItems class";
-    private static final long INSERT_TIMESTAMP = 3007;
-    private static final String INSERT_COMMENTS = "1337 people reshared this";
-
-    private static final String UPDATE_TEXT = "Wrote more tests for the StreamItems class";
-    private static final long UPDATE_TIMESTAMP = 8008;
-    private static final String UPDATE_COMMENTS = "3007 people reshared this";
-
-    private ContentResolver mResolver;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResolver = mContext.getContentResolver();
-    }
-
-    public void testContentDirectoryUri() throws Exception {
-        long rawContactId = insertRawContact(mResolver);
-        Uri streamItemUri = insertViaContentDirectoryUri(mResolver, rawContactId);
-        long streamItemId = ContentUris.parseId(streamItemUri);
-        assertTrue(streamItemId != -1);
-
-        // Check that the provider returns the stream id in it's URI.
-        assertEquals(streamItemUri,
-                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId).buildUpon()
-                        .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY)
-                        .appendPath(Long.toString(streamItemId))
-                        .build());
-
-        // Check that the provider stored what we put into it.
-        assertInsertedItem(streamItemUri);
-
-        // Update the stream item.
-        ContentValues values = new ContentValues();
-        values.put(Data.RAW_CONTACT_ID, rawContactId);
-        values.put(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE);
-        values.put(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME);
-        values.put(StreamItems.TEXT, UPDATE_TEXT);
-        values.put(StreamItems.TIMESTAMP, UPDATE_TIMESTAMP);
-        values.put(StreamItems.COMMENTS, UPDATE_COMMENTS);
-
-        assertEquals(1, mResolver.update(streamItemUri, values, null, null));
-        assertUpdatedItem(streamItemUri);
-    }
-
-    static long insertRawContact(ContentResolver resolver) {
-        // Create a contact to attach the stream item to it.
-        ContentValues values = new ContentValues();
-        values.put(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE);
-        values.put(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME);
-
-        Uri contactUri = resolver.insert(RawContacts.CONTENT_URI, values);
-        long rawContactId = ContentUris.parseId(contactUri);
-        assertTrue(rawContactId != -1);
-        return rawContactId;
-    }
-
-    static Uri insertViaContentDirectoryUri(ContentResolver resolver, long rawContactId) {
-        // Attach a stream item to the contact.
-        ContentValues values = new ContentValues();
-        values.put(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE);
-        values.put(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME);
-        values.put(StreamItems.TEXT, INSERT_TEXT);
-        values.put(StreamItems.TIMESTAMP, INSERT_TIMESTAMP);
-        values.put(StreamItems.COMMENTS, INSERT_COMMENTS);
-
-        Uri contactStreamUri = Uri.withAppendedPath(
-                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
-                RawContacts.StreamItems.CONTENT_DIRECTORY);
-        return resolver.insert(contactStreamUri, values);
-    }
-
-    public void testContentUri() throws Exception {
-        // Create a contact with one stream item in it.
-        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
-
-        ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
-                .withValue(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE)
-                .withValue(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME)
-                .build());
-
-        ops.add(ContentProviderOperation.newInsert(StreamItems.CONTENT_URI)
-                .withValueBackReference(Data.RAW_CONTACT_ID, 0)
-                .withValue(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE)
-                .withValue(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME)
-                .withValue(StreamItems.TEXT, INSERT_TEXT)
-                .withValue(StreamItems.TIMESTAMP, INSERT_TIMESTAMP)
-                .withValue(StreamItems.COMMENTS, INSERT_COMMENTS)
-                .build());
-
-        ContentProviderResult[] results = mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
-        long rawContactId = ContentUris.parseId(results[0].uri);
-        assertTrue(rawContactId != -1);
-
-        Uri streamItemUri = results[1].uri;
-        long streamItemId = ContentUris.parseId(streamItemUri);
-        assertTrue(streamItemId != -1);
-
-        // Check that the provider returns the stream id in it's URI.
-        assertEquals(streamItemUri,
-                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId));
-
-        // Check that the provider stored what we put into it.
-        assertInsertedItem(streamItemUri);
-
-        // Update the stream item.
-        ops.clear();
-        ops.add(ContentProviderOperation.newUpdate(streamItemUri)
-                .withValue(Data.RAW_CONTACT_ID, rawContactId)
-                .withValue(RawContacts.ACCOUNT_TYPE, ACCOUNT_TYPE)
-                .withValue(RawContacts.ACCOUNT_NAME, ACCOUNT_NAME)
-                .withValue(StreamItems.TEXT, UPDATE_TEXT)
-                .withValue(StreamItems.TIMESTAMP, UPDATE_TIMESTAMP)
-                .withValue(StreamItems.COMMENTS, UPDATE_COMMENTS)
-                .build());
-
-        results = mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
-        assertEquals(Integer.valueOf(1), results[0].count);
-        assertUpdatedItem(streamItemUri);
-    }
-
-    private void assertInsertedItem(Uri itemUri) {
-        assertStreamItem(itemUri, INSERT_TEXT, INSERT_TIMESTAMP, INSERT_COMMENTS);
-    }
-
-    private void assertUpdatedItem(Uri itemUri) {
-        assertStreamItem(itemUri, UPDATE_TEXT, UPDATE_TIMESTAMP, UPDATE_COMMENTS);
-    }
-
-    private void assertStreamItem(Uri uri, String text, long timestamp, String comments) {
-        Cursor cursor = mResolver.query(uri, null, null, null, null);
-        try {
-            assertTrue(cursor.moveToFirst());
-            assertEquals(text, cursor.getString(
-                    cursor.getColumnIndexOrThrow(StreamItems.TEXT)));
-            assertEquals(timestamp, cursor.getLong(
-                    cursor.getColumnIndexOrThrow(StreamItems.TIMESTAMP)));
-            assertEquals(comments, cursor.getString(
-                    cursor.getColumnIndexOrThrow(StreamItems.COMMENTS)));
-        } finally {
-            cursor.close();
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
index a923584..15b228c 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
@@ -16,9 +16,6 @@
 
 package android.provider.cts.contacts;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
 import android.content.ContentProviderClient;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -32,22 +29,22 @@
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 
+import junit.framework.Assert;
+import junit.framework.ComparisonFailure;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import junit.framework.Assert;
-import junit.framework.ComparisonFailure;
-
 /**
  * A test data builder for ContactsContract tests.
  */
 public class ContactsContract_TestDataBuilder {
     private ContentProviderClient mProvider;
-    private ArrayList<Builder<?>> mCreatedRows = Lists.newArrayList();
-    private HashSet<Builder<?>> mLoadedRows = Sets.newHashSet();
+    private ArrayList<Builder<?>> mCreatedRows = new ArrayList<>();
+    private HashSet<Builder<?>> mLoadedRows = new HashSet<>();
 
     private interface IdQuery {
         String[] COLUMNS = new String[] {
@@ -170,7 +167,7 @@
             mLoadedRows.add(this);
 
             StringBuilder selection = new StringBuilder();
-            ArrayList<String> selectionArgs = Lists.newArrayList();
+            ArrayList<String> selectionArgs = new ArrayList<>();
             Set<Map.Entry<String, Object>> entries = mValues.valueSet();
             for (Map.Entry<String, Object> entry : entries) {
                 String column = entry.getKey();
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
index ced09d6..4f82a49 100755
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
@@ -16,26 +16,18 @@
 
 package android.provider.cts.contacts;
 
-import static android.provider.cts.contacts.DatabaseAsserts.ContactIdPair;
-
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.os.SystemClock;
-import android.provider.cts.contacts.CommonDatabaseUtils;
-import android.provider.cts.contacts.ContactUtil;
-import android.provider.cts.contacts.DataUtil;
-import android.provider.cts.contacts.DatabaseAsserts;
-import android.provider.cts.contacts.DeletedContactUtil;
-import android.provider.cts.contacts.RawContactUtil;
+import android.provider.cts.contacts.DatabaseAsserts.ContactIdPair;
 import android.provider.cts.contacts.account.StaticAccountAuthenticator;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
-import com.google.android.collect.Lists;
-
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 @MediumTest
 public class ContactsProvider2_AccountRemovalTest extends AndroidTestCase {
@@ -102,7 +94,7 @@
      */
     public void testAccountRemovalWithMergedContact_deletesContacts() {
         mAccountManager.addAccountExplicitly(ACCT_1, null, null);
-        ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
+        List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
         mAccountManager.removeAccount(ACCT_1, null, null);
         assertContactsDeletedEventually(System.currentTimeMillis(), idList);
     }
@@ -114,7 +106,7 @@
     public void testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated() {
         mAccountManager.addAccountExplicitly(ACCT_1, null, null);
         mAccountManager.addAccountExplicitly(ACCT_2, null, null);
-        ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2);
+        List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2);
         long contactId = idList.get(0).mContactId;
 
         long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
@@ -131,13 +123,13 @@
 
     public void testAccountRemovalWithMergedContact_hasDeleteLogsForContacts() {
         mAccountManager.addAccountExplicitly(ACCT_1, null, null);
-        ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
+        List<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1);
         long start = System.currentTimeMillis();
         mAccountManager.removeAccount(ACCT_1, null, null);
         assertContactsInDeleteLogEventually(start, idList);
     }
 
-    private ArrayList<ContactIdPair> createAndAssertMergedContact(Account acct, Account acct2) {
+    private List<ContactIdPair> createAndAssertMergedContact(Account acct, Account acct2) {
         ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct,
                 "merge me");
         DataUtil.insertPhoneNumber(mResolver, ids1.mRawContactId, "555-5555");
@@ -154,7 +146,7 @@
         ids1.mContactId = mergedContactId;
         ids2.mContactId = mergedContactId;
 
-        return Lists.newArrayList(ids1, ids2);
+        return Arrays.asList(ids1, ids2);
     }
 
     private long assertMerged(long start, long rawContactId, long rawContactId2) {
@@ -178,7 +170,7 @@
         return NOT_MERGED;
     }
 
-    private void assertContactsInDeleteLogEventually(long start, ArrayList<ContactIdPair> idList) {
+    private void assertContactsInDeleteLogEventually(long start, List<ContactIdPair> idList) {
         // Can not use newArrayList() because the version that accepts size is missing.
         ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
         remaining.addAll(idList);
@@ -189,7 +181,7 @@
                     " are not in delete log after account removal.");
 
             // Need a second list to remove since we can't remove from the list while iterating.
-            ArrayList<ContactIdPair> toBeRemoved = Lists.newArrayList();
+            ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
             for (ContactIdPair ids : remaining) {
                 long deletedTime = DeletedContactUtil.queryDeletedTimestampForContactId(mResolver,
                         ids.mContactId);
@@ -209,7 +201,7 @@
      * Polls every so often to see if all contacts have been deleted.  If not deleted in the
      * pre-defined threshold, fails.
      */
-    private void assertContactsDeletedEventually(long start, ArrayList<ContactIdPair> idList) {
+    private void assertContactsDeletedEventually(long start, List<ContactIdPair> idList) {
         // Can not use newArrayList() because the version that accepts size is missing.
         ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size());
         remaining.addAll(idList);
@@ -219,7 +211,7 @@
             assertWithinTimeoutLimit(start, "Contacts have not been deleted after account"
                     + " removal.");
 
-            ArrayList<ContactIdPair> toBeRemoved = Lists.newArrayList();
+            ArrayList<ContactIdPair> toBeRemoved = new ArrayList<>();
             for (ContactIdPair ids : remaining) {
                 if (!RawContactUtil.rawContactExistsById(mResolver, ids.mRawContactId)) {
                     toBeRemoved.add(ids);
@@ -244,7 +236,7 @@
      * Creates a given number of contacts for an account.
      */
     private ArrayList<ContactIdPair> createContacts(Account account, int numContacts) {
-        ArrayList<ContactIdPair> accountIds = Lists.newArrayList();
+        ArrayList<ContactIdPair> accountIds = new ArrayList<>();
         for (int i = 0; i < numContacts; i++) {
             accountIds.add(DatabaseAsserts.assertAndCreateContact(mResolver, account));
         }
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
index 91f53d4..3b4854b 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
@@ -16,14 +16,10 @@
 
 package android.provider.cts.contacts;
 
-import android.content.Context;
 import android.provider.Contacts;
 import android.provider.Contacts.ContactMethods;
 import android.test.AndroidTestCase;
 
-import com.android.internal.R;
-
-
 public class Contacts_ContactMethodsTest extends AndroidTestCase {
     public void testAddPostalLocation() {
     }
@@ -57,49 +53,8 @@
                 ContactMethods.TYPE_CUSTOM, label).toString();
         assertEquals(label, display);
 
-        CharSequence[] labels = getContext().getResources().getTextArray(
-                com.android.internal.R.array.emailAddressTypes);
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_EMAIL,
-                ContactMethods.TYPE_HOME, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_HOME - 1], display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_EMAIL,
-                ContactMethods.TYPE_OTHER, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_OTHER - 1], display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_EMAIL,
-                ContactMethods.TYPE_WORK, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_WORK - 1], display);
-
-        String untitled = getContext().getString(R.string.untitled);
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_IM,
-                ContactMethods.TYPE_CUSTOM, label).toString();
-        assertEquals(untitled, display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_ORGANIZATION,
-                ContactMethods.TYPE_CUSTOM, label).toString();
-        assertEquals(untitled, display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_PHONE,
-                ContactMethods.TYPE_CUSTOM, label).toString();
-        assertEquals(untitled, display);
-
         display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_POSTAL,
                 ContactMethods.TYPE_CUSTOM, label).toString();
         assertEquals(label, display);
-
-        labels = getContext().getResources().getTextArray(
-                com.android.internal.R.array.postalAddressTypes);
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_POSTAL,
-                ContactMethods.TYPE_HOME, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_HOME - 1], display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_POSTAL,
-                ContactMethods.TYPE_OTHER, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_OTHER - 1], display);
-
-        display = ContactMethods.getDisplayLabel(getContext(), Contacts.KIND_POSTAL,
-                ContactMethods.TYPE_WORK, label).toString();
-        assertEquals(labels[ContactMethods.TYPE_WORK - 1], display);
     }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
index 814c845..37e3266 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
@@ -16,7 +16,6 @@
 
 package android.provider.cts.contacts;
 
-import android.content.Context;
 import android.provider.Contacts.Organizations;
 import android.test.AndroidTestCase;
 
@@ -26,15 +25,5 @@
         String display = Organizations.getDisplayLabel(getContext(),
                 Organizations.TYPE_CUSTOM, label).toString();
         assertEquals(label, display);
-
-        CharSequence[] labels = getContext().getResources().getTextArray(
-                com.android.internal.R.array.organizationTypes);
-        display = Organizations.getDisplayLabel(getContext(),
-                Organizations.TYPE_OTHER, label).toString();
-        assertEquals(labels[Organizations.TYPE_OTHER - 1], display);
-
-        display = Organizations.getDisplayLabel(getContext(),
-                Organizations.TYPE_WORK, label).toString();
-        assertEquals(labels[Organizations.TYPE_WORK - 1], display);
     }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
index 363ba61..ab296a3 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
@@ -26,36 +26,6 @@
         String display = Phones.getDisplayLabel(getContext(),
                 Phones.TYPE_CUSTOM, label).toString();
         assertEquals(label, display);
-
-        CharSequence[] labels = getContext().getResources().getTextArray(
-                com.android.internal.R.array.phoneTypes);
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_HOME, label).toString();
-        assertEquals(labels[Phones.TYPE_HOME - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_MOBILE, label).toString();
-        assertEquals(labels[Phones.TYPE_MOBILE - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_WORK, label).toString();
-        assertEquals(labels[Phones.TYPE_WORK - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_FAX_WORK, label).toString();
-        assertEquals(labels[Phones.TYPE_FAX_WORK - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_FAX_HOME, label).toString();
-        assertEquals(labels[Phones.TYPE_FAX_HOME - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_PAGER, label).toString();
-        assertEquals(labels[Phones.TYPE_PAGER - 1], display);
-
-        display = Phones.getDisplayLabel(getContext(),
-                Phones.TYPE_OTHER, label).toString();
-        assertEquals(labels[Phones.TYPE_OTHER - 1], display);
     }
 
     public void testGetDisplayLabelCharSequenceArray() {
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
index 38ff33d..b8e1576 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
@@ -33,10 +33,10 @@
 
     private static final Uri URI = ContactsContract.RawContacts.CONTENT_URI;
 
-    public static void update(ContentResolver resolver, long rawContactId,
+    public static int update(ContentResolver resolver, long rawContactId,
             ContentValues values) {
         Uri uri = ContentUris.withAppendedId(URI, rawContactId);
-        resolver.update(uri, values, null, null);
+        return resolver.update(uri, values, null, null);
     }
 
     public static long createRawContactWithName(ContentResolver resolver, Account account,
diff --git a/tests/tests/provider/src/libcore/util/HexEncoding.java b/tests/tests/provider/src/libcore/util/HexEncoding.java
new file mode 100644
index 0000000..992acbd
--- /dev/null
+++ b/tests/tests/provider/src/libcore/util/HexEncoding.java
@@ -0,0 +1,138 @@
+/*
+ * 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 libcore.util;
+
+/**
+ * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
+ * @hide
+ */
+public class HexEncoding {
+
+    /** Hidden constructor to prevent instantiation. */
+    private HexEncoding() {}
+
+    private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static char[] encode(byte[] data) {
+        return encode(data, 0, data.length);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static char[] encode(byte[] data, int offset, int len) {
+        char[] result = new char[len * 2];
+        for (int i = 0; i < len; i++) {
+            byte b = data[offset + i];
+            int resultIndex = 2 * i;
+            result[resultIndex] = (HEX_DIGITS[(b >>> 4) & 0x0f]);
+            result[resultIndex + 1] = (HEX_DIGITS[b & 0x0f]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static String encodeToString(byte[] data) {
+        return new String(encode(data));
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array.  Odd-length inputs
+     * are not allowed.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(String encoded) throws IllegalArgumentException {
+        return decode(encoded.toCharArray());
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
+     * is {@code true} odd-length inputs are allowed and the first character is interpreted
+     * as the lower bits of the first result byte.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(String encoded, boolean allowSingleChar) throws IllegalArgumentException {
+        return decode(encoded.toCharArray(), allowSingleChar);
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array.  Odd-length inputs
+     * are not allowed.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(char[] encoded) throws IllegalArgumentException {
+        return decode(encoded, false);
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
+     * is {@code true} odd-length inputs are allowed and the first character is interpreted
+     * as the lower bits of the first result byte.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException {
+        int resultLengthBytes = (encoded.length + 1) / 2;
+        byte[] result = new byte[resultLengthBytes];
+
+        int resultOffset = 0;
+        int i = 0;
+        if (allowSingleChar) {
+            if ((encoded.length % 2) != 0) {
+                // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
+                result[resultOffset++] = (byte) toDigit(encoded, i);
+                i++;
+            }
+        } else {
+            if ((encoded.length % 2) != 0) {
+                throw new IllegalArgumentException("Invalid input length: " + encoded.length);
+            }
+        }
+
+        for (int len = encoded.length; i < len; i += 2) {
+            result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
+        }
+
+        return result;
+    }
+
+    private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
+        // NOTE: that this isn't really a code point in the traditional sense, since we're
+        // just rejecting surrogate pairs outright.
+        int pseudoCodePoint = str[offset];
+
+        if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
+            return pseudoCodePoint - '0';
+        } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
+            return 10 + (pseudoCodePoint - 'a');
+        } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
+            return 10 + (pseudoCodePoint - 'A');
+        }
+
+        throw new IllegalArgumentException("Illegal char: " + str[offset] +
+                " at offset " + offset);
+    }
+}
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
new file mode 100644
index 0000000..40f9673
--- /dev/null
+++ b/tests/tests/role/Android.bp
@@ -0,0 +1,35 @@
+// 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.
+
+android_test {
+    name: "CtsRoleTestCases",
+    sdk_version: "test_current",
+
+    srcs: [
+        "src/**/*.java"
+    ],
+
+    static_libs: [
+        "android-support-test",
+        "compatibility-device-util",
+        "ctstestrunner",
+        "truth-prebuilt"
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ]
+}
diff --git a/tests/tests/role/AndroidManifest.xml b/tests/tests/role/AndroidManifest.xml
new file mode 100644
index 0000000..60e53b1
--- /dev/null
+++ b/tests/tests/role/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.
+ -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.app.role.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application>
+
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".WaitForResultActivity" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.app.role.cts"
+        android:label="CTS tests of android.app.role">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/role/AndroidTest.xml b/tests/tests/role/AndroidTest.xml
new file mode 100644
index 0000000..8adcbc1
--- /dev/null
+++ b/tests/tests/role/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+
+<configuration description="Config for CTS role test cases">
+
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsRoleTestCases.apk" />
+        <option name="test-file-name" value="CtsRoleTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.app.role.cts" />
+        <option name="runtime-hint" value="5m" />
+    </test>
+</configuration>
diff --git a/tests/tests/role/CtsRoleTestApp/Android.bp b/tests/tests/role/CtsRoleTestApp/Android.bp
new file mode 100644
index 0000000..74c1b76
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test {
+    name: "CtsRoleTestApp",
+    sdk_version: "test_current",
+
+    srcs: [
+        "src/**/*.java"
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ]
+}
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..03f9513
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -0,0 +1,83 @@
+<?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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.app.role.cts.app">
+
+    <application>
+
+        <activity
+            android:name=".RequestRoleActivity"
+            android:exported="true" />
+
+        <!-- Dialer -->
+        <activity android:name=".DialerDialActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel" />
+            </intent-filter>
+        </activity>
+        <service
+            android:name=".DialerInCallService"
+            android:permission="android.permission.BIND_INCALL_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.InCallService" />
+            </intent-filter>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
+        </service>
+
+        <!-- Sms -->
+        <activity android:name=".SmsSendToActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="smsto" />
+            </intent-filter>
+        </activity>
+        <service
+            android:name=".SmsRespondViaMessageService"
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="smsto" />
+            </intent-filter>
+        </service>
+        <receiver
+            android:name=".SmsDelieverReceiver"
+            android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+        <receiver
+            android:name=".SmsWapPushDelieverReceiver"
+            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/EmptyActivity.java b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/EmptyActivity.java
new file mode 100644
index 0000000..353ec45
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/EmptyActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * An empty activity that finishes immediately.
+ */
+public class EmptyActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        finish();
+    }
+}
diff --git a/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
new file mode 100644
index 0000000..46d07a4
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
@@ -0,0 +1,57 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that requests a role.
+ */
+public class RequestRoleActivity extends Activity {
+
+    private static final String EXTRA_ROLE_NAME = "android.app.role.cts.app.extra.ROLE_NAME";
+
+    private static final int REQUEST_CODE_REQUEST_ROLE = 1;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String roleName = getIntent().getStringExtra(EXTRA_ROLE_NAME);
+        if (TextUtils.isEmpty(roleName)) {
+            throw new IllegalArgumentException("Role name in extras cannot be null or empty");
+        }
+
+        RoleManager roleManager = getSystemService(RoleManager.class);
+        Intent intent = roleManager.createRequestRoleIntent(roleName);
+        startActivityForResult(intent, REQUEST_CODE_REQUEST_ROLE);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_CODE_REQUEST_ROLE) {
+            setResult(resultCode, data);
+            finish();
+        } else {
+            super.onActivityResult(requestCode, resultCode, data);
+        }
+    }
+}
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
new file mode 100644
index 0000000..9613c15
--- /dev/null
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.app.role.cts;
+
+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.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.Instrumentation;
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+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.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+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.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link RoleManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RoleManagerTest {
+
+    private static final String LOG_TAG = "RoleManagerTest";
+
+    private static final long TIMEOUT_MILLIS = 15 * 1000;
+
+    private static final String ROLE_NAME = RoleManager.ROLE_DIALER;
+
+    private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
+    private static final String APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME
+            + ".RequestRoleActivity";
+    private static final String APP_REQUEST_ROLE_EXTRA_ROLE_NAME = APP_PACKAGE_NAME
+            + ".extra.ROLE_NAME";
+
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
+    private static final UiDevice sUiDevice = UiDevice.getInstance(sInstrumentation);
+
+    @Rule
+    public ActivityTestRule<WaitForResultActivity> mActivityRule =
+            new ActivityTestRule<>(WaitForResultActivity.class);
+
+    // TODO: STOPSHIP: Remove once we automatically revoke role upon uninstallation.
+    @Before
+    @After
+    public void removeRoleHolder() throws Exception {
+        removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+    }
+
+    @Test
+    public void roleIsAvailable() {
+        assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
+    }
+
+    @Test
+    public void addRoleHolderThenIsRoleHolder() throws Exception {
+        addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
+        addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+        removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+    }
+
+    @Test
+    public void requestRoleAndRejectThenIsNotRoleHolder() throws Exception {
+        requestRole(ROLE_NAME);
+        respondToRoleRequest(false);
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+    }
+
+    @Test
+    public void requestRoleAndApproveThenIsRoleHolder() throws Exception {
+        requestRole(ROLE_NAME);
+        respondToRoleRequest(true);
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void revokeSingleRoleThenEnsureOtherRolesAppopsIntact() throws Exception {
+        addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
+        addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+        removeRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+        assertThat(AppOpsUtils.getOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_SEND_SMS))
+                .isEqualTo(AppOpsManager.MODE_ALLOWED);
+    }
+
+    @Test
+    public void migratedRoleHoldersNotEmpty() throws Exception {
+        assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
+    }
+
+    private void requestRole(@NonNull String roleName) {
+        Intent intent = new Intent()
+                .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
+                .putExtra(APP_REQUEST_ROLE_EXTRA_ROLE_NAME, roleName);
+        mActivityRule.getActivity().startActivityToWaitForResult(intent);
+    }
+
+    private void respondToRoleRequest(boolean ok, boolean expectResultOk)
+            throws InterruptedException, IOException {
+        wakeUpScreen();
+        String buttonId = ok ? "android:id/button1" : "android:id/button2";
+        UiObject2 button = sUiDevice.wait(Until.findObject(By.res(buttonId)), TIMEOUT_MILLIS);
+        if (button == null) {
+            dumpWindowHierarchy();
+            throw new AssertionError("Cannot find button to click");
+        }
+        button.click();
+        Pair<Integer, Intent> result = mActivityRule.getActivity().waitForActivityResult(
+                TIMEOUT_MILLIS);
+        int expectedResult = expectResultOk ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+        assertThat(result.first).isEqualTo(expectedResult);
+    }
+
+    private void respondToRoleRequest(boolean ok) throws InterruptedException, IOException {
+        respondToRoleRequest(ok, ok);
+    }
+
+    private void wakeUpScreen() throws IOException {
+        runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
+    }
+
+    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);
+        }
+    }
+
+    private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName,
+            boolean shouldBeRoleHolder) throws Exception {
+        List<String> packageNames = getRoleHolders(roleName);
+        if (shouldBeRoleHolder) {
+            assertThat(packageNames).contains(packageName);
+        } else {
+            assertThat(packageNames).doesNotContain(packageName);
+        }
+     }
+
+    private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
+        return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
+    }
+
+    private void addRoleHolder(@NonNull String roleName, @NonNull String packageName)
+            throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = sContext.getMainExecutor();
+        boolean[] successful = new boolean[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        runWithShellPermissionIdentity(() -> sRoleManager.addRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        successful[0] = true;
+                        latch.countDown();
+                    }
+                    @Override
+                    public void onFailure() {
+                        successful[0] = false;
+                        latch.countDown();
+                    }
+                }));
+        latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        assertThat(successful[0]).isTrue();
+    }
+
+    private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName)
+            throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = sContext.getMainExecutor();
+        boolean[] successful = new boolean[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        runWithShellPermissionIdentity(() -> sRoleManager.removeRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        successful[0] = true;
+                        latch.countDown();
+                    }
+                    @Override
+                    public void onFailure() {
+                        successful[0] = false;
+                        latch.countDown();
+                    }
+                }));
+        latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        assertThat(successful[0]).isTrue();
+    }
+}
diff --git a/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java b/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java
new file mode 100644
index 0000000..8f889bf
--- /dev/null
+++ b/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java
@@ -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 android.app.role.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.util.Pair;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Activity that can start another Activity and wait for its result.
+ */
+public class WaitForResultActivity extends Activity {
+
+    private static final int REQUEST_CODE_WAIT_FOR_RESULT = 1;
+
+    private CountDownLatch mLatch;
+    private int mResultCode;
+    private Intent mData;
+
+    public void startActivityToWaitForResult(Intent intent) {
+        mLatch = new CountDownLatch(1);
+        startActivityForResult(intent, REQUEST_CODE_WAIT_FOR_RESULT);
+    }
+
+    public Pair<Integer, Intent> waitForActivityResult(long timeoutMillis)
+            throws InterruptedException {
+        mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+        return new Pair<>(mResultCode, mData);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_CODE_WAIT_FOR_RESULT) {
+            mResultCode = resultCode;
+            mData = data;
+            mLatch.countDown();
+        } else {
+            super.onActivityResult(requestCode, resultCode, data);
+        }
+    }
+}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 51d48e7..4da499e 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -50,10 +50,6 @@
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-
-        <activity android:name="android.security.cts.ActivityManagerTest$NormalActivity" />
-        <activity android:name="android.security.cts.ActivityManagerTest$MaliciousActivity" />
-        <service android:name="android.security.cts.ActivityManagerTest$AppMonitoringService" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/res/raw/bug_64710074.mp4 b/tests/tests/security/res/raw/bug_64710074.mp4
deleted file mode 100644
index 5544ffe..0000000
--- a/tests/tests/security/res/raw/bug_64710074.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index 7e57319..5a035dd 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -15,47 +15,17 @@
  */
 package android.security.cts;
 
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Process;
 import android.platform.test.annotations.SecurityTest;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-import android.view.WindowManager;
 
 import junit.framework.TestCase;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 @SecurityTest
 public class ActivityManagerTest extends TestCase {
 
-    private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
-    private static CountDownLatch sLatch;
-    private static volatile int sNormalActivityUserId;
-    private static volatile boolean sCannotReflect;
-    private static volatile boolean sIsAppForeground;
-
-    private static final String TAG = "ActivityManagerTest";
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-
-        sLatch = new CountDownLatch(2);
-        sNormalActivityUserId = -1;
-        sCannotReflect = false;
-        sIsAppForeground = false;
     }
 
     @SecurityTest(minPatchLevel = "2015-03")
@@ -74,117 +44,4 @@
             // Patched devices should throw this exception
         }
     }
-
-    public void testIsAppInForegroundNormal() throws Exception {
-        /* Verify that isAppForeground can be called by the caller on itself. */
-        launchActivity(NormalActivity.class);
-        sNormalActivityUserId = InstrumentationRegistry.getTargetContext().getPackageManager()
-                .getPackageUid(SECURITY_CTS_PACKAGE_NAME, 0);
-        sLatch.await(5, TimeUnit.SECONDS); // Ensure the service has ran at least twice.
-        if (sCannotReflect) return; // If reflection is not possible, pass the test.
-        assertTrue("isAppForeground failed to query for uid on itself.", sIsAppForeground);
-    }
-
-    public void testIsAppInForegroundMalicious() throws Exception {
-        /* Verify that isAppForeground cannot be called by another app on a known uid. */
-        launchActivity(MaliciousActivity.class);
-        launchSettingsActivity();
-        sLatch.await(5, TimeUnit.SECONDS); // Ensure the service has ran at least twice.
-        if (sCannotReflect) return; // If reflection is not possible, pass the test.
-        assertFalse("isAppForeground successfully queried for a uid other than itself.",
-                sIsAppForeground);
-    }
-
-    private void launchActivity(Class<? extends Activity> clazz) {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
-    }
-
-    private void launchSettingsActivity() {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
-    }
-
-    public static class NormalActivity extends Activity {
-
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-            Intent intent = new Intent(this, AppMonitoringService.class);
-            intent.putExtra(AppMonitoringService.EXTRA_UID, sNormalActivityUserId);
-            startService(intent);
-        }
-    }
-
-    public static class MaliciousActivity extends Activity {
-
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-            Intent intent = new Intent(this, AppMonitoringService.class);
-            intent.putExtra(AppMonitoringService.EXTRA_UID, Process.SYSTEM_UID);
-            startService(intent);
-            finish();
-        }
-    }
-
-    public static class AppMonitoringService extends Service {
-
-        private static final String EXTRA_UID = "android.security.cts.extra.UID";
-        private int uid;
-
-        @Override
-        public int onStartCommand(Intent intent, int flags, int startId) {
-            uid = intent.getIntExtra(EXTRA_UID, -1);
-            return super.onStartCommand(intent, flags, startId);
-        }
-
-        public AppMonitoringService() {
-            super.onCreate();
-
-            final Handler handler = new Handler();
-            handler.postDelayed(new Runnable() {
-                public void run() {
-                    try {
-                        ActivityManager activityManager = (ActivityManager) getSystemService(
-                                ACTIVITY_SERVICE);
-                        Field field = activityManager.getClass().getDeclaredField(
-                                "IActivityManagerSingleton");
-                        field.setAccessible(true);
-                        Object fieldValue = field.get(activityManager);
-                        Method method = fieldValue.getClass().getDeclaredMethod("create");
-                        method.setAccessible(true);
-                        Object IActivityInstance = method.invoke(fieldValue);
-                        Method isAppForeground = IActivityInstance.getClass().getDeclaredMethod(
-                                "isAppForeground", int.class);
-                        isAppForeground.setAccessible(true);
-                        boolean res = (boolean) isAppForeground.invoke(IActivityInstance, uid);
-                        if (res) {
-                            sIsAppForeground = true;
-                        }
-                    } catch (Exception e) {
-                        Log.e(TAG, "Failed to fetch/invoke field/method via reflection.", e);
-                        sCannotReflect = true;
-                    }
-                    sLatch.countDown();
-                    handler.postDelayed(this, 200);
-
-                }
-            }, 0);
-        }
-
-        @Override
-        public IBinder onBind(Intent intent) {
-            throw new UnsupportedOperationException("Not yet implemented");
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
index 47dc8f3..c960101 100644
--- a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
+++ b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
@@ -33,7 +33,7 @@
 
 public class AmbiguousBundlesTest extends AndroidTestCase {
 
-    @SecurityTest(minPatchLevel = "2018-05")
+    @SecurityTest(minPatchLevel = "2017-10")
     public void test_android_CVE_2017_0806() throws Exception {
         Ambiguator ambiguator = new Ambiguator() {
             @Override
@@ -133,8 +133,6 @@
         testAmbiguator(ambiguator);
     }
 
-
-
     @SecurityTest(minPatchLevel = "2018-05")
     public void test_android_CVE_2017_13311() throws Exception {
         Ambiguator ambiguator = new Ambiguator() {
diff --git a/tests/tests/security/src/android/security/cts/BannedFilesTest.java b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
index 46b5ba9..8847a84 100644
--- a/tests/tests/security/src/android/security/cts/BannedFilesTest.java
+++ b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
@@ -148,24 +148,6 @@
         assertNotSetugid("/vendor/bin/tcpdump-arm");
     }
 
-    /**
-     * enforce that the xaac codec has not been included on the device
-     */
-    public void testNoXaac() {
-        String libraries[] = new String[] {
-            "libstagefright_soft_xaacdec.so", "libstagefright_soft_c2xaacdec.so"
-        };
-        String directories[] = new String[] {
-            "/system/lib", "/system/lib64", "/vendor/lib", "/vendor/lib64"
-        };
-        for (String f : libraries) {
-            for (String d : directories) {
-                String fullPath = d + "/" + f;
-                assertFalse(fullPath, new File(fullPath).exists());
-            }
-        }
-    }
-
     private static void assertNotSetugid(String file) {
         FileUtils.FileStatus fs = new FileUtils.FileStatus();
         if (!FileUtils.getFileStatus(file, fs, false)) {
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index 76f86d5..ef27222 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -28,9 +28,7 @@
 class CertificateData {
   static final String[] CERTIFICATE_DATA = {
       "91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
-      "22:FD:D0:B7:FD:A2:4E:0D:AC:49:2C:A0:AC:A6:7B:6A:1F:E3:F7:66",
       "D1:CB:CA:5D:B2:D5:2A:7F:69:3B:67:4D:E5:F0:5A:1D:0C:95:7D:F0",
-      "C4:18:F6:4D:46:D1:DF:00:3D:27:30:13:72:43:A9:12:11:C6:75:FB",
       "69:69:56:2E:40:80:F4:24:A1:E7:19:9F:14:BA:F3:EE:58:AB:6A:BB",
       "92:5A:8F:8D:2C:6D:04:E0:66:5F:59:6A:FF:22:D8:63:E8:25:6F:3F",
       "75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
@@ -49,17 +47,19 @@
       "1F:24:C6:30:CD:A4:18:EF:20:69:FF:AD:4F:DD:5F:46:3A:1B:69:AA",
       "DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
       "74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
+      "2D:0D:52:14:FF:9E:AD:99:24:01:74:20:47:6E:6C:85:27:27:F5:43",
+      "28:F9:78:16:19:7A:FF:18:25:18:AA:44:FE:C1:A0:CE:5C:B6:4C:8A",
       "31:43:64:9B:EC:CE:27:EC:ED:3A:3F:0B:8F:0D:E4:E8:91:DD:EE:CA",
       "B7:AB:33:08:D1:EA:44:77:BA:14:80:12:5A:6F:BD:A9:36:49:0C:BB",
       "5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
       "2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
-      "79:5F:88:60:C5:AB:7C:3D:92:E6:CB:F4:8D:E1:45:CD:11:EF:60:0B",
       "A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36",
       "59:22:A1:E1:5A:EA:16:35:21:F8:98:39:6A:46:46:B0:44:1B:0F:A9",
       "D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74",
       "02:FA:F3:E2:91:43:54:68:60:78:57:69:4D:F5:E4:5B:68:85:18:68",
       "76:E2:7E:C1:4F:DB:82:C1:C0:A6:75:B5:05:BE:3D:29:B4:ED:DB:BB",
       "D8:C5:38:8A:B7:30:1B:1B:6E:D4:7A:E6:45:25:3A:6F:9F:1A:27:61",
+      "E0:11:84:5E:34:DE:BE:88:81:B9:9C:F6:16:26:D1:96:1F:C3:B9:31",
       "93:05:7A:88:15:C6:4F:CE:88:2F:FA:91:16:52:28:78:BC:53:64:17",
       "59:AF:82:79:91:86:C7:B4:75:07:CB:CF:03:57:46:EB:04:DD:B7:16",
       "50:30:06:09:1D:97:D4:F5:AE:39:F7:CB:E7:92:7D:7D:65:2D:34:31",
@@ -75,6 +75,7 @@
       "74:3A:F0:52:9B:D0:32:A0:F4:4A:83:CD:D4:BA:A9:7B:7C:2E:C4:9A",
       "D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
       "66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
+      "2A:1D:60:27:D9:4A:B1:0A:1C:4D:91:5C:CD:33:A0:CB:3E:2D:54:CB",
       "DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
       "22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A",
       "F3:73:B3:87:06:5A:28:84:8A:F2:F3:4A:CE:19:2B:DD:C7:8E:9C:AC",
@@ -83,7 +84,7 @@
       "43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
       "F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
       "05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
-      "70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
+      "30:D4:24:6F:07:FF:DB:91:89:8A:0B:E9:49:66:11:EB:8C:5E:46:E5",
       "D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
       "B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
       "4C:DD:51:A3:D1:F5:20:32:14:B0:C6:C5:32:23:03:91:C7:46:42:6D",
@@ -109,11 +110,9 @@
       "07:E0:32:E0:20:B7:2C:3F:19:2F:06:28:A2:59:3A:19:A7:0F:06:9E",
       "D6:DA:A8:20:8D:09:D2:15:4D:24:B5:2F:CB:34:6E:B2:58:B2:8A:58",
       "32:3C:11:8E:1B:F7:B8:B6:52:54:E2:E2:10:0D:D6:02:90:37:F0:96",
-      "79:91:E8:34:F7:E2:EE:DD:08:95:01:52:E9:55:2D:14:E9:58:D5:7E",
+      "80:94:64:0E:B5:A7:A1:CA:11:9C:1F:DD:D5:9F:81:02:63:A7:FB:D1",
       "67:65:0D:F1:7E:8E:7E:5B:82:40:A4:F4:56:4B:CF:E2:3D:69:C6:F0",
-      "FE:B8:C4:32:DC:F9:76:9A:CE:AE:3D:D8:90:8F:FD:28:86:65:64:7D",
       "4A:BD:EE:EC:95:0D:35:9C:89:AE:C7:52:A1:2C:5B:29:F6:D6:AA:0C",
-      "33:9B:6B:14:50:24:9B:55:7A:01:87:72:84:D9:E0:2F:C3:D2:D8:E9",
       "DD:FB:16:CD:49:31:C9:73:A2:03:7D:3F:C8:3A:4D:7D:77:5D:05:E4",
       "36:B1:2B:49:F9:81:9E:D7:4C:9E:BC:38:0F:C6:56:8F:5D:AC:B2:F7",
       "37:F7:6D:E6:07:7C:90:C5:B1:3E:93:1A:B7:41:10:B4:F2:E4:9A:27",
@@ -134,6 +133,7 @@
       "58:D1:DF:95:95:67:6B:63:C0:F0:5B:1C:17:4D:8B:84:0B:C8:78:BD",
       "F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
       "3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
+      "D2:73:96:2A:2A:5E:39:9F:73:3F:E1:C7:1E:64:3F:03:38:34:FC:4D",
       "03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
       "1E:0E:56:19:0A:D1:8B:25:98:B2:04:44:FF:66:8A:04:17:99:5F:3F",
       "DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4",
@@ -151,6 +151,7 @@
       "9D:70:BB:01:A5:A4:A0:18:11:2E:F7:1C:01:B9:32:C5:34:E7:88:A8",
       "96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
       "4F:65:8E:1F:E9:06:D8:28:02:E9:54:47:41:C9:54:25:5D:69:CC:1A",
+      "A3:A1:B0:6F:24:61:23:4A:E3:36:A5:C2:37:FC:A6:FF:DD:F0:D7:3A",
       "D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
       "01:0C:06:95:A6:98:19:14:FF:BF:5F:C6:B0:B6:95:EA:29:E9:12:A6",
       "0F:F9:40:76:18:D3:D7:6A:4B:98:F0:A8:35:9E:0C:FD:27:AC:CC:ED",
@@ -159,7 +160,7 @@
       "E6:21:F3:35:43:79:05:9A:4B:68:30:9D:8A:2F:74:22:15:87:EC:79",
       "89:DF:74:FE:5C:F4:0F:4A:80:F9:E3:37:7D:54:DA:91:E1:01:31:8E",
       "7E:04:DE:89:6A:3E:66:6D:00:E6:87:D3:3F:FA:D9:3B:E8:3D:34:9E",
-      "6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
+      "E1:C9:50:E6:EF:22:F8:4C:56:45:72:8B:92:20:60:D7:D5:A7:A3:E8",
       "4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
       "5A:8C:EF:45:D7:A6:98:59:76:7A:8C:8B:44:96:B5:78:CF:47:4B:1A",
       "8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16",
diff --git a/tests/tests/security/src/android/security/cts/ListeningPortsTest.java b/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
deleted file mode 100755
index d7fc915..0000000
--- a/tests/tests/security/src/android/security/cts/ListeningPortsTest.java
+++ /dev/null
@@ -1,399 +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.security.cts;
-
-import android.content.pm.PackageManager;
-import android.os.Process;
-import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import com.android.compatibility.common.util.FeatureUtil;
-import junit.framework.AssertionFailedError;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Scanner;
-import java.util.regex.Pattern;
-
-/**
- * Verifies that Android devices are not listening on accessible
- * open ports. Open ports are often targeted by attackers looking to break
- * into computer systems remotely, and minimizing the number of open ports
- * is considered a security best practice.
- */
-public class ListeningPortsTest extends AndroidTestCase {
-    private static final String TAG = "ListeningPortsTest";
-
-    private static final int CONN_TIMEOUT_IN_MS = 5000;
-
-    private boolean mIsTelevision;
-
-    /** Ports that are allowed to be listening. */
-    private static final List<String> EXCEPTION_PATTERNS = new ArrayList<String>(6);
-
-    static {
-        // IPv4 exceptions
-        // Patterns containing ":" are allowed address port combinations
-        // Pattterns contains " " are allowed address UID combinations
-        // Patterns containing both are allowed address, port, and UID combinations
-        EXCEPTION_PATTERNS.add("0.0.0.0:5555");     // emulator port
-        EXCEPTION_PATTERNS.add("0.0.0.0:9101");     // verified ports
-        EXCEPTION_PATTERNS.add("0.0.0.0:9551");     // verified ports
-        EXCEPTION_PATTERNS.add("0.0.0.0:9552");     // verified ports
-        EXCEPTION_PATTERNS.add("10.0.2.15:5555");   // net forwarding for emulator
-        EXCEPTION_PATTERNS.add("127.0.0.1:5037");   // adb daemon "smart sockets"
-        EXCEPTION_PATTERNS.add("0.0.0.0 1020");     // used by the cast receiver
-        EXCEPTION_PATTERNS.add("0.0.0.0 10000");    // used by the cast receiver
-        EXCEPTION_PATTERNS.add("127.0.0.1 10000");  // used by the cast receiver
-        EXCEPTION_PATTERNS.add(":: 1002");          // used by remote control
-        EXCEPTION_PATTERNS.add(":: 1020");          // used by remote control
-        EXCEPTION_PATTERNS.add("0.0.0.0:7275");     // used by supl 
-        //no current patterns involve address, port and UID combinations
-        //Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
-
-        // IPv6 exceptions
-        // TODO: this is not standard notation for IPv6. Use [$addr]:$port instead as per RFC 3986.
-        EXCEPTION_PATTERNS.add(":::5555");          // emulator port for adb
-        EXCEPTION_PATTERNS.add(":::7275");          // used by supl
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mIsTelevision = FeatureUtil.isTV();
-    }
-
-    /**
-     * Remotely accessible ports are often used by attackers to gain
-     * unauthorized access to computers systems without user knowledge or
-     * awareness.
-     */
-    public void testNoRemotelyAccessibleListeningTcpPorts() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/tcp", true, false);
-    }
-
-    /**
-     * Remotely accessible ports are often used by attackers to gain
-     * unauthorized access to computers systems without user knowledge or
-     * awareness.
-     */
-    public void testNoRemotelyAccessibleListeningTcp6Ports() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/tcp6", true, false);
-    }
-
-    /**
-     * Remotely accessible ports are often used by attackers to gain
-     * unauthorized access to computers systems without user knowledge or
-     * awareness.
-     */
-    public void testNoRemotelyAccessibleListeningUdpPorts() throws Exception {
-        assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp", false);
-    }
-
-    /**
-     * Remotely accessible ports are often used by attackers to gain
-     * unauthorized access to computers systems without user knowledge or
-     * awareness.
-     */
-    /* Disabling this test due to ims_rtp_daemon listening on a random UDP6 port per b/110264058.
-    public void testNoRemotelyAccessibleListeningUdp6Ports() throws Exception {
-        assertNoRemotelyAccessibleListeningUdpPorts("/proc/net/udp6", false);
-    }
-    */
-
-    /**
-     * Locally accessible ports are often targeted by malicious locally
-     * installed programs to gain unauthorized access to program data or
-     * cause system corruption.
-     *
-     * In all cases, a local listening IP port can be replaced by a UNIX domain
-     * socket. Unix domain sockets can be protected with unix filesystem
-     * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
-     * determine if a program is authorized to connect to your socket.
-     *
-     * Please convert loopback IP connections to unix domain sockets.
-     */
-    public void testNoListeningLoopbackTcpPorts() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/tcp", true, true);
-    }
-
-    /**
-     * Locally accessible ports are often targeted by malicious locally
-     * installed programs to gain unauthorized access to program data or
-     * cause system corruption.
-     *
-     * In all cases, a local listening IP port can be replaced by a UNIX domain
-     * socket. Unix domain sockets can be protected with unix filesystem
-     * permission. Alternatively, you can use getsockopt(SO_PEERCRED) to
-     * determine if a program is authorized to connect to your socket.
-     *
-     * Please convert loopback IP connections to unix domain sockets.
-     */
-    public void testNoListeningLoopbackTcp6Ports() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/tcp6", true, true);
-    }
-
-    /**
-     * Locally accessible ports are often targeted by malicious locally
-     * installed programs to gain unauthorized access to program data or
-     * cause system corruption.
-     *
-     * In all cases, a local listening IP port can be replaced by a UNIX domain
-     * socket. Unix domain sockets can be protected with unix filesystem
-     * permission.  Alternately, or you can use setsockopt(SO_PASSCRED) to
-     * send credentials, and recvmsg to retrieve the passed credentials.
-     *
-     * Please convert loopback IP connections to unix domain sockets.
-     */
-    public void testNoListeningLoopbackUdpPorts() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/udp", false, true);
-    }
-
-    /**
-     * Locally accessible ports are often targeted by malicious locally
-     * installed programs to gain unauthorized access to program data or
-     * cause system corruption.
-     *
-     * In all cases, a local listening IP port can be replaced by a UNIX domain
-     * socket. Unix domain sockets can be protected with unix filesystem
-     * permission.  Alternately, or you can use setsockopt(SO_PASSCRED) to
-     * send credentials, and recvmsg to retrieve the passed credentials.
-     *
-     * Please convert loopback IP connections to unix domain sockets.
-     */
-    public void testNoListeningLoopbackUdp6Ports() throws Exception {
-        assertNoAccessibleListeningPorts("/proc/net/udp6", false, true);
-    }
-
-    private static final int RETRIES_MAX = 6;
-
-    /**
-     * UDP tests can be flaky due to DNS lookups.  Compensate.
-     */
-    private void assertNoRemotelyAccessibleListeningUdpPorts(
-            String procFilePath, boolean loopback)
-            throws Exception {
-        for (int i = 0; i < RETRIES_MAX; i++) {
-            try {
-                assertNoAccessibleListeningPorts(procFilePath, false, loopback);
-                return;
-            } catch (ListeningPortsAssertionError e) {
-                if (i == RETRIES_MAX - 1) {
-                    throw e;
-                }
-                Thread.sleep(2 * 1000 * i);
-            }
-        }
-        throw new IllegalStateException("unreachable");
-    }
-
-    /**
-     * Remotely accessible ports (loopback==false) are often used by
-     * attackers to gain unauthorized access to computers systems without
-     * user knowledge or awareness.
-     *
-     * Locally accessible ports (loopback==true) are often targeted by
-     * malicious locally installed programs to gain unauthorized access to
-     * program data or cause system corruption.
-     */
-    private void assertNoAccessibleListeningPorts(
-            String procFilePath, boolean isTcp, boolean loopback) throws IOException {
-        String errors = "";
-        List<ParsedProcEntry> entries = ParsedProcEntry.parse(procFilePath);
-        for (ParsedProcEntry entry : entries) {
-            String addrPort = entry.localAddress.getHostAddress() + ':' + entry.port;
-            String addrUid = entry.localAddress.getHostAddress() + ' ' + entry.uid;
-            String addrPortUid = addrPort + ' ' + entry.uid;
-
-            if (isPortListening(entry.state, isTcp)
-                    && !(isException(addrPort) || isException(addrUid) || isException(addrPortUid))
-                    && (!entry.localAddress.isLoopbackAddress() ^ loopback)) {
-                if (isTcp && !isTcpConnectable(entry.localAddress, entry.port)) {
-                    continue;
-                }
-                // allow non-system processes to listen
-                int appId = UserHandle.getAppId(entry.uid);
-                if (appId >= Process.FIRST_APPLICATION_UID
-                        && appId <= Process.LAST_APPLICATION_UID) {
-                    continue;
-                }
-
-                errors += "\nFound port listening on addr="
-                        + entry.localAddress.getHostAddress() + ", port="
-                        + entry.port + ", UID=" + entry.uid
-                        + " " + uidToPackage(entry.uid) + " in "
-                        + procFilePath;
-            }
-        }
-        if (!errors.equals("")) {
-            throw new ListeningPortsAssertionError(errors);
-        }
-    }
-
-    private String uidToPackage(int uid) {
-        PackageManager pm = this.getContext().getPackageManager();
-        String[] packages = pm.getPackagesForUid(uid);
-        if (packages == null) {
-            return "[unknown]";
-        }
-        return Arrays.asList(packages).toString();
-    }
-
-    private boolean isTcpConnectable(InetAddress address, int port) {
-        Socket socket = new Socket();
-
-        try {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Trying to connect " + address + ":" + port);
-            }
-            socket.connect(new InetSocketAddress(address, port), CONN_TIMEOUT_IN_MS);
-        } catch (IOException ioe) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "Unable to connect:" + ioe);
-            }
-            return false;
-        } finally {
-            try {
-                socket.close();
-            } catch (IOException closeError) {
-                Log.e(TAG, "Unable to close socket: " + closeError);
-            }
-        }
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, address + ":" + port + " is connectable.");
-        }
-        return true;
-    }
-
-    private static boolean isException(String localAddress) {
-        return isPatternMatch(EXCEPTION_PATTERNS, localAddress);
-    }
-
-    private static boolean isPatternMatch(List<String> patterns, String input) {
-        for (String pattern : patterns) {
-            pattern = Pattern.quote(pattern);
-            if (Pattern.matches(pattern, input)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean isPortListening(String state, boolean isTcp) {
-        // 0A = TCP_LISTEN from include/net/tcp_states.h
-        String listeningState = isTcp ? "0A" : "07";
-        return listeningState.equals(state);
-    }
-
-    private static class ListeningPortsAssertionError extends AssertionFailedError {
-        private ListeningPortsAssertionError(String msg) {
-            super(msg);
-        }
-    }
-
-    private static class ParsedProcEntry {
-        private final InetAddress localAddress;
-        private final int port;
-        private final String state;
-        private final int uid;
-
-        private ParsedProcEntry(InetAddress addr, int port, String state, int uid) {
-            this.localAddress = addr;
-            this.port = port;
-            this.state = state;
-            this.uid = uid;
-        }
-
-
-        private static List<ParsedProcEntry> parse(String procFilePath) throws IOException {
-
-            List<ParsedProcEntry> retval = new ArrayList<ParsedProcEntry>();
-            /*
-            * Sample output of "cat /proc/net/tcp" on emulator:
-            *
-            * sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  ...
-            * 0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0   ...
-            * 1: 00000000:15B3 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0   ...
-            * 2: 0F02000A:15B3 0202000A:CE8A 01 00000000:00000000 00:00000000 00000000     0   ...
-            *
-            */
-
-            File procFile = new File(procFilePath);
-            Scanner scanner = null;
-            try {
-                scanner = new Scanner(procFile);
-                while (scanner.hasNextLine()) {
-                    String line = scanner.nextLine().trim();
-
-                    // Skip column headers
-                    if (line.startsWith("sl")) {
-                        continue;
-                    }
-
-                    String[] fields = line.split("\\s+");
-                    final int expectedNumColumns = 12;
-                    assertTrue(procFilePath + " should have at least " + expectedNumColumns
-                            + " columns of output " + Arrays.toString(fields),
-                            fields.length >= expectedNumColumns);
-
-                    String state = fields[3];
-                    int uid = Integer.parseInt(fields[7]);
-                    InetAddress localIp = addrToInet(fields[1].split(":")[0]);
-                    int localPort = Integer.parseInt(fields[1].split(":")[1], 16);
-
-                    retval.add(new ParsedProcEntry(localIp, localPort, state, uid));
-                }
-            } finally {
-                if (scanner != null) {
-                    scanner.close();
-                }
-            }
-            return retval;
-        }
-
-        /**
-         * Convert a string stored in little endian format to an IP address.
-         */
-        private static InetAddress addrToInet(String s) throws UnknownHostException {
-            int len = s.length();
-            if (len != 8 && len != 32) {
-                throw new IllegalArgumentException(len + "");
-            }
-            byte[] retval = new byte[len / 2];
-
-            for (int i = 0; i < len / 2; i += 4) {
-                retval[i] = (byte) ((Character.digit(s.charAt(2*i + 6), 16) << 4)
-                        + Character.digit(s.charAt(2*i + 7), 16));
-                retval[i + 1] = (byte) ((Character.digit(s.charAt(2*i + 4), 16) << 4)
-                        + Character.digit(s.charAt(2*i + 5), 16));
-                retval[i + 2] = (byte) ((Character.digit(s.charAt(2*i + 2), 16) << 4)
-                        + Character.digit(s.charAt(2*i + 3), 16));
-                retval[i + 3] = (byte) ((Character.digit(s.charAt(2*i), 16) << 4)
-                        + Character.digit(s.charAt(2*i + 1), 16));
-            }
-            return InetAddress.getByAddress(retval);
-        }
-    }
-}
diff --git a/tests/tests/security/src/android/security/cts/MotionEventTest.java b/tests/tests/security/src/android/security/cts/MotionEventTest.java
index d36e420..cc8bb51 100644
--- a/tests/tests/security/src/android/security/cts/MotionEventTest.java
+++ b/tests/tests/security/src/android/security/cts/MotionEventTest.java
@@ -84,16 +84,17 @@
     public void testActionOutsideDoesNotContainedObscuredInformation() throws Throwable {
         enableAppOps();
         final OnTouchListener listener = new OnTouchListener();
+        final WindowManager wm = mActivity.getSystemService(WindowManager.class);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+                        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
+                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
         FutureTask<View> addViewTask = new FutureTask<>(() -> {
-            final WindowManager wm = mActivity.getSystemService(WindowManager.class);
             final Point size = new Point();
             wm.getDefaultDisplay().getSize(size);
 
-            WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
-                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
-                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
-                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
             wmlp.width = size.x / 4;
             wmlp.height = size.y / 4;
             wmlp.gravity = Gravity.TOP | Gravity.LEFT;
@@ -127,6 +128,14 @@
         WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, view, null /*runnable*/,
                 true /*forceLayout*/);
 
+	// This ensures the window is visible, where the code above ensures
+        // the view is on screen.
+        mActivityRule.runOnUiThread(() -> {
+            // This will force WindowManager to relayout, ensuring the
+	    // transaction to show the window are sent to the graphics code.
+            wm.updateViewLayout(view, wmlp);
+        });
+
         FutureTask<Point> clickLocationTask = new FutureTask<>(() -> {
             final int[] viewLocation = new int[2];
             view.getLocationOnScreen(viewLocation);
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 784ce59..fb3d13b 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -991,23 +991,6 @@
         doStagefrightTest(R.raw.cve_2016_3879);
     }
 
-    @SecurityTest(minPatchLevel = "2017-07")
-    public void testStagefright_xaac_not_present() throws Exception {
-        // ensure that the xaac codec is not present
-        MediaCodec codec;
-        String names[] = new String[] { "c2.android.xaac.decoder", "OMX.google.xaac.decoder" };
-        for (String name : names) {
-            Log.w(TAG, "trying to create codec: " + name);
-            try {
-                codec = MediaCodec.createByCodecName(name);
-                fail("not allowed to createByCodecName() for " + name);
-            } catch (IllegalArgumentException e) {
-                // expected
-                Log.w(TAG, "correctly unable to instantiate code for " + name);
-            }
-        }
-    }
-
     private void doStagefrightTest(final int rid) throws Exception {
         doStagefrightTestMediaPlayer(rid);
         doStagefrightTestMediaCodec(rid);
@@ -1487,6 +1470,7 @@
         thr.join();
     }
 
+    @SecurityTest(minPatchLevel = "2017-07")
     public void testBug36215950() throws Exception {
         doStagefrightTestRawBlob(R.raw.bug_36215950, "video/hevc", 320, 240);
     }
diff --git a/tests/tests/selinux/selinuxTargetSdk25/Android.mk b/tests/tests/selinux/selinuxTargetSdk25/Android.mk
index f9bfc95..040bc62 100644
--- a/tests/tests/selinux/selinuxTargetSdk25/Android.mk
+++ b/tests/tests/selinux/selinuxTargetSdk25/Android.mk
@@ -40,6 +40,9 @@
 LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk25TestCases
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MIN_SDK_VERSION := 21
+
 include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdk27/Android.mk b/tests/tests/selinux/selinuxTargetSdk27/Android.mk
index 37660a3..ae9e5bf 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/Android.mk
+++ b/tests/tests/selinux/selinuxTargetSdk27/Android.mk
@@ -40,6 +40,9 @@
 LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk27TestCases
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MIN_SDK_VERSION := 21
+
 include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk
index 63bc768..fa65d3d 100644
--- a/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.mk
@@ -40,6 +40,9 @@
 LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdkCurrentTestCases
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MIN_SDK_VERSION := 21
+
 include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/AndroidTest.xml b/tests/tests/shortcutmanager/AndroidTest.xml
index 1d38171..f2f6a46 100644
--- a/tests/tests/shortcutmanager/AndroidTest.xml
+++ b/tests/tests/shortcutmanager/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for ShortcutManager CTS test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't access ShortcutManager -->
+    <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" />
         <option name="test-file-name" value="CtsShortcutManagerTestCases.apk" />
diff --git a/tests/tests/shortcutmanager/throttling/Android.mk b/tests/tests/shortcutmanager/throttling/Android.mk
index 5a174cc..eb62622 100644
--- a/tests/tests/shortcutmanager/throttling/Android.mk
+++ b/tests/tests/shortcutmanager/throttling/Android.mk
@@ -38,5 +38,6 @@
     ShortcutManagerTestUtils
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 25
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/slice/AndroidTest.xml b/tests/tests/slice/AndroidTest.xml
index 34fdb6f..1110d65 100644
--- a/tests/tests/slice/AndroidTest.xml
+++ b/tests/tests/slice/AndroidTest.xml
@@ -15,7 +15,7 @@
 -->
 <configuration description="Config for CTS Slice test cases">
     <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="misc" />
+    <option name="config-descriptor:metadata" key="component" value="sysui" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSliceTestCases.apk" />
diff --git a/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java b/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java
index 53b13cb..b6c162b 100644
--- a/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java
+++ b/tests/tests/slice/src/android/slice/cts/LocalSliceProvider.java
@@ -47,7 +47,7 @@
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
         mSliceService = mock(SliceManager.class, withSettings()
-                .spiedInstance(context.getSystemService(Context.SLICE_SERVICE))
+                .spiedInstance(context.getSystemService(SliceManager.class))
                 .defaultAnswer(invocation -> {
                     Answer s = sAnswer != null ? sAnswer : Answers.CALLS_REAL_METHODS;
                     return s.answer(invocation);
@@ -55,7 +55,7 @@
         Context wrapped = new ContextWrapper(context) {
             @Override
             public Object getSystemService(String name) {
-                if (Context.SLICE_SERVICE.equals(name)) {
+                if (getSystemServiceName(SliceManager.class).equals(name)) {
                     return mSliceService;
                 }
                 return super.getSystemService(name);
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 8e54d31..fa00b92 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -85,6 +85,32 @@
         }
     }
 
+    public void testMaxSpeechInputLength() {
+      File sampleFile = new File(Environment.getExternalStorageDirectory(), SAMPLE_FILE_NAME);
+      try {
+          assertFalse(sampleFile.exists());
+          TextToSpeech tts = getTts();
+
+          int maxLength = tts.getMaxSpeechInputLength();
+          StringBuilder sb = new StringBuilder();
+          for (int i = 0; i < maxLength; i++) {
+            sb.append("c");
+          }
+          String valid = sb.toString();
+          sb.append("c");
+          String invalid = sb.toString();
+
+          assertEquals(maxLength, valid.length());
+          assertTrue(invalid.length() > maxLength);
+          assertEquals(TextToSpeech.ERROR,
+                  tts.synthesizeToFile(invalid, createParams("mockToFile"), sampleFile.getPath()));
+          assertEquals(TextToSpeech.SUCCESS,
+                  tts.synthesizeToFile(valid, createParams("mockToFile"), sampleFile.getPath()));
+      } finally {
+          sampleFile.delete();
+      }
+    }
+
     public void testSpeak() throws Exception {
         int result = getTts().speak(UTTERANCE, TextToSpeech.QUEUE_FLUSH, createParams("mockspeak"));
         assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechWrapper.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechWrapper.java
index c5e3655..1461d91 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechWrapper.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechWrapper.java
@@ -229,9 +229,8 @@
         public void onStart(String utteranceId) {
             mLock.lock();
             try {
-                // TODO: Due to a bug in the framework onStart() is called twice for
-                //       synthesizeToFile requests. Once that is fixed we should assert here that we
-                //       expect only one onStart() per utteranceId.
+                Assert.assertNotNull(utteranceId);
+                Assert.assertFalse(mStartedUtterances.contains(utteranceId));
                 mStartedUtterances.add(utteranceId);
             } finally {
                 mLock.unlock();
@@ -250,7 +249,8 @@
         }
 
         @Override
-        public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
+        public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
+                int channelCount) {
             Assert.assertNotNull(utteranceId);
             Assert.assertTrue(sampleRateInHz > 0);
             Assert.assertTrue(audioFormat == android.media.AudioFormat.ENCODING_PCM_8BIT
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 0668895..07fe6f6 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -35,6 +35,8 @@
         <option name="run-command" value="am set-standby-bucket android.content.syncmanager.cts.app1 10" />
         <option name="run-command" value="am set-standby-bucket android.content.syncmanager.cts.app2 10" />
         <option name="run-command" value="setprop log.tag.SyncManager VERBOSE" />
+        <option name="run-command" value="cmd thermalservice override-status 0" />
+        <option name="teardown-command" value="cmd thermalservice reset" />
         <option name="teardown-command" value="setprop log.tag.SyncManager INFO" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/systemui/AndroidTest.xml b/tests/tests/systemui/AndroidTest.xml
index e7852c5..fc95b45 100644
--- a/tests/tests/systemui/AndroidTest.xml
+++ b/tests/tests/systemui/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS SystemUI test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="sysui" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/systemui/src/android/systemui/cts/LightBarTestBase.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
index 14d4bcb..1fd281f 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTestBase.java
@@ -48,6 +48,8 @@
     public static final Path DUMP_PATH = FileSystems.getDefault()
             .getPath("/sdcard/LightBarTestBase/");
 
+    public static final int WAIT_TIME = 2000;
+
     private ArrayList<Rect> mCutouts;
 
     protected Bitmap takeStatusBarScreenshot(LightBarBaseActivity activity) {
@@ -111,9 +113,11 @@
                 PackageManager.FEATURE_EMBEDDED));
 
         // No bars on TVs and watches.
+        // Automotive navigation bar is not transparent
         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
                 || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
-                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                || pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
 
 
         // Non-highEndGfx devices don't do colored system bars.
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index c9234d7..d40cb71 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -49,8 +49,6 @@
 
     public static final String TAG = "LightStatusBarTests";
 
-    private static final int WAIT_TIME = 2000;
-
     /**
      * Color may be slightly off-spec when resources are resized for lower densities. Use this error
      * margin to accommodate for that when comparing colors.
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java b/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
index 207b321..b8bb8fd 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarThemeTest.java
@@ -80,6 +80,9 @@
         // Wait until the activity is fully visible
         mDevice.waitForIdle();
 
+        // Wait until window animation is finished
+        Thread.sleep(WAIT_TIME);
+
         final Context instrumentationContext = getInstrumentation().getContext();
         checkNavigationBarDivider(mActivityRule.getActivity(),
                 instrumentationContext.getColor(R.color.navigationBarDividerColor),
diff --git a/tests/tests/telecom/Android.mk b/tests/tests/telecom/Android.mk
index 8f46fd7..68ab907 100644
--- a/tests/tests/telecom/Android.mk
+++ b/tests/tests/telecom/Android.mk
@@ -31,11 +31,25 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.base.stubs
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+	           $(call all-java-files-under, CallScreeningServiceTestApp) \
+                   $(call all-Iaidl-files-under, CallScreeningServiceTestApp) \
+                   $(call all-java-files-under, ThirdPtyInCallServiceTestApp) \
+                   $(call all-Iaidl-files-under, ThirdPtyInCallServiceTestApp) \
+                   $(call all-java-files-under, CallRedirectionServiceTestApp) \
+                   $(call all-Iaidl-files-under, CallRedirectionServiceTestApp)
+
+LOCAL_AIDL_INCLUDES := ThirdPtyInCallServiceTestApp/aidl/ \
+                       CallRedirectionServiceTestApp/aidl/ \
+	               CallScreeningServiceTestApp/aidl/
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 21
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+
 include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 485b130..b1868f3 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index 46de0b7..a30eb19 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -21,7 +21,10 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CallRedirectionServiceTestApp.apk" />
         <option name="test-file-name" value="CtsTelecomTestCases.apk" />
+        <option name="test-file-name" value="ThirdPtyInCallServiceTestApp.apk" />
+        <option name="test-file-name" value="CallScreeningServiceTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.telecom.cts" />
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/Android.mk b/tests/tests/telecom/CallRedirectionServiceTestApp/Android.mk
new file mode 100644
index 0000000..5b89784
--- /dev/null
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+src_dirs := src
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) \
+                   $(call all-Iaidl-files-under, aidl)
+
+LOCAL_AIDL_INCLUDES := aidl/
+
+LOCAL_PACKAGE_NAME := CallRedirectionServiceTestApp
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := test_current
+LOCAL_COMPATIBILITY_SUITE := cts vts
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+include $(BUILD_PACKAGE)
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..ff23b74
--- /dev/null
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.telecom.cts.redirectiontestapp">
+    <permission android:name="android.telecom.cts.redirectiontestapp.CTS_SERVICE_PERMISSION"
+                android.protectionLevel="signature"/>
+    <application android:label="CTSCRTest">
+        <service android:name=".CtsCallRedirectionService"
+                 android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallRedirectionService" />
+            </intent-filter>
+        </service>
+        <service android:name=".CtsCallRedirectionServiceController">
+            <intent-filter>
+                <action android:name="android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl b/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl
new file mode 100644
index 0000000..22ac265
--- /dev/null
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl
@@ -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.telecom.cts.redirectiontestapp;
+
+import android.net.Uri;
+import android.telecom.PhoneAccountHandle;
+
+interface ICtsCallRedirectionServiceController {
+    // Reset the state of the service
+    void reset();
+
+    void setRedirectCall(in Uri targetHandle, in PhoneAccountHandle targetPhoneAccount,
+            boolean confirmFirst);
+
+    void setCancelCall();
+
+    void setPlaceCallUnmodified();
+}
\ No newline at end of file
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java
new file mode 100644
index 0000000..7253e1e
--- /dev/null
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.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 android.telecom.cts.redirectiontestapp;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.telecom.Call;
+import android.telecom.CallRedirectionService;
+import android.telecom.PhoneAccountHandle;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Provides a CTS-test implementation of {@link CallRedirectionService}.
+ * This emulates a third-party implementation of {@link CallRedirectionService}.
+ */
+public class CtsCallRedirectionService extends CallRedirectionService {
+    private static final String TAG = CtsCallRedirectionService.class.getSimpleName();
+
+    @Override
+    public void onPlaceCall(Uri handle, PhoneAccountHandle initialPhoneAccount,
+            boolean allowInteractiveResponse) {
+        Log.i(TAG, "onPlaceCall");
+        CtsCallRedirectionServiceController controller =
+                CtsCallRedirectionServiceController.getInstance();
+        if (controller != null) {
+            int decision = controller.getCallRedirectionDecision();
+            if (decision == CtsCallRedirectionServiceController.PLACE_CALL_UNMODIFIED) {
+                placeCallUnmodified();
+            } else if (decision == CtsCallRedirectionServiceController.CANCEL_CALL) {
+                cancelCall();
+            } else if (decision == CtsCallRedirectionServiceController.PLACE_REDIRECTED_CALL) {
+                redirectCall(controller.getTargetHandle(), controller.getTargetPhoneAccount(),
+                        controller.isConfirmFirst());
+            }
+        } else {
+            Log.w(TAG, "No control interface.");
+        }
+    }
+}
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java
new file mode 100644
index 0000000..4f44475
--- /dev/null
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom.cts.redirectiontestapp;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.PhoneAccountHandle;
+import android.os.IBinder;
+import android.telecom.CallRedirectionService;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class CtsCallRedirectionServiceController extends Service {
+    private static final String TAG = CallRedirectionService.class.getSimpleName();
+    public static final String CONTROL_INTERFACE_ACTION =
+            "android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE";
+    public static final ComponentName CONTROL_INTERFACE_COMPONENT =
+            ComponentName.unflattenFromString(
+                    "android.telecom.cts.redirectiontestapp/.CtsCallRedirectionServiceController");
+
+    // Constants for call redirection decisions
+    public static final int NO_DECISION_YET = 0;
+    public static final int RESPONSE_TIMEOUT = 1;
+    public static final int PLACE_CALL_UNMODIFIED = 2;
+    public static final int PLACE_REDIRECTED_CALL = 3;
+    public static final int CANCEL_CALL = 4;
+
+    private int mDecision = NO_DECISION_YET;
+
+    // Redirection information, only valid if decision is PLACE_REDIRECTED_CALL.
+    private Uri mTargetHandle = null;
+    private PhoneAccountHandle mTargetPhoneAccount = null;
+    private boolean mConfirmFirst = false;
+
+    private static CtsCallRedirectionServiceController sCallRedirectionServiceController = null;
+
+    private final IBinder mControllerInterface = new ICtsCallRedirectionServiceController.Stub() {
+                @Override
+                public void reset() {
+                    mDecision = NO_DECISION_YET;
+                }
+
+                @Override
+                public void setRedirectCall(Uri targetHandle, PhoneAccountHandle targetPhoneAccount,
+                                         boolean confirmFirst) {
+                    Log.i(TAG, "redirectCall");
+                    mDecision = PLACE_REDIRECTED_CALL;
+                    mTargetHandle = targetHandle;
+                    mTargetPhoneAccount = targetPhoneAccount;
+                    mConfirmFirst = confirmFirst;
+                }
+
+                @Override
+                public void setCancelCall() {
+                    Log.i(TAG, "cancelCall");
+                    mDecision = CANCEL_CALL;
+                }
+
+                @Override
+                public void setPlaceCallUnmodified() {
+                    Log.i(TAG, "placeCallUnmodified");
+                    mDecision = PLACE_CALL_UNMODIFIED;
+                }
+            };
+
+    public static CtsCallRedirectionServiceController getInstance() {
+        return sCallRedirectionServiceController;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
+            Log.i(TAG, "onBind: returning control interface");
+            sCallRedirectionServiceController = this;
+            return mControllerInterface;
+        }
+        Log.i(TAG, "onBind: ACTION is not matched");
+        return null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        sCallRedirectionServiceController = null;
+        return false;
+    }
+
+    public int getCallRedirectionDecision() {
+        return mDecision;
+    }
+
+    public Uri getTargetHandle() {
+        return mTargetHandle;
+    }
+
+    public PhoneAccountHandle getTargetPhoneAccount() {
+        return mTargetPhoneAccount;
+    }
+
+    public boolean isConfirmFirst() {
+        return mConfirmFirst;
+    }
+}
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/Android.mk b/tests/tests/telecom/CallScreeningServiceTestApp/Android.mk
new file mode 100644
index 0000000..9f140ef
--- /dev/null
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/Android.mk
@@ -0,0 +1,38 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build the Call Screening test app.
+# This is intentionally packaged separately so that the app can use its own
+# package name which does not conflict with the CallScreeningService
+# associated with the CTS InCallService.
+include $(CLEAR_VARS)
+
+src_dirs := src
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) \
+                   $(call all-Iaidl-files-under, aidl)
+
+LOCAL_AIDL_INCLUDES := aidl/
+
+LOCAL_PACKAGE_NAME := CallScreeningServiceTestApp
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := test_current
+LOCAL_COMPATIBILITY_SUITE := cts vts
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+include $(BUILD_PACKAGE)
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..c3b39a7
--- /dev/null
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.telecom.cts.screeningtestapp">
+    <permission android:name="android.telecom.cts.screeningtestapp.CTS_SERVICE_PERMISSION"
+                android.protectionLevel="signature"/>
+    <application android:label="CTSCSTest">
+        <service android:name=".CtsCallScreeningService"
+                 android:permission="android.permission.BIND_SCREENING_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallScreeningService" />
+            </intent-filter>
+        </service>
+        <service android:name=".CallScreeningServiceControl">
+            <intent-filter>
+                <action android:name="android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/aidl/android/telecom/cts/screeningtestapp/ICallScreeningControl.aidl b/tests/tests/telecom/CallScreeningServiceTestApp/aidl/android/telecom/cts/screeningtestapp/ICallScreeningControl.aidl
new file mode 100644
index 0000000..43c75df
--- /dev/null
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/aidl/android/telecom/cts/screeningtestapp/ICallScreeningControl.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.telecom.cts.screeningtestapp;
+
+import android.graphics.drawable.Icon;
+
+interface ICallScreeningControl {
+    // Reset the state of the service
+    void reset();
+
+    void setProviderCallIdentification(String name, String description, String details,
+            in Icon icon, int confidence);
+    void setCallResponse(boolean shouldDisallowCall, boolean shouldRejectCall,
+            boolean shouldSkipCallLog, boolean shouldSkipNotification);
+}
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CallScreeningServiceControl.java b/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CallScreeningServiceControl.java
new file mode 100644
index 0000000..1563fb5
--- /dev/null
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CallScreeningServiceControl.java
@@ -0,0 +1,122 @@
+/*
+ * 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.telecom.cts.screeningtestapp;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.telecom.CallIdentification;
+import android.telecom.CallScreeningService;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class CallScreeningServiceControl extends Service {
+    private static final String TAG = CallScreeningServiceControl.class.getSimpleName();
+    public static final String CONTROL_INTERFACE_ACTION =
+            "android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE";
+    public static final ComponentName CONTROL_INTERFACE_COMPONENT =
+            ComponentName.unflattenFromString(
+                    "android.telecom.cts.screeningtestapp/.CallScreeningServiceControl");
+
+    private static CallScreeningServiceControl sCallScreeningServiceControl = null;
+
+    private final IBinder mControlInterface =
+            new android.telecom.cts.screeningtestapp.ICallScreeningControl.Stub() {
+                @Override
+                public void reset() {
+                    mCallIdentification = null;
+                    mCallResponse = new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .build();
+                }
+
+                @Override
+                public void setProviderCallIdentification(String name, String description, String details,
+                        Icon icon, int confidence) {
+                    Log.i(TAG, "setProviderCallIdentification: got test id info");
+                    if (TextUtils.isEmpty(name)) {
+                        mCallIdentification = null;
+                    } else {
+                        mCallIdentification = new CallIdentification.Builder()
+                                .setName(name)
+                                .setDetails(details)
+                                .setDescription(description)
+                                .setPhoto(icon)
+                                .setNuisanceConfidence(confidence)
+                                .build();
+                    }
+                }
+
+                @Override
+                public void setCallResponse(boolean shouldDisallowCall,
+                        boolean shouldRejectCall,
+                        boolean shouldSkipCallLog,
+                        boolean shouldSkipNotification) {
+                    Log.i(TAG, "setCallResponse");
+                    mCallResponse = new CallScreeningService.CallResponse.Builder()
+                            .setSkipNotification(shouldSkipNotification)
+                            .setSkipCallLog(shouldSkipCallLog)
+                            .setDisallowCall(shouldDisallowCall)
+                            .setRejectCall(shouldRejectCall)
+                            .build();
+                }
+            };
+
+    private CallIdentification mCallIdentification = null;
+    private boolean mIsAcceptingCall = true;
+    private CallScreeningService.CallResponse mCallResponse =
+            new CallScreeningService.CallResponse.Builder()
+                    .setDisallowCall(false)
+                    .setRejectCall(false)
+                    .setSkipCallLog(false)
+                    .setSkipNotification(false)
+                    .build();
+
+    public static CallScreeningServiceControl getInstance() {
+        return sCallScreeningServiceControl;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
+            Log.i(TAG, "onBind: returning control interface");
+            sCallScreeningServiceControl = this;
+            return mControlInterface;
+        }
+        Log.i(TAG, "onBind: uh oh");
+        return null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        sCallScreeningServiceControl = null;
+        return false;
+    }
+
+    public CallIdentification getCallIdentification() {
+        return mCallIdentification;
+    }
+
+    public CallScreeningService.CallResponse getCallResponse() {
+        return mCallResponse;
+    }
+}
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CtsCallScreeningService.java b/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CtsCallScreeningService.java
new file mode 100644
index 0000000..3a15ad0
--- /dev/null
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/src/android/telecom/cts/screeningtestapp/CtsCallScreeningService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.telecom.cts.screeningtestapp;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.telecom.Call;
+import android.telecom.CallIdentification;
+import android.telecom.CallScreeningService;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Provides a CTS-test implementation of {@link CallScreeningService}.
+ * This emulates a third-party implementation of {@link CallScreeningService}, where
+ * {@code MockCallScreeningService} is intended to be the {@link CallScreeningService} bundled with
+ * the default dialer app.
+ */
+public class CtsCallScreeningService extends CallScreeningService {
+    private static final String TAG = CtsCallScreeningService.class.getSimpleName();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind: returning actual service");
+        return super.onBind(intent);
+    }
+
+    @Override
+    public void onScreenCall(Call.Details callDetails) {
+        Log.i(TAG, "onScreenCall");
+        CallScreeningServiceControl control = CallScreeningServiceControl.getInstance();
+        if (control != null) {
+            CallIdentification callIdentification = control.getCallIdentification();
+            if (callIdentification != null ) {
+                Log.i(TAG, "onScreenCall: providing call id " + callIdentification);
+                provideCallIdentification(callDetails, control.getCallIdentification());
+            }
+
+            respondToCall(callDetails, control.getCallResponse());
+        } else {
+            Log.w(TAG, "No control interface.");
+        }
+    }
+}
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.mk b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.mk
new file mode 100644
index 0000000..420f5e9
--- /dev/null
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.mk
@@ -0,0 +1,46 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+src_dirs := src \
+    ../src/android/telecom/cts/MockInCallService.java \
+    ../src/android/telecom/cts/MockVideoCallCallback.java \
+    ../src/android/telecom/cts/MockVideoProvider.java \
+    ../src/android/telecom/cts/TestUtils.java \
+    ../src/android/telecom/cts/MockConnection.java \
+    ../src/android/telecom/cts/SelfManagedConnection.java \
+    ../src/android/telecom/cts/CtsSelfManagedConnectionService.java
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) \
+                   $(call all-Iaidl-files-under, aidl)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	compatibility-device-util \
+	ctstestrunner \
+	android-support-test
+
+LOCAL_AIDL_INCLUDES := aidl/
+
+LOCAL_PACKAGE_NAME := ThirdPtyInCallServiceTestApp
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := test_current
+LOCAL_COMPATIBILITY_SUITE := cts vts
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..4f439fb
--- /dev/null
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.telecom.cts.thirdptyincallservice"
+          android:versionCode="1"
+          android:versionName="1.0" >
+
+    <!-- sdk 15 is the max for read call log -->
+    <uses-sdk android:minSdkVersion="15" />
+
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.CALL_COMPANION_APP" />
+
+    <application android:label="ThirdPtyCTSInCallService">
+        <service android:name=".CtsThirdPartyInCallService"
+                 android:permission="android.permission.BIND_INCALL_SERVICE"
+                 android:launchMode="singleInstance"
+                 android:exported="true">
+            <!--  indicates it's a non-UI service, required by non-Ui InCallService -->
+            <intent-filter>
+                <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
+                       android:value="true" />
+        </service>
+        <service android:name=".CtsThirdPartyInCallServiceControl"
+                 android:launchMode="singleInstance">
+            <intent-filter>
+                <action
+                    android:name="android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL"/>
+            </intent-filter>
+        </service>
+        <service android:name=".CtsThirdPartyInCallService"
+                 android:permission="android.permission.BIND_INCALL_SERVICE"
+                 android:launchMode="singleInstance"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+                       android:value="true" />
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
+                       android:value="true" />
+        </service>
+    </application>
+
+</manifest>
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/aidl/android/telecom/cts/thirdptyincallservice/ICtsThirdPartyInCallServiceControl.aidl b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/aidl/android/telecom/cts/thirdptyincallservice/ICtsThirdPartyInCallServiceControl.aidl
new file mode 100644
index 0000000..19cad8b
--- /dev/null
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/aidl/android/telecom/cts/thirdptyincallservice/ICtsThirdPartyInCallServiceControl.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.telecom.cts.thirdptyincallservice;
+
+interface ICtsThirdPartyInCallServiceControl {
+
+    boolean checkBindStatus(boolean bind);
+
+    void resetCalls();
+
+    int getLocalCallCount();
+
+    void resetLatchForServiceBound(boolean bind);
+}
\ No newline at end of file
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallService.java b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallService.java
new file mode 100644
index 0000000..4eea8ad
--- /dev/null
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallService.java
@@ -0,0 +1,127 @@
+/*
+ * 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.telecom.cts.thirdptyincallservice;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.cts.MockInCallService;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class CtsThirdPartyInCallService extends MockInCallService {
+
+    public static final String PACKAGE_NAME = "android.telecom.cts.thirdptyincallservice";
+    private static final String TAG = CtsThirdPartyInCallService.class.getSimpleName();
+    protected static final int TIMEOUT = 10000;
+
+    private static CountDownLatch sServiceBoundLatch;
+    private static CountDownLatch sServiceUnboundlatch;
+
+    private static Set<Call> sCalls = new HashSet<>();
+
+    /**
+     * Used to bind a call
+     * @param intent
+     * @return
+     */
+    @Override
+    public android.os.IBinder onBind(Intent intent) {
+        long olderState = sServiceBoundLatch.getCount();
+        sServiceBoundLatch.countDown();
+        Log.d(TAG, "In Call Service on bind, " + olderState + " -> " + sServiceBoundLatch);
+        return super.onBind(intent);
+    }
+
+    /**
+     * Used to unbind a call
+     * @param intent
+     * @return
+     */
+    @Override
+    public boolean onUnbind(Intent intent) {
+        long olderState = sServiceUnboundlatch.getCount();
+        sServiceUnboundlatch.countDown();
+        Log.d(TAG, "In Call Service unbind, " + olderState + " -> " + sServiceUnboundlatch);
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        Log.i(TAG, "onCallAdded");
+        super.onCallAdded(call);
+        sCalls.add(call);
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        Log.i(TAG, "onCallRemoved");
+        super.onCallRemoved(call);
+        sCalls.add(call);
+    }
+
+    private static boolean checkLatch(CountDownLatch latch) {
+        synchronized (sLock) {
+            boolean success = false;
+            try {
+                success = latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                success = false;
+            }
+            return success;
+        }
+    }
+
+    public static void resetLatchForServiceBound(boolean bind) {
+        synchronized (sLock) {
+            if (bind) {
+                // Set bind to true, reset unbind latch for unbinding.
+                sServiceUnboundlatch = new CountDownLatch(1);
+            } else {
+                // Set bind to false, reset bind latch for binding.
+                sServiceBoundLatch = new CountDownLatch(1);
+            }
+        }
+    }
+
+    public static boolean checkBindStatus(boolean bind) {
+        Log.i(CtsThirdPartyInCallService.class.getSimpleName(),
+                "checking latch status: service " + (bind ? "bound" : "not bound"));
+        return bind ?
+                checkLatch(sServiceBoundLatch)
+                : checkLatch(sServiceUnboundlatch);
+    }
+
+    public static void resetCalls() {
+        synchronized (sLock) {
+            Log.i(TAG, "clearing all the third party calls.");
+            for (Call c : sCalls) {
+                c.disconnect();
+            }
+            sCalls.clear();
+        }
+    }
+
+    public static int getLocalCallCount() {
+        synchronized (sLock) {
+            return sCalls.size();
+        }
+    }
+}
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallServiceControl.java b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallServiceControl.java
new file mode 100644
index 0000000..118b1f4
--- /dev/null
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/src/android/telecom/cts/thirdptyincallservice/CtsThirdPartyInCallServiceControl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.telecom.cts.thirdptyincallservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class CtsThirdPartyInCallServiceControl extends Service {
+
+    private static final String TAG = CtsThirdPartyInCallServiceControl.class.getSimpleName();
+    public static final String CONTROL_INTERFACE_ACTION =
+            "android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL";
+
+    private final IBinder mCtsCompanionAppControl = new ICtsThirdPartyInCallServiceControl.Stub() {
+
+        @Override
+        public boolean checkBindStatus(boolean bind) {
+            return CtsThirdPartyInCallService.checkBindStatus(bind);
+        }
+
+        @Override
+        public int getLocalCallCount() {
+            return CtsThirdPartyInCallService.getLocalCallCount();
+        }
+
+        @Override
+        public void resetLatchForServiceBound(boolean bind) {
+            CtsThirdPartyInCallService.resetLatchForServiceBound(bind);
+        }
+
+        @Override
+        public void resetCalls() {
+            CtsThirdPartyInCallService.resetCalls();
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
+            Log.d(TAG, "onBind: return control interface.");
+            return mCtsCompanionAppControl;
+        }
+        Log.d(TAG, "onBind: invalid intent.");
+        return null;
+    }
+
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index a2610e4..a6d9fc1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -51,6 +51,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -668,8 +669,13 @@
         if (!VideoProfile.isAudioOnly(videoState)) {
             extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
         }
-
-        mTelecomManager.placeCall(createTestNumber(), extras);
+        Uri number;
+        if (extras.containsKey(TestUtils.EXTRA_PHONE_NUMBER)) {
+            number = extras.getParcelable(TestUtils.EXTRA_PHONE_NUMBER);
+        } else {
+            number = createTestNumber();
+        }
+        mTelecomManager.placeCall(number, extras);
     }
 
     /**
@@ -681,6 +687,18 @@
         return Uri.fromParts("tel", String.valueOf(++sCounter), null);
     }
 
+    /**
+     * Creates a new random phone number in the range:
+     * 000-000-0000
+     * to
+     * 999-999-9999
+     * @return Randomized phone number.
+     */
+    Uri createRandomTestNumber() {
+        return Uri.fromParts("tel", String.format("%06d", new Random().nextInt(999999))
+                + String.format("%04d", new Random().nextInt(9999)), null);
+    }
+
     public static Uri getTestNumber() {
         return Uri.fromParts("tel", String.valueOf(sCounter), null);
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
new file mode 100644
index 0000000..d3e0958
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom.cts;
+
+import static android.telecom.cts.TestUtils.shouldTestTelecom;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.UserHandle;
+
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.cts.redirectiontestapp.CtsCallRedirectionService;
+import android.telecom.cts.redirectiontestapp.CtsCallRedirectionServiceController;
+import android.telecom.cts.redirectiontestapp.ICtsCallRedirectionServiceController;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class CallRedirectionServiceTest extends BaseTelecomTestWithMockServices {
+    private static final String TAG = CallRedirectionServiceTest.class.getSimpleName();
+    private static final String TEST_APP_NAME = "CTSCRTest";
+    private static final String TEST_APP_PACKAGE = "android.telecom.cts.redirectiontestapp";
+    private static final String TEST_APP_COMPONENT = TEST_APP_PACKAGE
+                    + "/android.telecom.cts.redirectiontestapp.CtsCallRedirectionService";
+    private static final String ROLE_CALL_REDIRECTION = "android.app.role.PROXY_CALLING_APP";
+
+    private static final Uri SAMPLE_HANDLE = Uri.fromParts(PhoneAccount.SCHEME_TEL, "0001112222",
+            null);
+
+    private static final PhoneAccountHandle SAMPLE_PHONE_ACCOUNT = new PhoneAccountHandle(
+            new ComponentName("android.telecom.cts",
+                    "android.telecom.cts.CallRedirectionServiceTest"),
+            "CTS_PHONE_ACCOUNT_ID");
+
+    private static final int ASYNC_TIMEOUT = 10000;
+    private RoleManager mRoleManager;
+    private Handler mHandler;
+    private String mPreviousCallRedirectionPackage;
+    private ICtsCallRedirectionServiceController mCallRedirectionServiceController;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        mHandler = new Handler(Looper.getMainLooper());
+        mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
+        setupControlBinder();
+        setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+        rememberPreviousCallRedirectionApp();
+        // Ensure CTS app holds the call redirection role.
+        addRoleHolder(ROLE_CALL_REDIRECTION,
+                CtsCallRedirectionService.class.getPackage().getName());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        if (mCallRedirectionServiceController != null) {
+            mCallRedirectionServiceController.reset();
+        }
+        // Remove the test app from the redirection role.
+        removeRoleHolder(ROLE_CALL_REDIRECTION,
+                CtsCallRedirectionService.class.getPackage().getName());
+
+        if (!TextUtils.isEmpty(mPreviousCallRedirectionPackage)) {
+            addRoleHolder(ROLE_CALL_REDIRECTION, mPreviousCallRedirectionPackage);
+        }
+    }
+
+    public void testRedirectedCall() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        mCallRedirectionServiceController.setRedirectCall(
+                SAMPLE_HANDLE, SAMPLE_PHONE_ACCOUNT, false);
+        placeAndVerifyCall();
+        // TODO verify redirection information
+    }
+
+    public void testCancelCall() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        mCallRedirectionServiceController.setCancelCall();
+        placeAndVerifyCall();
+        // TODO verify redirection information
+    }
+
+    public void testPlaceCallUnmodified() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        mCallRedirectionServiceController.setPlaceCallUnmodified();
+        placeAndVerifyCall();
+        // TODO verify redirection information
+    }
+    /**
+     * Sets up a binder used to control the CallRedirectionServiceCtsTestApp.
+     * This app is a standalone APK so that it can reside in a package name outside of the one the
+     * CTS test itself runs in.
+     * @throws InterruptedException
+     */
+    private void setupControlBinder() throws InterruptedException {
+        Intent bindIntent = new Intent(
+                CtsCallRedirectionServiceController.CONTROL_INTERFACE_ACTION);
+        bindIntent.setComponent(CtsCallRedirectionServiceController.CONTROL_INTERFACE_COMPONENT);
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+
+        boolean success = mContext.bindService(bindIntent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mCallRedirectionServiceController =
+                        ICtsCallRedirectionServiceController.Stub.asInterface(service);
+                bindLatch.countDown();
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mCallRedirectionServiceController = null;
+            }
+        }, Context.BIND_AUTO_CREATE);
+        if (!success) {
+            fail("Failed to get control interface -- bind error");
+        }
+        bindLatch.await(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Use RoleManager to query the previous call redirection app so we can restore it later.
+     */
+    private void rememberPreviousCallRedirectionApp() {
+        runWithShellPermissionIdentity(() -> {
+            List<String> callRedirectionApps = mRoleManager.getRoleHolders(ROLE_CALL_REDIRECTION);
+            if (!callRedirectionApps.isEmpty()) {
+                mPreviousCallRedirectionPackage = callRedirectionApps.get(0);
+            } else {
+                mPreviousCallRedirectionPackage = null;
+            }
+        });
+    }
+
+    private void addRoleHolder(String roleName, String packageName) throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue(1);
+
+        runWithShellPermissionIdentity(() -> mRoleManager.addRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        try {
+                            queue.put(true);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    @Override
+                    public void onFailure() {
+                        try {
+                            queue.put(false);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }));
+        boolean result = queue.poll(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(result);
+    }
+
+    private void removeRoleHolder(String roleName, String packageName)
+            throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue(1);
+
+        runWithShellPermissionIdentity(() -> mRoleManager.removeRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        try {
+                            queue.put(true);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    @Override
+                    public void onFailure() {
+                        try {
+                            queue.put(false);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }));
+        boolean result = queue.poll(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(result);
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsRoleManagerAdapter.java b/tests/tests/telecom/src/android/telecom/cts/CtsRoleManagerAdapter.java
new file mode 100644
index 0000000..27a7010
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsRoleManagerAdapter.java
@@ -0,0 +1,207 @@
+/*
+ * 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.telecom.cts;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+import static android.telecom.cts.TestUtils.executeShellCommand;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Instrumentation;
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
+import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class CtsRoleManagerAdapter {
+
+    private static final String TAG = CtsRoleManagerAdapter.class.getSimpleName();
+    private static final String ROLE_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+    private static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+    private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
+            "telecom add-or-remove-call-companion-app";
+    private static final String COMMAND_SET_AUTO_MODE_APP = "telecom set-test-auto-mode-app";
+
+    private Context mContext;
+    private RoleManager mRoleManager;
+    private Instrumentation mInstrumentation;
+    private ConcurrentHashMap<String, List<String>> mRoleHolders;
+
+    public CtsRoleManagerAdapter(Instrumentation instrumentation) {
+        mInstrumentation = instrumentation;
+        mContext = instrumentation.getContext();
+        mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
+        mRoleHolders = new ConcurrentHashMap<>();
+    }
+
+    public void addCompanionAppRoleHolder(String packageName)
+            throws Exception {
+        if (mRoleManager != null) {
+            addRoleHolder(ROLE_COMPANION_APP, packageName);
+        } else {
+            String command = String.format("%s %s %d",
+                    COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP, packageName, 1);
+            executeShellCommand(mInstrumentation, command);
+            addRoleHolderToMap(ROLE_COMPANION_APP, packageName);
+        }
+    }
+
+    public void removeCompanionAppRoleHolder(String packageName) throws Exception {
+        if (mRoleManager != null) {
+            removeRoleHolder(ROLE_COMPANION_APP, packageName);
+        } else {
+            String command = String.format("%s %s %d",
+                    COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP, packageName, 0);
+            executeShellCommand(mInstrumentation, command);
+            removeRoleHolderFromMap(ROLE_COMPANION_APP, packageName);
+        }
+    }
+
+    public void addAutomotiveRoleHolder(String packageName)
+            throws Exception {
+        if (mRoleManager != null) {
+            addRoleHolder(ROLE_CAR_MODE_DIALER_APP, packageName);
+        } else {
+            String command = String.format("%s %s",
+                    COMMAND_SET_AUTO_MODE_APP, packageName);
+            executeShellCommand(mInstrumentation, command);
+            addRoleHolderToMap(ROLE_CAR_MODE_DIALER_APP, packageName);
+        }
+    }
+
+    public void removeAutomotiveRoleHolder(String packageName) throws Exception {
+        if (mRoleManager != null) {
+            removeRoleHolder(ROLE_CAR_MODE_DIALER_APP, packageName);
+        } else {
+            removeRoleHolderFromMap(ROLE_CAR_MODE_DIALER_APP, packageName);
+
+            // Reset the car mode ui to rest of automotive apps assigned. If no other automotive
+            // apps are available, set car mode ui back to null.
+            if (mRoleHolders.containsKey(ROLE_CAR_MODE_DIALER_APP)) {
+                String nextPackage = getRoleHolders(ROLE_CAR_MODE_DIALER_APP).get(0);
+                String command = String.format("%s %s",
+                        COMMAND_SET_AUTO_MODE_APP, nextPackage);
+                executeShellCommand(mInstrumentation, command);
+            } else {
+                executeShellCommand(mInstrumentation, COMMAND_SET_AUTO_MODE_APP);
+            }
+        }
+    }
+
+    public List<String> getRoleHolders(String role) {
+        if (mRoleManager != null) {
+            return getRoleHolder(role);
+        } else {
+            return mRoleHolders.containsKey(role) ?
+                    mRoleHolders.get(role) : new LinkedList<>();
+        }
+    }
+
+    private void addRoleHolderToMap(String role, String packageName) {
+        if (!mRoleHolders.containsKey(role)) {
+            mRoleHolders.put(role, new LinkedList<>());
+        }
+
+        List<String> roleHolders = mRoleHolders.get(role);
+        if (!roleHolders.contains(packageName)) {
+            roleHolders.add(packageName);
+        }
+    }
+
+    private void removeRoleHolderFromMap(String role, String packageName) {
+        List<String> companionAppRoleHolders = mRoleHolders.get(role);
+        if (companionAppRoleHolders == null) {
+            return;
+        }
+
+        companionAppRoleHolders.remove(packageName);
+        if (companionAppRoleHolders.isEmpty()) {
+            mRoleHolders.remove(role);
+        }
+    }
+
+    private List<String> getRoleHolder(String roleName) {
+        List<String> holders = new ArrayList<>();
+        runWithShellPermissionIdentity(() -> {
+            List<String> previousHolders = mRoleManager.getRoleHolders(roleName);
+            if (previousHolders != null && !previousHolders.isEmpty()) {
+                holders.addAll(previousHolders);
+            }
+        });
+        return holders;
+    }
+
+    private void addRoleHolder(String roleName, String packageName) throws InterruptedException {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<String> q = new LinkedBlockingQueue<>(1);
+        runWithShellPermissionIdentity(() -> {
+            mRoleManager.addRoleHolderAsUser(roleName, packageName, user, executor,
+                    new RoleManagerCallback() {
+                        @Override
+                        public void onSuccess() {
+                            q.add(roleName + packageName);
+                        }
+
+                        @Override
+                        public void onFailure() {
+                            Log.e(TAG, "Add role holder failed.");
+                        }
+                    });
+        });
+        String res = q.poll(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertEquals(roleName + packageName, res);
+    }
+
+    private void removeRoleHolder(String roleName, String packageName)
+            throws InterruptedException {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<String> q = new LinkedBlockingQueue<>(1);
+        runWithShellPermissionIdentity(() -> {
+            mRoleManager.removeRoleHolderAsUser(roleName, packageName, user, executor,
+                    new RoleManagerCallback() {
+                        @Override
+                        public void onSuccess() {
+                            q.add(roleName + packageName);
+                        }
+
+                        @Override
+                        public void onFailure() {
+                            Log.e(TAG, "Remove role holder failed.");
+                        }
+                    });
+        });
+        String res = q.poll(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertEquals(roleName + packageName, res);
+    }
+
+
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index 338a225..ca02bd3 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -39,7 +39,7 @@
     private Map<Call, MockVideoCallCallback> mVideoCallCallbacks =
             new ArrayMap<Call, MockVideoCallCallback>();
 
-    private static final Object sLock = new Object();
+    protected static final Object sLock = new Object();
     private static boolean mIsServiceBound = false;
 
     public static abstract class InCallServiceCallbacks {
@@ -354,6 +354,12 @@
         }
     }
 
+    public void rejectAllCalls() {
+        for (final Call call: mCalls) {
+            call.reject(false, null);
+        }
+    }
+
     public void disconnectAllConferenceCalls() {
         for (final Call call: mConferenceCalls) {
             call.disconnect();
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
index a78ef29..71bfa2f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
@@ -281,6 +281,8 @@
         CtsSelfManagedConnectionService.waitForBinding();
         assertTrue(CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
                 CtsSelfManagedConnectionService.CREATE_OUTGOING_CONNECTION_FAILED_LOCK));
+
+        assertFalse(mTelecomManager.isOutgoingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_1));
     }
 
     /**
@@ -293,8 +295,13 @@
         if (!mShouldTestTelecom) {
             return;
         }
+        assertTrue(mTelecomManager.isOutgoingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_1));
         placeAndVerifyOutgoingCall(TestUtils.TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+
+        assertTrue(mTelecomManager.isOutgoingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_2));
         placeAndVerifyOutgoingCall(TestUtils.TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_3);
+
+        assertTrue(mTelecomManager.isOutgoingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_3));
         placeAndVerifyOutgoingCall(TestUtils.TEST_SELF_MANAGED_HANDLE_3, TEST_ADDRESS_4);
     }
 
@@ -398,6 +405,7 @@
         SelfManagedConnection connection = TestUtils.waitForAndGetConnection(TEST_ADDRESS_1);
         setActiveAndVerify(connection);
 
+        assertTrue(mTelecomManager.isIncomingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_2));
         // Attempt to create a new incoming call for the other PhoneAccount; it should succeed.
         TestUtils.addIncomingCall(getInstrumentation(), mTelecomManager,
                 TestUtils.TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_2);
@@ -415,6 +423,7 @@
             return;
         }
 
+        assertTrue(mTelecomManager.isIncomingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_1));
         // Attempt to create a new Incoming self-managed call
         TestUtils.addIncomingCall(getInstrumentation(), mTelecomManager,
                 TestUtils.TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
@@ -468,6 +477,7 @@
         SelfManagedConnection connection = TestUtils.waitForAndGetConnection(TEST_ADDRESS_1);
         connection.setRinging();
 
+        assertFalse(mTelecomManager.isIncomingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_1));
         // WHEN create a new incoming call for the the same PhoneAccount
         TestUtils.addIncomingCall(getInstrumentation(), mTelecomManager,
                 TestUtils.TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
@@ -492,6 +502,8 @@
         for (int ix = 0; ix < 10; ix++) {
             Uri address = Uri.fromParts("sip", "test" + ix + "@test.com", null);
             // Create an ongoing call in the first self-managed PhoneAccount.
+            assertTrue(mTelecomManager.isOutgoingCallPermitted(
+                    TestUtils.TEST_SELF_MANAGED_HANDLE_1));
             TestUtils.placeOutgoingCall(getInstrumentation(), mTelecomManager,
                     TestUtils.TEST_SELF_MANAGED_HANDLE_1, address);
             SelfManagedConnection connection = TestUtils.waitForAndGetConnection(address);
@@ -500,6 +512,7 @@
         }
 
         // Try adding an 11th.  It should fail to be created.
+        assertFalse(mTelecomManager.isIncomingCallPermitted(TestUtils.TEST_SELF_MANAGED_HANDLE_1));
         TestUtils.addIncomingCall(getInstrumentation(), mTelecomManager,
                 TestUtils.TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_2);
         assertTrue("Expected onCreateIncomingConnectionFailed callback",
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index 951541c..38e1a25 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -71,6 +71,7 @@
     public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService";
     public static final String ACCOUNT_ID_1 = "xtstest_CALL_PROVIDER_ID";
     public static final String ACCOUNT_ID_2 = "xtstest_CALL_PROVIDER_ID";
+    public static final String EXTRA_PHONE_NUMBER = "android.telecom.cts.extra.PHONE_NUMBER";
     public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
             new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID_1);
     public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE_2 =
diff --git a/tests/tests/telecom/src/android/telecom/cts/ThirdPartyCallScreeningServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ThirdPartyCallScreeningServiceTest.java
new file mode 100644
index 0000000..7d1bae2
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ThirdPartyCallScreeningServiceTest.java
@@ -0,0 +1,508 @@
+/*
+ * 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.telecom.cts;
+
+import static android.telecom.cts.TestUtils.shouldTestTelecom;
+import static android.telecom.cts.TestUtils.waitOnAllHandlers;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.UserHandle;
+
+import android.provider.CallLog;
+import android.telecom.Call;
+import android.telecom.CallIdentification;
+import android.telecom.CallScreeningService;
+import android.telecom.TelecomManager;
+import android.telecom.cts.screeningtestapp.CallScreeningServiceControl;
+import android.telecom.cts.screeningtestapp.CtsCallScreeningService;
+import android.telecom.cts.screeningtestapp.ICallScreeningControl;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class ThirdPartyCallScreeningServiceTest extends BaseTelecomTestWithMockServices {
+    private static final String TAG = ThirdPartyCallScreeningServiceTest.class.getSimpleName();
+    private static final String TEST_APP_NAME = "CTSCSTest";
+    private static final String TEST_APP_PACKAGE = "android.telecom.cts.screeningtestapp";
+    private static final String TEST_APP_COMPONENT =
+            "android.telecom.cts.screeningtestapp/"
+                    + "android.telecom.cts.screeningtestapp.CtsCallScreeningService";
+    private static final int ASYNC_TIMEOUT = 10000;
+    private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
+    private static final CallIdentification SAMPLE_CALL_ID = new CallIdentification.Builder()
+            .setName("Joe's Laundromat")
+            .setDescription("1234 Dirtysocks Lane, Cleanville, USA")
+            .setDetails("Open 24 hrs; free parking")
+            .setPhoto(null)
+            .setNuisanceConfidence(CallIdentification.CONFIDENCE_LIKELY_NOT_NUISANCE)
+            .build();
+
+    private ICallScreeningControl mCallScreeningControl;
+    private RoleManager mRoleManager;
+    private String mPreviousCallScreeningPackage;
+    private Handler mHandler;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        mHandler = new Handler(Looper.getMainLooper());
+        mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
+        setupControlBinder();
+        setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+        rememberPreviousCallScreeningApp();
+        // Ensure CTS app holds the call screening role.
+        addRoleHolder(ROLE_CALL_SCREENING,
+                CtsCallScreeningService.class.getPackage().getName());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        if (mCallScreeningControl != null) {
+            mCallScreeningControl.reset();
+        }
+
+        // Remove the test app from the screening role.
+        removeRoleHolder(ROLE_CALL_SCREENING, CtsCallScreeningService.class.getPackage().getName());
+
+        if (!TextUtils.isEmpty(mPreviousCallScreeningPackage)) {
+            addRoleHolder(ROLE_CALL_SCREENING, mPreviousCallScreeningPackage);
+        }
+    }
+
+    /**
+     * Verifies that a {@link android.telecom.CallScreeningService} can provide call identification
+     * information and that it will be passed on to the {@link android.telecom.InCallService}.
+     * Also verifies that the call identification information is logged to the call log as expected.
+     * @throws Exception
+     */
+    public void testProvideCallIdentificationForIncoming() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+
+        // Tell the test app to set its call id info.
+        mCallScreeningControl.setProviderCallIdentification(
+                SAMPLE_CALL_ID.getName(),
+                SAMPLE_CALL_ID.getDescription(),
+                SAMPLE_CALL_ID.getDetails(),
+                SAMPLE_CALL_ID.getPhoto(),
+                SAMPLE_CALL_ID.getNuisanceConfidence());
+
+        // Setup content observer to notify us when we call log entry is added.
+        CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+        Uri phoneNumber = createRandomTestNumber();
+        // Create a new incoming call.
+        addAndVerifyNewIncomingCall(phoneNumber, null);
+
+        // Wait for call id to be passed back to InCallService
+        assertCallIdentification(SAMPLE_CALL_ID, TEST_APP_NAME, TEST_APP_PACKAGE);
+
+        // Disconnect the call
+        mInCallCallbacks.getService().rejectAllCalls();
+        assertNumCalls(mInCallCallbacks.getService(), 0);
+
+        // Wait for it to log.
+        waitOnAllHandlers(getInstrumentation());
+
+        // Wait for call log update.
+        callLogEntryLatch.await();
+
+        // Query the latest entry into the call log and verify the call identification information
+        // was logged appropriately
+        Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
+                null, null, CallLog.Calls._ID + " DESC limit 1;");
+        int numberIndex = callsCursor.getColumnIndex(CallLog.Calls.NUMBER);
+        int callTypeIndex = callsCursor.getColumnIndex(CallLog.Calls.TYPE);
+        int blockReasonIndex = callsCursor.getColumnIndex(CallLog.Calls.BLOCK_REASON);
+        int callIdAppNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_APP_NAME);
+        int callIdPackageNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_PACKAGE_NAME);
+        int callIdNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_NAME);
+        int callIdDescriptionIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_DESCRIPTION);
+        int callIdDetailsIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_DETAILS);
+        int callIdConfidenceIndex = callsCursor.getColumnIndex(
+                CallLog.Calls.CALL_ID_NUISANCE_CONFIDENCE);
+        if (callsCursor.moveToNext()) {
+            String number = callsCursor.getString(numberIndex);
+            int callType = callsCursor.getInt(callTypeIndex);
+            int blockReason = callsCursor.getInt(blockReasonIndex);
+            String idAppName = callsCursor.getString(callIdAppNameIndex);
+            String idPackage = callsCursor.getString(callIdPackageNameIndex);
+            String name = callsCursor.getString(callIdNameIndex);
+            String description = callsCursor.getString(callIdDescriptionIndex);
+            String details = callsCursor.getString(callIdDetailsIndex);
+            int confidence = callsCursor.getInt(callIdConfidenceIndex);
+            // Make sure nobody sneaky tries to pre-populate the call log and fudge the tests.
+            assertEquals(phoneNumber.getSchemeSpecificPart(), number);
+            assertEquals(CallLog.Calls.REJECTED_TYPE, callType);
+            assertEquals(CallLog.Calls.BLOCK_REASON_NOT_BLOCKED, blockReason);
+            assertEquals(TEST_APP_NAME, idAppName);
+            assertEquals(TEST_APP_PACKAGE, idPackage);
+            assertEquals(SAMPLE_CALL_ID.getName(), name);
+            assertEquals(SAMPLE_CALL_ID.getDetails(), details);
+            assertEquals(SAMPLE_CALL_ID.getDescription(), description);
+            assertEquals(SAMPLE_CALL_ID.getNuisanceConfidence(), confidence);
+        } else {
+            fail("Call was not logged; bee-boop");
+        }
+    }
+
+    /**
+     * Verifies that a {@link android.telecom.CallScreeningService} can reject an incoming call.
+     * Ensures that the system logs the blocked call to the call log.
+     *
+     * @throws Exception
+     */
+    public void testRejectCall() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+
+        // Tell the test app to block the call.
+        mCallScreeningControl.setCallResponse(true /* shouldDisallowCall */,
+                true /* shouldRejectCall */, false /* shouldSkipCallLog */,
+                true /* shouldSkipNotification */);
+
+        addIncomingAndVerifyBlocked();
+    }
+
+    /**
+     * Similar to {@link #testRejectCall()}, except the {@link android.telecom.CallScreeningService}
+     * tries to skip logging the call to the call log.  We verify that Telecom still logs the call
+     * to the call log, retaining the API behavior documented in
+     * {@link android.telecom.CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
+     * @throws Exception
+     */
+    public void testRejectCallAndTryToSkipCallLog() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+
+        // Tell the test app to block the call; also try to skip logging the call.
+        mCallScreeningControl.setCallResponse(true /* shouldDisallowCall */,
+                true /* shouldRejectCall */, true /* shouldSkipCallLog */,
+                true /* shouldSkipNotification */);
+
+        addIncomingAndVerifyBlocked();
+    }
+
+    public void testProvideCallIdentificationForOutgoing() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+
+        // Tell the test app to set its call id info.
+        mCallScreeningControl.setProviderCallIdentification(
+                SAMPLE_CALL_ID.getName(),
+                SAMPLE_CALL_ID.getDescription(),
+                SAMPLE_CALL_ID.getDetails(),
+                SAMPLE_CALL_ID.getPhoto(),
+                SAMPLE_CALL_ID.getNuisanceConfidence());
+
+        // Setup content observer to notify us when we call log entry is added.
+        CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+        Uri phoneNumber = createRandomTestNumber();
+        Bundle extras = new Bundle();
+        extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, phoneNumber);
+        // Create a new outgoing call.
+        placeAndVerifyCall(extras);
+
+        // Wait for call id to be passed back to InCallService
+        assertCallIdentification(SAMPLE_CALL_ID, TEST_APP_NAME, TEST_APP_PACKAGE);
+
+        // Disconnect the call
+        mInCallCallbacks.getService().disconnectAllCalls();
+        assertNumCalls(mInCallCallbacks.getService(), 0);
+
+        // Wait for it to log.
+        callLogEntryLatch.await(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Query the latest entry into the call log and verify the call identification information
+        // was logged appropriately
+        Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
+                null, null, CallLog.Calls._ID + " DESC limit 1;");
+        int numberIndex = callsCursor.getColumnIndex(CallLog.Calls.NUMBER);
+        int callTypeIndex = callsCursor.getColumnIndex(CallLog.Calls.TYPE);
+        int blockReasonIndex = callsCursor.getColumnIndex(CallLog.Calls.BLOCK_REASON);
+        int callIdAppNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_APP_NAME);
+        int callIdPackageNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_PACKAGE_NAME);
+        int callIdNameIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_NAME);
+        int callIdDescriptionIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_DESCRIPTION);
+        int callIdDetailsIndex = callsCursor.getColumnIndex(CallLog.Calls.CALL_ID_DETAILS);
+        int callIdConfidenceIndex = callsCursor.getColumnIndex(
+                CallLog.Calls.CALL_ID_NUISANCE_CONFIDENCE);
+        if (callsCursor.moveToNext()) {
+            String number = callsCursor.getString(numberIndex);
+            int callType = callsCursor.getInt(callTypeIndex);
+            int blockReason = callsCursor.getInt(blockReasonIndex);
+            String idAppName = callsCursor.getString(callIdAppNameIndex);
+            String idPackage = callsCursor.getString(callIdPackageNameIndex);
+            String name = callsCursor.getString(callIdNameIndex);
+            String description = callsCursor.getString(callIdDescriptionIndex);
+            String details = callsCursor.getString(callIdDetailsIndex);
+            int confidence = callsCursor.getInt(callIdConfidenceIndex);
+            // Make sure nobody sneaky tries to pre-populate the call log and fudge the tests.
+            assertEquals(phoneNumber.getSchemeSpecificPart(), number);
+            assertEquals(CallLog.Calls.OUTGOING_TYPE, callType);
+            assertEquals(CallLog.Calls.BLOCK_REASON_NOT_BLOCKED, blockReason);
+            assertEquals(TEST_APP_NAME, idAppName);
+            assertEquals(TEST_APP_PACKAGE, idPackage);
+            assertEquals(SAMPLE_CALL_ID.getName(), name);
+            assertEquals(SAMPLE_CALL_ID.getDetails(), details);
+            assertEquals(SAMPLE_CALL_ID.getDescription(), description);
+            assertEquals(SAMPLE_CALL_ID.getNuisanceConfidence(), confidence);
+        } else {
+            fail("Call was not logged; bee-boop");
+        }
+    }
+
+    /**
+     * Verify parceling and unparceling of {@link CallIdentification} information.
+     */
+    public void testParcelUnparcel() {
+        Parcel p = Parcel.obtain();
+        SAMPLE_CALL_ID.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        CallIdentification unparceled = SAMPLE_CALL_ID.CREATOR.createFromParcel(p);
+        assertEquals(SAMPLE_CALL_ID, unparceled);
+    }
+
+    private void addIncomingAndVerifyBlocked() throws Exception {
+        // Add call through TelecomManager; we can't use the test methods since they assume a call
+        // makes it through to the InCallService; this is blocked so it shouldn't.
+        Uri testNumber = createRandomTestNumber();
+
+        // Setup content observer to notify us when we call log entry is added.
+        CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, testNumber);
+        mTelecomManager.addNewIncomingCall(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, extras);
+
+        // Wait until the new incoming call is processed.
+        waitOnAllHandlers(getInstrumentation());
+
+        // Wait for the content observer to report that we have gotten a new call log entry.
+        callLogEntryLatch.await(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Query the latest entry into the call log.
+        Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
+                null, null, CallLog.Calls._ID + " DESC limit 1;");
+        int numberIndex = callsCursor.getColumnIndex(CallLog.Calls.NUMBER);
+        int callTypeIndex = callsCursor.getColumnIndex(CallLog.Calls.TYPE);
+        int blockReasonIndex = callsCursor.getColumnIndex(CallLog.Calls.BLOCK_REASON);
+        int callScreeningAppNameIndex = callsCursor.getColumnIndex(
+                CallLog.Calls.CALL_SCREENING_APP_NAME);
+        int callScreeningCmpNameIndex = callsCursor.getColumnIndex(
+                CallLog.Calls.CALL_SCREENING_COMPONENT_NAME);
+        if (callsCursor.moveToNext()) {
+            String number = callsCursor.getString(numberIndex);
+            int callType = callsCursor.getInt(callTypeIndex);
+            int blockReason = callsCursor.getInt(blockReasonIndex);
+            String screeningAppName = callsCursor.getString(callScreeningAppNameIndex);
+            String screeningComponentName = callsCursor.getString(callScreeningCmpNameIndex);
+            assertEquals(testNumber.getSchemeSpecificPart(), number);
+            assertEquals(CallLog.Calls.BLOCKED_TYPE, callType);
+            assertEquals(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, blockReason);
+            assertEquals(TEST_APP_NAME, screeningAppName);
+            assertEquals(TEST_APP_COMPONENT, screeningComponentName);
+        } else {
+            fail("Blocked call was not logged.");
+        }
+    }
+
+    private CountDownLatch getCallLogEntryLatch() {
+        CountDownLatch changeLatch = new CountDownLatch(1);
+        mContext.getContentResolver().registerContentObserver(
+                CallLog.Calls.CONTENT_URI, true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        mContext.getContentResolver().unregisterContentObserver(this);
+                        changeLatch.countDown();
+                        super.onChange(selfChange);
+                    }
+                });
+        return changeLatch;
+    }
+
+    /**
+     * Sets up a binder used to control the CallScreeningServiceCtsTestApp.
+     * This app is a standalone APK so that it can reside in a package name outside of the one the
+     * CTS test itself runs in (since that APK is where the CTS InCallService resides).
+     * @throws InterruptedException
+     */
+    private void setupControlBinder() throws InterruptedException {
+        Intent bindIntent = new Intent(CallScreeningServiceControl.CONTROL_INTERFACE_ACTION);
+        bindIntent.setComponent(CallScreeningServiceControl.CONTROL_INTERFACE_COMPONENT);
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+
+        boolean success = mContext.bindService(bindIntent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mCallScreeningControl = ICallScreeningControl.Stub.asInterface(service);
+                bindLatch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mCallScreeningControl = null;
+            }
+        }, Context.BIND_AUTO_CREATE);
+        if (!success) {
+            fail("Failed to get control interface -- bind error");
+        }
+        bindLatch.await(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+    }
+
+    private void assertCallIdentification(final CallIdentification expectedIdentification,
+            final String expectedAppName, final String expectedPackageName) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        CallIdentification callIdentification = mInCallCallbacks.getService()
+                                .getLastCall()
+                                .getDetails()
+                                .getCallIdentification();
+                        return ((expectedIdentification == null) == (callIdentification == null))
+                                && expectedIdentification.getNuisanceConfidence()
+                                        == callIdentification.getNuisanceConfidence()
+                                && expectedIdentification.getName().equals(
+                                        callIdentification.getName())
+                                && expectedIdentification.getDescription().equals(
+                                        callIdentification.getDescription())
+                                && expectedIdentification.getDetails().equals(
+                                        callIdentification.getDetails())
+                                && expectedAppName.equals(
+                                        callIdentification.getCallScreeningAppName())
+                                && expectedPackageName.equals(
+                                        callIdentification.getCallScreeningPackageName());
+                    }
+                },
+                TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+                "Expected call Id " + expectedIdentification
+        );
+    }
+
+    /**
+     * Use RoleManager to query the previous call screening app so we can restore it later.
+     */
+    private void rememberPreviousCallScreeningApp() {
+        runWithShellPermissionIdentity(() -> {
+            List<String> callScreeningApps = mRoleManager.getRoleHolders(ROLE_CALL_SCREENING);
+            if (!callScreeningApps.isEmpty()) {
+                mPreviousCallScreeningPackage = callScreeningApps.get(0);
+            } else {
+                mPreviousCallScreeningPackage = null;
+            }
+        });
+    }
+
+    private void addRoleHolder(String roleName, String packageName)
+            throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue(1);
+
+        runWithShellPermissionIdentity(() -> mRoleManager.addRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        try {
+                            queue.put(true);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    @Override
+                    public void onFailure() {
+                        try {
+                            queue.put(false);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }));
+        boolean result = queue.poll(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(result);
+    }
+
+    private void removeRoleHolder(String roleName, String packageName)
+            throws Exception {
+        UserHandle user = Process.myUserHandle();
+        Executor executor = mContext.getMainExecutor();
+        LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue(1);
+
+        runWithShellPermissionIdentity(() -> mRoleManager.removeRoleHolderAsUser(roleName,
+                packageName, user, executor, new RoleManagerCallback() {
+                    @Override
+                    public void onSuccess() {
+                        try {
+                            queue.put(true);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    @Override
+                    public void onFailure() {
+                        try {
+                            queue.put(false);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }));
+        boolean result = queue.poll(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(result);
+    }
+
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ThirdPartyInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ThirdPartyInCallServiceTest.java
new file mode 100644
index 0000000..ef24240
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ThirdPartyInCallServiceTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.telecom.cts;
+
+import static android.telecom.cts.TestUtils.TEST_PHONE_ACCOUNT_HANDLE;
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.app.UiModeManager;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecom.TelecomManager;
+import android.telecom.cts.thirdptyincallservice.CtsThirdPartyInCallService;
+import android.telecom.cts.thirdptyincallservice.CtsThirdPartyInCallServiceControl;
+import android.telecom.cts.thirdptyincallservice.ICtsThirdPartyInCallServiceControl;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ThirdPartyInCallServiceTest extends BaseTelecomTestWithMockServices {
+
+    private static final String TAG = ThirdPartyInCallServiceTest.class.getSimpleName();
+    private static final String ROLE_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+    private static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+    private static final Uri sTestUri = Uri.parse("tel:555-TEST");
+    private Context mContext;
+    private UiModeManager mUiModeManager;
+    private TelecomManager mTelecomManager;
+    private CtsRoleManagerAdapter mCtsRoleManagerAdapter;
+    ICtsThirdPartyInCallServiceControl mICtsThirdPartyInCallServiceControl;
+    private boolean mSkipNullUnboundLatch;
+    private String mPreviousRoleHolder;
+    private String mThirdPartyPackageName;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        mCtsRoleManagerAdapter = new CtsRoleManagerAdapter(getInstrumentation());
+        mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+        mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+        setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+        setUpControl();
+        mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(false);
+        mSkipNullUnboundLatch = false;
+
+        mThirdPartyPackageName = CtsThirdPartyInCallService.class.getPackage().getName();
+
+        // Ensure no ThirdPartyInCallService serves as a car mode dialer app or companion app.
+        // (Probably from previous test failures, if any.)
+        mCtsRoleManagerAdapter.removeAutomotiveRoleHolder(mThirdPartyPackageName);
+        mCtsRoleManagerAdapter.removeCompanionAppRoleHolder(mThirdPartyPackageName);
+
+        // Cache current role holder.
+        cacheCurrentRoleHolder(ROLE_CAR_MODE_DIALER_APP);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mICtsThirdPartyInCallServiceControl.resetCalls();
+
+        // Disable car mode and remove any third party car mode assigned before tear down.
+        mCtsRoleManagerAdapter.removeAutomotiveRoleHolder(mThirdPartyPackageName);
+        // Remove the third party companion app before tear down.
+        mCtsRoleManagerAdapter.removeCompanionAppRoleHolder(mThirdPartyPackageName);
+
+        // Restore cached car mode role holder.
+        if (!TextUtils.isEmpty(mPreviousRoleHolder)) {
+            mCtsRoleManagerAdapter.addAutomotiveRoleHolder(mPreviousRoleHolder);
+        }
+        mUiModeManager.disableCarMode(0);
+
+        super.tearDown();
+        if (!mSkipNullUnboundLatch) {
+            assertBindStatus(/* true: bind, false: unbind */false, /* expected result */true);
+        }
+    }
+
+    public void testCallWithoutThirdPartyApp() throws Exception {
+        // No companion apps
+        List<String> previousHolders = mCtsRoleManagerAdapter.getRoleHolders(ROLE_COMPANION_APP);
+        assertEquals(0, previousHolders.size());
+
+        // No car mode dialer
+        assertFalse(mThirdPartyPackageName.equals(mPreviousRoleHolder));
+
+        int previousCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount();
+        addAndVerifyNewIncomingCall(sTestUri, null);
+        assertBindStatus(/* true: bind, false: unbind */true, /* expected result */false);
+        assertCallCount(previousCallCount);
+        // Third Party InCallService hasn't been bound yet, unbound latch can be null when tearDown.
+        mSkipNullUnboundLatch = true;
+    }
+
+    public void testCallWithCompanionApps() throws Exception {
+        // Set companion app default.
+        mCtsRoleManagerAdapter.addCompanionAppRoleHolder(mThirdPartyPackageName);
+        List<String> previousHolders = mCtsRoleManagerAdapter.getRoleHolders(ROLE_COMPANION_APP);
+        assertEquals(1, previousHolders.size());
+        assertEquals(mThirdPartyPackageName, previousHolders.get(0));
+
+        int previousCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount();
+        addAndVerifyNewIncomingCall(sTestUri, null);
+        assertBindStatus(/* true: bind, false: unbind */true, /* expected result */true);
+        assertCallCount(previousCallCount + 1);
+        mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(true);
+    }
+
+    public void testCallWithThirdPartyCarModeApp() throws Exception {
+        // Set car mode app default.
+        mCtsRoleManagerAdapter.addAutomotiveRoleHolder(mThirdPartyPackageName);
+        assertEquals(mThirdPartyPackageName,
+                mCtsRoleManagerAdapter.getRoleHolders(ROLE_CAR_MODE_DIALER_APP).get(0));
+
+        mUiModeManager.enableCarMode(0);
+        addAndVerifyNewIncomingCallInCarMode(sTestUri, null);
+        mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(true);
+    }
+
+    private void addAndVerifyNewIncomingCallInCarMode(Uri incomingHandle, Bundle extras)
+            throws RemoteException {
+        int currentCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount();
+        if (extras == null) {
+            extras = new Bundle();
+        }
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, incomingHandle);
+        mTelecomManager.addNewIncomingCall(TEST_PHONE_ACCOUNT_HANDLE, extras);
+        assertBindStatus(/* true: bind, false: unbind */true, /* expected result */true);
+        assertCallCount(currentCallCount + 1);
+    }
+
+    /**
+     *
+     * @param bind: check the status of InCallService bind latches.
+     *             Values: true (bound latch), false (unbound latch).
+     * @param success: whether the latch should have counted down.
+     */
+    private void assertBindStatus(boolean bind, boolean success) {
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return success;
+            }
+
+            @Override
+            public Object actual() {
+                try {
+                    return mICtsThirdPartyInCallServiceControl.checkBindStatus(bind);
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Remote exception when checking bind status: " + re);
+                    return false;
+                }
+            }
+        }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, "Unable to " + (bind ? "Bind" : "Unbind")
+                + " third party in call service");
+    }
+
+    private void assertCallCount(int expected) {
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return expected;
+            }
+
+            @Override
+            public Object actual() {
+                try {
+                    return mICtsThirdPartyInCallServiceControl.getLocalCallCount();
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Remote exception when getting local call count: " + re);
+                    return -1;
+                }
+
+            }
+        }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+                "Failed to match localCallCount and expected: " + expected);
+    }
+
+    private void setUpControl() throws InterruptedException {
+        Intent bindIntent = new Intent(CtsThirdPartyInCallServiceControl.CONTROL_INTERFACE_ACTION);
+        // mContext is android.telecom.cts, which doesn't include thirdptyincallservice.
+        ComponentName controlComponentName =
+                ComponentName.createRelative(
+                        CtsThirdPartyInCallServiceControl.class.getPackage().getName(),
+                        CtsThirdPartyInCallServiceControl.class.getName());
+
+        bindIntent.setComponent(controlComponentName);
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+        boolean success = mContext.bindService(bindIntent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.i(TAG, "Service Connected: " + name);
+                mICtsThirdPartyInCallServiceControl =
+                        ICtsThirdPartyInCallServiceControl.Stub.asInterface(service);
+                bindLatch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mICtsThirdPartyInCallServiceControl = null;
+            }
+        }, Context.BIND_AUTO_CREATE);
+        if (!success) {
+            fail("Failed to get control interface -- bind error");
+        }
+        bindLatch.await(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    private void cacheCurrentRoleHolder(String roleName) {
+        runWithShellPermissionIdentity(() -> {
+            List<String> previousHolders = mCtsRoleManagerAdapter.getRoleHolders(roleName);
+            if (previousHolders == null || previousHolders.isEmpty()) {
+                mPreviousRoleHolder = null;
+            } else {
+                mPreviousRoleHolder = previousHolders.get(0);
+            }
+        });
+    }
+
+}
diff --git a/tests/tests/telecom2/Android.mk b/tests/tests/telecom2/Android.mk
index 5b22a92..9ceb4d4 100644
--- a/tests/tests/telecom2/Android.mk
+++ b/tests/tests/telecom2/Android.mk
@@ -48,6 +48,7 @@
     --rename-manifest-package android.telecom2.cts \
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/telecom3/Android.mk b/tests/tests/telecom3/Android.mk
index 3138e6c..fc95b23 100644
--- a/tests/tests/telecom3/Android.mk
+++ b/tests/tests/telecom3/Android.mk
@@ -45,6 +45,7 @@
     --rename-manifest-package android.telecom3.cts \
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 25
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/telephony/TestFinancialSmsApp/Android.bp b/tests/tests/telephony/TestFinancialSmsApp/Android.bp
new file mode 100644
index 0000000..68f5c79
--- /dev/null
+++ b/tests/tests/telephony/TestFinancialSmsApp/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+android_test {
+    name: "TestFinancialSmsApp",
+
+    srcs: ["src/**/*.kt", "src/**/*.java"],
+
+    static_libs: [
+        "compatibility-device-util",
+    ],
+
+    test_suites: [
+        "cts",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestFinancialSmsApp/AndroidManifest.xml b/tests/tests/telephony/TestFinancialSmsApp/AndroidManifest.xml
new file mode 100644
index 0000000..ef34f4c
--- /dev/null
+++ b/tests/tests/telephony/TestFinancialSmsApp/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="android.telephony.cts.financialsms">
+
+    <uses-permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"/>
+
+    <application android:label="TestFinancialSmsApp">
+        <activity
+            android:name="android.telephony.cts.financialsms.MainActivity"
+            android:exported="true"/>
+    </application>
+</manifest>
diff --git a/tests/tests/telephony/TestFinancialSmsApp/res/layout/main_activity.xml b/tests/tests/telephony/TestFinancialSmsApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..54b24f1
--- /dev/null
+++ b/tests/tests/telephony/TestFinancialSmsApp/res/layout/main_activity.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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+    <TextView android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/messages"/>
+</RelativeLayout>
diff --git a/tests/tests/telephony/TestFinancialSmsApp/src/android/telephony/cts/financialsms/MainActivity.java b/tests/tests/telephony/TestFinancialSmsApp/src/android/telephony/cts/financialsms/MainActivity.java
new file mode 100644
index 0000000..148f4d9
--- /dev/null
+++ b/tests/tests/telephony/TestFinancialSmsApp/src/android/telephony/cts/financialsms/MainActivity.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 android.telephony.cts.financialsms;
+
+import android.app.Activity;
+import android.database.CursorWindow;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MainActivity extends Activity {
+    private static int rowNum = -1;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            SmsManager.getDefault().getSmsMessagesForFinancialApp(new Bundle(),
+                    getMainExecutor(),
+                    new SmsManager.FinancialSmsCallback() {
+                        public void onFinancialSmsMessages(CursorWindow msgs) {
+                            rowNum = -1;
+                            if (msgs != null) {
+                                rowNum = msgs.getNumRows();
+                            }
+                    }});
+        } catch (Exception e) {
+            Log.w("MainActivity", "received Exception e:" + e);
+        }finally {
+            Bundle result = new Bundle();
+            result.putString("class", getClass().getName());
+            result.putInt("rowNum", rowNum);
+            getIntent().<RemoteCallback>getParcelableExtra("callback").sendResult(result);
+        }
+        finish();
+    }
+}
diff --git a/tests/tests/telephony/TestSmsApp/Android.bp b/tests/tests/telephony/TestSmsApp/Android.bp
new file mode 100644
index 0000000..201d6c3
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test {
+    name: "TestSmsApp",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt", "src/**/*.java"],
+
+    static_libs: [
+        "compatibility-device-util",
+    ],
+
+    test_suites: [
+        "cts",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
new file mode 100644
index 0000000..210a6ee
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.telephony.cts.sms">
+
+    <uses-permission android:name="android.permission.READ_SMS"/>
+
+    <application android:label="TestSmsApp">
+        <activity
+            android:name="android.telephony.cts.sms.MainActivity"
+            android:exported="true"/>
+
+        <!-- BroadcastReceiver that listens for incoming SMS messages -->
+        <receiver android:name=".SmsReceiver"
+                  android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <!-- BroadcastReceiver that listens for incoming MMS messages -->
+        <receiver android:name=".MmsReceiver"
+                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Activity that allows the user to send new SMS/MMS messages -->
+        <activity android:name=".ComposeSmsActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </activity>
+
+        <!-- Service that delivers messages from the phone "quick response" -->
+        <service android:name=".HeadlessSmsSendService"
+                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+                 android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
+
diff --git a/tests/tests/telephony/TestSmsApp/src/android/telephony/cts/sms/MainActivity.java b/tests/tests/telephony/TestSmsApp/src/android/telephony/cts/sms/MainActivity.java
new file mode 100644
index 0000000..041b76d
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp/src/android/telephony/cts/sms/MainActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.telephony.cts.sms;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.provider.Telephony.Sms;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MainActivity extends Activity {
+
+    public static final int REQUEST_CODE_READ_SMS = 1;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        requestPermissions(
+                new String[]{android.Manifest.permission.READ_SMS}, REQUEST_CODE_READ_SMS);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        if (requestCode == REQUEST_CODE_READ_SMS) {
+            Cursor cursor = null;
+            Throwable t = null;
+            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+                t = new SecurityException("Permission denied");
+            }
+            try {
+                cursor = getContentResolver()
+                        .query(Sms.CONTENT_URI, null, null, null, null);
+            } catch (Throwable ex) {
+                List<Throwable> causes = causes(ex);
+                causes.get(causes.size() - 1).initCause(t);
+                t = ex;
+            } finally {
+                Bundle result = new Bundle();
+                result.putString("class", getClass().getName());
+                result.putInt("queryCount", cursor == null ? -1 : cursor.getCount());
+                result.putString("exceptionMessage",
+                        causes(t).stream().map(Throwable::getMessage)
+                                .collect(Collectors.joining("\n")));
+                getIntent().<RemoteCallback>getParcelableExtra("callback").sendResult(result);
+
+                if (cursor != null) {
+                    cursor.close();
+                }
+                finish();
+            }
+        } else {
+            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        }
+    }
+
+    private static List<Throwable> causes(Throwable t) {
+        ArrayList<Throwable> result = new ArrayList<>();
+        while (t != null) {
+            result.add(t);
+            t = t.getCause();
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp22/Android.bp b/tests/tests/telephony/TestSmsApp22/Android.bp
new file mode 100644
index 0000000..f1ebe33
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp22/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test {
+    name: "TestSmsApp22",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt", "src/**/*.java"],
+
+    static_libs: [
+        "compatibility-device-util",
+    ],
+
+    test_suites: [
+        "cts",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
new file mode 100644
index 0000000..de0047d
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
@@ -0,0 +1,76 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.telephony.cts.sms23">
+
+    <uses-sdk android:targetSdkVersion="22"/>
+
+    <uses-permission android:name="android.permission.READ_SMS"/>
+
+    <application android:label="TestSmsApp">
+        <activity
+            android:name="android.telephony.cts.sms23.MainActivity"
+            android:exported="true"/>
+
+        <!-- BroadcastReceiver that listens for incoming SMS messages -->
+        <receiver android:name=".SmsReceiver"
+                  android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <!-- BroadcastReceiver that listens for incoming MMS messages -->
+        <receiver android:name=".MmsReceiver"
+                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Activity that allows the user to send new SMS/MMS messages -->
+        <activity android:name=".ComposeSmsActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </activity>
+
+        <!-- Service that delivers messages from the phone "quick response" -->
+        <service android:name=".HeadlessSmsSendService"
+                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+                 android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
+
diff --git a/tests/tests/telephony/TestSmsApp22/src/android/telephony/cts/sms23/MainActivity.java b/tests/tests/telephony/TestSmsApp22/src/android/telephony/cts/sms23/MainActivity.java
new file mode 100644
index 0000000..14888ad
--- /dev/null
+++ b/tests/tests/telephony/TestSmsApp22/src/android/telephony/cts/sms23/MainActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.telephony.cts.sms23;
+
+import android.app.Activity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.provider.Telephony.Sms;
+
+public class MainActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Cursor cursor = null;
+        Throwable t = null;
+        try {
+            cursor = getContentResolver()
+                    .query(Sms.CONTENT_URI, null, null, null, null);
+        } catch (Throwable ex) {
+            t = ex;
+        } finally {
+            Bundle result = new Bundle();
+            result.putString("class", getClass().getName());
+            result.putInt("queryCount", cursor == null ? -1 : cursor.getCount());
+            result.putString("exceptionMessage",
+                    t == null ? null : t.getMessage());
+            getIntent().<RemoteCallback>getParcelableExtra("callback").sendResult(result);
+
+            if (cursor != null) {
+                cursor.close();
+            }
+            finish();
+        }
+    }
+}
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/Android.bp b/tests/tests/telephony/TestSmsRetrieverApp/Android.bp
new file mode 100644
index 0000000..d0c1a74
--- /dev/null
+++ b/tests/tests/telephony/TestSmsRetrieverApp/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+android_test {
+    name: "TestSmsRetrieverApp",
+
+    srcs: ["src/**/*.kt", "src/**/*.java"],
+
+    static_libs: [
+        "compatibility-device-util",
+    ],
+
+    test_suites: [
+        "cts",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
new file mode 100644
index 0000000..2ac0079f
--- /dev/null
+++ b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.telephony.cts.smsretriever">
+
+    <application android:label="TestSmsRetrieverApp">
+        <activity
+            android:name="android.telephony.cts.smsretriever.MainActivity"
+            android:exported="true"/>
+	<receiver android:name="android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.telephony.cts.action.SMS_RETRIEVED"></action>
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/res/layout/main_activity.xml b/tests/tests/telephony/TestSmsRetrieverApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..8758e66
--- /dev/null
+++ b/tests/tests/telephony/TestSmsRetrieverApp/res/layout/main_activity.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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+</RelativeLayout>
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
new file mode 100644
index 0000000..1f89db5
--- /dev/null
+++ b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.smsretriever;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MainActivity extends Activity {
+    private static final String SMS_RETRIEVER_ACTION = "CTS_SMS_RETRIEVER_ACTION";
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent("android.telephony.cts.action.SMS_RETRIEVED")
+                        .setComponent(new ComponentName(
+                                "android.telephony.cts.smsretriever",
+                                "android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"));
+        PendingIntent pIntent = PendingIntent.getBroadcast(
+                getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+        String token = null;
+        try {
+            token = SmsManager.getDefault().createAppSpecificSmsTokenWithPackageInfo(
+                    "testprefix1,testprefix2", pIntent);
+        } catch (Exception e) {
+            Log.w("MainActivity", "received Exception e:" + e);
+        }
+
+        if (getIntent().getAction() == null) {
+            Bundle result = new Bundle();
+            result.putString("class", getClass().getName());
+            result.putString("token", token);
+            sendResult(result);
+        } else {
+            // Launched by broadcast receiver
+            assertThat(getIntent().getStringExtra("message"),
+                    equalTo("testprefix1This is a test message" + token));
+            Intent bIntent = new Intent(SMS_RETRIEVER_ACTION);
+            sendBroadcast(bIntent);
+            finish();
+        }
+    }
+
+    public void sendResult(Bundle result) {
+        getIntent().<RemoteCallback>getParcelableExtra("callback").sendResult(result);
+    }
+
+
+}
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/SmsRetrieverBroadcastReceiver.java b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/SmsRetrieverBroadcastReceiver.java
new file mode 100644
index 0000000..ebb17b9
--- /dev/null
+++ b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/SmsRetrieverBroadcastReceiver.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 android.telephony.cts.smsretriever;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class SmsRetrieverBroadcastReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+          if (intent.getAction().equals("android.telephony.cts.action.SMS_RETRIEVED")) {
+              context.startActivity(new Intent("any.action")
+                      .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                      .setComponent(new ComponentName("android.telephony.cts.smsretriever",
+                              "android.telephony.cts.smsretriever.MainActivity"))
+                      .putExtra("message", intent.getStringExtra(SmsManager.EXTRA_SMS_MESSAGE)));
+          }
+        }
+}
diff --git a/tests/tests/telephony/current/AndroidTest.xml b/tests/tests/telephony/current/AndroidTest.xml
index 8239ea7..8c6478b 100644
--- a/tests/tests/telephony/current/AndroidTest.xml
+++ b/tests/tests/telephony/current/AndroidTest.xml
@@ -26,6 +26,10 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTelephonyTestCases.apk" />
         <option name="test-file-name" value="EmbmsMiddlewareCtsTestApp.apk"/>
+        <option name="test-file-name" value="TestSmsApp22.apk"/>
+        <option name="test-file-name" value="TestSmsApp.apk"/>
+        <option name="test-file-name" value="TestSmsRetrieverApp.apk"/>
+        <option name="test-file-name" value="TestFinancialSmsApp.apk"/>
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.telephony.cts" />
diff --git a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.mk b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.mk
index 1b9c910..a11c94d 100644
--- a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.mk
+++ b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.mk
@@ -1,3 +1,17 @@
+# 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.
+
 LOCAL_PATH:= $(call my-dir)
 
 # Build the Sample Embms Services
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index e1854cc..a5731d2 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -16,14 +16,23 @@
 
 package android.telephony.cts;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_READ_PHONE_STATE;
+
+import static com.android.compatibility.common.util.AppOpsUtils.setOpMode;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.SecurityTest;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 
+import java.io.IOException;
+
 public class CarrierConfigManagerTest extends AndroidTestCase {
     private CarrierConfigManager mConfigManager;
     private TelephonyManager mTelephonyManager;
@@ -37,6 +46,16 @@
                 getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+        } catch (IOException e) {
+            fail();
+        }
+        super.tearDown();
+    }
+
     /**
      * Checks whether the telephony stack should be running on this device.
      *
@@ -91,6 +110,29 @@
         checkConfig(config);
     }
 
+    @SecurityTest
+    public void testRevokePermission() {
+        PersistableBundle config;
+
+        try {
+            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_IGNORED);
+        } catch (IOException e) {
+            fail();
+        }
+
+        config = mConfigManager.getConfig();
+        assertTrue(config.isEmptyParcel());
+
+        try {
+            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+        } catch (IOException e) {
+            fail();
+        }
+
+        config = mConfigManager.getConfig();
+        checkConfig(config);
+    }
+
     public void testGetConfigForSubId() {
         PersistableBundle config =
                 mConfigManager.getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId());
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 06e86fe..7b5024b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -22,23 +22,24 @@
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoCdma;
 import android.telephony.CellInfoGsm;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
 import android.telephony.CellInfoWcdma;
 import android.telephony.CellSignalStrengthCdma;
 import android.telephony.CellSignalStrengthGsm;
 import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Test TelephonyManager.getAllCellInfo()
@@ -162,6 +163,8 @@
                 verifyGsmInfo((CellInfoGsm) cellInfo);
             } else if (cellInfo instanceof CellInfoCdma) {
                 verifyCdmaInfo((CellInfoCdma) cellInfo);
+            } else if (cellInfo instanceof CellInfoNr) {
+                verifyNrInfo((CellInfoNr) cellInfo);
             }
         }
 
@@ -188,7 +191,7 @@
 
         CellInfoCdma newCi = CellInfoCdma.CREATOR.createFromParcel(p);
         assertTrue(cdma.equals(newCi));
-        assertEquals("hashCode() did not get right hasdCode", cdma.hashCode(), newCi.hashCode());
+        assertEquals("hashCode() did not get right hashCode", cdma.hashCode(), newCi.hashCode());
     }
 
     private void verifyCellIdentityCdma(CellIdentityCdma cdma) {
@@ -297,7 +300,6 @@
         // mncStr must either be null or match mnc integer.
         assertTrue("MncString must match Mnc Integer, str=" + mncStr + " int=" + mnc,
                 mncStr == null || mnc == Integer.parseInt(mncStr));
-
     }
 
     // Verify lte cell information is within correct range.
@@ -310,6 +312,81 @@
         verifyCellSignalStrengthLteParcel(lte.getCellSignalStrength());
     }
 
+    // Verify NR 5G cell information is within correct range.
+    private void verifyNrInfo(CellInfoNr nr) {
+        verifyCellConnectionStatus(nr.getCellConnectionStatus());
+        verifyCellIdentityNr((CellIdentityNr) nr.getCellIdentity());
+        verifyCellIdentityNrParcel((CellIdentityNr) nr.getCellIdentity());
+        verifyCellSignalStrengthNr((CellSignalStrengthNr) nr.getCellSignalStrength());
+        verifyCellSignalStrengthNrParcel((CellSignalStrengthNr) nr.getCellSignalStrength());
+    }
+
+    private void verifyCellSignalStrengthNrParcel(CellSignalStrengthNr nr) {
+        Parcel p = Parcel.obtain();
+        nr.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        CellSignalStrengthNr newCss = CellSignalStrengthNr.CREATOR.createFromParcel(p);
+        assertEquals(nr, newCss);
+    }
+
+    private void verifyCellIdentityNrParcel(CellIdentityNr nr) {
+        Parcel p = Parcel.obtain();
+        nr.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        CellIdentityNr newCi = CellIdentityNr.CREATOR.createFromParcel(p);
+        assertEquals(nr, newCi);
+    }
+
+    private void verifyCellIdentityNr(CellIdentityNr nr) {
+        int pci = nr.getPci();
+        assertTrue("getPci() out of range [0, 1007], pci = " + pci, 0 <= pci && pci <= 1007);
+
+        int tac = nr.getTac();
+        assertTrue("getTac() out of range [0, 65536], tac = " + tac, 0 <= tac && tac <= 65536);
+
+        int channelNumber = nr.getChannelNumber();
+        assertTrue("getChannelNumber() out of range [0, 3279165], channelNumber = " + channelNumber,
+                0 <= channelNumber && channelNumber <= 3279165);
+
+        String mccStr = nr.getMccString();
+        String mncStr = nr.getMncString();
+        // mccStr is set as NULL if empty, unknown or invalid.
+        assertTrue("getMccString() out of range [0, 999], mcc=" + mccStr,
+                mccStr == null || mccStr.matches("^[0-9]{3}$"));
+
+        // mncStr is set as NULL if empty, unknown or invalid.
+        assertTrue("getMncString() out of range [0, 999], mnc=" + mncStr,
+                mncStr == null || mncStr.matches("^[0-9]{2,3}$"));
+
+        assertNotNull("getOperatorAlphaLong() returns NULL!", nr.getOperatorAlphaLong());
+        assertNotNull("getOperatorAlphaShort() returns NULL!", nr.getOperatorAlphaShort());
+    }
+
+    private void verifyCellSignalStrengthNr(CellSignalStrengthNr nr) {
+        int csiRsrp = nr.getCsiRsrp();
+        int csiRsrq = nr.getCsiRsrq();
+        int csiSinr = nr.getSsSinr();
+        int ssRsrp = nr.getSsRsrp();
+        int ssRsrq = nr.getSsRsrq();
+        int ssSinr = nr.getSsSinr();
+
+        assertTrue("getCsiRsrp() out of range [-140, -44] | Integer.MAX_INTEGER, csiRsrp = "
+                        + csiRsrp, -140 <= csiRsrp && csiRsrp <= -44
+                || csiRsrp == Integer.MAX_VALUE);
+        assertTrue("getCsiRsrq() out of range [-20, -3] | Integer.MAX_INTEGER, csiRsrq = "
+                + csiRsrq, -20 <= csiRsrq && csiRsrq <= -3 || csiRsrq == Integer.MAX_VALUE);
+        assertTrue("getCsiSinr() out of range [-23, 40] | Integer.MAX_INTEGER, csiSinr = "
+                + csiSinr, -23 <= csiSinr && csiSinr <= 40 || csiSinr == Integer.MAX_VALUE);
+        assertTrue("getSsRsrp() out of range [-140, -44] | Integer.MAX_INTEGER, ssRsrp = "
+                        + ssRsrp, -140 <= ssRsrp && ssRsrp <= -44 || ssRsrp == Integer.MAX_VALUE);
+        assertTrue("getSsRsrq() out of range [-20, -3] | Integer.MAX_INTEGER, ssRsrq = "
+                + ssRsrq, -20 <= ssRsrq && ssRsrq <= -3 || ssRsrq == Integer.MAX_VALUE);
+        assertTrue("getSsSinr() out of range [-23, 40] | Integer.MAX_INTEGER, ssSinr = "
+                + ssSinr, -23 <= ssSinr && ssSinr <= 40 || ssSinr == Integer.MAX_VALUE);
+    }
+
     private void verifyCellInfoLteParcelandHashcode(CellInfoLte lte) {
         Parcel p = Parcel.obtain();
         lte.writeToParcel(p, 0);
@@ -317,7 +394,7 @@
 
         CellInfoLte newCi = CellInfoLte.CREATOR.createFromParcel(p);
         assertTrue(lte.equals(newCi));
-        assertEquals("hashCode() did not get right hasdCode", lte.hashCode(), newCi.hashCode());
+        assertEquals("hashCode() did not get right hashCode", lte.hashCode(), newCi.hashCode());
     }
 
     private void verifyCellIdentityLte(CellIdentityLte lte) {
@@ -457,7 +534,7 @@
 
         CellInfoWcdma newCi = CellInfoWcdma.CREATOR.createFromParcel(p);
         assertTrue(wcdma.equals(newCi));
-        assertEquals("hashCode() did not get right hasdCode", wcdma.hashCode(), newCi.hashCode());
+        assertEquals("hashCode() did not get right hashCode", wcdma.hashCode(), newCi.hashCode());
     }
 
     private void verifyCellIdentityWcdma(CellIdentityWcdma wcdma) {
@@ -547,7 +624,7 @@
 
         CellInfoGsm newCi = CellInfoGsm.CREATOR.createFromParcel(p);
         assertTrue(gsm.equals(newCi));
-        assertEquals("hashCode() did not get right hasdCode", gsm.hashCode(), newCi.hashCode());
+        assertEquals("hashCode() did not get right hashCode", gsm.hashCode(), newCi.hashCode());
     }
 
     private void verifyCellIdentityGsm(CellIdentityGsm gsm) {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index 7745497..ada3d81 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -20,18 +20,25 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
+import android.telephony.PreciseCallState;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.net.ConnectivityManager;
+import android.telephony.ims.ImsReasonInfo;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.TestThread;
+import static com.google.common.truth.Truth.assertThat;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.Executor;
 
-public class PhoneStateListenerTest extends  AndroidTestCase{
+public class PhoneStateListenerTest extends AndroidTestCase{
 
     public static final long WAIT_TIME = 1000;
 
@@ -46,12 +53,48 @@
     private boolean mOnCellInfoChangedCalled;
     private boolean mOnServiceStateChangedCalled;
     private boolean mOnSignalStrengthChangedCalled;
+    private boolean mOnPreciseCallStateChangedCalled;
+    private boolean mOnCallDisconnectCauseChangedCalled;
+    private boolean mOnImsCallDisconnectCauseChangedCalled;
+    private boolean mOnPreciseDataConnectionStateChanged;
+    private boolean mOnRadioPowerStateChangedCalled;
+    private boolean mVoiceActivationStateChangedCalled;
+    private boolean mSrvccStateChangedCalled;
+    @TelephonyManager.RadioPowerState private int mRadioPowerState;
+    @TelephonyManager.SimActivationState private int mVoiceActivationState;
+    private PreciseDataConnectionState mPreciseDataConnectionState;
+    private PreciseCallState mPreciseCallState;
     private SignalStrength mSignalStrength;
     private TelephonyManager mTelephonyManager;
     private PhoneStateListener mListener;
     private final Object mLock = new Object();
     private static final String TAG = "android.telephony.cts.PhoneStateListenerTest";
     private static ConnectivityManager mCm;
+    private static final List<Integer> DATA_CONNECTION_STATE = Arrays.asList(
+            TelephonyManager.DATA_CONNECTED,
+            TelephonyManager.DATA_DISCONNECTED,
+            TelephonyManager.DATA_CONNECTING,
+            TelephonyManager.DATA_UNKNOWN,
+            TelephonyManager.DATA_SUSPENDED
+    );
+    private static final List<Integer> PRECISE_CALL_STATE = Arrays.asList(
+            PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
+            PreciseCallState.PRECISE_CALL_STATE_ALERTING,
+            PreciseCallState.PRECISE_CALL_STATE_DIALING,
+            PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
+            PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING,
+            PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+            PreciseCallState.PRECISE_CALL_STATE_IDLE,
+            PreciseCallState.PRECISE_CALL_STATE_INCOMING,
+            PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
+            PreciseCallState.PRECISE_CALL_STATE_WAITING
+    );
+    private Executor mSimpleExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
 
     @Override
     protected void setUp() throws Exception {
@@ -114,8 +157,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnServiceStateChangedCalled){
-                mLock.wait();
+            if (!mOnServiceStateChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -151,8 +194,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnSignalStrengthChangedCalled){
-                mLock.wait();
+            if (!mOnSignalStrengthChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -188,8 +231,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(mSignalStrength == null) {
-                mLock.wait();
+            if (mSignalStrength == null) {
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -237,14 +280,319 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnMessageWaitingIndicatorChangedCalled){
-                mLock.wait();
+            if (!mOnMessageWaitingIndicatorChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
         assertTrue(mOnMessageWaitingIndicatorChangedCalled);
     }
 
+    /*
+     * The tests below rely on the framework to immediately call the installed listener upon
+     * registration. There is no simple way to emulate state changes for testing the listeners.
+     */
+    public void testOnPreciseCallStateChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onPreciseCallStateChanged(PreciseCallState preciseCallState) {
+                        synchronized (mLock) {
+                            mOnPreciseCallStateChangedCalled = true;
+                            mPreciseCallState = preciseCallState;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener, PhoneStateListener.LISTEN_PRECISE_CALL_STATE));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mOnPreciseCallStateChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnPreciseCallStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        Log.d(TAG, "testOnPreciseCallStateChanged: " + mOnPreciseCallStateChangedCalled);
+        assertThat(mOnPreciseCallStateChangedCalled).isTrue();
+        assertThat(mPreciseCallState.getForegroundCallState()).isIn(PRECISE_CALL_STATE);
+        assertThat(mPreciseCallState.getBackgroundCallState()).isIn(PRECISE_CALL_STATE);
+        assertThat(mPreciseCallState.getRingingCallState()).isIn(PRECISE_CALL_STATE);
+    }
+
+    /*
+     * The tests below rely on the framework to immediately call the installed listener upon
+     * registration. There is no simple way to emulate state changes for testing the listeners.
+     */
+    public void testOnCallDisconnectCauseChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onCallDisconnectCauseChanged(int disconnectCause,
+                                                             int preciseDisconnectCause) {
+                        synchronized (mLock) {
+                            mOnCallDisconnectCauseChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                            (tm) -> tm.listen(mListener,
+                                    PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnCallDisconnectCauseChangedCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertThat(mOnCallDisconnectCauseChangedCalled).isTrue();
+    }
+
+    /*
+     * The tests below rely on the framework to immediately call the installed listener upon
+     * registration. There is no simple way to emulate state changes for testing the listeners.
+     */
+    public void testOnImsCallDisconnectCauseChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onImsCallDisconnectCauseChanged(ImsReasonInfo imsReason) {
+                        synchronized (mLock) {
+                            mOnImsCallDisconnectCauseChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mOnImsCallDisconnectCauseChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnImsCallDisconnectCauseChangedCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertThat(mOnImsCallDisconnectCauseChangedCalled).isTrue();
+    }
+
+    public void testOnPhoneStateListenerExecutorWithSrvccChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener(mSimpleExecutor) {
+                    @Override
+                    public void onSrvccStateChanged(int state) {
+                        synchronized (mLock) {
+                            mSrvccStateChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mSrvccStateChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mSrvccStateChangedCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        assertThat(mSrvccStateChangedCalled).isTrue();
+        t.checkException();
+        Log.d(TAG, "testOnPhoneStateListenerExecutorWithSrvccChanged");
+    }
+
+    /*
+    * The tests below rely on the framework to immediately call the installed listener upon
+    * registration. There is no simple way to emulate state changes for testing the listeners.
+    */
+    public void testOnRadioPowerStateChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onRadioPowerStateChanged(int state) {
+                        synchronized(mLock) {
+                            mRadioPowerState = state;
+                            mOnRadioPowerStateChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED));
+                Looper.loop();
+            }
+        });
+        assertThat(mOnRadioPowerStateChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnRadioPowerStateChangedCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        Log.d(TAG, "testOnRadioPowerStateChanged: " + mRadioPowerState);
+        assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(mRadioPowerState);
+    }
+
+    /*
+     * The tests below rely on the framework to immediately call the installed listener upon
+     * registration. There is no simple way to emulate state changes for testing the listeners.
+     */
+    public void testOnVoiceActivationStateChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onVoiceActivationStateChanged(int state) {
+                        synchronized(mLock) {
+                            mVoiceActivationState = state;
+                            mVoiceActivationStateChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE));
+                Looper.loop();
+            }
+        });
+        assertThat(mVoiceActivationStateChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mVoiceActivationStateChangedCalled){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        Log.d(TAG, "onVoiceActivationStateChanged: " + mVoiceActivationState);
+        int state = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getVoiceActivationState());
+        assertEquals(state, mVoiceActivationState);
+    }
+
+    /*
+    * The tests below rely on the framework to immediately call the installed listener upon
+    * registration. There is no simple way to emulate state changes for testing the listeners.
+    */
+    public void testOnPreciseDataConnectionStateChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onPreciseDataConnectionStateChanged(
+                            PreciseDataConnectionState state) {
+                        synchronized(mLock) {
+                            mOnPreciseDataConnectionStateChanged = true;
+                            mPreciseDataConnectionState = state;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnPreciseDataConnectionStateChanged){
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertThat(mOnPreciseDataConnectionStateChanged).isTrue();
+        assertThat(mPreciseDataConnectionState.getDataConnectionState())
+                .isIn(DATA_CONNECTION_STATE);
+        // basic test to verify there is no exception thrown.
+        mPreciseDataConnectionState.getDataConnectionApnTypeBitMask();
+        mPreciseDataConnectionState.getDataConnectionApn();
+        mPreciseDataConnectionState.getDataConnectionFailCause();
+    }
+
     public void testOnCallForwardingIndicatorChanged() throws Throwable {
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
@@ -276,8 +624,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnCallForwardingIndicatorChangedCalled){
-                mLock.wait();
+            if (!mOnCallForwardingIndicatorChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -313,8 +661,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnCellLocationChangedCalled){
-                mLock.wait();
+            if (!mOnCellLocationChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -350,8 +698,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnCallStateChangedCalled){
-                mLock.wait();
+            if (!mOnCallStateChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -396,9 +744,9 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnDataConnectionStateChangedCalled ||
+            if (!mOnDataConnectionStateChangedCalled ||
                     !mOnDataConnectionStateChangedWithNetworkTypeCalled){
-                mLock.wait();
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -435,8 +783,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnDataActivityCalled){
-                mLock.wait();
+            if (!mOnDataActivityCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -472,8 +820,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnCellInfoChangedCalled){
-                mLock.wait();
+            if (!mOnCellInfoChangedCalled){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
@@ -510,8 +858,8 @@
         t.start();
 
         synchronized (mLock) {
-            while(!mOnUserMobileDataStateChanged){
-                mLock.wait();
+            if (!mOnUserMobileDataStateChanged){
+                mLock.wait(WAIT_TIME);
             }
         }
         t.checkException();
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 74f9d13..5e3c48e 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -16,37 +16,60 @@
 
 package android.telephony.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.CursorWindow;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
 import android.os.SystemClock;
 import android.provider.BlockedNumberContract;
+import android.provider.Settings;
 import android.provider.Telephony;
+import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.SmsApplication;
+
 import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.StringBuilder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for {@link android.telephony.SmsManager}.
@@ -71,6 +94,11 @@
     private static final String SMS_DELIVERY_ACTION = "CTS_SMS_DELIVERY_ACTION";
     private static final String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
     public static final String SMS_DELIVER_DEFAULT_APP_ACTION = "CTS_SMS_DELIVERY_ACTION_DEFAULT_APP";
+    public static final String LEGACY_SMS_APP = "android.telephony.cts.sms23";
+    public static final String MODERN_SMS_APP = "android.telephony.cts.sms";
+    private static final String SMS_RETRIEVER_APP = "android.telephony.cts.smsretriever";
+    private static final String SMS_RETRIEVER_ACTION = "CTS_SMS_RETRIEVER_ACTION";
+    private static final String FINANCIAL_SMS_APP = "android.telephony.cts.financialsms";
 
     private TelephonyManager mTelephonyManager;
     private PackageManager mPackageManager;
@@ -81,6 +109,7 @@
     private SmsBroadcastReceiver mDataSmsReceiver;
     private SmsBroadcastReceiver mSmsDeliverReceiver;
     private SmsBroadcastReceiver mSmsReceivedReceiver;
+    private SmsBroadcastReceiver mSmsRetrieverReceiver;
     private PendingIntent mSentIntent;
     private PendingIntent mDeliveredIntent;
     private Intent mSendIntent;
@@ -91,8 +120,9 @@
     private boolean mDeliveryReportSupported;
     private static boolean mReceivedDataSms;
     private static String mReceivedText;
+    private static boolean sHasShellPermissionIdentity = false;
 
-    private static final int TIME_OUT = 1000 * 60 * 5;
+    private static final int TIME_OUT = 1000 * 60 * 4;
     private static final int NO_CALLS_TIMEOUT_MILLIS = 1000; // 1 second
 
     @Override
@@ -162,6 +192,39 @@
         return longText.equals(actualMessage);
     }
 
+    public void testSmsRetriever() throws Exception {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
+                TextUtils.isEmpty(mDestAddr));
+
+        String mccmnc = mTelephonyManager.getSimOperator();
+        setupBroadcastReceivers();
+        init();
+
+        CompletableFuture<Bundle> callbackResult = new CompletableFuture<>();
+
+        mContext.startActivity(new Intent()
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setComponent(new ComponentName(
+                        SMS_RETRIEVER_APP, SMS_RETRIEVER_APP + ".MainActivity"))
+                .putExtra("callback", new RemoteCallback(callbackResult::complete)));
+
+
+        Bundle bundle = callbackResult.get(200, TimeUnit.SECONDS);
+        String token = bundle.getString("token");
+        assertThat(bundle.getString("class"), startsWith(SMS_RETRIEVER_APP));
+        assertNotNull(token);
+
+        String composedText = "testprefix1" + mText + token;
+        sendTextMessage(mDestAddr, composedText, null, null);
+
+        assertTrue("[RERUN] SMS retriever message not received. Check signal.",
+                mSmsRetrieverReceiver.waitForCalls(1, TIME_OUT));
+    }
+
     public void testSendAndReceiveMessages() throws Exception {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -283,6 +346,57 @@
         }
     }
 
+    public void testGetSmsMessagesForFinancialAppPermissionNotRequested() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        try {
+            getSmsManager().getSmsMessagesForFinancialApp(new Bundle(),
+                    getInstrumentation().getContext().getMainExecutor(),
+                    new SmsManager.FinancialSmsCallback() {
+                        public void onFinancialSmsMessages(CursorWindow msgs) {
+                            assertNull(msgs);
+                            latch.countDown();
+                    }});
+            assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+        } catch (Exception e) {
+            // do nothing
+        }
+    }
+
+    public void testGetSmsMessagesForFinancialAppPermissionRequestedNotGranted() throws Exception {
+        CompletableFuture<Bundle> callbackResult = new CompletableFuture<>();
+
+        mContext.startActivity(new Intent()
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setComponent(new ComponentName(FINANCIAL_SMS_APP, FINANCIAL_SMS_APP + ".MainActivity"))
+                .putExtra("callback", new RemoteCallback(callbackResult::complete)));
+
+        Bundle bundle = callbackResult.get(500, TimeUnit.SECONDS);
+
+        assertThat(bundle.getString("class"), startsWith(FINANCIAL_SMS_APP));
+        assertThat(bundle.getInt("rowNum"), equalTo(-1));
+    }
+
+    public void testGetSmsMessagesForFinancialAppPermissionRequestedGranted() throws Exception {
+        CompletableFuture<Bundle> callbackResult = new CompletableFuture<>();
+        String ctsPackageName = getInstrumentation().getContext().getPackageName();
+
+        executeWithShellPermissionIdentity(() -> {
+            setModeForOps(FINANCIAL_SMS_APP,
+                    AppOpsManager.MODE_ALLOWED,
+                    AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS);
+            });
+        mContext.startActivity(new Intent()
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setComponent(new ComponentName(FINANCIAL_SMS_APP, FINANCIAL_SMS_APP + ".MainActivity"))
+                .putExtra("callback", new RemoteCallback(callbackResult::complete)));
+
+
+        Bundle bundle = callbackResult.get(500, TimeUnit.SECONDS);
+
+        assertThat(bundle.getString("class"), startsWith(FINANCIAL_SMS_APP));
+        assertThat(bundle.getInt("rowNum"), equalTo(-1));
+    }
 
     public void testSmsNotPersisted_failsWithoutCarrierPermissions() throws Exception {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
@@ -301,12 +415,174 @@
         }
     }
 
+    public void testContentProviderAccessRestriction() throws Exception {
+        Uri dummySmsUri = null;
+        Context context = getInstrumentation().getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        int originalWriteSmsMode = -1;
+        String ctsPackageName = context.getPackageName();
+        try {
+            // Insert some dummy sms
+            originalWriteSmsMode = context.getSystemService(AppOpsManager.class)
+                    .unsafeCheckOpNoThrow(AppOpsManager.OPSTR_WRITE_SMS,
+                            getPackageUid(ctsPackageName), ctsPackageName);
+            dummySmsUri = executeWithShellPermissionIdentity(() -> {
+                setModeForOps(ctsPackageName,
+                        AppOpsManager.MODE_ALLOWED, AppOpsManager.OPSTR_WRITE_SMS);
+                ContentValues contentValues = new ContentValues();
+                contentValues.put(Telephony.TextBasedSmsColumns.ADDRESS, "addr");
+                contentValues.put(Telephony.TextBasedSmsColumns.READ, 1);
+                contentValues.put(Telephony.TextBasedSmsColumns.SUBJECT, "subj");
+                contentValues.put(Telephony.TextBasedSmsColumns.BODY, "created_at_" +
+                        new Date().toString().replace(" ", "_"));
+                int originalSmsAccessRestrictionEnabled = getSmsAccessRestrictionEnabled();
+                setSmsAccessRestrictionEnabled(0);
+                try {
+                    return contentResolver.insert(Telephony.Sms.CONTENT_URI, contentValues);
+                } finally {
+                    setSmsAccessRestrictionEnabled(originalSmsAccessRestrictionEnabled);
+                }
+            });
+            assertNotNull("Failed to insert dummy sms", dummySmsUri);
+            assertNotEquals("Failed to insert dummy sms", dummySmsUri.getLastPathSegment(), "0");
+            testContentProviderAccessRestriction(false);
+            testContentProviderAccessRestriction(true);
+        } finally {
+            if (dummySmsUri != null && !"/0".equals(dummySmsUri.getLastPathSegment())) {
+                final Uri finalDummySmsUri = dummySmsUri;
+                executeWithShellPermissionIdentity(() -> contentResolver.delete(finalDummySmsUri,
+                        null, null));
+            }
+            if (originalWriteSmsMode >= 0) {
+                int finalOriginalWriteSmsMode = originalWriteSmsMode;
+                executeWithShellPermissionIdentity(() ->
+                        setModeForOps(ctsPackageName,
+                                finalOriginalWriteSmsMode, AppOpsManager.OPSTR_WRITE_SMS));
+            }
+        }
+    }
+
+    private void testContentProviderAccessRestriction(boolean accessRestrictionEnabled)
+            throws Exception {
+        int originalSmsAccessRestrictionEnabled = getSmsAccessRestrictionEnabled();
+        setSmsAccessRestrictionEnabled(accessRestrictionEnabled ? 1 : 0);
+        try {
+            testSmsAccessAboutDefaultApp(LEGACY_SMS_APP, accessRestrictionEnabled);
+            testSmsAccessAboutDefaultApp(MODERN_SMS_APP, accessRestrictionEnabled);
+        } finally {
+            setSmsAccessRestrictionEnabled(originalSmsAccessRestrictionEnabled);
+        }
+    }
+
+    private int getSmsAccessRestrictionEnabled() {
+        return Settings.Global.getInt(getInstrumentation().getContext().getContentResolver(),
+                Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0);
+    }
+
+    private void setSmsAccessRestrictionEnabled(int enabled) throws Exception {
+        executeWithShellPermissionIdentity(() ->
+                Settings.Global.putInt(getInstrumentation().getContext().getContentResolver(),
+                        Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, enabled));
+    }
+
+    private void testSmsAccessAboutDefaultApp(String pkg, boolean accessRestrictionEnabled)
+            throws Exception {
+        String originalSmsApp = Settings.Secure.getString(
+                getInstrumentation().getContext().getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION);
+        assertNotEquals(pkg, originalSmsApp);
+        assertCanAccessSms(pkg, !accessRestrictionEnabled);
+        try {
+            setSmsApp(pkg);
+            assertCanAccessSms(pkg, true);
+        } finally {
+            resetReadWriteSmsAppOps(pkg);
+            setSmsApp(originalSmsApp);
+        }
+    }
+
+    private void resetReadWriteSmsAppOps(String pkg) throws Exception {
+        setModeForOps(pkg, AppOpsManager.MODE_DEFAULT,
+                AppOpsManager.OPSTR_READ_SMS, AppOpsManager.OPSTR_WRITE_SMS);
+    }
+
+    private void setModeForOps(String pkg, int mode, String... ops) throws Exception {
+        // We cannot reset these app ops to DEFAULT via current API, so we reset them manually here
+        // temporarily as we will rewrite how the default SMS app is setup later.
+        executeWithShellPermissionIdentity(() -> {
+            int uid = getPackageUid(pkg);
+            AppOpsManager appOpsManager =
+                    getInstrumentation().getContext().getSystemService(AppOpsManager.class);
+            for (String op : ops) {
+                appOpsManager.setUidMode(op, uid, mode);
+            }
+        });
+    }
+
+    private int getPackageUid(String pkg) throws PackageManager.NameNotFoundException {
+        return getInstrumentation().getContext().getPackageManager().getPackageUid(pkg, 0);
+    }
+
+    private void setSmsApp(String pkg) throws Exception {
+        executeWithShellPermissionIdentity(() -> {
+            Context context = getInstrumentation().getContext();
+            Settings.Secure.putString(context.getContentResolver(),
+                    Settings.Secure.SMS_DEFAULT_APPLICATION, pkg);
+            // Modifying settings by-passes SmsApplication, so we try to fix it with this.
+            SmsApplication.getDefaultSmsApplication(context, true);
+        });
+    }
+
+    private <T> T executeWithShellPermissionIdentity(Callable<T> callable) throws Exception {
+        if (sHasShellPermissionIdentity) {
+            return callable.call();
+        }
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        uiAutomation.adoptShellPermissionIdentity();
+        try {
+            sHasShellPermissionIdentity = true;
+            return callable.call();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+            sHasShellPermissionIdentity = false;
+        }
+    }
+
+    private void executeWithShellPermissionIdentity(RunnableWithException runnable)
+            throws Exception {
+        executeWithShellPermissionIdentity(() -> {
+            runnable.run();
+            return null;
+        });
+    }
+
+    private interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    private void assertCanAccessSms(String pkg, boolean expectAccess) throws Exception {
+        CompletableFuture<Bundle> callbackResult = new CompletableFuture<>();
+        mContext.startActivity(new Intent()
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setComponent(new ComponentName(pkg, pkg + ".MainActivity"))
+                .putExtra("callback", new RemoteCallback(callbackResult::complete)));
+
+        Bundle bundle = callbackResult.get(20, TimeUnit.SECONDS);
+
+        assertThat(bundle.getString("class"), startsWith(pkg));
+        assertThat(bundle.getString("exceptionMessage"), anyOf(equalTo(null), emptyString()));
+        assertThat(bundle.getInt("queryCount"),
+                expectAccess ? greaterThan(0) : lessThanOrEqualTo(0));
+    }
+
     private void init() {
         mSendReceiver.reset();
         mDeliveryReceiver.reset();
         mDataSmsReceiver.reset();
         mSmsDeliverReceiver.reset();
         mSmsReceivedReceiver.reset();
+        mSmsRetrieverReceiver.reset();
         mReceivedDataSms = false;
         mSentIntent = PendingIntent.getBroadcast(mContext, 0, mSendIntent,
                 PendingIntent.FLAG_ONE_SHOT);
@@ -324,6 +600,7 @@
         IntentFilter smsDeliverIntentFilter = new IntentFilter(SMS_DELIVER_DEFAULT_APP_ACTION);
         IntentFilter smsReceivedIntentFilter =
                 new IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION);
+        IntentFilter smsRetrieverIntentFilter = new IntentFilter(SMS_RETRIEVER_ACTION);
         dataSmsReceivedIntentFilter.addDataScheme("sms");
         dataSmsReceivedIntentFilter.addDataAuthority("localhost", "19989");
 
@@ -332,12 +609,14 @@
         mDataSmsReceiver = new SmsBroadcastReceiver(DATA_SMS_RECEIVED_ACTION);
         mSmsDeliverReceiver = new SmsBroadcastReceiver(SMS_DELIVER_DEFAULT_APP_ACTION);
         mSmsReceivedReceiver = new SmsBroadcastReceiver(Telephony.Sms.Intents.SMS_RECEIVED_ACTION);
+        mSmsRetrieverReceiver = new SmsBroadcastReceiver(SMS_RETRIEVER_ACTION);
 
         mContext.registerReceiver(mSendReceiver, sendIntentFilter);
         mContext.registerReceiver(mDeliveryReceiver, deliveryIntentFilter);
         mContext.registerReceiver(mDataSmsReceiver, dataSmsReceivedIntentFilter);
         mContext.registerReceiver(mSmsDeliverReceiver, smsDeliverIntentFilter);
         mContext.registerReceiver(mSmsReceivedReceiver, smsReceivedIntentFilter);
+        mContext.registerReceiver(mSmsRetrieverReceiver, smsRetrieverIntentFilter);
     }
 
     /**
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 43fd5d6..bb5857e 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -43,6 +43,7 @@
 import android.telephony.SubscriptionPlan;
 
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.internal.util.ArrayUtils;
 
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -144,12 +145,32 @@
     @Test
     public void testGetActiveSubscriptionInfoCount() throws Exception {
         if (!isSupported()) return;
-
         assertTrue(mSm.getActiveSubscriptionInfoCount() <=
                 mSm.getActiveSubscriptionInfoCountMax());
     }
 
     @Test
+    public void testIsActiveSubscriptionId() throws Exception {
+        if (!isSupported()) return;
+        assertTrue(mSm.isActiveSubscriptionId(mSubId));
+    }
+
+    @Test
+    public void testGetSubscriptionIds() throws Exception {
+        if (!isSupported()) return;
+        int slotId = SubscriptionManager.getSlotIndex(mSubId);
+        int[] subIds = mSm.getSubscriptionIds(slotId);
+        assertNotNull(subIds);
+        assertTrue(ArrayUtils.contains(subIds, mSubId));
+    }
+
+    @Test
+    public void testIsUsableSubscriptionId() throws Exception {
+        if (!isSupported()) return;
+        assertTrue(SubscriptionManager.isUsableSubIdValue(mSubId));
+    }
+
+    @Test
     public void testActiveSubscriptions() 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 10efcd3..7081b8b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -34,26 +34,34 @@
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CellLocation;
+import android.telephony.NetworkRegistrationState;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.TestThread;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 /**
@@ -64,20 +72,53 @@
 @RunWith(AndroidJUnit4.class)
 public class TelephonyManagerTest {
     private TelephonyManager mTelephonyManager;
+    private SubscriptionManager mSubscriptionManager;
     private PackageManager mPackageManager;
     private boolean mOnCellLocationChangedCalled = false;
+    private boolean mServiceStateChangedCalled = false;
+    private boolean mRadioRebootTriggered = false;
+    private boolean mHasRadioPowerOff = false;
     private ServiceState mServiceState;
     private final Object mLock = new Object();
     private static final int TOLERANCE = 1000;
     private PhoneStateListener mListener;
     private static ConnectivityManager mCm;
     private static final String TAG = "TelephonyManagerTest";
+    private static final List<Integer> ROAMING_TYPES = Arrays.asList(
+            ServiceState.ROAMING_TYPE_DOMESTIC,
+            ServiceState.ROAMING_TYPE_INTERNATIONAL,
+            ServiceState.ROAMING_TYPE_NOT_ROAMING,
+            ServiceState.ROAMING_TYPE_UNKNOWN);
+    private static final List<Integer> NETWORK_TYPES = Arrays.asList(
+            TelephonyManager.NETWORK_TYPE_UNKNOWN,
+            TelephonyManager.NETWORK_TYPE_GPRS,
+            TelephonyManager.NETWORK_TYPE_EDGE,
+            TelephonyManager.NETWORK_TYPE_UMTS,
+            TelephonyManager.NETWORK_TYPE_CDMA,
+            TelephonyManager.NETWORK_TYPE_EVDO_0,
+            TelephonyManager.NETWORK_TYPE_EVDO_A,
+            TelephonyManager.NETWORK_TYPE_1xRTT,
+            TelephonyManager.NETWORK_TYPE_HSDPA,
+            TelephonyManager.NETWORK_TYPE_HSUPA,
+            TelephonyManager.NETWORK_TYPE_HSPA,
+            TelephonyManager.NETWORK_TYPE_IDEN,
+            TelephonyManager.NETWORK_TYPE_EVDO_B,
+            TelephonyManager.NETWORK_TYPE_LTE,
+            TelephonyManager.NETWORK_TYPE_EHRPD,
+            TelephonyManager.NETWORK_TYPE_HSPAP,
+            TelephonyManager.NETWORK_TYPE_GSM,
+            TelephonyManager.NETWORK_TYPE_TD_SCDMA,
+            TelephonyManager.NETWORK_TYPE_IWLAN,
+            TelephonyManager.NETWORK_TYPE_LTE_CA,
+            TelephonyManager.NETWORK_TYPE_NR);
 
     @Before
     public void setUp() throws Exception {
         mTelephonyManager =
                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
         mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        mSubscriptionManager = (SubscriptionManager) getContext()
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mPackageManager = getContext().getPackageManager();
     }
 
@@ -201,21 +242,28 @@
         mTelephonyManager.getSimPreciseCarrierId();
         mTelephonyManager.getSimPreciseCarrierIdName();
         mTelephonyManager.getCarrierIdFromSimMccMnc();
-        mTelephonyManager.getSimSerialNumber();
+        mTelephonyManager.isDataRoamingEnabled();
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getSimSerialNumber());
         mTelephonyManager.getSimOperator();
         mTelephonyManager.getSignalStrength();
         mTelephonyManager.getNetworkOperatorName();
-        mTelephonyManager.getSubscriberId();
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getSubscriberId());
         mTelephonyManager.getLine1Number();
         mTelephonyManager.getNetworkOperator();
         mTelephonyManager.getSimCountryIso();
         mTelephonyManager.getVoiceMailAlphaTag();
         mTelephonyManager.isNetworkRoaming();
-        mTelephonyManager.getDeviceId();
-        mTelephonyManager.getDeviceId(mTelephonyManager.getSlotIndex());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getDeviceId());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getDeviceId(mTelephonyManager.getSlotIndex()));
         mTelephonyManager.getDeviceSoftwareVersion();
-        mTelephonyManager.getImei();
-        mTelephonyManager.getImei(mTelephonyManager.getSlotIndex());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getImei());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getImei(mTelephonyManager.getSlotIndex()));
         mTelephonyManager.getPhoneCount();
         mTelephonyManager.getDataEnabled();
         mTelephonyManager.getNetworkSpecifier();
@@ -239,7 +287,11 @@
         PhoneAccountHandle handle =
                 telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
         TelephonyManager telephonyManager = mTelephonyManager.createForPhoneAccountHandle(handle);
-        assertEquals(mTelephonyManager.getSubscriberId(), telephonyManager.getSubscriberId());
+        String globalSubscriberId = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.getSubscriberId());
+        String localSubscriberId = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                telephonyManager, (tm) -> tm.getSubscriberId());
+        assertEquals(globalSubscriberId, localSubscriberId);
     }
 
     @Test
@@ -275,7 +327,9 @@
      */
     @Test
     public void testGetDeviceId() {
-        verifyDeviceId(mTelephonyManager.getDeviceId());
+        String deviceId = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getDeviceId());
+        verifyDeviceId(deviceId);
     }
 
     /**
@@ -284,11 +338,16 @@
      */
     @Test
     public void testGetDeviceIdForSlot() {
-        String deviceId = mTelephonyManager.getDeviceId(mTelephonyManager.getSlotIndex());
+        String deviceId = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getDeviceId(mTelephonyManager.getSlotIndex()));
         verifyDeviceId(deviceId);
         // Also verify that no exception is thrown for any slot index (including invalid ones)
         for (int i = -1; i <= mTelephonyManager.getPhoneCount(); i++) {
-            mTelephonyManager.getDeviceId(i);
+            // The compiler error 'local variables referenced from a lambda expression must be final
+            // or effectively final' is reported when using i, so assign it to a final variable.
+            final int currI = i;
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.getDeviceId(currI));
         }
     }
 
@@ -503,12 +562,29 @@
         assertEquals(mServiceState, mTelephonyManager.getServiceState());
     }
 
+    @Test
+    public void testGetSimLocale() throws InterruptedException {
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG,"skipping test that requires Telephony");
+            return;
+        }
+        if (SubscriptionManager.getDefaultSubscriptionId()
+                == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            fail("Expected SIM inserted");
+        }
+        String locale = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getSimLocale());
+        Log.d(TAG, "testGetSimLocale: " + locale);
+        assertNotNull(locale);
+    }
+
     /**
      * Tests that the device properly reports either a valid IMEI or null.
      */
     @Test
     public void testGetImei() {
-        String imei = mTelephonyManager.getImei();
+        String imei = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getImei());
 
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
@@ -527,23 +603,251 @@
         }
 
         for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
-            String imei = mTelephonyManager.getImei(i);
+            // The compiler error 'local variables referenced from a lambda expression must be final
+            // or effectively final' is reported when using i, so assign it to a final variable.
+            final int currI = i;
+            String imei = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.getImei(currI));
             if (!TextUtils.isEmpty(imei)) {
                 assertImei(imei);
             }
         }
 
         // Also verify that no exception is thrown for any slot index (including invalid ones)
-        mTelephonyManager.getImei(-1);
-        mTelephonyManager.getImei(mTelephonyManager.getPhoneCount());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getImei(-1));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getImei(mTelephonyManager.getPhoneCount()));
     }
 
     /**
+     * Verifies that {@link TelephonyManager#getRadioPowerState()} does not throw any exception
+     * and returns radio on.
+     */
+    @Test
+    public void testGetRadioPowerState() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        // Also verify that no exception is thrown.
+        assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(
+                TelephonyManager.RADIO_POWER_ON);
+    }
+
+    /**
+     * Verifies that {@link TelephonyManager#setCarrierDataEnabled(boolean)} does not throw any
+     * exception. TODO enhance later if we have an API to get data enabled state.
+     */
+    @Test
+    public void testSetCarrierDataEnabled() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // Also verify that no exception is thrown.
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                (tm) -> tm.setCarrierDataEnabled(false));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                (tm) -> tm.setCarrierDataEnabled(true));
+    }
+
+    /**
+     * Verifies that {@link TelephonyManager#rebootRadio()} does not throw any exception
+     * and final radio state is radio power on.
+     */
+    @Test
+    public void testRebootRadio() throws Throwable {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        assertEquals(mTelephonyManager.getServiceState().getState(), ServiceState.STATE_IN_SERVICE);
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onRadioPowerStateChanged(
+                            @TelephonyManager.RadioPowerState int state) {
+                        synchronized (mLock) {
+                            if (state == TelephonyManager.RADIO_POWER_ON && mHasRadioPowerOff) {
+                                mRadioRebootTriggered = true;
+                                mLock.notify();
+                            } else if (state == TelephonyManager.RADIO_POWER_OFF) {
+                                // reboot must go to power off
+                                mHasRadioPowerOff = true;
+                            }
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED));
+                Looper.loop();
+            }
+        });
+
+        assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(
+                TelephonyManager.RADIO_POWER_ON);
+        assertThat(mRadioRebootTriggered).isFalse();
+        assertThat(mHasRadioPowerOff).isFalse();
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.rebootRadio());
+        t.start();
+        synchronized (mLock) {
+            // reboot takes longer time
+            if (!mRadioRebootTriggered) {
+                mLock.wait(10000);
+            }
+        }
+        assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(
+                TelephonyManager.RADIO_POWER_ON);
+        assertThat(mRadioRebootTriggered).isTrue();
+
+        // note, other telephony states might not resumes properly at this point. e.g, service state
+        // might still in the transition from OOS to In service. Thus we need to wait for in
+        // service state before running next tests.
+        t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onServiceStateChanged(ServiceState serviceState) {
+                        synchronized (mLock) {
+                            if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
+                                mServiceStateChangedCalled = true;
+                                mLock.notify();
+                            }
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE));
+                Looper.loop();
+            }
+        });
+
+        synchronized (mLock) {
+            t.start();
+            if (!mServiceStateChangedCalled) {
+                mLock.wait(10000);
+            }
+        }
+        assertThat(mTelephonyManager.getServiceState().getState()).isEqualTo(
+                ServiceState.STATE_IN_SERVICE);
+    }
+
+    /**
+     * Verifies that {@link TelephonyManager#getAidForAppType(int)} does not throw any exception
+     * for all supported subscription app type.
+     */
+    @Test
+    public void testGetAidForAppType() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_SIM));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_CSIM));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_RUIM));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_ISIM));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_USIM));
+    }
+
+    /**
+     * Verifies that {@link TelephonyManager#getIsimDomain()} does not throw any exception
+     */
+    @Test
+    public void testGetIsimDomain() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getIsimDomain());
+    }
+
+    /**
+     * Basic test to ensure {@link NetworkRegistrationState#isRoaming()} does not throw any
+     * exception.
+     */
+    @Test
+    public void testNetworkRegistrationStateIsRoaming() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // get NetworkRegistration object
+        NetworkRegistrationState nwReg = mTelephonyManager.getServiceState()
+                .getNetworkRegistrationState(NetworkRegistrationState.DOMAIN_CS,
+                        AccessNetworkConstants.TransportType.WWAN);
+        assertThat(nwReg).isNotNull();
+        nwReg.isRoaming();
+    }
+
+    /**
+     * Basic test to ensure {@link NetworkRegistrationState#getRoamingType()} ()} does not throw any
+     * exception and returns valid result
+     * @see ServiceState.RoamingType
+     */
+    @Test
+    public void testNetworkRegistrationStateGetRoamingType() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // get NetworkRegistration object for voice
+        NetworkRegistrationState nwReg = mTelephonyManager.getServiceState()
+                .getNetworkRegistrationState(NetworkRegistrationState.DOMAIN_CS,
+                        AccessNetworkConstants.TransportType.WWAN);
+        assertNotNull(nwReg);
+        assertThat(nwReg.getRoamingType()).isIn(ROAMING_TYPES);
+
+        // getNetworkRegistration object for data
+        // get NetworkRegistration object for voice
+        nwReg = mTelephonyManager.getServiceState()
+                .getNetworkRegistrationState(NetworkRegistrationState.DOMAIN_PS,
+                        AccessNetworkConstants.TransportType.WWAN);
+        assertThat(nwReg).isNotNull();
+        assertThat(nwReg.getRoamingType()).isIn(ROAMING_TYPES);
+    }
+
+    /**
+     * Basic test to ensure {@link NetworkRegistrationState#getAccessNetworkTechnology()} not
+     * throw any exception and returns valid result
+     * @see TelephonyManager.NetworkType
+     */
+    @Test
+    public void testNetworkRegistationStateGetAccessNetworkTechnology() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // get NetworkRegistration object for voice
+        NetworkRegistrationState nwReg = mTelephonyManager.getServiceState()
+                .getNetworkRegistrationState(NetworkRegistrationState.DOMAIN_CS,
+                        AccessNetworkConstants.TransportType.WWAN);
+        assertThat(nwReg).isNotNull();
+        assertThat(nwReg.getAccessNetworkTechnology()).isIn(NETWORK_TYPES);
+
+        // get NetworkRegistation object for data
+        nwReg = mTelephonyManager.getServiceState()
+                .getNetworkRegistrationState(NetworkRegistrationState.DOMAIN_PS,
+                        AccessNetworkConstants.TransportType.WWAN);
+        assertThat(nwReg).isNotNull();
+        assertThat(nwReg.getAccessNetworkTechnology()).isIn(NETWORK_TYPES);
+    }
+
+
+    /**
      * Tests that the device properly reports either a valid MEID or null.
      */
     @Test
     public void testGetMeid() {
-        String meid = mTelephonyManager.getMeid();
+        String meid = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getMeid());
 
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
@@ -571,7 +875,10 @@
                 int subId = subInfo.getSubscriptionId();
                 TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId);
                 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
-                    String meid = mTelephonyManager.getMeid(slotIndex);
+                    String meid = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                            mTelephonyManager,
+                            (telephonyManager) -> telephonyManager.getMeid(slotIndex));
+
                     if (!TextUtils.isEmpty(meid)) {
                         assertMeidEsn(meid);
                     }
@@ -580,8 +887,10 @@
         }
 
         // Also verify that no exception is thrown for any slot index (including invalid ones)
-        mTelephonyManager.getMeid(-1);
-        mTelephonyManager.getMeid(mTelephonyManager.getPhoneCount());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getMeid(-1));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getMeid(mTelephonyManager.getPhoneCount()));
     }
 
     /**
@@ -673,4 +982,65 @@
         } catch (SecurityException expected) {
         }
     }
+
+    /**
+     * Tests TelephonyManager.getCurrentEmergencyNumberList.
+     */
+    @Test
+    public void testGetCurrentEmergencyNumberList() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        Map<Integer, List<EmergencyNumber>> emergencyNumberList
+          = mTelephonyManager.getCurrentEmergencyNumberList();
+        // TODO enhance it later
+    }
+
+    /**
+     * Tests TelephonyManager.isCurrentEmergencyNumber.
+     */
+    @Test
+    public void testIsCurrentEmergencyNumber() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        boolean isEmergencyNumber = mTelephonyManager.isCurrentEmergencyNumber("911");
+        // TODO enhance it later
+    }
+
+    /**
+     * Tests TelephonyManager.isCurrentPotentialEmergencyNumber.
+     */
+    @Test
+    public void testIsCurrentPotentialEmergencyNumber() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // use shell permission to run system api
+        boolean isEmergencyNumber =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                        (tm) -> tm.isCurrentPotentialEmergencyNumber("911"));
+        // TODO enhance it later
+    }
+
+    /**
+     * Tests {@link TelephonyManager#getSupportedRadioAccessFamily()}
+     */
+    @Test
+    public void testGetRadioAccessFamily() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        long raf = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getSupportedRadioAccessFamily());
+        assertThat(raf).isNotEqualTo(TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN);
+    }
+
+    public static void waitForMs(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "InterruptedException while waiting: " + e);
+        }
+    }
 }
diff --git a/tests/tests/telephony3/Android.mk b/tests/tests/telephony3/Android.mk
new file mode 100644
index 0000000..5f98ec0
--- /dev/null
+++ b/tests/tests/telephony3/Android.mk
@@ -0,0 +1,41 @@
+# 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.
+
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsTelephony3TestCases
+# The SDK version is set to 28 to test device identifier access for apps with
+# the READ_PHONE_STATE permission targeting pre-Q.
+LOCAL_SDK_VERSION := 28
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telephony3/AndroidManifest.xml b/tests/tests/telephony3/AndroidManifest.xml
new file mode 100644
index 0000000..8616bbd
--- /dev/null
+++ b/tests/tests/telephony3/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.telephony3.cts">
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.telephony3.cts">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/telephony3/AndroidTest.xml b/tests/tests/telephony3/AndroidTest.xml
new file mode 100644
index 0000000..3413ecb
--- /dev/null
+++ b/tests/tests/telephony3/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Config for CTS Telephony test cases targeting pre-Q">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="telecom" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTelephony3TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.telephony3.cts" />
+        <option name="runtime-hint" value="7m30s" />
+    </test>
+</configuration>
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
new file mode 100644
index 0000000..ee1eb95
--- /dev/null
+++ b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.telephony3.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.TelephonyManager;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Verifies the APIs for apps with the READ_PHONE_STATE permission targeting pre-Q.
+ *
+ * @see android.telephony.cts.TelephonyManagerTest
+
+ */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyManagerTest {
+    private TelephonyManager mTelephonyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mTelephonyManager =
+                (TelephonyManager) InstrumentationRegistry.getContext().getSystemService(
+                        Context.TELEPHONY_SERVICE);
+    }
+
+    @Test
+    public void testDeviceIdentifiersAreNotAccessible() throws Exception {
+        // Apps with the READ_PHONE_STATE permission should no longer have access to device
+        // identifiers. If an app's target SDK is less than Q and it has been granted the
+        // READ_PHONE_STATE permission then a null value should be returned when querying for device
+        // identifiers; this test verifies a null value is returned for device identifier queries.
+        try {
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getDeviceId",
+                    mTelephonyManager.getDeviceId());
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getImei",
+                    mTelephonyManager.getImei());
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getMeid",
+                    mTelephonyManager.getMeid());
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getSubscriberId",
+                    mTelephonyManager.getSubscriberId());
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getSimSerialNumber",
+                    mTelephonyManager.getSimSerialNumber());
+            // 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.
+            assertEquals(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive " + Build.UNKNOWN + " when invoking Build.getSerial",
+                    Build.getSerial(), Build.UNKNOWN);
+        } catch (SecurityException e) {
+            fail("An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                    + "receive null (or "
+                    + Build.UNKNOWN
+                    + " for Build#getSerial) when querying for device identifiers");
+        }
+    }
+}
diff --git a/tests/tests/text/Android.bp b/tests/tests/text/Android.bp
index edd6b4c..8670814 100644
--- a/tests/tests/text/Android.bp
+++ b/tests/tests/text/Android.bp
@@ -28,6 +28,7 @@
         "ctstestrunner",
         "mockito-target-minus-junit4",
         "android-support-test",
+        "ub-uiautomator",
     ],
 
     libs: [
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index bae8481..e2a77cd 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -74,6 +74,8 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.text.cts.MockActivity" />
+
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/text/AndroidTest.xml b/tests/tests/text/AndroidTest.xml
index b8da91e..477f610 100644
--- a/tests/tests/text/AndroidTest.xml
+++ b/tests/tests/text/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Text test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsTextTestCases.apk" />
diff --git a/tests/tests/text/res/values/style.xml b/tests/tests/text/res/values/style.xml
index b122101..905a3b9 100644
--- a/tests/tests/text/res/values/style.xml
+++ b/tests/tests/text/res/values/style.xml
@@ -16,11 +16,101 @@
  -->
 
 <resources>
-  <style name="customFont">
-    <item name="android:fontFamily">@font/samplefont</item>
-  </style>
-  <style name="customFontWithStyle">
-    <item name="android:fontFamily">@font/samplefont</item>
-    <item name="android:textStyle">bold|italic</item>
-  </style>
+    <style name="customFont">
+        <item name="android:fontFamily">@font/samplefont</item>
+    </style>
+    <style name="customFontWithStyle">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+    </style>
+
+    <style name="textAppearanceWithFontWeight">
+        <item name="android:textFontWeight">500</item>
+    </style>
+
+    <style name="textAppearanceWithTextLocales">
+        <item name="android:textLocale">ja-JP,zh-CN</item>
+    </style>
+
+    <style name="textAppearanceWithShadow">
+        <item name="android:shadowColor">#00FFFF</item>
+        <item name="android:shadowDx">1.0</item>
+        <item name="android:shadowDy">2.0</item>
+        <item name="android:shadowRadius">3.0</item>
+    </style>
+
+    <style name="textAppearanceWithElegantTextHeight">
+        <item name="android:elegantTextHeight">true</item>
+    </style>
+
+    <style name="textAppearanceWithLetterSpacing">
+        <item name="android:letterSpacing">1.0</item>
+    </style>
+
+    <style name="textAppearanceWithFontFeatureSettings">
+        <item name="android:fontFeatureSettings">\"smcp\"</item>
+    </style>
+
+    <style name="textAppearanceWithFontVariationSettings">
+        <item name="android:fontVariationSettings">\'wdth\' 150</item>
+    </style>
+
+    <style name="TextAppearanceWithTextColor">
+        <item name="android:textColor">#0F0E0D</item>
+    </style>
+
+    <style name="TextAppearanceWithTextColorLink">
+        <item name="android:textColorLink">#0A0B0C</item>
+    </style>
+
+    <style name="TextAppearanceWithTextSize">
+        <item name="android:textSize">13sp</item>
+    </style>
+
+    <style name="TextAppearanceWithTypeface">
+        <item name="android:typeface">sans</item>
+    </style>
+
+    <style name="TextAppearanceWithTypefaceAndBold">
+        <item name="android:typeface">serif</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearanceWithTypefaceAndWeight">
+      <item name="android:typeface">monospace</item>
+      <item name="android:textFontWeight">200</item>
+    </style>
+
+    <style name="TextAppearanceWithBoldAndWeight">
+      <item name="android:textStyle">bold</item>
+      <item name="android:textFontWeight">500</item>
+    </style>
+
+    <style name="TextAppearanceWithFontFamily">
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="TextAppearanceWithFontFamilyAndTypeface">
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:typeface">monospace</item>
+    </style>
+
+    <style name="textAppearanceWithAllAttributes">
+        <item name="android:fontFamily">@font/samplefont</item>
+        <item name="android:textStyle">bold|italic</item>
+        <item name="android:textSize">160px</item>
+        <item name="android:textColor">#FF00FF</item>
+        <item name="android:textColorLink">#00FFFF</item>
+        <item name="android:textLocale">ja-JP,zh-CN</item>
+        <item name="android:shadowColor">#00FFFF</item>
+        <item name="android:shadowDx">1.0</item>
+        <item name="android:shadowDy">2.0</item>
+        <item name="android:shadowRadius">3.0</item>
+        <item name="android:elegantTextHeight">true</item>
+        <item name="android:letterSpacing">1.0</item>
+        <item name="android:fontFeatureSettings">\"smcp\"</item>
+        <item name="android:fontVariationSettings">\'wdth\' 150</item>
+    </style>
+
+    <style name="textAppearanceWithNoAttributes"/>
 </resources>
diff --git a/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java b/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
index 39eadce..dd0bc3d 100644
--- a/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
+++ b/tests/tests/text/src/android/text/cts/ClipboardManagerTest.java
@@ -20,9 +20,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
 import android.text.ClipboardManager;
 
 import org.junit.Before;
@@ -35,11 +40,15 @@
 @RunWith(AndroidJUnit4.class)
 public class ClipboardManagerTest {
     private ClipboardManager mClipboardManager;
+    private UiDevice mUiDevice;
+    private Context mContext;
 
     @Before
     public void setup() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+        mContext = InstrumentationRegistry.getTargetContext();
+        mClipboardManager = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        launchActivity(MockActivity.class);
     }
 
     @Test
@@ -61,4 +70,13 @@
         mClipboardManager.setText(null);
         assertFalse(mClipboardManager.hasText());
     }
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(mContext.getPackageName(), clazz.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+    }
+
 }
diff --git a/tests/tests/text/src/android/text/cts/EmojiTest.java b/tests/tests/text/src/android/text/cts/EmojiTest.java
index cb719e9..06e3481 100644
--- a/tests/tests/text/src/android/text/cts/EmojiTest.java
+++ b/tests/tests/text/src/android/text/cts/EmojiTest.java
@@ -34,20 +34,26 @@
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.View;
+import android.webkit.WebView.VisualStateCallback;
 import android.webkit.cts.WebViewOnUiThread;
 import android.widget.EditText;
 import android.widget.TextView;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 
+import com.google.common.util.concurrent.SettableFuture;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class EmojiTest {
+    private static final long TEST_TIMEOUT_MS = 20000L; // 20s
     private Context mContext;
     private EditText mEditText;
 
@@ -90,62 +96,79 @@
         return sb.toString();
     }
 
+    // These emojis should have different characters
+    private static final int sComparedCodePoints[][] = {
+        {0x1F436, 0x1F435},      // Dog(U+1F436) and Monkey(U+1F435)
+        {0x26BD, 0x26BE},        // Soccer ball(U+26BD) and Baseball(U+26BE)
+        {0x1F47B, 0x1F381},      // Ghost(U+1F47B) and wrapped present(U+1F381)
+        {0x2764, 0x1F494},       // Heavy black heart(U+2764) and broken heart(U+1F494)
+        {0x1F603, 0x1F33B}       // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B)
+    };
+
     /**
      * Tests Emoji has different glyph for different meaning characters.
-     * Test on Canvas, TextView, EditText and WebView
+     * Test on Canvas, TextView and EditText
      */
     @UiThreadTest
     @Test
     public void testEmojiGlyph() {
         CaptureCanvas ccanvas = new CaptureCanvas(mContext);
 
-        Bitmap mBitmapA, mBitmapB;  // Emoji displayed Bitmaps to compare
+        Bitmap bitmapA, bitmapB;  // Emoji displayed Bitmaps to compare
 
-        int comparedCodePoints[][] = {   // Emojis should have different characters
-            {0x1F436, 0x1F435},      // Dog(U+1F436) and Monkey(U+1F435)
-            {0x26BD, 0x26BE},        // Soccer ball(U+26BD) and Baseball(U+26BE)
-            {0x1F47B, 0x1F381},      // Ghost(U+1F47B) and wrapped present(U+1F381)
-            {0x2764, 0x1F494},       // Heavy black heart(U+2764) and broken heart(U+1F494)
-            {0x1F603, 0x1F33B}       // Smiling face with open mouth(U+1F603) and sunflower(U+1F33B)
-        };
+        for (int i = 0; i < sComparedCodePoints.length; i++) {
+            String baseMessage = "Glyph for U+" + Integer.toHexString(sComparedCodePoints[i][0])
+                    + " should be different from glyph for U+"
+                    + Integer.toHexString(sComparedCodePoints[i][1]) + ". ";
 
-        for (int i = 0; i < comparedCodePoints.length; i++) {
-            String baseMessage = "Glyph for U+" + Integer.toHexString(comparedCodePoints[i][0]) +
-                " should be different from glyph for U+" +
-                Integer.toHexString(comparedCodePoints[i][1]) + ". ";
+            bitmapA = ccanvas.capture(Character.toChars(sComparedCodePoints[i][0]));
+            bitmapB = ccanvas.capture(Character.toChars(sComparedCodePoints[i][1]));
 
-            mBitmapA = ccanvas.capture(Character.toChars(comparedCodePoints[i][0]));
-            mBitmapB = ccanvas.capture(Character.toChars(comparedCodePoints[i][1]));
-
-            String bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
-            assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
+            String bmpDiffMessage = describeBitmap(bitmapA) + "vs" + describeBitmap(bitmapB);
+            assertFalse(baseMessage + bmpDiffMessage, bitmapA.sameAs(bitmapB));
 
             // cannot reuse CaptureTextView as 2nd setText call throws NullPointerException
             CaptureTextView cviewA = new CaptureTextView(mContext);
-            mBitmapA = cviewA.capture(Character.toChars(comparedCodePoints[i][0]));
+            bitmapA = cviewA.capture(Character.toChars(sComparedCodePoints[i][0]));
             CaptureTextView cviewB = new CaptureTextView(mContext);
-            mBitmapB = cviewB.capture(Character.toChars(comparedCodePoints[i][1]));
+            bitmapB = cviewB.capture(Character.toChars(sComparedCodePoints[i][1]));
 
-            bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
-            assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
+            bmpDiffMessage = describeBitmap(bitmapA) + "vs" + describeBitmap(bitmapB);
+            assertFalse(baseMessage + bmpDiffMessage, bitmapA.sameAs(bitmapB));
 
             CaptureEditText cedittextA = new CaptureEditText(mContext);
-            mBitmapA = cedittextA.capture(Character.toChars(comparedCodePoints[i][0]));
+            bitmapA = cedittextA.capture(Character.toChars(sComparedCodePoints[i][0]));
             CaptureEditText cedittextB = new CaptureEditText(mContext);
-            mBitmapB = cedittextB.capture(Character.toChars(comparedCodePoints[i][1]));
+            bitmapB = cedittextB.capture(Character.toChars(sComparedCodePoints[i][1]));
 
-            bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
-            assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
+            bmpDiffMessage = describeBitmap(bitmapA) + "vs" + describeBitmap(bitmapB);
+            assertFalse(baseMessage + bmpDiffMessage, bitmapA.sameAs(bitmapB));
+        }
+    }
 
-            // Trigger activity bringup so we can determine if a WebView is available on this
-            // device.
-            if (NullWebViewUtils.isWebViewAvailable()) {
-                CaptureWebView cwebview = new CaptureWebView();
-                mBitmapA = cwebview.capture(Character.toChars(comparedCodePoints[i][0]));
-                mBitmapB = cwebview.capture(Character.toChars(comparedCodePoints[i][1]));
-                bmpDiffMessage = describeBitmap(mBitmapA) + "vs" + describeBitmap(mBitmapB);
-                assertFalse(baseMessage + bmpDiffMessage, mBitmapA.sameAs(mBitmapB));
-            }
+    /**
+     * Tests Emoji has different glyph for different meaning characters.
+     * Test on WebView
+     */
+    @Test
+    public void testEmojiGlyphWebView() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        Bitmap bitmapA, bitmapB;  // Emoji displayed Bitmaps to compare
+
+        CaptureWebView cwebview = new CaptureWebView();
+        for (int i = 0; i < sComparedCodePoints.length; i++) {
+            String baseMessage = "Glyph for U+" + Integer.toHexString(sComparedCodePoints[i][0])
+                    + " should be different from glyph for U+"
+                    + Integer.toHexString(sComparedCodePoints[i][1]) + ". ";
+
+            bitmapA = cwebview.capture(Character.toChars(sComparedCodePoints[i][0]));
+            bitmapB = cwebview.capture(Character.toChars(sComparedCodePoints[i][1]));
+
+            String bmpDiffMessage = describeBitmap(bitmapA) + "vs" + describeBitmap(bitmapB);
+            assertFalse(baseMessage + bmpDiffMessage, bitmapA.sameAs(bitmapB));
         }
     }
 
@@ -265,38 +288,46 @@
     }
 
 
-    private class CaptureWebView {
+    private static long sRequestId = 0;
 
+    private class CaptureWebView {
         WebViewOnUiThread webViewOnUiThread;
-        Bitmap bitmap;
+
         CaptureWebView() {
-            webViewOnUiThread = new WebViewOnUiThread(mActivityRule,
-                    mActivityRule.getActivity().getWebView());
+            webViewOnUiThread = new WebViewOnUiThread(mActivityRule.getActivity().getWebView());
+            // Offscreen pre-raster ensures that visibile region of the WebView is not used to
+            // determine which tiles to render, and instead the full WebView size is treated
+            // as the visible region.
+            webViewOnUiThread.getSettings().setOffscreenPreRaster(true);
         }
 
         Bitmap capture(char c[]) {
-
             webViewOnUiThread.loadDataAndWaitForCompletion(
                     "<html><body>" + String.valueOf(c) + "</body></html>",
                     "text/html; charset=utf-8", "utf-8");
-            // The Chromium-powered WebView renders asynchronously and there's nothing reliable
-            // we can easily wait for to be sure that capturePicture will return a fresh frame.
-            // So, just sleep for a sufficient time.
+
+            // Wait for the loaded DOM state to be ready to draw.
+            final SettableFuture<Void> future = SettableFuture.create();
+            webViewOnUiThread.postVisualStateCallback(sRequestId++, new VisualStateCallback() {
+                @Override
+                public void onComplete(long requestId) {
+                    future.set(null);
+                }
+            });
             try {
-                Thread.sleep(250);
-            } catch (InterruptedException e) {
+                future.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (Exception e) {
                 return null;
             }
 
             Picture picture = webViewOnUiThread.capturePicture();
             if (picture == null || picture.getHeight() <= 0 || picture.getWidth() <= 0) {
                 return null;
-            } else {
-                bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
-                        Bitmap.Config.ARGB_8888);
-                Canvas canvas = new Canvas(bitmap);
-                picture.draw(canvas);
             }
+            Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
+                    Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            picture.draw(canvas);
 
             return bitmap;
         }
diff --git a/tests/tests/text/src/android/text/cts/FontCoverageTest.java b/tests/tests/text/src/android/text/cts/FontCoverageTest.java
index 465277d..e01b4b9 100644
--- a/tests/tests/text/src/android/text/cts/FontCoverageTest.java
+++ b/tests/tests/text/src/android/text/cts/FontCoverageTest.java
@@ -368,7 +368,9 @@
                 + "\u9CEF\u9CF4\u9D8F\u9E7F\u9E97\u9EA6\u9EBB\u9EC4\u9ED2\u9ED9"
                 + "\u9F13\u9F20\u9F3B\u9F62\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F"
                 + "\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF61-\uFF65]"));
-        EXEMPLAR_MAP.put("ka", new UnicodeSet("[\u10A0-\u10C5\u10D0-\u10FB\u2116\u2D00-\u2D25]"));
+        EXEMPLAR_MAP.put("ka", new UnicodeSet("[\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FF"
+                + "\u2D00-\u2D25\u2D27\u2D2D"
+                + "\u1C90-\u1CBA\u1CBD-\u1CBF]"));
         EXEMPLAR_MAP.put("kkj", new UnicodeSet("[\u2039\u203A{A\u0327}{I\u0327}{U\u0327}{a\u0327}"
                 + "{i\u0327}{u\u0327}{\u0186\u0302}{\u0186\u0327}{\u0190\u0302}{\u0190\u0327}"
                 + "{\u0254\u0302}{\u0254\u0327}{\u025B\u0302}{\u025B\u0327}]"));
diff --git a/tests/tests/text/src/android/text/cts/MakeSafeForPresentationTest.java b/tests/tests/text/src/android/text/cts/MakeSafeForPresentationTest.java
new file mode 100644
index 0000000..83a3793
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/MakeSafeForPresentationTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.text.cts;
+
+import static android.text.TextUtils.SAFE_STRING_FLAG_FIRST_LINE;
+import static android.text.TextUtils.SAFE_STRING_FLAG_SINGLE_LINE;
+import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM;
+import static android.text.TextUtils.makeSafeForPresentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class MakeSafeForPresentationTest {
+    private static final String LONG_STRING = "Lorem ipsum dolor sit amet, consectetur adipiscing "
+            + "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim "
+            + "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
+            + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
+            + "cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non "
+            + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+
+    private static final String VERY_LONG_STRING = LONG_STRING + LONG_STRING + LONG_STRING
+            + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING
+            + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING + LONG_STRING;
+
+    private static final int DEFAULT_MAX_LABEL_SIZE_PX = 500;
+
+    /**
+     * Convert a string into a list of code-points
+     */
+    private static String strToCodePoints(String in) {
+        StringBuilder out = new StringBuilder();
+        int offset = 0;
+        while (offset < in.length()) {
+            if (out.length() != 0) {
+                out.append(", ");
+            }
+            int charCount = Character.charCount(in.codePointAt(offset));
+            out.append(Integer.toHexString(in.codePointAt(offset))).append("=\"")
+                    .append(in.substring(offset, offset + charCount)).append("\"");
+            offset += charCount;
+        }
+
+        return out.toString();
+    }
+
+    /**
+     * Assert that {@code a.equals(b)} and if not throw a nicely formatted error.
+     *
+     * @param a First string to compare
+     * @param b String to compare first string to
+     */
+    private static void assertStringEquals(String a, CharSequence b) {
+        if (a.equals(b.toString())) {
+            return;
+        }
+
+        throw new AssertionError("[" + strToCodePoints(a) + "] != ["
+                + strToCodePoints(b.toString()) + "]");
+    }
+
+    /**
+     * Make sure {@code abbreviated} is an abbreviation of {@code full}.
+     *
+     * @param full        The non-abbreviated string
+     * @param abbreviated The abbreviated string
+     * @return {@code true} iff the string was abbreviated
+     */
+    private static boolean isAbbreviation(String full, CharSequence abbreviated) {
+        String abbrStr = abbreviated.toString();
+        int abbrLength = abbrStr.length();
+        int offset = 0;
+
+        while (offset < abbrLength) {
+            final int abbrCodePoint = abbrStr.codePointAt(offset);
+            final int fullCodePoint = full.codePointAt(offset);
+            final int codePointLen = Character.charCount(abbrCodePoint);
+
+            if (abbrCodePoint != fullCodePoint) {
+                return (abbrLength == offset + codePointLen)
+                        && (/* ellipses */8230 == (abbrStr.codePointAt(offset)));
+            }
+
+            offset += codePointLen;
+        }
+
+        return false;
+    }
+
+    @Test
+    public void removeIllegal() {
+        assertStringEquals("ab", makeSafeForPresentation("a&#x8;b", 0, 0, 0));
+    }
+
+    @Test
+    public void convertHTMLCharacters() {
+        assertStringEquals("a&b", makeSafeForPresentation("a&amp;b", 0, 0, 0));
+    }
+
+    @Test
+    public void convertHTMLGTAndLT() {
+        assertStringEquals("a<br>b", makeSafeForPresentation("a&lt;br&gt;b", 0, 0, 0));
+    }
+
+    @Test
+    public void removeHTMLFormatting() {
+        assertStringEquals("ab", makeSafeForPresentation("a<b>b</>", 0, 0, 0));
+    }
+
+    @Test
+    public void replaceImgTags() {
+        assertStringEquals("a\ufffcb", makeSafeForPresentation("a<img src=\"myimg.jpg\" />b", 0, 0,
+                0));
+    }
+
+    @Test
+    public void replacePTags() {
+        assertStringEquals("a\n\nb\n\n", makeSafeForPresentation("a<p>b", 0, 0, 0));
+    }
+
+    @Test
+    public void aTagIsIgnored() {
+        assertStringEquals("ab", makeSafeForPresentation("a<a href=\"foo.bar\">b</a>", 0, 0, 0));
+    }
+
+    @Test
+    public void leadingAndMoreThanOneTrailingSpacesAreAlwaysTrimmed() {
+        assertStringEquals("ab ", makeSafeForPresentation("    ab    ", 0, 0, 0));
+    }
+
+    @Test
+    public void preIsIgnored() {
+        assertStringEquals("a b ", makeSafeForPresentation("<pre>  a\n b   </pre>", 0, 0, 0));
+    }
+
+    @Test
+    public void removeSpecialNotHtmlCharacters() {
+        assertStringEquals("ab", makeSafeForPresentation("a\bb", 0, 0, 0));
+    }
+
+    @Test
+    public void collapseInternalConsecutiveSpaces() {
+        assertStringEquals("a b", makeSafeForPresentation("a  b", 0, 0, 0));
+    }
+
+    @Test
+    public void replaceNonHtmlNewLineWithSpace() {
+        assertStringEquals("a b", makeSafeForPresentation("a\nb", 0, 0, 0));
+    }
+
+    @Test
+    public void keepNewLines() {
+        assertStringEquals("a\nb", makeSafeForPresentation("a<br/>b", 0, 0, 0));
+    }
+
+    @Test
+    public void removeNewLines() {
+        assertStringEquals("ab", makeSafeForPresentation("a<br/>b", 0, 0,
+                SAFE_STRING_FLAG_SINGLE_LINE));
+    }
+
+    @Test
+    public void truncateAtNewLine() {
+        assertStringEquals("a", makeSafeForPresentation("a<br/>b", 0, 0,
+                SAFE_STRING_FLAG_FIRST_LINE));
+    }
+
+    @Test
+    public void nbspIsTrimmed() {
+        /*
+         * A non breaking white space "&nbsp;" in HTML is not a white space according to
+         * Character.isWhitespace, but for clean string it is treated as shite-space
+         */
+        assertStringEquals("a b", makeSafeForPresentation("&nbsp; a b", 0, 0,
+                SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void trimFront() {
+        assertStringEquals("a b", makeSafeForPresentation("<br/> <br/> a b", 0, 0,
+                SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void trimAll() {
+        assertStringEquals("", makeSafeForPresentation(" <br/> <br/> ", 0, 0,
+                SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void trimEnd() {
+        assertStringEquals("a b", makeSafeForPresentation("a b <br/> <br/>", 0, 0,
+                SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void trimBoth() {
+        assertStringEquals("a b", makeSafeForPresentation("<br/> <br/> a b <br/> <br/>", 0, 0,
+                SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void ellipsizeShort() {
+        assertTrue(isAbbreviation(VERY_LONG_STRING, makeSafeForPresentation(VERY_LONG_STRING, 0,
+                DEFAULT_MAX_LABEL_SIZE_PX, 0)));
+    }
+
+    @Test
+    public void ellipsizeLong() {
+        assertTrue(isAbbreviation(VERY_LONG_STRING, makeSafeForPresentation(VERY_LONG_STRING, 0,
+                DEFAULT_MAX_LABEL_SIZE_PX * 20, 0)));
+    }
+
+    @Test
+    public void thousandDipLengthIs50Characters() {
+        assertEquals(50, makeSafeForPresentation(VERY_LONG_STRING, 0, 1000, 0).length(), 10);
+    }
+
+    @Test
+    public void ellipsizeShouldBeProportional() {
+        assertEquals(20, (float) makeSafeForPresentation(VERY_LONG_STRING, 0,
+                DEFAULT_MAX_LABEL_SIZE_PX * 20, 0).length() / makeSafeForPresentation(
+                VERY_LONG_STRING, 0, DEFAULT_MAX_LABEL_SIZE_PX, 0).length(), 3);
+    }
+
+    @Test
+    public void ignoreLastCharacters() {
+        assertEquals(42, makeSafeForPresentation(VERY_LONG_STRING, 42, 0, 0).length());
+    }
+
+    @Test
+    public void ignoreLastCharactersShortStirng() {
+        assertEquals("abc", makeSafeForPresentation("abc", 42, 0, 0));
+    }
+
+    @Test
+    public void ignoreLastCharactersWithTrim() {
+        assertEquals("abc", makeSafeForPresentation("abc ", 4, 0, SAFE_STRING_FLAG_TRIM));
+    }
+
+    @Test
+    public void ignoreLastCharactersWithHtml() {
+        assertEquals("a\nbcd", makeSafeForPresentation("a<br />bcdef", 10, 0, 0));
+    }
+
+    @Test
+    public void ignoreLastCharactersWithTrailingWhitespace() {
+        assertEquals("abc ", makeSafeForPresentation("abc ", 4, 0, 0));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void cannotKeepAndTruncateNewLines() {
+        makeSafeForPresentation("", 0, 0,
+                SAFE_STRING_FLAG_SINGLE_LINE | SAFE_STRING_FLAG_FIRST_LINE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidFlags() {
+        makeSafeForPresentation("", 0, 0, 0xffff);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidEllipsizeDip() {
+        makeSafeForPresentation("", 0, -1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidMaxCharacters() {
+        makeSafeForPresentation("", -1, 0, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullString() {
+        makeSafeForPresentation(null, 0, 0, 0);
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/MockActivity.java b/tests/tests/text/src/android/text/cts/MockActivity.java
new file mode 100644
index 0000000..34f7d78
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/MockActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.text.cts;
+
+import android.app.Activity;
+
+public class MockActivity extends Activity {
+
+}
diff --git a/tests/tests/text/src/android/text/cts/MyanmarTest.java b/tests/tests/text/src/android/text/cts/MyanmarTest.java
index 916b451..0f729bd 100644
--- a/tests/tests/text/src/android/text/cts/MyanmarTest.java
+++ b/tests/tests/text/src/android/text/cts/MyanmarTest.java
@@ -17,75 +17,179 @@
 package android.text.cts;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Paint;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.SystemFonts;
+import android.icu.util.ULocale;
+import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.text.TextUtils;
+import android.util.TypedValue;
 import android.widget.TextView;
 
+import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class MyanmarTest {
+
+    private static final String SCRIPT_QAAG = "Qaag";
+    private static final String SCRIPT_MYMR = "Mymr";
+
     /**
-     * Tests Unicode composition semantics.
+     * The following will be rendered correctly as a single cluster for both Unicode and Zawgyi.
      */
-    @UiThreadTest
+    private static final String UNICODE_CORRECT_ORDER = "\u1019\u102d\u102f";
+
+    /**
+     * The following will not be rendered correctly as a single cluster in Unicode. However it will
+     * render correctly with Zawgyi.
+     */
+    private static final String UNICODE_WRONG_ORDER = "\u1019\u102f\u102d";
+
+    private static boolean sHasBurmeseLocale;
+    private static List<Locale> sZawgyiLocales = new ArrayList<>();
+    private static List<Locale> sMymrLocales = new ArrayList<>();
+    private Context mContext;
+
+    @BeforeClass
+    public static void setupLocales() {
+        final String[] supportedLocaleNames = getSupportedLocales();
+        for (String localeName : supportedLocaleNames) {
+            final Locale locale = Locale.forLanguageTag(localeName);
+            final String script = ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript();
+
+            if (SCRIPT_QAAG.equals(script)) {
+                sZawgyiLocales.add(locale);
+            } else if (SCRIPT_MYMR.equals(script)) {
+                sMymrLocales.add(locale);
+            }
+
+            sHasBurmeseLocale = (sMymrLocales.size() + sZawgyiLocales.size()) > 0;
+        }
+    }
+
+    @Before
+    public void setupTest() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
     @Test
-    public void testCompositionSemantics() {
-        boolean isMyanmarSupported = false;
-        final String[] localeNames = Resources.getSystem().getStringArray(
+    public void testIfZawgyiExistsThenUnicodeExists() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sZawgyiLocales.isEmpty());
+
+        assertTrue("If the system supports Zawgyi, it should also support Unicode Myanmar",
+                sMymrLocales.size() > 0);
+    }
+
+    @Test
+    public void testShouldHaveGlyphs() {
+        assumeTrue(sHasBurmeseLocale);
+
+        final Paint paint = new Paint();
+        int index = 0;
+        while (index < UNICODE_CORRECT_ORDER.length()) {
+            int codepoint = UNICODE_CORRECT_ORDER.codePointAt(index);
+
+            assertTrue("Should have glyph for " + Integer.toHexString(codepoint),
+                    paint.hasGlyph(new String(Character.toChars(codepoint))));
+
+            index += Character.charCount(codepoint);
+        }
+    }
+
+    @Test
+    public void testZawgyiRenders() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sZawgyiLocales.isEmpty());
+
+        assertTrue("Should render Zawgyi text correctly with Zawgyi system locale",
+                isZawgyiRendersCorrectly(mContext, new LocaleList(sZawgyiLocales.get(0))));
+    }
+
+    @Test
+    public void testZawgyiRenders_withValidLocaleList() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sZawgyiLocales.isEmpty());
+
+        final LocaleList[] testLocales = new LocaleList[]{
+                LocaleList.forLanguageTags("my-Qaag"),
+                LocaleList.forLanguageTags("my-Qaag,my-Mymr"),
+                LocaleList.forLanguageTags("my-Qaag-MM,my-Mymr-MM"),
+                LocaleList.forLanguageTags("en-Latn,my-Qaag"),
+                LocaleList.forLanguageTags("en-Latn-US,my-Qaag-MM"),
+                LocaleList.forLanguageTags("en-Qaag,my-Mymr"),
+                LocaleList.forLanguageTags("en-Qaag-MM,my-Mymr-MM"),
+        };
+
+        for (LocaleList localeList : testLocales) {
+            assertTrue("Should render Zawgyi text correctly in locale " + localeList.toString(),
+                    isZawgyiRendersCorrectly(mContext, localeList));
+        }
+    }
+
+    @Test
+    public void testIfZawgyiLocaleIsSupported_fontWithQaagShouldExists() {
+        assumeTrue(sHasBurmeseLocale);
+        assumeTrue(!sZawgyiLocales.isEmpty());
+
+        boolean qaagFontExists = false;
+        final Set<Font> availableFonts = SystemFonts.getAvailableFonts();
+        for (Font font : availableFonts) {
+            final LocaleList localeList = font.getLocaleList();
+            for (int index = 0; index < localeList.size(); index++) {
+                final Locale fontLocale = localeList.get(index);
+                final ULocale uLocale = ULocale.addLikelySubtags(ULocale.forLocale(fontLocale));
+                final String script = uLocale.getScript();
+                if (SCRIPT_QAAG.equals(script)) {
+                    qaagFontExists = true;
+                    break;
+                }
+            }
+        }
+
+        assertTrue(qaagFontExists);
+    }
+
+    private static boolean isZawgyiRendersCorrectly(Context context, LocaleList localeList) {
+        final Bitmap bitmapCorrect = CaptureTextView.capture(context, localeList,
+                UNICODE_CORRECT_ORDER);
+        final Bitmap bitmapWrong = CaptureTextView.capture(context, localeList,
+                UNICODE_WRONG_ORDER);
+
+        return bitmapCorrect.sameAs(bitmapWrong);
+    }
+
+    private static String[] getSupportedLocales() {
+        return Resources.getSystem().getStringArray(
                 Resources.getSystem().getIdentifier("supported_locales", "array", "android"));
-        for (String localeName : localeNames) {
-            if (TextUtils.equals("my", Locale.forLanguageTag(localeName).getLanguage())) {
-                isMyanmarSupported = true;
-                break;
-            }
-        }
-        if (!isMyanmarSupported) {
-            // Ignoring since no Myanmar font guarantee if Myanmar is not listed in supported
-            // locales.
-            return;
-        }
-
-        Context context = InstrumentationRegistry.getTargetContext();
-        String textA = "\u1019\u102d\u102f";
-        String textB = "\u1019\u102f\u102d"; // wrong order for Unicode
-
-        CaptureTextView cviewA = new CaptureTextView(context);
-        Bitmap bitmapA = cviewA.capture(textA);
-        CaptureTextView cviewB = new CaptureTextView(context);
-        Bitmap bitmapB = cviewB.capture(textB);
-        if (bitmapA.sameAs(bitmapB)) {
-            // if textA and textB render identically, test against replacement characters
-            String textC = "\ufffd\ufffd\ufffd"; // replacement characters are acceptable
-            CaptureTextView cviewC = new CaptureTextView(context);
-            Bitmap bitmapC = cviewC.capture(textC);
-            if (!bitmapA.sameAs(bitmapC)) {
-                // ...or against blank/empty glyphs
-                Bitmap bitmapD = Bitmap.createBitmap(bitmapC.getWidth(), bitmapC.getHeight(),
-                        bitmapC.getConfig());
-                assertTrue(bitmapA.sameAs(bitmapD));
-            }
-        }
     }
 
     private static class CaptureTextView extends TextView {
 
         CaptureTextView(Context context) {
             super(context);
+            final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 8,
+                    context.getResources().getDisplayMetrics());
+            setTextSize(textSize);
         }
 
-        Bitmap capture(String text) {
+        private Bitmap capture(String text) {
             setText(text);
 
             invalidate();
@@ -93,12 +197,18 @@
             setDrawingCacheEnabled(true);
             measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            layout(0, 0, 200,200);
+            layout(0, 0, 200, 200);
 
             Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
             setDrawingCacheEnabled(false);
             return bitmap;
         }
 
+        static Bitmap capture(Context context, LocaleList localeList, String string) {
+            final CaptureTextView textView = new CaptureTextView(context);
+            if (localeList != null) textView.setTextLocales(localeList);
+            return textView.capture(string);
+        }
     }
+
 }
diff --git a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
index 9491eff..1b471d3 100644
--- a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
+++ b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
@@ -22,10 +22,13 @@
 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.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Typeface;
@@ -43,6 +46,10 @@
 import android.text.style.BackgroundColorSpan;
 import android.text.style.LocaleSpan;
 import android.text.style.TextAppearanceSpan;
+import android.text.style.TypefaceSpan;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -175,6 +182,17 @@
     }
 
     @Test
+    public void testCreateForDifferentDirection() {
+        final Params param = new Params.Builder(PAINT).setTextDirection(LTR).build();
+        final PrecomputedText textWithLTR = PrecomputedText.create(STRING, param);
+        final Params newParam = new Params.Builder(PAINT).setTextDirection(RTL).build();
+        final PrecomputedText textWithRTL = PrecomputedText.create(textWithLTR, newParam);
+        assertNotNull(textWithRTL);
+        assertNotSame(textWithLTR, textWithRTL);
+        assertEquals(textWithLTR.toString(), textWithRTL.toString());
+    }
+
+    @Test
     public void testCharSequenceInteface() {
         final Params param = new Params.Builder(PAINT).build();
         final CharSequence s = PrecomputedText.create(STRING, param);
@@ -620,4 +638,93 @@
         PrecomputedText.create("a\nb", param).getBounds(0, 3, rect);
     }
 
+    private static Bitmap drawToBitmap(@NonNull CharSequence cs,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end,
+            @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd,
+            @NonNull TextPaint paint) {
+
+        Rect rect = new Rect();
+        paint.getTextBounds(cs.toString(), start, end, rect);
+        final Bitmap bmp = Bitmap.createBitmap(rect.width(),
+                rect.height(), Bitmap.Config.ARGB_8888);
+        final Canvas c = new Canvas(bmp);
+        c.save();
+        c.translate(0, 0);
+        c.drawTextRun(cs, start, end, ctxStart, ctxEnd, 0, 0, false /* isRtl */, paint);
+        c.restore();
+        return bmp;
+    }
+
+    private static void assertSameOutput(@NonNull CharSequence cs,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end,
+            @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd,
+            @NonNull TextPaint paint) {
+        final Params params = new Params.Builder(paint).build();
+        final PrecomputedText pt = PrecomputedText.create(cs, params);
+
+        final Params rtlParams = new Params.Builder(paint)
+                .setTextDirection(TextDirectionHeuristics.RTL).build();
+        final PrecomputedText rtlPt = PrecomputedText.create(cs, rtlParams);
+        // FIRSTSTRONG_LTR is the default direction.
+        final PrecomputedText ptFromRtl = PrecomputedText.create(rtlPt,
+                new Params.Builder(params).setTextDirection(
+                        TextDirectionHeuristics.FIRSTSTRONG_LTR).build());
+
+        final Bitmap originalDrawOutput = drawToBitmap(cs, start, end, ctxStart, ctxEnd, paint);
+        final Bitmap precomputedDrawOutput = drawToBitmap(pt, start, end, ctxStart, ctxEnd, paint);
+        final Bitmap precomputedFromDifferentDirectionDrawOutput =
+                drawToBitmap(pt, start, end, ctxStart, ctxEnd, paint);
+        assertTrue(originalDrawOutput.sameAs(precomputedDrawOutput));
+        assertTrue(originalDrawOutput.sameAs(precomputedFromDifferentDirectionDrawOutput));
+    }
+
+    @Test
+    public void testDrawText() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(32.0f);
+
+        final SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World");
+        assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint);
+        assertSameOutput(ssb, 3, ssb.length() - 3, 0, ssb.length(), paint);
+        assertSameOutput(ssb, 5, ssb.length() - 5, 2, ssb.length() - 2, paint);
+    }
+
+    @Test
+    public void testDrawText_MultiStyle() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(32.0f);
+
+        final SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World");
+        ssb.setSpan(new TypefaceSpan("serif"), 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint);
+        assertSameOutput(ssb, 3, ssb.length() - 3, 0, ssb.length(), paint);
+        assertSameOutput(ssb, 5, ssb.length() - 5, 2, ssb.length() - 2, paint);
+    }
+
+    @Test
+    public void testDrawText_MultiParagraph() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(32.0f);
+
+        final SpannableStringBuilder ssb = new SpannableStringBuilder(
+                "Hello, World\nHello, Android");
+
+        // The first line
+        final int firstLineLen = "Hello, World\n".length();
+        assertSameOutput(ssb, 0, firstLineLen, 0, firstLineLen, paint);
+        assertSameOutput(ssb, 3, firstLineLen - 3, 0, firstLineLen, paint);
+        assertSameOutput(ssb, 3, firstLineLen - 3, 2, firstLineLen - 2, paint);
+
+        // The second line.
+        assertSameOutput(ssb, firstLineLen, ssb.length(), firstLineLen, ssb.length(), paint);
+        assertSameOutput(ssb, firstLineLen + 3, ssb.length() - 3,
+                firstLineLen, ssb.length(), paint);
+        assertSameOutput(ssb, firstLineLen + 5, ssb.length() - 5,
+                firstLineLen + 2, ssb.length() - 2, paint);
+
+        // Across the paragraph
+        assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint);
+        assertSameOutput(ssb, 3, firstLineLen - 3, 0, ssb.length(), paint);
+        assertSameOutput(ssb, 3, firstLineLen - 3, 2, ssb.length() - 2, paint);
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
index b8f3f42..28baff3 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringBuilderTest.java
@@ -18,6 +18,7 @@
 
 
 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;
@@ -770,6 +771,68 @@
         assertEquals(fourth, spans[1]);
     }
 
+    // Same as SpannableStringTest.
+    @Test
+    public void testEquals() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableStringBuilder bd1 = new SpannableStringBuilder(text);
+        bd1.setSpan(span1, 0, 5, 0);
+        bd1.setSpan(span2, 1, 5, 0);
+
+        assertEquals(bd1, bd1);
+
+        final SpannableStringBuilder bd2 = new SpannableStringBuilder(text);
+        bd2.setSpan(span1, 0, 5, 0);
+        bd2.setSpan(span2, 1, 5, 0);
+
+        assertEquals(bd1, bd2);
+        assertEquals(bd2, bd1);
+    }
+
+    // Same as SpannableStringTest
+    @Test
+    public void testEqualsWithPriority() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableStringBuilder bd1 = new SpannableStringBuilder(text);
+        bd1.setSpan(span1, 0, 5, 0);
+        bd1.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+
+        assertEquals(bd1, bd1);
+
+        final SpannableStringBuilder bd2 = new SpannableStringBuilder(text);
+        bd2.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+        bd2.setSpan(span1, 0, 5, 0);
+
+        // This should work because the span2 has priority.
+        assertEquals(bd1, bd2);
+    }
+
+    // Same as SpannableStringTest
+    @Test
+    public void testEqualsWithDifferentSequence() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableStringBuilder bd1 = new SpannableStringBuilder(text);
+        bd1.setSpan(span1, 0, 5, Spanned.SPAN_PRIORITY);
+        bd1.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+
+        final SpannableStringBuilder bd2 = new SpannableStringBuilder(text);
+        bd2.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+        bd2.setSpan(span1, 0, 5, Spanned.SPAN_PRIORITY);
+
+        // Both span1 and span2 have priority but they are applied in a different sequence,
+        // so bd1 should not equal to bd2.
+        assertFalse(bd1.equals(bd2));
+    }
+
     @Test
     public void testLength() {
         SpannableStringBuilder builder = new SpannableStringBuilder("hello");
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringTest.java b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
index 3771613..10040b6 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
@@ -17,6 +17,7 @@
 package android.text.cts;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.fail;
@@ -238,4 +239,65 @@
 
         assertEquals(0, copiedSpans.length);
     }
+
+    @Test
+    public void testEquals() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableString ss1 = new SpannableString(text);
+        ss1.setSpan(span1, 0, 5, 0);
+        ss1.setSpan(span2, 1, 5, 0);
+
+        assertEquals(ss1, ss1);
+
+        final SpannableString ss2 = new SpannableString(text);
+        ss2.setSpan(span1, 0, 5, 0);
+        ss2.setSpan(span2, 1, 5, 0);
+
+        assertEquals(ss1, ss2);
+        assertEquals(ss2, ss1);
+    }
+
+    @Test
+    public void testEqualsWithPriority() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableString ss1 = new SpannableString(text);
+        ss1.setSpan(span1, 0, 5, 0);
+        ss1.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+
+        assertEquals(ss1, ss1);
+
+        final SpannableString ss2 = new SpannableString(text);
+        ss2.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+        ss2.setSpan(span1, 0, 5, 0);
+
+        // This should work because the span2 has priority.
+        assertEquals(ss1, ss2);
+    }
+
+    @Test
+    public void testEqualsWithDifferentSequence() {
+        final String text = "this is a test!";
+        final Object span1 = new Object();
+        final Object span2 = new Object();
+
+        final SpannableString ss1 = new SpannableString(text);
+        ss1.setSpan(span1, 0, 5, Spanned.SPAN_PRIORITY);
+        ss1.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+
+        final SpannableString ss2 = new SpannableString(text);
+        ss2.setSpan(span2, 1, 5, Spanned.SPAN_PRIORITY);
+        ss2.setSpan(span1, 0, 5, Spanned.SPAN_PRIORITY);
+
+        // Both span1 and span2 have priority but they are applied in a different sequence,
+        // so ss1 should not equal to ss2.
+        assertFalse(ss1.equals(ss2));
+    }
+
+
 }
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutGetLineLeftRightTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutGetLineLeftRightTest.java
new file mode 100644
index 0000000..9254eb2
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutGetLineLeftRightTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.text.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.LeadingMarginSpan;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public final class StaticLayoutGetLineLeftRightTest {
+
+    // Here we should have a string without any space in case layout omit the
+    // space, which will cause troubles for us to verify the length  of each line.
+    // And in this test, we assume TEST_STR is LTR. It's initialized in setUp()
+    static final String TEST_STR;
+
+    static {
+        String test_str = "";
+        for (int i = 0; i < 100; ++i) {
+            test_str += "a";
+        }
+        TEST_STR = test_str;
+    }
+    // Because Alignment.LEFT and Alignment.RIGHT are hidden,
+    // following integers are used to represent alignment.
+    static final int ALIGN_LEFT = 0;
+    static final int ALIGN_RIGHT = 1;
+    static final int ALIGN_CENTER = 2;
+
+    final CharSequence mText;
+    final TextPaint mPaint;
+    final int mWidth;
+    final Layout mLayout;
+
+    final int mLeadingMargin;
+    final Alignment mAlign;
+    final TextDirectionHeuristic mDir;
+    final int mExpectedAlign;
+
+    @Parameterized.Parameters(name = "leadingMargin {0} align {1} dir {2}")
+    public static Collection<Object[]> cases() {
+        final Alignment[] aligns = new Alignment[]{Alignment.ALIGN_NORMAL, Alignment.ALIGN_OPPOSITE,
+                Alignment.ALIGN_CENTER, null};
+        // Use String to generate a meaningful test name,
+        final TextDirectionHeuristic[] dirs = new TextDirectionHeuristic[]
+                {TextDirectionHeuristics.LTR, TextDirectionHeuristics.RTL};
+        final int[] leadingMargins = new int[] {0, 17, 23, 37};
+
+        final ArrayList<Object[]> params = new ArrayList<>();
+        for (int leadingMargin: leadingMargins) {
+            for (Alignment align: aligns) {
+                for (TextDirectionHeuristic dir: dirs) {
+                    final String dirName =
+                            dir == TextDirectionHeuristics.LTR ? "DIR_LTR" : "DIR_RTL";
+                    params.add(new Object[] {leadingMargin, align, dirName, dir});
+                }
+            }
+        }
+        return params;
+    }
+
+    public StaticLayoutGetLineLeftRightTest(int leadingMargin, Alignment align,
+            String dirName, TextDirectionHeuristic dir) {
+        mLeadingMargin = leadingMargin;
+        mAlign = align;
+        mDir = dir;
+
+        if (mLeadingMargin > 0) {
+            final SpannableString ss = new SpannableString(TEST_STR);
+            ss.setSpan(new LeadingMarginSpan.Standard(mLeadingMargin),
+                    0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            mText = ss;
+        } else {
+            mText = TEST_STR;
+        }
+
+        mPaint = new TextPaint();
+        // Set TextSize to 32 in case the default is too small.
+        mPaint.setTextSize(32);
+        // Try to make 10 lines.
+        mWidth = mLeadingMargin + (int) mPaint.measureText(mText, 0, mText.length()) / 10;
+        mLayout = StaticLayout.Builder.obtain(mText, 0, mText.length(), mPaint, mWidth)
+                .setAlignment(mAlign)
+                .setTextDirection(mDir)
+                .build();
+
+        if (mAlign == null) {
+            mExpectedAlign = ALIGN_CENTER;
+            return;
+        }
+        switch (mAlign) {
+            case ALIGN_NORMAL:
+                if (dir == TextDirectionHeuristics.RTL) {
+                    mExpectedAlign = ALIGN_RIGHT;
+                } else {
+                    mExpectedAlign = ALIGN_LEFT;
+                }
+                break;
+            case ALIGN_OPPOSITE:
+                if (dir == TextDirectionHeuristics.RTL) {
+                    mExpectedAlign = ALIGN_LEFT;
+                } else {
+                    mExpectedAlign = ALIGN_RIGHT;
+                }
+                break;
+            default: /* mAlign == Alignment.ALIGN_CENTER */
+                mExpectedAlign = ALIGN_CENTER;
+        }
+    }
+
+    @Test
+    public void testGetLineLeft() {
+        final int lineCount = mLayout.getLineCount();
+        for (int line = 0; line < lineCount; ++line) {
+            final int start = mLayout.getLineStart(line);
+            final int end = mLayout.getLineEnd(line);
+            final float textWidth = mPaint.measureText(mText, start, end);
+            final float expectedLeft;
+            if (mExpectedAlign == ALIGN_LEFT) {
+                expectedLeft = 0.0f;
+            } else if (mExpectedAlign == ALIGN_RIGHT) {
+                expectedLeft = mWidth - textWidth - mLeadingMargin;
+            } else {
+                if (mDir == TextDirectionHeuristics.RTL) {
+                    expectedLeft = (float) Math.floor((mWidth - mLeadingMargin - textWidth) / 2);
+                } else {
+                    expectedLeft = (float) Math.floor(mLeadingMargin
+                            + (mWidth - mLeadingMargin - textWidth) / 2);
+                }
+            }
+            assertEquals(expectedLeft, mLayout.getLineLeft(line), 0.0f);
+        }
+    }
+
+    @Test
+    public void testGetLineRight() {
+        final int lineCount = mLayout.getLineCount();
+        for (int line = 0; line < lineCount; ++line) {
+            final int start = mLayout.getLineStart(line);
+            final int end = mLayout.getLineEnd(line);
+            final float textWidth = mPaint.measureText(mText, start, end);
+            final float expectedRight;
+            if (mExpectedAlign == ALIGN_LEFT) {
+                expectedRight = mLeadingMargin + textWidth;
+            } else if (mExpectedAlign == ALIGN_RIGHT) {
+                expectedRight = mWidth;
+            } else {
+                if (mDir == TextDirectionHeuristics.RTL) {
+                    expectedRight = (float) Math.ceil(mWidth - mLeadingMargin
+                            - (mWidth - mLeadingMargin - textWidth) / 2);
+                } else {
+                    expectedRight = (float) Math.ceil(mWidth
+                            - (mWidth - mLeadingMargin - textWidth) / 2);
+                }
+            }
+            assertEquals(expectedRight, mLayout.getLineRight(line), 0.0f);
+        }
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
index 870dd34..d7e59cc 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
@@ -30,6 +30,7 @@
 import android.text.StaticLayout;
 import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
+import android.text.TextUtils;
 import android.text.style.MetricAffectingSpan;
 import android.util.Log;
 
@@ -456,4 +457,16 @@
         layoutMaxLines("CC", new int[] {1}, 1);
         layoutMaxLines("CC", new int[] {1}, 2);
     }
+
+    // Test for b/114454225
+    @Test
+    public void test_staticlayout_doesNotAccessInvalidIndex() {
+        final String str = "IIIII\nIIIII\nIIIII\n";
+        // the following should not throw an exception.
+        StaticLayout.Builder.obtain(str, 0, str.length(), sTextPaint, 4 /* width */)
+            .setEllipsize(TextUtils.TruncateAt.END)
+            .setEllipsizedWidth(4)
+            .setMaxLines(3).build();
+
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 17455e0..f362a3e 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -35,7 +35,6 @@
 import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.Suppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.Editable;
 import android.text.Layout;
@@ -52,6 +51,8 @@
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.method.cts.EditorState;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.LineHeightSpan;
 import android.text.style.ReplacementSpan;
 import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
@@ -1068,23 +1069,23 @@
         state.assertEquals("| U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E");
 
         // Keycap + variation selector
-        state.setByString("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3");
+        state.setByString("| '1' U+FE0F U+20E3 '1' U+FE0F U+20E3 '1' U+FE0F U+20E3");
         moveCursorToRightCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 | '1' U+FE0E U+20E3 '1' U+FE0E U+20E3");
+        state.assertEquals("'1' U+FE0F U+20E3 | '1' U+FE0F U+20E3 '1' U+FE0F U+20E3");
         moveCursorToRightCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 | '1' U+FE0E U+20E3");
+        state.assertEquals("'1' U+FE0F U+20E3 '1' U+FE0F U+20E3 | '1' U+FE0F U+20E3");
         moveCursorToRightCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 |");
+        state.assertEquals("'1' U+FE0F U+20E3 '1' U+FE0F U+20E3 '1' U+FE0F U+20E3 |");
         moveCursorToRightCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 |");
+        state.assertEquals("'1' U+FE0F U+20E3 '1' U+FE0F U+20E3 '1' U+FE0F U+20E3 |");
         moveCursorToLeftCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 | '1' U+FE0E U+20E3");
+        state.assertEquals("'1' U+FE0F U+20E3 '1' U+FE0F U+20E3 | '1' U+FE0F U+20E3");
         moveCursorToLeftCursorableOffset(state);
-        state.assertEquals("'1' U+FE0E U+20E3 | '1' U+FE0E U+20E3 '1' U+FE0E U+20E3");
+        state.assertEquals("'1' U+FE0F U+20E3 | '1' U+FE0F U+20E3 '1' U+FE0F U+20E3");
         moveCursorToLeftCursorableOffset(state);
-        state.assertEquals("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3");
+        state.assertEquals("| '1' U+FE0F U+20E3 '1' U+FE0F U+20E3 '1' U+FE0F U+20E3");
         moveCursorToLeftCursorableOffset(state);
-        state.assertEquals("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3");
+        state.assertEquals("| '1' U+FE0F U+20E3 '1' U+FE0F U+20E3 '1' U+FE0F U+20E3");
 
         // Flags
         // U+1F1E6 U+1F1E8 is Ascension Island flag.
@@ -1262,10 +1263,8 @@
             .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED).build();
     }
 
-    // TODO: Re-enable once http://b/65207701 is fixed.
     @Test
-    @Suppress
-    public void testGetLineWidth() {
+    public void testGetLineMax() {
         final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
         final int lineWidth = (int) (wholeWidth / 10.0f);  // Make 10 lines per paragraph.
         final String multiParaTestString =
@@ -1274,13 +1273,11 @@
                 multiParaTestString.length(), mDefaultPaint, lineWidth)
                 .build();
         for (int i = 0; i < layout.getLineCount(); i++) {
-            assertTrue(layout.getLineWidth(i) <= lineWidth);
+            assertTrue(layout.getLineMax(i) <= lineWidth);
         }
     }
 
-    // TODO: Re-enable once http://b/65207701 is fixed.
     @Test
-    @Suppress
     public void testIndent() {
         final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
         final int lineWidth = (int) (wholeWidth / 10.0f);  // Make 10 lines per paragraph.
@@ -1292,7 +1289,7 @@
                 .setIndents(new int[] { indentWidth }, null)
                 .build();
         for (int i = 0; i < layout.getLineCount(); i++) {
-            assertTrue(layout.getLineWidth(i) <= lineWidth - indentWidth);
+            assertTrue(layout.getLineMax(i) <= lineWidth - indentWidth);
         }
     }
 
@@ -1536,4 +1533,151 @@
         assertEquals(-200, secondMetrics.ascent);
         assertEquals(40, secondMetrics.descent);
     }
+
+    @Test
+    public void testChangeFontMetricsLineHeightBySpanTest() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(50);
+        final SpannableString spanStr0 = new SpannableString(LOREM_IPSUM);
+        // Make sure the final layout contain multiple lines.
+        final int width = (int) paint.measureText(spanStr0.toString()) / 5;
+        final int expectedHeight0 = 25;
+
+        spanStr0.setSpan(new LineHeightSpan.Standard(expectedHeight0), 0, spanStr0.length(),
+                SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+        StaticLayout layout0 = StaticLayout.Builder.obtain(spanStr0, 0, spanStr0.length(),
+                paint, width).build();
+
+        // We need at least 3 lines for testing.
+        assertTrue(layout0.getLineCount() > 2);
+        // Omit the first and last line, because their line hight might be different due to padding.
+        for (int i = 1; i < layout0.getLineCount() - 1; ++i) {
+            assertEquals(expectedHeight0, layout0.getLineBottom(i) - layout0.getLineTop(i));
+        }
+
+        final SpannableString spanStr1 = new SpannableString(LOREM_IPSUM);
+        int expectedHeight1 = 100;
+
+        spanStr1.setSpan(new LineHeightSpan.Standard(expectedHeight1), 0, spanStr1.length(),
+                SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+        StaticLayout layout1 = StaticLayout.Builder.obtain(spanStr1, 0, spanStr1.length(),
+                paint, width).build();
+
+        for (int i = 1; i < layout1.getLineCount() - 1; ++i) {
+            assertEquals(expectedHeight1, layout1.getLineBottom(i) - layout1.getLineTop(i));
+        }
+    }
+
+    @Test
+    public void testChangeFontMetricsLineHeightBySpanMultipleTimesTest() {
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(50);
+        final SpannableString spanStr = new SpannableString(LOREM_IPSUM);
+        final int width = (int) paint.measureText(spanStr.toString()) / 5;
+        final int expectedHeight = 100;
+
+        spanStr.setSpan(new LineHeightSpan.Standard(25), 0, spanStr.length(),
+                SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+        // Only the last span is effective.
+        spanStr.setSpan(new LineHeightSpan.Standard(expectedHeight), 0, spanStr.length(),
+                SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+        StaticLayout layout = StaticLayout.Builder.obtain(spanStr, 0, spanStr.length(),
+                paint, width).build();
+
+        assertTrue(layout.getLineCount() > 2);
+        for (int i = 1; i < layout.getLineCount() - 1; ++i) {
+            assertEquals(expectedHeight, layout.getLineBottom(i) - layout.getLineTop(i));
+        }
+    }
+
+    private class FakeLineBackgroundSpan implements LineBackgroundSpan {
+        // Whenever drawBackground() is called, the start and end of
+        // the line will be stored into mHistory as an array in the
+        // format of [start, end].
+        private final List<int[]> mHistory;
+
+        FakeLineBackgroundSpan() {
+            mHistory = new ArrayList<int[]>();
+        }
+
+        @Override
+        public void drawBackground(Canvas c, Paint p,
+                int left, int right,
+                int top, int baseline, int bottom,
+                CharSequence text, int start, int end,
+                int lnum) {
+            mHistory.add(new int[] {start, end});
+        }
+
+        List<int[]> getHistory() {
+            return mHistory;
+        }
+    }
+
+    private void testLineBackgroundSpanInRange(String text, int start, int end) {
+        final SpannableString spanStr = new SpannableString(text);
+        final FakeLineBackgroundSpan span = new FakeLineBackgroundSpan();
+        spanStr.setSpan(span, start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTextSize(50);
+        final int width = (int) paint.measureText(spanStr.toString()) / 5;
+        final StaticLayout layout = StaticLayout.Builder.obtain(spanStr, 0, spanStr.length(),
+                paint, width).build();
+
+        // One line is too simple, need more to test.
+        assertTrue(layout.getLineCount() > 1);
+        drawToBitmap(layout);
+        List<int[]> history = span.getHistory();
+
+        if (history.size() == 0) {
+            // drawBackground() of FakeLineBackgroundSpan was never called.
+            // This only happens when the length of the span is zero.
+            assertTrue(start >= end);
+            return;
+        }
+
+        // Check if drawBackground() is corrected called for each affected line.
+        int lastLineEnd = history.get(0)[0];
+        for (int[] lineRange: history) {
+            // The range of line must intersect with the span.
+            assertTrue(lineRange[0] < end && lineRange[1] > start);
+            // Check:
+            // 1. drawBackground() is called in the correct sequence.
+            // 2. drawBackground() is called only once for each affected line.
+            assertEquals(lastLineEnd, lineRange[0]);
+            lastLineEnd = lineRange[1];
+        }
+
+        int[] firstLineRange = history.get(0);
+        int[] lastLineRange = history.get(history.size() - 1);
+
+        // Check if affected lines match the span coverage.
+        assertTrue(firstLineRange[0] <= start && end <= lastLineRange[1]);
+    }
+
+    @Test
+    public void testDrawWithLineBackgroundSpanCoverWholeText() {
+        testLineBackgroundSpanInRange(LOREM_IPSUM, 0, LOREM_IPSUM.length());
+    }
+
+    @Test
+    public void testDrawWithLineBackgroundSpanCoverNothing() {
+        int i = 0;
+        // Zero length Spans.
+        testLineBackgroundSpanInRange(LOREM_IPSUM, i, i);
+        i = LOREM_IPSUM.length() / 2;
+        testLineBackgroundSpanInRange(LOREM_IPSUM, i, i);
+    }
+
+    @Test
+    public void testDrawWithLineBackgroundSpanCoverPart() {
+        int start = 0;
+        int end = LOREM_IPSUM.length() / 2;
+        testLineBackgroundSpanInRange(LOREM_IPSUM, start, end);
+
+        start = LOREM_IPSUM.length() / 2;
+        end = LOREM_IPSUM.length();
+        testLineBackgroundSpanInRange(LOREM_IPSUM, start, end);
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/TextPaintTest.java b/tests/tests/text/src/android/text/cts/TextPaintTest.java
index 85fb300..2834268 100644
--- a/tests/tests/text/src/android/text/cts/TextPaintTest.java
+++ b/tests/tests/text/src/android/text/cts/TextPaintTest.java
@@ -53,6 +53,8 @@
         textPaintSrc.linkColor = Color.BLUE;
         textPaintSrc.drawableState = drawableState;
         textPaintSrc.setTypeface(Typeface.DEFAULT_BOLD);
+        textPaintSrc.underlineColor = 0x00112233;
+        textPaintSrc.underlineThickness = 12.0f;
 
         TextPaint textPaint = new TextPaint();
         assertEquals(0, textPaint.bgColor);
@@ -60,6 +62,8 @@
         assertEquals(0, textPaint.linkColor);
         assertNull(textPaint.drawableState);
         assertNull(textPaint.getTypeface());
+        assertEquals(0, textPaint.underlineColor);
+        assertEquals(0.0f, textPaint.underlineThickness, 0.0f);
 
         textPaint.set(textPaintSrc);
         assertEquals(textPaintSrc.bgColor, textPaint.bgColor);
@@ -68,6 +72,8 @@
         assertSame(textPaintSrc.drawableState, textPaint.drawableState);
         assertEquals(textPaintSrc.getTypeface(), textPaint.getTypeface());
         assertEquals(textPaintSrc.getFlags(), textPaint.getFlags());
+        assertEquals(0x00112233, textPaint.underlineColor);
+        assertEquals(12.0f, textPaint.underlineThickness, 0.0f);
 
         try {
             textPaint.set(null);
diff --git a/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java b/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java
new file mode 100644
index 0000000..3440484
--- /dev/null
+++ b/tests/tests/text/src/android/text/style/cts/LineBackgroundSpan_StandardTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.text.style.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.style.LineBackgroundSpan;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LineBackgroundSpan_StandardTest {
+
+    static final int COLOR = 0x04CB2F;
+
+    @Test
+    public void testGetColor() {
+        final LineBackgroundSpan.Standard span = new LineBackgroundSpan.Standard(COLOR);
+        assertEquals(COLOR, span.getColor());
+    }
+
+
+    @Test
+    public void testWriteToParcel() {
+        final LineBackgroundSpan.Standard span = new LineBackgroundSpan.Standard(COLOR);
+        final Parcel p = Parcel.obtain();
+        try {
+            span.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            final LineBackgroundSpan.Standard parcelSpan = new LineBackgroundSpan.Standard(p);
+            assertEquals(COLOR, parcelSpan.getColor());
+            assertEquals(span.getSpanTypeId(), parcelSpan.getSpanTypeId());
+        } finally {
+            p.recycle();
+        }
+    }
+
+    @Test
+    public void testDrawBackground() {
+        final LineBackgroundSpan span = new LineBackgroundSpan.Standard(COLOR);
+
+        final Canvas canvas = mock(Canvas.class);
+        final Paint paint = mock(Paint.class);
+        final int left = 10;
+        final int right = 200;
+        final int top = 20;
+        final int baseline = 40;
+        final int bottom = 50;
+        final String text = "this is the test text!";
+
+        span.drawBackground(canvas, paint, left, right, top, baseline, bottom,
+                text, 0, text.length(), 0);
+        verify(canvas).drawRect(left, top, right, bottom, paint);
+    }
+}
+
diff --git a/tests/tests/text/src/android/text/style/cts/LineHeightSpan_StandardTest.java b/tests/tests/text/src/android/text/style/cts/LineHeightSpan_StandardTest.java
new file mode 100644
index 0000000..7c8c869
--- /dev/null
+++ b/tests/tests/text/src/android/text/style/cts/LineHeightSpan_StandardTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.text.style.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Paint.FontMetricsInt;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.style.LineHeightSpan;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LineHeightSpan_StandardTest {
+    @Test
+    public void testHeightPositive() {
+        // Shouldn't be any exception.
+        new LineHeightSpan.Standard(100);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHeightZero() {
+        new LineHeightSpan.Standard(0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHeightNegative() {
+        new LineHeightSpan.Standard(-1);
+    }
+
+    @Test
+    public void testChooseLineHeight() {
+        final int expectHeight = 50;
+        LineHeightSpan span = new LineHeightSpan.Standard(expectHeight);
+        FontMetricsInt fm = new FontMetricsInt();
+
+        fm.ascent = -20;
+        fm.bottom = 20;
+        span.chooseHeight("helloworld", 0, 9, 0, fm.descent - fm.ascent, fm);
+        assertEquals(expectHeight, fm.descent - fm.ascent);
+
+        fm.ascent = -50;
+        fm.descent = 30;
+        span.chooseHeight("helloworld", 0, 9, 0, fm.descent - fm.ascent, fm);
+        assertEquals(expectHeight, fm.descent - fm.ascent);
+    }
+
+    @Test
+    public void testChooseLineHeightWithNegativeOriginHeight() {
+        // Nothing changes when origin height is negative
+        final int ascent = 30;
+        final int descent = -10;
+        LineHeightSpan span = new LineHeightSpan.Standard(50);
+        FontMetricsInt fm = new FontMetricsInt();
+
+        fm.ascent = ascent;
+        fm.descent = descent;
+        span.chooseHeight("helloworld", 0, 9, 0, fm.descent - fm.ascent, fm);
+        assertEquals(ascent, fm.ascent);
+        assertEquals(descent, fm.descent);
+    }
+
+    @Test
+    public void testGetHeight() {
+        final int height = 23;
+        LineHeightSpan.Standard span = new LineHeightSpan.Standard(height);
+
+        assertEquals(height, span.getHeight());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        final LineHeightSpan.Standard span = new LineHeightSpan.Standard(20);
+        final Parcel parcel = Parcel.obtain();
+        span.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final LineHeightSpan.Standard parcelSpan = new LineHeightSpan.Standard(parcel);
+        assertEquals(span.getHeight(), parcelSpan.getHeight());
+        assertEquals(span.getSpanTypeId(), parcelSpan.getSpanTypeId());
+    }
+}
diff --git a/tests/tests/text/src/android/text/style/cts/SuggestionSpanTest.java b/tests/tests/text/src/android/text/style/cts/SuggestionSpanTest.java
index 4e20ca6..f6b202b 100644
--- a/tests/tests/text/src/android/text/style/cts/SuggestionSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/SuggestionSpanTest.java
@@ -23,10 +23,9 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.os.LocaleList;
 import android.os.Parcel;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -36,6 +35,9 @@
 import android.text.TextPaint;
 import android.text.style.SuggestionSpan;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -248,4 +250,35 @@
             }
         }
     }
+
+    @Test
+    public void testGetUnderlineColor_NoUnderline() {
+        final String[] suggestions = new String[0];
+        final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions, 0);
+        assertEquals(span.getUnderlineColor(), 0);
+    }
+
+    @Test
+    public void testGetUnderlineColor_EasyCorrectUnderline() {
+        final String[] suggestions = new String[0];
+        final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions,
+                SuggestionSpan.FLAG_EASY_CORRECT);
+        assertEquals(span.getUnderlineColor(), Color.BLACK);
+    }
+
+    @Test
+    public void testGetUnderlineColor_MisspelledUnderline() {
+        final String[] suggestions = new String[0];
+        final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions,
+                SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
+        assertEquals(span.getUnderlineColor(), Color.BLACK);
+    }
+
+    @Test
+    public void testGetUnderlineColor_AutoCorrectionUnderline() {
+        final String[] suggestions = new String[0];
+        final SuggestionSpan span = new SuggestionSpan(Locale.forLanguageTag("en"), suggestions,
+                SuggestionSpan.FLAG_AUTO_CORRECTION);
+        assertEquals(span.getUnderlineColor(), Color.BLACK);
+    }
 }
diff --git a/tests/tests/text/src/android/text/style/cts/TextAppearanceSpanTest.java b/tests/tests/text/src/android/text/style/cts/TextAppearanceSpanTest.java
index 489edb2..6ae705f 100644
--- a/tests/tests/text/src/android/text/style/cts/TextAppearanceSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/TextAppearanceSpanTest.java
@@ -20,17 +20,24 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.LocaleList;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.TextPaint;
 import android.text.style.TextAppearanceSpan;
+import android.widget.TextView;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -158,6 +165,102 @@
     }
 
     @Test
+    public void testGetTextFontWeight() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithFontWeight);
+        assertEquals(500, textAppearanceSpan.getTextFontWeight());
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(-1, textAppearanceSpan.getTextFontWeight());
+    }
+
+    @Test
+    public void testGetTextLocales() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithTextLocales);
+        assertEquals(textAppearanceSpan.getTextLocales(),
+                LocaleList.forLanguageTags("ja-JP,zh-CN"));
+    }
+
+    @Test
+    public void testGetShadowColor() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithShadow);
+        assertEquals(Color.parseColor("#00FFFF"), textAppearanceSpan.getShadowColor());
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(0, textAppearanceSpan.getShadowColor());
+    }
+
+    @Test
+    public void testGetShadowDx() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithShadow);
+        assertEquals(1.0f, textAppearanceSpan.getShadowDx(), 0.0f);
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(0.0f, textAppearanceSpan.getShadowDx(), 0.0f);
+    }
+
+    @Test
+    public void testGetShadowDy() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithShadow);
+        assertEquals(2.0f , textAppearanceSpan.getShadowDy(), 0.0f);
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(0.0f, textAppearanceSpan.getShadowDy(), 0.0f);
+    }
+
+    @Test
+    public void testGetShadowRadius() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithShadow);
+        assertEquals(3.0f , textAppearanceSpan.getShadowRadius(), 0.0f);
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(0.0f, textAppearanceSpan.getShadowRadius(), 0.0f);
+    }
+
+    @Test
+    public void testGetFontFeatureSettings() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithFontFeatureSettings);
+        assertEquals("\"smcp\"" , textAppearanceSpan.getFontFeatureSettings());
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(null, textAppearanceSpan.getFontFeatureSettings());
+    }
+
+    @Test
+    public void testGetFontVariationSettings() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithFontVariationSettings);
+        assertEquals("\'wdth\' 150" , textAppearanceSpan.getFontVariationSettings());
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(null, textAppearanceSpan.getFontVariationSettings());
+    }
+
+    @Test
+    public void testIsElegantTextHeight() {
+        TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithElegantTextHeight);
+        assertEquals(true , textAppearanceSpan.isElegantTextHeight());
+
+        textAppearanceSpan = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithNoAttributes);
+        assertEquals(false, textAppearanceSpan.isElegantTextHeight());
+    }
+
+    @Test
     public void testUpdateDrawState() {
         TextAppearanceSpan textAppearanceSpan =
                 new TextAppearanceSpan("sans", 1, 6, mColorStateList, mColorStateList);
@@ -216,6 +319,42 @@
     }
 
     @Test
+    public void testCreateFromStyle_ElegantTextHeight() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithElegantTextHeight);
+        final TextPaint tp = new TextPaint();
+        span.updateDrawState(tp);
+        assertTrue(tp.isElegantTextHeight());
+    }
+
+    @Test
+    public void testCreateFromStyle_LetterSpacing() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithLetterSpacing);
+        final TextPaint tp = new TextPaint();
+        span.updateDrawState(tp);
+        assertEquals(1.0f, tp.getLetterSpacing(), 0.0f);
+    }
+
+    @Test
+    public void testCreateFromStyle_FontFeatureSettings() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithFontFeatureSettings);
+        final TextPaint tp = new TextPaint();
+        span.updateDrawState(tp);
+        assertEquals("\"smcp\"", tp.getFontFeatureSettings());
+    }
+
+    @Test
+    public void testCreateFromStyle_FontVariationSettings() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithFontVariationSettings);
+        final TextPaint tp = new TextPaint();
+        span.updateDrawState(tp);
+        assertEquals("'wdth' 150", tp.getFontVariationSettings());
+    }
+
+    @Test
     public void testWriteReadParcel_FontResource() {
         final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
                 android.text.cts.R.style.customFont);
@@ -250,6 +389,46 @@
     }
 
     @Test
+    public void testWriteReadParcel_WithAllAttributes() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.textAppearanceWithAllAttributes);
+
+        final Parcel p = Parcel.obtain();
+        span.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        final TextAppearanceSpan unparceledSpan = new TextAppearanceSpan(p);
+        final ColorStateList originalTextColor = span.getTextColor();
+        final ColorStateList unparceledTextColor = unparceledSpan.getTextColor();
+        final ColorStateList originalLinkTextColor = span.getLinkTextColor();
+        final ColorStateList unparceledLinkTextColor = unparceledSpan.getLinkTextColor();
+
+        assertEquals(span.getFamily(), unparceledSpan.getFamily());
+        // ColorStateList doesn't implement equals(), so we borrow this code
+        // from ColorStateListTest.java to test correctness of parceling.
+        assertEquals(originalTextColor.isStateful(), unparceledTextColor.isStateful());
+        assertEquals(originalTextColor.getDefaultColor(), unparceledTextColor.getDefaultColor());
+        assertEquals(originalLinkTextColor.isStateful(),
+                unparceledLinkTextColor.isStateful());
+        assertEquals(originalLinkTextColor.getDefaultColor(),
+                unparceledLinkTextColor.getDefaultColor());
+
+        assertEquals(span.getTextSize(), unparceledSpan.getTextSize());
+        assertEquals(span.getTextStyle(), unparceledSpan.getTextStyle());
+        assertEquals(span.getTextFontWeight(), unparceledSpan.getTextFontWeight());
+        assertEquals(span.getTextLocales(), unparceledSpan.getTextLocales());
+        assertEquals(span.getTypeface(), unparceledSpan.getTypeface());
+
+        assertEquals(span.getShadowColor(), unparceledSpan.getShadowColor());
+        assertEquals(span.getShadowDx(), unparceledSpan.getShadowDx(), 0.0f);
+        assertEquals(span.getShadowDy(), unparceledSpan.getShadowDy(), 0.0f);
+        assertEquals(span.getShadowRadius(), unparceledSpan.getShadowRadius(), 0.0f);
+
+        assertEquals(span.getFontFeatureSettings(), unparceledSpan.getFontFeatureSettings());
+        assertEquals(span.getFontVariationSettings(), unparceledSpan.getFontVariationSettings());
+        assertEquals(span.isElegantTextHeight(), unparceledSpan.isElegantTextHeight());
+    }
+
+    @Test
     public void testRestrictContext() throws PackageManager.NameNotFoundException {
         final Context ctx = mContext.createPackageContext(mContext.getPackageName(),
                 Context.CONTEXT_RESTRICTED);
@@ -262,4 +441,179 @@
         assertEquals(originalTextWidth, tp.measureText("a"), 0.0f);
 
     }
+
+    @Test
+    public void testSameAsTextView_TextColor() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTextColor);
+        final TextView tv = new TextView(mContext);
+        tv.setTextAppearance(android.text.cts.R.style.TextAppearanceWithTextColor);
+
+        // No equals implemented in ColorStateList
+        assertEquals(span.getTextColor().toString(), tv.getTextColors().toString());
+    }
+
+    @Test
+    public void testSameAsTextView_TextColorLink() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTextColorLink);
+        final TextView tv = new TextView(mContext);
+        tv.setTextAppearance(android.text.cts.R.style.TextAppearanceWithTextColorLink);
+
+        // No equals implemented in ColorStateList
+        assertEquals(span.getLinkTextColor().toString(), tv.getLinkTextColors().toString());
+    }
+
+    @Test
+    public void testSameAsTextView_TextSize() {
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTextSize);
+        final TextView tv = new TextView(mContext);
+        tv.setTextAppearance(android.text.cts.R.style.TextAppearanceWithTextSize);
+
+        assertEquals((float) span.getTextSize(), tv.getTextSize(), 0.0f);
+    }
+
+    // Currently we don't have a good way to compare typefaces. So this function simply draws
+    // text on Bitmap use the input typeface and then compare the result Bitmaps.
+    private void assertTypefaceSame(Typeface typeface0, Typeface typeface1) {
+        final int width = 200;
+        final int height = 100;
+        final String testStr = "Ez 123*!\u7B80";
+
+        final Bitmap bitmap0 = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+        final Bitmap bitmap1 = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+        final Canvas canvas0 = new Canvas(bitmap0);
+        final Canvas canvas1 = new Canvas(bitmap1);
+        final TextPaint paint0 = new TextPaint();
+        final TextPaint paint1 = new TextPaint();
+        // Set the textSize smaller than the bitmap.
+        paint0.setTextSize(20);
+        paint1.setTextSize(20);
+        // Set the bitmap background color to white.
+        canvas0.drawColor(0xFFFFFFFF);
+        canvas1.drawColor(0xFFFFFFFF);
+        // Set paint a different color from the background.
+        paint0.setColor(0xFF000000);
+        paint1.setColor(0xFF000000);
+        paint0.setTypeface(typeface0);
+        paint1.setTypeface(typeface1);
+
+        final Rect bound0 = new Rect();
+        final Rect bound1 = new Rect();
+
+        paint0.getTextBounds(testStr, 0, testStr.length(), bound0);
+        paint1.getTextBounds(testStr, 0, testStr.length(), bound1);
+
+        // The bounds measured by two paints should be the same.
+        assertEquals(bound0, bound1);
+
+        canvas0.drawText(testStr, -bound0.left, -bound0.top, paint0);
+        canvas1.drawText(testStr, -bound1.left, -bound1.top, paint1);
+
+        assertTrue(bitmap0.sameAs(bitmap1));
+    }
+
+    @Test
+    public void testSameAsTextView_Typeface() {
+        // Here we need textView0 to get a default typeface, which will be updated by
+        // TextAppearanceSpan, and the result of update should be the same as that of
+        // textView1.setTextAppearance().
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTypeface);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(android.text.cts.R.style.TextAppearanceWithTypeface);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
+
+    @Test
+    public void testSameAsTextView_TypefaceWithStyle() {
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTypefaceAndBold);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(android.text.cts.R.style.TextAppearanceWithTypefaceAndBold);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
+
+    @Test
+    public void testSameAsTextView_TypefaceWithWeight() {
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithTypefaceAndWeight);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(
+                android.text.cts.R.style.TextAppearanceWithTypefaceAndWeight);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
+
+    // The priority of fontWeight and bold should be identical in both classes.
+    @Test
+    public void testSameAsTextView_WeightAndBold() {
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithBoldAndWeight);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(android.text.cts.R.style.TextAppearanceWithBoldAndWeight);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
+
+    @Test
+    public void testSameAsTextView_FontFamily() {
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithFontFamily);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(android.text.cts.R.style.TextAppearanceWithFontFamily);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
+
+    // The priority of fontFamily and typeface should be identical in both classes.
+    @Test
+    public void testSameAsTextView_FontFamilyAndTypeface() {
+        final TextView textView0 = new TextView(mContext);
+        final TextView textView1 = new TextView(mContext);
+
+        final TextPaint paint = new TextPaint();
+        paint.setTypeface(textView0.getTypeface());
+        final TextAppearanceSpan span = new TextAppearanceSpan(mContext,
+                android.text.cts.R.style.TextAppearanceWithFontFamilyAndTypeface);
+        span.updateDrawState(paint);
+
+        textView1.setTextAppearance(
+                android.text.cts.R.style.TextAppearanceWithFontFamilyAndTypeface);
+
+        assertTypefaceSame(paint.getTypeface(), textView1.getTypeface());
+    }
 }
diff --git a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
index eee20a2..7c7694e 100644
--- a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
+++ b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
@@ -16,8 +16,10 @@
 
 package android.text.util.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;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -40,6 +42,7 @@
 import org.mockito.ArgumentMatcher;
 
 import java.util.Locale;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -452,6 +455,51 @@
         assertEquals("https://android.com", spans[0].getURL());
     }
 
+    public class MyUrlSpan extends URLSpan {
+        public MyUrlSpan(String url) {
+            super(url);
+        }
+    }
+
+    @Test
+    public void testAddLinks_UrlSpanFactory_withSpannable() {
+        final String text = "a https://android.com a +1 123 456 7878 a android@android.com a";
+        final Spannable spannable = new SpannableString(text);
+        final int mask = Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES;
+        final Function<String, URLSpan> spanFactory = (String string) -> new MyUrlSpan(string);
+
+        Linkify.addLinks(spannable, mask, spanFactory);
+
+        final MyUrlSpan[] myUrlSpans = spannable.getSpans(0, spannable.length(), MyUrlSpan.class);
+        assertNotNull(myUrlSpans);
+        assertEquals(3, myUrlSpans.length);
+        assertEquals("https://android.com", myUrlSpans[0].getURL());
+        assertEquals("tel:+11234567878", myUrlSpans[1].getURL());
+        assertEquals("mailto:android@android.com", myUrlSpans[2].getURL());
+
+        final URLSpan[] urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+        assertArrayEquals(myUrlSpans, urlSpans);
+    }
+
+    @Test
+    public void testAddLinks_UrlSpanFactory_withSpannableAndFilter() {
+        final String text = "google.pattern, test:AZ0101.pattern";
+        final SpannableString spannable = new SpannableString(text);
+        final Function<String, URLSpan> spanFactory = (String string) -> new MyUrlSpan(string);
+
+        Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, "test:", null /*schemes*/,
+                null /*matchFilter*/, null /*transformFilter*/, spanFactory);
+
+        final MyUrlSpan[] myUrlSpans = spannable.getSpans(0, spannable.length(), MyUrlSpan.class);
+        assertNotNull(myUrlSpans);
+        assertEquals(2, myUrlSpans.length);
+        assertEquals("test:google.pattern", myUrlSpans[0].getURL());
+        assertEquals("test:AZ0101.pattern", myUrlSpans[1].getURL());
+
+        final URLSpan[] urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+        assertArrayEquals(myUrlSpans, urlSpans);
+    }
+
     // WEB_URLS Related Tests
 
     @Test
diff --git a/tests/tests/toast/Android.mk b/tests/tests/toast/Android.mk
index bcf7b7e..9ab8465 100644
--- a/tests/tests/toast/Android.mk
+++ b/tests/tests/toast/Android.mk
@@ -29,6 +29,7 @@
 
 LOCAL_PACKAGE_NAME := CtsToastTestCases
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 25
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 0f00913..e56f6a4 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for Toast 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="instant_app" />
     <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/toastlegacy/Android.mk b/tests/tests/toastlegacy/Android.mk
index 217fe55..390222d 100644
--- a/tests/tests/toastlegacy/Android.mk
+++ b/tests/tests/toastlegacy/Android.mk
@@ -30,6 +30,7 @@
 
 LOCAL_PACKAGE_NAME := CtsToastLegacyTestCases
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 25
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/tools/processors/view_inspector/Android.bp b/tests/tests/tools/processors/view_inspector/Android.bp
new file mode 100644
index 0000000..bcebde1
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+android_test {
+    name: "CtsViewInspectorAnnotationProcessorTestCases",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.java"],
+
+    plugins: ["view-inspector-annotation-processor"],
+
+    static_libs: [
+        "android-support-test",
+        "compatibility-device-util",
+        "ctstestrunner",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ]
+}
diff --git a/tests/tests/tools/processors/view_inspector/AndroidManifest.xml b/tests/tests/tools/processors/view_inspector/AndroidManifest.xml
new file mode 100644
index 0000000..e6574d7
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.processor.view.inspector.cts">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.processor.view.inspector.cts"
+                     android:label="CTS tests of android.processor.view.inspector">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/tools/processors/view_inspector/AndroidTest.xml b/tests/tests/tools/processors/view_inspector/AndroidTest.xml
new file mode 100644
index 0000000..dab96b2
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<configuration description="Config for CTS view inspector annotation processor test cases">
+
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsViewInspectorAnnotationProcessorTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.processor.view.inspector.cts" />
+        <option name="runtime-hint" value="30s" />
+    </test>
+</configuration>
diff --git a/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/PlatformInspectableProcessorTest.java b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/PlatformInspectableProcessorTest.java
new file mode 100644
index 0000000..ac4cedb
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/PlatformInspectableProcessorTest.java
@@ -0,0 +1,542 @@
+/*
+ * 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.processor.view.inspector.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.R;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inspector.InspectableNodeName;
+import android.view.inspector.InspectableProperty;
+import android.view.inspector.InspectableProperty.EnumMap;
+import android.view.inspector.InspectableProperty.FlagMap;
+import android.view.inspector.InspectableProperty.ValueType;
+import android.view.inspector.InspectionCompanion;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorLong;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Behavioral tests for {@link android.processor.view.inspector.PlatformInspectableProcessor}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PlatformInspectableProcessorTest {
+    private Random mRandom;
+    private TestPropertyMapper mPropertyMapper;
+    private TestPropertyReader mPropertyReader;
+
+    @Before
+    public void setup() {
+        mRandom = new Random();
+        mPropertyMapper = new TestPropertyMapper();
+        mPropertyReader = new TestPropertyReader(mPropertyMapper);
+    }
+
+    @InspectableNodeName("my_node")
+    class NodeNameTest {
+    }
+
+
+    @Test
+    public void testNodeName() {
+        assertEquals("my_node", loadCompanion(NodeNameTest.class).getNodeName());
+        assertNull(loadCompanion(IntPropertyTest.class).getNodeName());
+    }
+
+    class IntPropertyTest {
+        private final int mValue;
+
+        IntPropertyTest(Random seed) {
+            mValue = seed.nextInt();
+        }
+
+        @InspectableProperty
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    @Test
+    public void testMapAndReadInt() {
+        IntPropertyTest node = new IntPropertyTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getValue(), mPropertyReader.get("value"));
+    }
+
+    @Test
+    public void testInferredAttributeId() {
+        loadCompanion(IntPropertyTest.class).mapProperties(mPropertyMapper);
+        assertEquals(R.attr.value, mPropertyMapper.getAttributeId("value"));
+    }
+
+    @Test(expected = InspectionCompanion.UninitializedPropertyMapException.class)
+    public void testUninitializedPropertyMap() {
+        IntPropertyTest node = new IntPropertyTest(mRandom);
+        loadCompanion(IntPropertyTest.class).readProperties(node, mPropertyReader);
+    }
+
+    class NamedPropertyTest {
+        private final int mValue;
+
+        NamedPropertyTest(Random seed) {
+            mValue = seed.nextInt();
+        }
+
+        @InspectableProperty(name = "myNamedValue", hasAttributeId = false)
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    @Test
+    public void testNamedProperty() {
+        NamedPropertyTest node = new NamedPropertyTest(mRandom);
+        mapAndRead(node);
+        assertEquals(0, mPropertyMapper.getId("value"));
+        assertEquals(node.getValue(), mPropertyReader.get("myNamedValue"));
+    }
+
+    class HasAttributeIdFalseTest {
+        @InspectableProperty(hasAttributeId = false)
+        public int getValue() {
+            return 0;
+        }
+    }
+
+    @Test
+    public void testHasAttributeIdFalse() {
+        loadCompanion(HasAttributeIdFalseTest.class).mapProperties(mPropertyMapper);
+        assertEquals(Resources.ID_NULL, mPropertyMapper.getAttributeId("value"));
+    }
+
+    class AttributeIdEqualsTest {
+        @InspectableProperty(attributeId = 0xdecafbad)
+        public int getValue() {
+            return 0;
+        }
+    }
+
+    @Test
+    public void testAttributeIdEquals() {
+        loadCompanion(AttributeIdEqualsTest.class).mapProperties(mPropertyMapper);
+        assertEquals(0xdecafbad, mPropertyMapper.getAttributeId("value"));
+    }
+
+    class InferredPropertyNameTest {
+        private final int mValueA;
+        private final int mValueB;
+        private final int mValueC;
+
+        InferredPropertyNameTest(Random seed) {
+            mValueA = seed.nextInt();
+            mValueB = seed.nextInt();
+            mValueC = seed.nextInt();
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public int getValueA() {
+            return mValueA;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public int isValueB() {
+            return mValueB;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public int obtainValueC() {
+            return mValueC;
+        }
+    }
+
+    @Test
+    public void testInferredPropertyName() {
+        InferredPropertyNameTest node = new InferredPropertyNameTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getValueA(), mPropertyReader.get("valueA"));
+        assertEquals(node.isValueB(), mPropertyReader.get("isValueB"));
+        assertEquals(node.obtainValueC(), mPropertyReader.get("obtainValueC"));
+    }
+
+    class InferredBooleanNameTest {
+        private final boolean mValueA;
+        private final boolean mValueB;
+        private final boolean mValueC;
+
+        InferredBooleanNameTest(Random seed) {
+            mValueA = seed.nextBoolean();
+            mValueB = seed.nextBoolean();
+            mValueC = seed.nextBoolean();
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public boolean getValueA() {
+            return mValueA;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public boolean isValueB() {
+            return mValueB;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public boolean obtainValueC() {
+            return mValueC;
+        }
+    }
+
+    @Test
+    public void testInferredBooleanName() {
+        InferredBooleanNameTest node = new InferredBooleanNameTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getValueA(), mPropertyReader.get("valueA"));
+        assertEquals(node.isValueB(), mPropertyReader.get("valueB"));
+        assertEquals(node.obtainValueC(), mPropertyReader.get("obtainValueC"));
+    }
+
+    class ColorTest {
+        private final int mColorInt;
+        private final long mColorLong;
+
+        private final Color mColorObject;
+
+        ColorTest(Random seed) {
+            mColorInt = seed.nextInt();
+            mColorLong = Color.pack(seed.nextInt());
+            mColorObject = Color.valueOf(seed.nextInt());
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        @ColorInt
+        public int getColorInt() {
+            return mColorInt;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        @ColorLong
+        public long getColorLong() {
+            return mColorLong;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public Color getColorObject() {
+            return mColorObject;
+        }
+    }
+
+    @Test
+    public void testColorTypeInference() {
+        ColorTest node = new ColorTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getColorInt(), mPropertyReader.get("colorInt"));
+        assertEquals(node.getColorLong(), mPropertyReader.get("colorLong"));
+        assertEquals(node.getColorObject(), mPropertyReader.get("colorObject"));
+        assertEquals(ValueType.COLOR, mPropertyMapper.getValueType("colorInt"));
+        assertEquals(ValueType.COLOR, mPropertyMapper.getValueType("colorLong"));
+        assertEquals(ValueType.COLOR, mPropertyMapper.getValueType("colorObject"));
+    }
+
+    class ValueTypeTest {
+        private final int mColor;
+        private final int mGravity;
+        private final int mValue;
+
+        ValueTypeTest(Random seed) {
+            mColor = seed.nextInt();
+            mGravity = seed.nextInt();
+            mValue = seed.nextInt();
+        }
+
+        @InspectableProperty(valueType = ValueType.COLOR)
+        public int getColor() {
+            return mColor;
+        }
+
+        @InspectableProperty(valueType = ValueType.GRAVITY)
+        public int getGravity() {
+            return mGravity;
+        }
+
+        @InspectableProperty(valueType = ValueType.NONE)
+        @ColorInt
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    @Test
+    public void testValueTypeEquals() {
+        ValueTypeTest node = new ValueTypeTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getColor(), mPropertyReader.get("color"));
+        assertEquals(node.getGravity(), mPropertyReader.get("gravity"));
+        assertEquals(node.getValue(), mPropertyReader.get("value"));
+        assertEquals(ValueType.COLOR, mPropertyMapper.getValueType("color"));
+        assertEquals(ValueType.GRAVITY, mPropertyMapper.getValueType("gravity"));
+        assertEquals(ValueType.NONE, mPropertyMapper.getValueType("value"));
+    }
+
+    class PrimitivePropertiesTest {
+        private final boolean mBoolean;
+        private final byte mByte;
+        private final char mChar;
+        private final double mDouble;
+        private final float mFloat;
+        private final int mInt;
+        private final long mLong;
+        private final short mShort;
+
+        PrimitivePropertiesTest(Random seed) {
+            mBoolean = seed.nextBoolean();
+            mByte = (byte) seed.nextInt();
+            mChar = randomLetter(seed);
+            mDouble = seed.nextDouble();
+            mFloat = seed.nextFloat();
+            mInt = seed.nextInt();
+            mLong = seed.nextLong();
+            mShort = (short) seed.nextInt();
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public boolean getBoolean() {
+            return mBoolean;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public byte getByte() {
+            return mByte;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public char getChar() {
+            return mChar;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public double getDouble() {
+            return mDouble;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public float getFloat() {
+            return mFloat;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public int getInt() {
+            return mInt;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public long getLong() {
+            return mLong;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public short getShort() {
+            return mShort;
+        }
+    }
+
+    @Test
+    public void testPrimitiveProperties() {
+        PrimitivePropertiesTest node = new PrimitivePropertiesTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getBoolean(), mPropertyReader.get("boolean"));
+        assertEquals(node.getByte(), mPropertyReader.get("byte"));
+        assertEquals(node.getChar(), mPropertyReader.get("char"));
+        assertEquals(node.getDouble(), mPropertyReader.get("double"));
+        assertEquals(node.getFloat(), mPropertyReader.get("float"));
+        assertEquals(node.getInt(), mPropertyReader.get("int"));
+        assertEquals(node.getLong(), mPropertyReader.get("long"));
+        assertEquals(node.getShort(), mPropertyReader.get("short"));
+    }
+
+    class ObjectPropertiesTest {
+        private final String mText;
+
+        ObjectPropertiesTest(Random seed) {
+            final StringBuilder stringBuilder = new StringBuilder();
+            final int length = seed.nextInt(8) + 8;
+
+            for (int i = 0; i < length; i++) {
+                stringBuilder.append(randomLetter(seed));
+            }
+
+            mText = stringBuilder.toString();
+        }
+
+        @InspectableProperty
+        public String getText() {
+            return mText;
+        }
+
+        @InspectableProperty(hasAttributeId = false)
+        public Objects getNull() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testObjectProperties() {
+        ObjectPropertiesTest node = new ObjectPropertiesTest(mRandom);
+        mapAndRead(node);
+        assertEquals(node.getText(), mPropertyReader.get("text"));
+        assertNull(mPropertyReader.get("null"));
+        assertNotEquals(0, mPropertyMapper.getId("null"));
+    }
+
+    class IntEnumTest {
+        private int mValue;
+
+        @InspectableProperty(enumMapping = {
+                @EnumMap(name = "ONE", value = 1),
+                @EnumMap(name = "TWO", value = 2)})
+        public int getValue() {
+            return mValue;
+        }
+
+        public void setValue(int value) {
+            mValue = value;
+        }
+    }
+
+    @Test
+    public void testIntEnum() {
+        IntEnumTest node = new IntEnumTest();
+        InspectionCompanion<IntEnumTest> companion = loadCompanion(IntEnumTest.class);
+        companion.mapProperties(mPropertyMapper);
+
+        node.setValue(1);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals("ONE", mPropertyReader.getIntEnum("value"));
+
+        node.setValue(2);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals("TWO", mPropertyReader.getIntEnum("value"));
+
+        node.setValue(3);
+        companion.readProperties(node, mPropertyReader);
+        assertNull(mPropertyReader.getIntEnum("value"));
+    }
+
+    class IntFlagTest {
+        private int mValue;
+
+        @InspectableProperty(flagMapping = {
+                @FlagMap(name = "ONE", target = 0x1, mask = 0x3),
+                @FlagMap(name = "TWO", target = 0x2, mask = 0x3),
+                @FlagMap(name = "THREE", target = 0x3, mask = 0x3),
+                @FlagMap(name = "FOUR", target = 0x4)})
+        public int getValue() {
+            return mValue;
+        }
+
+        public void setValue(int value) {
+            mValue = value;
+        }
+    }
+
+    @Test
+    public void testIntFlag() {
+        IntFlagTest node = new IntFlagTest();
+        InspectionCompanion<IntFlagTest> companion = loadCompanion(IntFlagTest.class);
+        companion.mapProperties(mPropertyMapper);
+
+        node.setValue(0);
+        companion.readProperties(node, mPropertyReader);
+        assertTrue(mPropertyReader.getIntFlag("value").isEmpty());
+
+        node.setValue(1);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals(setOf("ONE"), mPropertyReader.getIntFlag("value"));
+
+        node.setValue(2);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals(setOf("TWO"), mPropertyReader.getIntFlag("value"));
+
+        node.setValue(3);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals(setOf("THREE"), mPropertyReader.getIntFlag("value"));
+
+        node.setValue(4);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals(setOf("FOUR"), mPropertyReader.getIntFlag("value"));
+
+        node.setValue(5);
+        companion.readProperties(node, mPropertyReader);
+        assertEquals(setOf("FOUR", "ONE"), mPropertyReader.getIntFlag("value"));
+    }
+
+    private static <T> Set<T> setOf(T... items) {
+        Set<T> set = new HashSet<>(items.length);
+
+        for (T item : items) {
+            set.add(item);
+        }
+
+        return set;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> void mapAndRead(T node) {
+        InspectionCompanion<T> companion = loadCompanion((Class<T>) node.getClass());
+        companion.mapProperties(mPropertyMapper);
+        companion.readProperties(node, mPropertyReader);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> InspectionCompanion<T> loadCompanion(Class<T> cls) {
+        final ClassLoader classLoader = cls.getClassLoader();
+        final String companionName = String.format("%s$$InspectionCompanion", cls.getName());
+
+        try {
+            final Class<InspectionCompanion<T>> companion =
+                    (Class<InspectionCompanion<T>>) classLoader.loadClass(companionName);
+            return companion.newInstance();
+        } catch (ClassNotFoundException e) {
+            fail(String.format("Unable to load companion for %s", cls.getCanonicalName()));
+        } catch (InstantiationException | IllegalAccessException e) {
+            fail(String.format("Unable to instantiate companion for %s", cls.getCanonicalName()));
+        }
+
+        return null;
+    }
+
+    private char randomLetter(Random random) {
+        final String alphabet = "abcdefghijklmnopqrstuvwxyz";
+        return alphabet.charAt(random.nextInt(alphabet.length()));
+    }
+}
diff --git a/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyMapper.java b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyMapper.java
new file mode 100644
index 0000000..3c81e3c
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyMapper.java
@@ -0,0 +1,131 @@
+/*
+ * 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.processor.view.inspector.cts;
+
+import android.view.inspector.InspectableProperty.ValueType;
+import android.view.inspector.IntEnumMapping;
+import android.view.inspector.IntFlagMapping;
+import android.view.inspector.PropertyMapper;
+
+import java.util.HashMap;
+
+class TestPropertyMapper implements PropertyMapper {
+    private final HashMap<String, Integer> mPropertyIds = new HashMap<>();
+    private final HashMap<String, Integer> mAttributeIds = new HashMap<>();
+    private final HashMap<String, ValueType> mValueTypes = new HashMap<>();
+    private final HashMap<String, IntEnumMapping> mIntEnumMappings = new HashMap<>();
+    private final HashMap<String, IntFlagMapping> mIntFlagMappings = new HashMap<>();
+    private int mNextId = 1;
+
+    int getId(String name) {
+        return mPropertyIds.getOrDefault(name, 0);
+    }
+
+    int getAttributeId(String name) {
+        return mAttributeIds.getOrDefault(name, 0);
+    }
+
+    ValueType getValueType(String name) {
+        return mValueTypes.getOrDefault(name, ValueType.NONE);
+    }
+
+    IntEnumMapping getIntEnumMapping(String name) {
+        return mIntEnumMappings.get(name);
+    }
+
+    IntFlagMapping getIntFlagMapping(String name) {
+        return mIntFlagMappings.get(name);
+    }
+
+    @Override
+    public int mapBoolean(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapByte(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapChar(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapDouble(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapFloat(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapInt(String name, int attributeId) { return map(name, attributeId); }
+
+    @Override
+    public int mapLong(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapShort(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapObject(String name, int attributeId) {
+        return map(name, attributeId);
+    }
+
+    @Override
+    public int mapColor(String name, int attributeId) {
+        return map(name, attributeId, ValueType.COLOR);
+    }
+
+    @Override
+    public int mapGravity(String name, int attributeId) {
+        return map(name, attributeId, ValueType.GRAVITY);
+    }
+
+    @Override
+    public int mapIntEnum(String name, int attributeId, IntEnumMapping intEnumMapping) {
+        mIntEnumMappings.put(name, intEnumMapping);
+        return map(name, attributeId, ValueType.INT_ENUM);
+    }
+
+    @Override
+    public int mapIntFlag(String name, int attributeId, IntFlagMapping intFlagMapping) {
+        mIntFlagMappings.put(name, intFlagMapping);
+        return map(name, attributeId, ValueType.INT_FLAG);
+    }
+
+    private int map(String name, int attributeId) {
+        return map(name, attributeId, ValueType.NONE);
+    }
+
+    private int map(String name, int attributeId, ValueType valueType) {
+        if (mPropertyIds.containsKey(name)) {
+            throw new PropertyConflictException(name, "all", "all");
+        }
+        mAttributeIds.put(name, attributeId);
+        mValueTypes.put(name, valueType);
+        return mPropertyIds.computeIfAbsent(name, n -> mNextId++);
+    }
+}
diff --git a/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyReader.java b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyReader.java
new file mode 100644
index 0000000..aeee818
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/src/android/processor/view/inspector/cts/TestPropertyReader.java
@@ -0,0 +1,121 @@
+/*
+ * 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.processor.view.inspector.cts;
+
+import android.graphics.Color;
+import android.util.SparseArray;
+import android.view.inspector.PropertyReader;
+
+import java.util.Objects;
+import java.util.Set;
+
+
+class TestPropertyReader implements PropertyReader {
+    private final TestPropertyMapper mPropertyMapper;
+    private final SparseArray<Object> mProperties = new SparseArray<>();
+
+    TestPropertyReader(TestPropertyMapper propertyMapper) {
+        mPropertyMapper = Objects.requireNonNull(propertyMapper);
+    }
+
+    Object get(String name) {
+        return mProperties.get(mPropertyMapper.getId(name));
+    }
+
+    String getIntEnum(String name) {
+        return mPropertyMapper.getIntEnumMapping(name).get((Integer) get(name));
+    }
+
+    Set<String> getIntFlag(String name) {
+        return mPropertyMapper.getIntFlagMapping(name).get((Integer) get(name));
+    }
+
+    @Override
+    public void readBoolean(int id, boolean value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readByte(int id, byte value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readChar(int id, char value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readDouble(int id, double value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readFloat(int id, float value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readInt(int id, int value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readLong(int id, long value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readShort(int id, short value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readObject(int id, Object value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readColor(int id, int value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readColor(int id, long value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readColor(int id, Color value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readGravity(int id, int value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readIntEnum(int id, int value) {
+        mProperties.put(id, value);
+    }
+
+    @Override
+    public void readIntFlag(int id, int value) {
+        mProperties.put(id, value);
+    }
+}
diff --git a/tests/tests/transition/res/transition/transition_set.xml b/tests/tests/transition/res/transition/transition_set.xml
index 3b1ff3c..a685e4d 100644
--- a/tests/tests/transition/res/transition/transition_set.xml
+++ b/tests/tests/transition/res/transition/transition_set.xml
@@ -14,7 +14,9 @@
      limitations under the License.
 -->
 <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
-    android:transitionOrdering="sequential">
+    android:transitionOrdering="sequential"
+    android:duration="300">
+    <arcMotion/>
     <changeBounds/>
     <fade/>
 </transitionSet>
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 6c4de33..3dd5135 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -95,10 +95,12 @@
 
     @After
     public void cleanup() throws Throwable {
-        if (TargetActivity.sLastCreated != null) {
-            mActivityRule.runOnUiThread(() -> TargetActivity.sLastCreated.finish());
-        }
-        TargetActivity.sLastCreated = null;
+        mActivityRule.runOnUiThread(() -> {
+            for (TargetActivity activity : TargetActivity.sCreated) {
+                activity.finish();
+            }
+        });
+        TargetActivity.clearCreated();
     }
 
     // When using ActivityOptions.makeBasic(), no transitions should run
@@ -139,7 +141,8 @@
         });
 
         TargetActivity targetActivity = waitForTargetActivity();
-        verify(targetActivity.enterListener, within(3000)).onTransitionEnd(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionStart(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionEnd(any());
         verify(mExitListener, times(1)).onTransitionEnd(any());
 
         // Now check the targets... they should all be there
@@ -163,8 +166,11 @@
         assertEquals(1, targetActivity.findViewById(R.id.holder).getAlpha(), 0.01f);
 
         mActivityRule.runOnUiThread(() -> targetActivity.finishAfterTransition());
-        verify(mReenterListener, within(3000)).onTransitionEnd(any());
-        verify(mSharedElementReenterListener, within(3000)).onTransitionEnd(any());
+        verify(mReenterListener, within(5000)).onTransitionStart(any());
+        verify(mReenterListener, within(5000)).onTransitionEnd(any());
+        verify(mSharedElementReenterListener, within(5000)).onTransitionStart(any());
+        verify(mSharedElementReenterListener, within(5000)).onTransitionEnd(any());
+        verify(targetActivity.returnListener, times(1)).onTransitionStart(any());
         verify(targetActivity.returnListener, times(1)).onTransitionEnd(any());
 
         // return targets are stripped also
@@ -192,8 +198,6 @@
         assertEquals(1, mActivity.findViewById(R.id.redSquare).getAlpha(), 0.01f);
         assertEquals(1, mActivity.findViewById(R.id.greenSquare).getAlpha(), 0.01f);
         assertEquals(1, mActivity.findViewById(R.id.holder).getAlpha(), 0.01f);
-
-        TargetActivity.sLastCreated = null;
     }
 
     // Views that are outside the visible area during initial layout should be stripped from
@@ -211,7 +215,8 @@
         });
 
         TargetActivity targetActivity = waitForTargetActivity();
-        verify(targetActivity.enterListener, within(3000)).onTransitionEnd(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionStart(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionEnd(any());
         verify(mExitListener, times(1)).onTransitionEnd(any());
 
         // Now check the targets... they should all be stripped
@@ -234,8 +239,11 @@
         assertEquals(1, targetActivity.findViewById(R.id.holder).getAlpha(), 0.01f);
 
         mActivityRule.runOnUiThread(() -> targetActivity.finishAfterTransition());
-        verify(mReenterListener, within(3000)).onTransitionEnd(any());
-        verify(mSharedElementReenterListener, within(3000)).onTransitionEnd(any());
+        verify(mReenterListener, within(5000)).onTransitionStart(any());
+        verify(mReenterListener, within(5000)).onTransitionEnd(any());
+        verify(mSharedElementReenterListener, within(5000)).onTransitionStart(any());
+        verify(mSharedElementReenterListener, within(5000)).onTransitionEnd(any());
+        verify(targetActivity.returnListener, times(1)).onTransitionStart(any());
         verify(targetActivity.returnListener, times(1)).onTransitionEnd(any());
 
         // return targets are stripped also
@@ -261,8 +269,6 @@
         assertEquals(1, mActivity.findViewById(R.id.redSquare).getAlpha(), 0.01f);
         assertEquals(1, mActivity.findViewById(R.id.greenSquare).getAlpha(), 0.01f);
         assertEquals(1, mActivity.findViewById(R.id.holder).getAlpha(), 0.01f);
-
-        TargetActivity.sLastCreated = null;
     }
 
     // When an exit transition takes longer than it takes the activity to cover it (and onStop
@@ -285,8 +291,9 @@
         });
 
         TargetActivity targetActivity = waitForTargetActivity();
-        verify(targetActivity.enterListener, within(3000)).onTransitionEnd(any());
-        verify(mExitListener, within(3000)).onTransitionEnd(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionStart(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionEnd(any());
+        verify(mExitListener, within(5000)).onTransitionEnd(any());
 
         mActivityRule.runOnUiThread(() -> {
             // Verify that the exited views have an alpha of 1 and are visible
@@ -318,7 +325,7 @@
         TargetActivity targetActivity = waitForTargetActivity();
         assertTrue(targetActivity.isActivityTransitionRunning());
         mActivityRule.runOnUiThread(() -> { });
-        PollingCheck.waitFor(() -> !targetActivity.isActivityTransitionRunning());
+        PollingCheck.waitFor(5000, () -> !targetActivity.isActivityTransitionRunning());
 
         assertFalse(mActivity.isActivityTransitionRunning());
         mActivityRule.runOnUiThread(() -> {
@@ -367,7 +374,8 @@
             mActivity.startActivity(intent, options);
         });
 
-        verify(mExitListener, within(3000)).onTransitionEnd(any());
+        verify(mExitListener, within(5000)).onTransitionStart(any());
+        verify(mExitListener, within(5000)).onTransitionEnd(any());
 
         TargetActivity targetActivity = waitForTargetActivity();
 
@@ -389,9 +397,8 @@
         assertEquals(View.VISIBLE, targetActivity.endVisibility);
 
         assertTrue(targetActivity.transitionComplete.await(1, TimeUnit.SECONDS));
-        verify(mReenterListener, within(3000)).onTransitionEnd(any());
-
-        TargetActivity.sLastCreated = null;
+        verify(mReenterListener, within(5000)).onTransitionStart(any());
+        verify(mReenterListener, within(5000)).onTransitionEnd(any());
     }
 
     // Starting a shared element transition and then removing the view shouldn't cause problems.
@@ -414,13 +421,14 @@
 
 
         TargetActivity targetActivity = waitForTargetActivity();
-        verify(targetActivity.enterListener, within(3000)).onTransitionEnd(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionStart(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionEnd(any());
 
         mActivityRule.runOnUiThread(() -> targetActivity.finishAfterTransition());
         mActivityRule.runOnUiThread(() -> parent.removeAllViews());
 
+        verify(targetActivity.returnListener, times(1)).onTransitionStart(any());
         verify(targetActivity.returnListener, times(1)).onTransitionEnd(any());
-        TargetActivity.sLastCreated = null;
     }
 
     // Ensure that the shared element view copy is the correct image of the shared element view
@@ -439,7 +447,8 @@
         });
 
         TargetActivity targetActivity = waitForTargetActivity();
-        verify(targetActivity.enterListener, within(3000)).onTransitionEnd(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionStart(any());
+        verify(targetActivity.enterListener, within(5000)).onTransitionEnd(any());
         verify(mExitListener, times(1)).onTransitionEnd(any());
 
         final CountDownLatch startCalled = new CountDownLatch(1);
@@ -466,15 +475,23 @@
 
         // Should only take a short time, but there's no need to rush it on failure.
         assertTrue(startCalled.await(5, TimeUnit.SECONDS));
-
-        TargetActivity.sLastCreated = null;
     }
 
     private TargetActivity waitForTargetActivity() throws Throwable {
-        PollingCheck.waitFor(() -> TargetActivity.sLastCreated != null);
-        // Just make sure that we're not in the middle of running on the UI thread.
-        mActivityRule.runOnUiThread(() -> { });
-        return TargetActivity.sLastCreated;
+        verify(TargetActivity.sCreated, within(3000)).add(any());
+        TargetActivity[] activity = new TargetActivity[1];
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals(1, TargetActivity.sCreated.size());
+            activity[0] = TargetActivity.sCreated.get(0);
+        });
+        assertTrue("There was no draw call", activity[0].drawnOnce.await(3, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {
+            activity[0].getWindow().getDecorView().invalidate();
+        });
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(activity[0].preDrawCalls > 1);
+        });
+        return activity[0];
     }
 
     private Set<Integer> getTargetViewIds(TargetTracking transition) {
diff --git a/tests/tests/transition/src/android/transition/cts/BaseTransitionTest.java b/tests/tests/transition/src/android/transition/cts/BaseTransitionTest.java
index 5917ace..5117629 100644
--- a/tests/tests/transition/src/android/transition/cts/BaseTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/BaseTransitionTest.java
@@ -19,12 +19,14 @@
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.app.Instrumentation;
+import android.graphics.PointF;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.transition.Scene;
@@ -34,14 +36,17 @@
 import android.transition.Visibility;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public abstract class BaseTransitionTest {
     protected Instrumentation mInstrumentation;
@@ -148,6 +153,32 @@
         mAnimatedValue = animatedValue;
     }
 
+    List<PointF> captureTranslations(View view) throws Throwable {
+        final ArrayList<PointF> points = Mockito.spy(new ArrayList<>());
+        mActivityRule.runOnUiThread(() -> {
+            ViewTreeObserver.OnDrawListener listener = new ViewTreeObserver.OnDrawListener() {
+                @Override
+                public void onDraw() {
+                    float x = view.getTranslationX();
+                    float y = view.getTranslationY();
+                    if (points.isEmpty() || !points.get(points.size() - 1).equals(x, y)) {
+                        points.add(new PointF(x, y));
+                    }
+                    if (points.size() > 3 && x == 0f && y == 0f) {
+                        view.post(() -> {
+                            view.getViewTreeObserver().removeOnDrawListener(this);
+                        });
+                    }
+                }
+            };
+            view.getViewTreeObserver().addOnDrawListener(listener);
+            view.invalidate();
+        });
+        verify(points, timeout(1000).times(1)).add(any());
+        return points;
+    }
+
+
     public class TestTransition extends Visibility {
 
         public TestTransition() {
diff --git a/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java b/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
index 40ba46b..71bb9f1 100644
--- a/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ChangeBoundsTest.java
@@ -15,10 +15,14 @@
  */
 package android.transition.cts;
 
+import static com.android.compatibility.common.util.CtsMockitoUtils.within;
+
 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 static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.mock;
 
 import android.animation.Animator;
@@ -28,7 +32,9 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.ChangeBounds;
+import android.transition.Scene;
 import android.transition.Transition;
+import android.transition.TransitionManager;
 import android.transition.TransitionValues;
 import android.util.TypedValue;
 import android.view.View;
@@ -36,11 +42,13 @@
 import android.view.ViewTreeObserver;
 import android.view.animation.LinearInterpolator;
 
-import com.android.compatibility.common.util.PollingCheck;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -63,7 +71,7 @@
     private void resetChangeBoundsTransition() {
         mListener = mock(Transition.TransitionListener.class);
         mChangeBounds = new MyChangeBounds();
-        mChangeBounds.setDuration(400);
+        mChangeBounds.setDuration(500);
         mChangeBounds.addListener(mListener);
         mChangeBounds.setInterpolator(new LinearInterpolator());
         mTransition = mChangeBounds;
@@ -79,7 +87,7 @@
 
         startTransition(R.layout.scene6);
         // The update listener will validate that it is changing throughout the animation
-        waitForEnd(800);
+        waitForEnd(5000);
 
         validateInScene6();
     }
@@ -98,7 +106,7 @@
         startTransition(R.layout.scene6);
 
         // The update listener will validate that it is changing throughout the animation
-        waitForEnd(800);
+        waitForEnd(5000);
 
         validateInScene6();
     }
@@ -114,7 +122,7 @@
         startTransition(R.layout.scene1);
 
         // The update listener will validate that it is changing throughout the animation
-        waitForEnd(800);
+        waitForEnd(5000);
 
         validateInScene1();
     }
@@ -125,14 +133,15 @@
 
         validateInScene1();
 
-        startTransition(R.layout.scene6);
+        List<RedAndGreen> points1 = startTransitionAndWatch(R.layout.scene6);
 
-        waitForMiddleOfTransition();
+        waitForSizeIsMiddle(points1);
         resetChangeBoundsTransition();
-        startTransition(R.layout.scene6);
+        List<RedAndGreen> points2 = startTransitionAndWatch(R.layout.scene6);
 
-        assertFalse(isRestartingAnimation());
-        waitForEnd(1000);
+        waitForEnd(5000);
+
+        assertFalse(isRestartingAnimation(points2, R.layout.scene1));
         validateInScene6();
     }
 
@@ -143,17 +152,17 @@
 
         validateInScene1();
 
-        startTransition(R.layout.scene6);
+        List<RedAndGreen> points1 = startTransitionAndWatch(R.layout.scene6);
 
-        waitForMiddleOfTransition();
+        waitForClipIsMiddle(points1);
 
         resetChangeBoundsTransition();
         mChangeBounds.setResizeClip(true);
-        startTransition(R.layout.scene6);
+        List<RedAndGreen> points2 = startTransitionAndWatch(R.layout.scene6);
+        waitForEnd(5000);
 
-        assertFalse(isRestartingAnimation());
-        assertFalse(isRestartingClip());
-        waitForEnd(1000);
+        assertFalse(isRestartingAnimation(points2, R.layout.scene1));
+        assertFalse(isRestartingClip(points2, R.layout.scene1));
         validateInScene6();
     }
 
@@ -163,15 +172,15 @@
 
         validateInScene1();
 
-        startTransition(R.layout.scene6);
+        List<RedAndGreen> points1 = startTransitionAndWatch(R.layout.scene6);
 
-        waitForMiddleOfTransition();
+        waitForSizeIsMiddle(points1);
         // reverse the transition back to scene1
         resetChangeBoundsTransition();
-        startTransition(R.layout.scene1);
+        List<RedAndGreen> points2 = startTransitionAndWatch(R.layout.scene1);
+        waitForEnd(5000);
 
-        assertFalse(isRestartingAnimation());
-        waitForEnd(1000);
+        assertFalse(isRestartingAnimation(points2, R.layout.scene1));
         validateInScene1();
     }
 
@@ -182,57 +191,104 @@
 
         validateInScene1();
 
-        startTransition(R.layout.scene6);
-        waitForMiddleOfTransition();
+        List<RedAndGreen> points1 = startTransitionAndWatch(R.layout.scene6);
+        waitForClipIsMiddle(points1);
 
         // reverse the transition back to scene1
         resetChangeBoundsTransition();
         mChangeBounds.setResizeClip(true);
-        startTransition(R.layout.scene1);
+        List<RedAndGreen> points2 = startTransitionAndWatch(R.layout.scene1);
+        waitForEnd(5000);
 
-        assertFalse(isRestartingAnimation());
-        assertFalse(isRestartingClip());
-        waitForEnd(1000);
+        assertFalse(isRestartingAnimation(points2, R.layout.scene1));
+        assertFalse(isRestartingAnimation(points2, R.layout.scene6));
+        assertFalse(isRestartingClip(points2, R.layout.scene1));
+        assertFalse(isRestartingClip(points2, R.layout.scene6));
         validateInScene1();
     }
 
-    private void waitForMiddleOfTransition() throws Throwable {
-        Resources resources = mActivity.getResources();
-        float closestDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                SMALL_SQUARE_SIZE_DP / 2, resources.getDisplayMetrics());
-
-        final View red = mActivity.findViewById(R.id.redSquare);
-        final View green = mActivity.findViewById(R.id.greenSquare);
-
-        PollingCheck.waitFor(
-                () -> red.getTop() > closestDistance && green.getTop() > closestDistance);
-
-        assertTrue(red.getTop() > closestDistance);
-        assertTrue(green.getTop() > closestDistance);
+    private List<RedAndGreen> startTransitionAndWatch(int layoutId) throws Throwable {
+        final Scene scene = loadScene(layoutId);
+        final List<RedAndGreen> points = Mockito.spy(new ArrayList<>());
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene, mTransition);
+            mActivity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(() -> {
+                points.add(new RedAndGreen(mActivity));
+            });
+        });
+        return points;
     }
 
-    private boolean isRestartingAnimation() {
-        View red = mActivity.findViewById(R.id.redSquare);
-        View green = mActivity.findViewById(R.id.greenSquare);
+    private void waitForSizeIsMiddle(List<RedAndGreen> points) throws Throwable {
         Resources resources = mActivity.getResources();
-        float closestDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+        float middleSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                (SMALL_SQUARE_SIZE_DP + LARGE_SQUARE_SIZE_DP) / 2, resources.getDisplayMetrics());
+
+        Mockito.verify(points, within(3000)).add(argThat(redAndGreen ->
+                redAndGreen.red.position.width() > middleSize
+                        && redAndGreen.red.position.height() > middleSize
+                        && redAndGreen.green.position.width() > middleSize
+                        && redAndGreen.green.position.height() > middleSize
+        ));
+    }
+
+    private void waitForClipIsMiddle(List<RedAndGreen> points) throws Throwable {
+        Resources resources = mActivity.getResources();
+        float middleSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                (SMALL_SQUARE_SIZE_DP + LARGE_SQUARE_SIZE_DP) / 2, resources.getDisplayMetrics());
+
+        Mockito.verify(points, within(3000)).add(argThat(redAndGreen ->
+                redAndGreen.red.clip != null
+                        && redAndGreen.green.clip != null
+                        && redAndGreen.red.clip.width() > middleSize
+                        && redAndGreen.red.clip.height() > middleSize
+                        && redAndGreen.green.clip.width() > middleSize
+                        && redAndGreen.green.clip.height() > middleSize
+        ));
+    }
+
+    private boolean isRestartingAnimation(List<RedAndGreen> points, int startLayoutId) {
+        Resources resources = mActivity.getResources();
+        float errorPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 SMALL_OFFSET_DP, resources.getDisplayMetrics());
-        return red.getTop() < closestDistance || green.getTop() < closestDistance;
+
+        RedAndGreen start = points.get(0);
+        if (startLayoutId == R.layout.scene1) {
+            float smallSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    SMALL_SQUARE_SIZE_DP, resources.getDisplayMetrics());
+            return start.red.position.top == 0
+                    && Math.abs(smallSize - start.green.position.top) < errorPx;
+        } else if (startLayoutId == R.layout.scene6) {
+            float largeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    LARGE_SQUARE_SIZE_DP, resources.getDisplayMetrics());
+            return start.green.position.top == 0
+                    && Math.abs(largeSize - start.red.position.top) < errorPx;
+        } else {
+            fail("Don't know what to do with that layout id");
+            return false;
+        }
     }
 
-    private boolean isRestartingClip() {
+    private boolean isRestartingClip(List<RedAndGreen> points, int startLayoutId) {
         Resources resources = mActivity.getResources();
-        float smallDim = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                SMALL_SQUARE_SIZE_DP + SMALL_OFFSET_DP, resources.getDisplayMetrics());
-        float largeDim = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                LARGE_SQUARE_SIZE_DP - SMALL_OFFSET_DP, resources.getDisplayMetrics());
+        float errorPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                SMALL_OFFSET_DP, resources.getDisplayMetrics());
 
-        View red = mActivity.findViewById(R.id.redSquare);
-        Rect redClip = red.getClipBounds();
-        View green = mActivity.findViewById(R.id.greenSquare);
-        Rect greenClip = green.getClipBounds();
-        return redClip == null || redClip.width() < smallDim || redClip.width() > largeDim ||
-                greenClip == null || greenClip.width() < smallDim || greenClip.width() > largeDim;
+        RedAndGreen start = points.get(0);
+        if (startLayoutId == R.layout.scene1) {
+            float smallSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    SMALL_SQUARE_SIZE_DP, resources.getDisplayMetrics());
+            return start.red.clip.width() < smallSize + errorPx
+                    && start.green.clip.width() < smallSize + errorPx;
+        } else if (startLayoutId == R.layout.scene6) {
+            float largeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    LARGE_SQUARE_SIZE_DP, resources.getDisplayMetrics());
+            return start.red.clip.width() > largeSize - errorPx
+                    && start.green.clip.width() > largeSize - errorPx;
+        } else {
+            fail("Don't know what to do with that layout id");
+            return false;
+        }
     }
 
     private void validateInScene1() {
@@ -279,13 +335,9 @@
                 + expectedDim + ", but was " + dim, isWithinAPixel(expectedDim, dim));
     }
 
-    private static void assertNotWithinAPixel(float expectedDim, int dim) {
-        assertTrue("Expected dimension to not be within one pixel of "
-                + expectedDim + ", but was " + dim, !isWithinAPixel(expectedDim, dim));
-    }
-
     private class MyChangeBounds extends ChangeBounds {
         private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
+
         @Override
         public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                 TransitionValues endValues) {
@@ -399,5 +451,28 @@
         public void onAnimationRepeat(Animator animation) {
         }
     }
+
+    static class RedAndGreen {
+        public final PositionAndClip red;
+        public final PositionAndClip green;
+
+        RedAndGreen(TransitionActivity activity) {
+            View redView = activity.findViewById(R.id.redSquare);
+            red = new PositionAndClip(redView);
+            View greenView = activity.findViewById(R.id.redSquare);
+            green = new PositionAndClip(greenView);
+        }
+    }
+
+    static class PositionAndClip {
+        public final Rect position;
+        public final Rect clip;
+
+        PositionAndClip(View view) {
+            this.clip = view.getClipBounds();
+            this.position =
+                    new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+        }
+    }
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/ChangeImageTransformTest.java b/tests/tests/transition/src/android/transition/cts/ChangeImageTransformTest.java
index 6ae8a9f..23bbf0a 100644
--- a/tests/tests/transition/src/android/transition/cts/ChangeImageTransformTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ChangeImageTransformTest.java
@@ -15,23 +15,24 @@
  */
 package android.transition.cts;
 
+import static com.android.compatibility.common.util.CtsMockitoUtils.within;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.graphics.Color;
 import android.graphics.Matrix;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.ChangeImageTransform;
 import android.transition.TransitionManager;
-import android.transition.TransitionValues;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.ViewGroup;
@@ -42,6 +43,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -64,8 +70,8 @@
     }
 
     private void resetTransition() {
-        mChangeImageTransform = new CaptureMatrix();
-        mChangeImageTransform.setDuration(100);
+        mChangeImageTransform = new ChangeImageTransform();
+        mChangeImageTransform.setDuration(500);
         mTransition = mChangeImageTransform;
         resetListener();
     }
@@ -99,6 +105,32 @@
         verifyMatrixMatches(centerMatrix(), mEndMatrix);
     }
 
+    @Test
+    public void testNoChange() throws Throwable {
+        transformImage(ScaleType.CENTER, ScaleType.CENTER);
+        verifyMatrixMatches(centerMatrix(), mStartMatrix);
+        verifyMatrixMatches(centerMatrix(), mEndMatrix);
+    }
+
+    @Test
+    public void testNoAnimationForDrawableWithoutSize() throws Throwable {
+        transformImage(ScaleType.CENTER_CROP, ScaleType.FIT_XY, new ColorDrawable(Color.WHITE),
+                false, false, true);
+        Matrix identityMatrix = new Matrix();
+        assertEquals(identityMatrix, mStartMatrix);
+        assertEquals(identityMatrix, mEndMatrix);
+    }
+
+    @Test
+    public void testNullAnimatorKeepsImagePadding() throws Throwable {
+        transformImage(ScaleType.FIT_XY, ScaleType.FIT_XY, new ColorDrawable(Color.WHITE),
+                true, true, true);
+        assertEquals(mImage.getBounds().width(), mImageView.getWidth()
+                - mImageView.getPaddingLeft() - mImageView.getPaddingRight());
+        assertEquals(mImage.getBounds().height(), mImageView.getHeight()
+                - mImageView.getPaddingTop() - mImageView.getPaddingBottom());
+    }
+
     private Matrix centerMatrix() {
         int imageWidth = mImage.getIntrinsicWidth();
         int imageViewWidth = mImageView.getWidth();
@@ -228,24 +260,79 @@
         }
     }
 
-    private void transformImage(ScaleType startScale, final ScaleType endScale) throws Throwable {
-        final ImageView imageView = enterImageViewScene(startScale);
-        mActivityRule.runOnUiThread(() -> {
-            TransitionManager.beginDelayedTransition(mSceneRoot, mChangeImageTransform);
-            imageView.setScaleType(endScale);
-        });
-        waitForStart();
-        verify(mListener, (startScale == endScale) ? times(1) : never()).onTransitionEnd(any());
-        waitForEnd(1000);
+    private void transformImage(final ScaleType startScale,
+            final ImageView.ScaleType endScale) throws Throwable {
+        transformImage(startScale, endScale, null, false, false, startScale == endScale);
     }
 
-    private ImageView enterImageViewScene(final ScaleType scaleType) throws Throwable {
+    private void transformImage(final ScaleType startScale,
+            final ImageView.ScaleType endScale,
+            final Drawable customImage,
+            final boolean applyPadding,
+            final boolean withChangingSize,
+            final boolean noMatrixChangeExpected) throws Throwable {
+        final ImageView imageView = enterImageViewScene(startScale, customImage, applyPadding);
+        final List<Matrix> matrices = watchImageMatrix(imageView);
+
+        mActivityRule.runOnUiThread(() -> {
+            imageView.invalidate();
+        });
+
+        // Wait for one draw() call
+        verify(matrices, within(5000)).add(any());
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.beginDelayedTransition(mSceneRoot, mChangeImageTransform);
+            if (withChangingSize) {
+                imageView.getLayoutParams().height /= 2;
+                imageView.requestLayout();
+            }
+            imageView.setScaleType(endScale);
+            imageView.invalidate();
+        });
+        waitForStart();
+        waitForEnd(5000);
+        if (noMatrixChangeExpected) {
+            verify(matrices, times(1)).add(any());
+            assertEquals(1, matrices.size());
+        } else {
+            verify(matrices, timeout(5000).atLeast(3)).add(any());
+        }
+        mStartMatrix = matrices.get(0);
+        mEndMatrix = matrices.get(matrices.size() - 1);
+    }
+
+    private List<Matrix> watchImageMatrix(ImageView view) throws Throwable {
+        final List<Matrix> matrices = Mockito.spy(new ArrayList<>());
+        mActivityRule.runOnUiThread(() -> {
+            mActivity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(() -> {
+                Matrix matrix = view.getImageMatrix();
+                if (matrices.isEmpty()
+                        || !Objects.equals(matrix, matrices.get(matrices.size() - 1))) {
+                    if (matrix == null) {
+                        matrices.add(matrix);
+                    } else {
+                        matrices.add(new Matrix(matrix));
+                    }
+                }
+            });
+        });
+        return matrices;
+    }
+
+    private ImageView enterImageViewScene(final ScaleType scaleType,
+            final Drawable customImage,
+            final boolean withPadding) throws Throwable {
         enterScene(R.layout.scene4);
-        final ViewGroup container = (ViewGroup) mActivity.findViewById(R.id.holder);
+        final ViewGroup container = mActivity.findViewById(R.id.holder);
         final ImageView[] imageViews = new ImageView[1];
         mActivityRule.runOnUiThread(() -> {
             mImageView = new ImageView(mActivity);
-            mImage = mActivity.getDrawable(android.R.drawable.ic_media_play);
+            if (customImage != null) {
+                mImage = customImage;
+            } else {
+                mImage = mActivity.getDrawable(android.R.drawable.ic_media_play);
+            }
             mImageView.setImageDrawable(mImage);
             mImageView.setScaleType(scaleType);
             imageViews[0] = mImageView;
@@ -256,45 +343,14 @@
             layoutParams.width = Math.round(size);
             layoutParams.height = Math.round(size * 2);
             mImageView.setLayoutParams(layoutParams);
+            if (withPadding) {
+                int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
+                        metrics);
+                mImageView.setPadding(padding, padding, padding, padding);
+            }
         });
         mInstrumentation.waitForIdleSync();
         return imageViews[0];
     }
-
-    private class CaptureMatrix extends ChangeImageTransform {
-        @Override
-        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-                TransitionValues endValues) {
-            Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
-            animator.addListener(new CaptureMatrixListener((ImageView) endValues.view));
-            return animator;
-        }
-    }
-
-    private class CaptureMatrixListener extends AnimatorListenerAdapter {
-        private final ImageView mImageView;
-
-        public CaptureMatrixListener(ImageView view) {
-            mImageView = view;
-        }
-
-        @Override
-        public void onAnimationStart(Animator animation) {
-            mStartMatrix = copyMatrix();
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mEndMatrix = copyMatrix();
-        }
-
-        private Matrix copyMatrix() {
-            Matrix matrix = mImageView.getImageMatrix();
-            if (matrix != null) {
-                matrix = new Matrix(matrix);
-            }
-            return matrix;
-        }
-    }
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/ExplodeTest.java b/tests/tests/transition/src/android/transition/cts/ExplodeTest.java
index 52340e0..c1248a5 100644
--- a/tests/tests/transition/src/android/transition/cts/ExplodeTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ExplodeTest.java
@@ -16,21 +16,27 @@
 package android.transition.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.graphics.PointF;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.Explode;
 import android.transition.TransitionManager;
 import android.view.View;
 
+import com.android.compatibility.common.util.PollingCheck;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ExplodeTest extends BaseTransitionTest {
@@ -45,6 +51,7 @@
 
     private void resetTransition() {
         mExplode = new Explode();
+        mExplode.setDuration(500);
         mTransition = mExplode;
         resetListener();
     }
@@ -57,6 +64,11 @@
         final View blueSquare = mActivity.findViewById(R.id.blueSquare);
         final View yellowSquare = mActivity.findViewById(R.id.yellowSquare);
 
+        final List<PointF> redPoints = captureTranslations(redSquare);
+        final List<PointF> greenPoints = captureTranslations(greenSquare);
+        final List<PointF> bluePoints = captureTranslations(blueSquare);
+        final List<PointF> yellowPoints = captureTranslations(yellowSquare);
+
         mActivityRule.runOnUiThread(() -> {
             TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
             redSquare.setVisibility(View.INVISIBLE);
@@ -70,17 +82,12 @@
         assertEquals(View.VISIBLE, greenSquare.getVisibility());
         assertEquals(View.VISIBLE, blueSquare.getVisibility());
         assertEquals(View.VISIBLE, yellowSquare.getVisibility());
-        float redStartX = redSquare.getTranslationX();
-        float redStartY = redSquare.getTranslationY();
 
-        Thread.sleep(100);
-        verifyTranslation(redSquare, true, true);
-        verifyTranslation(greenSquare, false, true);
-        verifyTranslation(blueSquare, false, false);
-        verifyTranslation(yellowSquare, true, false);
-        assertTrue(redStartX > redSquare.getTranslationX()); // moving left
-        assertTrue(redStartY > redSquare.getTranslationY()); // moving up
-        waitForEnd(400);
+        waitForEnd(5000);
+        verifyMovement(redPoints, false, false, true);
+        verifyMovement(greenPoints, true, false, true);
+        verifyMovement(bluePoints, true, true, true);
+        verifyMovement(yellowPoints, false, true, true);
 
         verifyNoTranslation(redSquare);
         verifyNoTranslation(greenSquare);
@@ -100,6 +107,11 @@
         final View blueSquare = mActivity.findViewById(R.id.blueSquare);
         final View yellowSquare = mActivity.findViewById(R.id.yellowSquare);
 
+        final List<PointF> redPoints = captureTranslations(redSquare);
+        final List<PointF> greenPoints = captureTranslations(greenSquare);
+        final List<PointF> bluePoints = captureTranslations(blueSquare);
+        final List<PointF> yellowPoints = captureTranslations(yellowSquare);
+
         mActivityRule.runOnUiThread(() -> {
             redSquare.setVisibility(View.INVISIBLE);
             greenSquare.setVisibility(View.INVISIBLE);
@@ -121,17 +133,12 @@
         assertEquals(View.VISIBLE, greenSquare.getVisibility());
         assertEquals(View.VISIBLE, blueSquare.getVisibility());
         assertEquals(View.VISIBLE, yellowSquare.getVisibility());
-        float redStartX = redSquare.getTranslationX();
-        float redStartY = redSquare.getTranslationY();
 
-        Thread.sleep(100);
-        verifyTranslation(redSquare, true, true);
-        verifyTranslation(greenSquare, false, true);
-        verifyTranslation(blueSquare, false, false);
-        verifyTranslation(yellowSquare, true, false);
-        assertTrue(redStartX < redSquare.getTranslationX()); // moving right
-        assertTrue(redStartY < redSquare.getTranslationY()); // moving down
-        waitForEnd(400);
+        waitForEnd(5000);
+        verifyMovement(redPoints, true, true, false);
+        verifyMovement(greenPoints, false, true, false);
+        verifyMovement(bluePoints, false, false, false);
+        verifyMovement(yellowPoints, true, false, false);
 
         verifyNoTranslation(redSquare);
         verifyNoTranslation(greenSquare);
@@ -143,21 +150,33 @@
         assertEquals(View.VISIBLE, yellowSquare.getVisibility());
     }
 
-    private void verifyTranslation(View view, boolean goLeft, boolean goUp) {
-        float translationX = view.getTranslationX();
-        float translationY = view.getTranslationY();
+    private void verifyMovement(List<PointF> points, boolean moveRight, boolean moveDown,
+            boolean explode) {
+        int numPoints = points.size();
+        assertTrue(numPoints > 3);
 
-        if (goLeft) {
-            assertTrue(translationX < 0);
-        } else {
-            assertTrue(translationX > 0);
-        }
+        // skip the first point -- it is the value before the change
+        PointF firstPoint = points.get(1);
 
-        if (goUp) {
-            assertTrue(translationY < 0);
-        } else {
-            assertTrue(translationY > 0);
-        }
+        // Skip the last point -- it may be the settled value after the change
+        PointF lastPoint = points.get(numPoints - 2);
+
+        assertNotEquals(lastPoint.x, firstPoint.x);
+        assertNotEquals(lastPoint.y, firstPoint.y);
+        assertEquals(moveRight, firstPoint.x < lastPoint.x);
+        assertEquals(moveDown, firstPoint.y < lastPoint.y);
+
+        assertEquals(explode, Math.abs(firstPoint.x) < Math.abs(lastPoint.x));
+        assertEquals(explode, Math.abs(firstPoint.y) < Math.abs(lastPoint.y));
+    }
+
+    private void waitForMovement(View view, float startX, float startY) {
+        PollingCheck.waitFor(5000, () -> hasMoved(view, startX, startY));
+    }
+
+    private boolean hasMoved(View view, float x, float y) {
+        return Math.abs(view.getTranslationX() - x) > 2f
+                || Math.abs(view.getTranslationY() - y) > 2f;
     }
 
     private void verifyNoTranslation(View view) {
diff --git a/tests/tests/transition/src/android/transition/cts/PropagationTest.java b/tests/tests/transition/src/android/transition/cts/PropagationTest.java
index dfd7b03..062b5233 100644
--- a/tests/tests/transition/src/android/transition/cts/PropagationTest.java
+++ b/tests/tests/transition/src/android/transition/cts/PropagationTest.java
@@ -39,8 +39,8 @@
         enterScene(R.layout.scene10);
         CircularPropagation propagation = new CircularPropagation();
         mTransition.setPropagation(propagation);
-        final TransitionValues redValues = new TransitionValues();
-        redValues.view = mActivity.findViewById(R.id.redSquare);
+        final TransitionValues redValues = new TransitionValues(
+                mActivity.findViewById(R.id.redSquare));
         propagation.captureValues(redValues);
 
         // Only the reported propagation properties are set
@@ -218,8 +218,7 @@
     }
 
     private TransitionValues capturePropagationValues(int viewId) {
-        TransitionValues transitionValues = new TransitionValues();
-        transitionValues.view = mSceneRoot.findViewById(viewId);
+        TransitionValues transitionValues = new TransitionValues(mSceneRoot.findViewById(viewId));
         mTransition.getPropagation().captureValues(transitionValues);
         return transitionValues;
     }
diff --git a/tests/tests/transition/src/android/transition/cts/SceneTest.java b/tests/tests/transition/src/android/transition/cts/SceneTest.java
index 8c06f51..3a60ccd 100644
--- a/tests/tests/transition/src/android/transition/cts/SceneTest.java
+++ b/tests/tests/transition/src/android/transition/cts/SceneTest.java
@@ -15,6 +15,8 @@
  */
 package android.transition.cts;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -93,6 +95,13 @@
         constructorTest(scene);
     }
 
+    @Test
+    public void testGetCurrentScene() throws Throwable {
+        Scene scene = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, mActivity);
+        enterScene(scene);
+        assertThat(Scene.getCurrentScene(mSceneRoot), is(scene));
+    }
+
     /**
      * Tests that the Scene was constructed properly from a scene1
      */
diff --git a/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java b/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
index f70db09..63db5ef 100644
--- a/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
+++ b/tests/tests/transition/src/android/transition/cts/SlideEdgeTest.java
@@ -21,9 +21,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.graphics.PointF;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.Slide;
@@ -32,12 +32,13 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-
-import com.android.compatibility.common.util.PollingCheck;
+import android.view.ViewTreeObserver;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class SlideEdgeTest extends BaseTransitionTest  {
@@ -71,6 +72,7 @@
         for (int i = 0; i < sSlideEdgeArray.length; i++) {
             final int slideEdge = (Integer) (sSlideEdgeArray[i][0]);
             final Slide slide = new Slide(slideEdge);
+            slide.setDuration(500);
             final Transition.TransitionListener listener =
                     mock(Transition.TransitionListener.class);
             slide.addListener(listener);
@@ -82,54 +84,34 @@
             final View greenSquare = mActivity.findViewById(R.id.greenSquare);
             final View hello = mActivity.findViewById(R.id.hello);
             final ViewGroup sceneRoot = (ViewGroup) mActivity.findViewById(R.id.holder);
+            final List<PointF> redPoints = captureTranslations(redSquare);
+            final List<PointF> greenPoints = captureTranslations(greenSquare);
+            final List<PointF> helloPoints = captureTranslations(hello);
 
             mActivityRule.runOnUiThread(() -> {
                 TransitionManager.beginDelayedTransition(sceneRoot, slide);
                 redSquare.setVisibility(View.INVISIBLE);
                 greenSquare.setVisibility(View.INVISIBLE);
                 hello.setVisibility(View.INVISIBLE);
+                hello.getViewTreeObserver().addOnPreDrawListener(
+                        new ViewTreeObserver.OnPreDrawListener() {
+                            @Override
+                            public boolean onPreDraw() {
+                                assertEquals(View.VISIBLE, redSquare.getVisibility());
+                                assertEquals(View.VISIBLE, greenSquare.getVisibility());
+                                assertEquals(View.VISIBLE, hello.getVisibility());
+
+                                hello.getViewTreeObserver().removeOnPreDrawListener(this);
+                                return true;
+                            }
+                        });
             });
             verify(listener, within(1000)).onTransitionStart(any());
-            verify(listener, never()).onTransitionEnd(any());
-            assertEquals(View.VISIBLE, redSquare.getVisibility());
-            assertEquals(View.VISIBLE, greenSquare.getVisibility());
-            assertEquals(View.VISIBLE, hello.getVisibility());
-
-            float redStartX = redSquare.getTranslationX();
-            float redStartY = redSquare.getTranslationY();
-
-            Thread.sleep(200);
-            verifyTranslation(slideEdge, redSquare);
-            verifyTranslation(slideEdge, greenSquare);
-            verifyTranslation(slideEdge, hello);
-
-            final float redMidX = redSquare.getTranslationX();
-            final float redMidY = redSquare.getTranslationY();
-
-            switch (slideEdge) {
-                case Gravity.LEFT:
-                case Gravity.START:
-                    assertTrue(
-                            "isn't sliding out to left. Expecting " + redStartX + " > " + redMidX,
-                            redStartX > redMidX);
-                    break;
-                case Gravity.RIGHT:
-                case Gravity.END:
-                    assertTrue(
-                            "isn't sliding out to right. Expecting " + redStartX + " < " + redMidX,
-                            redStartX < redMidX);
-                    break;
-                case Gravity.TOP:
-                    assertTrue("isn't sliding out to top. Expecting " + redStartY + " > " + redMidY,
-                            redStartY > redSquare.getTranslationY());
-                    break;
-                case Gravity.BOTTOM:
-                    assertTrue(
-                            "isn't sliding out to bottom. Expecting " + redStartY + " < " + redMidY,
-                            redStartY < redSquare.getTranslationY());
-                    break;
-            }
             verify(listener, within(1000)).onTransitionEnd(any());
+
+            verifyMovement(redPoints, slideEdge, false);
+            verifyMovement(greenPoints, slideEdge, false);
+            verifyMovement(helloPoints, slideEdge, false);
             mInstrumentation.waitForIdleSync();
 
             verifyNoTranslation(redSquare);
@@ -146,6 +128,7 @@
         for (int i = 0; i < sSlideEdgeArray.length; i++) {
             final int slideEdge = (Integer) (sSlideEdgeArray[i][0]);
             final Slide slide = new Slide(slideEdge);
+            slide.setDuration(500);
             final Transition.TransitionListener listener =
                     mock(Transition.TransitionListener.class);
             slide.addListener(listener);
@@ -158,6 +141,10 @@
             final View hello = mActivity.findViewById(R.id.hello);
             final ViewGroup sceneRoot = (ViewGroup) mActivity.findViewById(R.id.holder);
 
+            final List<PointF> redPoints = captureTranslations(redSquare);
+            final List<PointF> greenPoints = captureTranslations(greenSquare);
+            final List<PointF> helloPoints = captureTranslations(hello);
+
             mActivityRule.runOnUiThread(() -> {
                 redSquare.setVisibility(View.INVISIBLE);
                 greenSquare.setVisibility(View.INVISIBLE);
@@ -171,49 +158,25 @@
                 redSquare.setVisibility(View.VISIBLE);
                 greenSquare.setVisibility(View.VISIBLE);
                 hello.setVisibility(View.VISIBLE);
+                hello.getViewTreeObserver().addOnPreDrawListener(
+                        new ViewTreeObserver.OnPreDrawListener() {
+                            @Override
+                            public boolean onPreDraw() {
+                                assertEquals(View.VISIBLE, redSquare.getVisibility());
+                                assertEquals(View.VISIBLE, greenSquare.getVisibility());
+                                assertEquals(View.VISIBLE, hello.getVisibility());
+
+                                hello.getViewTreeObserver().removeOnPreDrawListener(this);
+                                return true;
+                            }
+                        });
             });
             verify(listener, within(1000)).onTransitionStart(any());
-
-            verify(listener, never()).onTransitionEnd(any());
-            assertEquals(View.VISIBLE, redSquare.getVisibility());
-            assertEquals(View.VISIBLE, greenSquare.getVisibility());
-            assertEquals(View.VISIBLE, hello.getVisibility());
-
-            final float redStartX = redSquare.getTranslationX();
-            final float redStartY = redSquare.getTranslationY();
-
-            Thread.sleep(200);
-            verifyTranslation(slideEdge, redSquare);
-            verifyTranslation(slideEdge, greenSquare);
-            verifyTranslation(slideEdge, hello);
-            final float redMidX = redSquare.getTranslationX();
-            final float redMidY = redSquare.getTranslationY();
-
-            switch (slideEdge) {
-                case Gravity.LEFT:
-                case Gravity.START:
-                    assertTrue(
-                            "isn't sliding in from left. Expecting " + redStartX + " < " + redMidX,
-                            redStartX < redMidX);
-                    break;
-                case Gravity.RIGHT:
-                case Gravity.END:
-                    assertTrue(
-                            "isn't sliding in from right. Expecting " + redStartX + " > " + redMidX,
-                            redStartX > redMidX);
-                    break;
-                case Gravity.TOP:
-                    assertTrue(
-                            "isn't sliding in from top. Expecting " + redStartY + " < " + redMidY,
-                            redStartY < redSquare.getTranslationY());
-                    break;
-                case Gravity.BOTTOM:
-                    assertTrue("isn't sliding in from bottom. Expecting " + redStartY + " > "
-                                    + redMidY,
-                            redStartY > redSquare.getTranslationY());
-                    break;
-            }
             verify(listener, within(1000)).onTransitionEnd(any());
+
+            verifyMovement(redPoints, slideEdge, true);
+            verifyMovement(greenPoints, slideEdge, true);
+            verifyMovement(helloPoints, slideEdge, true);
             mInstrumentation.waitForIdleSync();
 
             verifyNoTranslation(redSquare);
@@ -225,34 +188,54 @@
         }
     }
 
-    private void verifyTranlationChanged(View view) {
-        final float startX = view.getTranslationX();
-        final float startY = view.getTranslationY();
-        PollingCheck.waitFor(
-                () -> view.getTranslationX() != startX || view.getTranslationY() != startY);
-    }
+    private void verifyMovement(List<PointF> points, int slideEdge, boolean in) {
+        int numPoints = points.size();
+        assertTrue(numPoints > 4);
 
-    private void verifyTranslation(int slideEdge, View view) {
+        // skip the first point -- it is the value before the change
+        PointF firstPoint = points.get(1);
+
+        PointF midPoint = points.get(numPoints / 2);
+
+        // Skip the last point -- it may be the settled value after the change
+        PointF lastPoint = points.get(numPoints - 2);
+
+        // Check nothing hapens in off-axis motion
         switch (slideEdge) {
             case Gravity.LEFT:
             case Gravity.START:
-                assertTrue(view.getTranslationX() < 0);
-                assertEquals(0f, view.getTranslationY(), 0.01f);
-                break;
             case Gravity.RIGHT:
             case Gravity.END:
-                assertTrue(view.getTranslationX() > 0);
-                assertEquals(0f, view.getTranslationY(), 0.01f);
+                assertEquals(0f, lastPoint.y, 0.01f);
+                assertEquals(0f, midPoint.y, 0.01f);
+                assertEquals(0f, firstPoint.y, 0.01f);
                 break;
             case Gravity.TOP:
-                assertTrue(view.getTranslationY() < 0);
-                assertEquals(0f, view.getTranslationX(), 0.01f);
-                break;
             case Gravity.BOTTOM:
-                assertTrue(view.getTranslationY() > 0);
-                assertEquals(0f, view.getTranslationX(), 0.01f);
+                assertEquals(0f, lastPoint.x, 0.01f);
+                assertEquals(0f, midPoint.x, 0.01f);
+                assertEquals(0f, firstPoint.x, 0.01f);
                 break;
         }
+
+        float startCoord;
+        float endCoord;
+        float midCoord;
+        boolean moveGreater;
+        if (slideEdge == Gravity.TOP || slideEdge == Gravity.BOTTOM) {
+            midCoord = midPoint.y;
+            startCoord = firstPoint.y;
+            endCoord = lastPoint.y;
+            moveGreater = in == (slideEdge == Gravity.TOP);
+        } else {
+            midCoord = midPoint.x;
+            startCoord = firstPoint.x;
+            endCoord = lastPoint.x;
+            moveGreater = in == (slideEdge == Gravity.START || slideEdge == Gravity.LEFT);
+        }
+        assertEquals(moveGreater, endCoord > midCoord);
+        assertEquals(moveGreater, midCoord > startCoord);
+        assertEquals(in, Math.abs(endCoord) < Math.abs(startCoord));
     }
 
     private void verifyNoTranslation(View view) {
diff --git a/tests/tests/transition/src/android/transition/cts/TargetActivity.java b/tests/tests/transition/src/android/transition/cts/TargetActivity.java
index a710ca0..a547979 100644
--- a/tests/tests/transition/src/android/transition/cts/TargetActivity.java
+++ b/tests/tests/transition/src/android/transition/cts/TargetActivity.java
@@ -28,6 +28,10 @@
 import com.android.compatibility.common.util.transition.TrackingTransition;
 import com.android.compatibility.common.util.transition.TrackingVisibility;
 
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 public class TargetActivity extends Activity {
@@ -43,11 +47,17 @@
     final TransitionListener enterListener = mock(TransitionListener.class);
     final TransitionListener returnListener = mock(TransitionListener.class);
 
-    public static TargetActivity sLastCreated;
+    public static List<TargetActivity> sCreated = Mockito.spy(new ArrayList<>());
 
     public int startVisibility = -1;
     public int endVisibility = -1;
     public CountDownLatch transitionComplete;
+    public CountDownLatch drawnOnce = new CountDownLatch(1);
+    public int preDrawCalls = 0;
+
+    public static void clearCreated() {
+        sCreated = Mockito.spy(new ArrayList<>());
+    }
 
     @Override
     public void onCreate(Bundle bundle){
@@ -99,6 +109,11 @@
         enterTransition.addListener(enterListener);
         returnTransition.addListener(returnListener);
 
-        sLastCreated = this;
+        getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(() -> {
+            preDrawCalls++;
+            drawnOnce.countDown();
+            return true;
+        });
+        sCreated.add(this);
     }
 }
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionInflaterTest.java b/tests/tests/transition/src/android/transition/cts/TransitionInflaterTest.java
index 9a7ab94..c8d45a8 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionInflaterTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionInflaterTest.java
@@ -162,6 +162,8 @@
         assertTrue(transition instanceof TransitionSet);
         TransitionSet set = (TransitionSet) transition;
         assertEquals(TransitionSet.ORDERING_SEQUENTIAL, set.getOrdering());
+        assertEquals(300, set.getDuration());
+        assertNotNull(set.getPathMotion());
         assertEquals(2, set.getTransitionCount());
         assertTrue(set.getTransitionAt(0) instanceof ChangeBounds);
         assertTrue(set.getTransitionAt(1) instanceof Fade);
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java b/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
index 27cd3f6..e401e88 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionManagerTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -256,5 +258,71 @@
         SystemClock.sleep(10);
         verify(mListener, never()).onTransitionEnd(any());
     }
+
+    @Test
+    public void testGo_enterAction() throws Throwable {
+        Scene scene = loadScene(R.layout.scene1);
+        Runnable enterCheck = mock(Runnable.class);
+        scene.setEnterAction(enterCheck);
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene);
+            verify(enterCheck, times(1)).run();
+        });
+    }
+
+    @Test
+    public void testGo_exitAction() throws Throwable {
+        Scene scene1 = loadScene(R.layout.scene1);
+        Runnable exitAction = mock(Runnable.class);
+        scene1.setExitAction(exitAction);
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene1);
+            verify(exitAction, never()).run();
+            clearInvocations(exitAction);
+        });
+
+        Scene scene2 = loadScene(R.layout.scene2);
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene2);
+            verify(exitAction, times(1)).run();
+        });
+    }
+
+    @Test
+    public void testGo_nullParameter_enterAction() throws Throwable {
+        Scene scene = loadScene(R.layout.scene1);
+        Runnable enterCheck = mock(Runnable.class);
+        scene.setEnterAction(enterCheck);
+
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene, null);
+            verify(enterCheck, times(1)).run();
+        });
+    }
+
+    @Test
+    public void testGo_nullParameter_exitAction() throws Throwable {
+        Scene scene1 = loadScene(R.layout.scene1);
+        Runnable exitAction = mock(Runnable.class);
+        scene1.setExitAction(exitAction);
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene1, null);
+            verify(exitAction, never()).run();
+            clearInvocations(exitAction);
+        });
+
+        Scene scene2 = loadScene(R.layout.scene2);
+
+        mActivityRule.runOnUiThread(() -> {
+            TransitionManager.go(scene2, null);
+            verify(exitAction, times(1)).run();
+        });
+    }
+
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java b/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
index 489fb2d..00de573 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionSetTest.java
@@ -18,18 +18,29 @@
 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.animation.TimeInterpolator;
+import android.graphics.Rect;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.transition.ArcMotion;
 import android.transition.ChangeBounds;
 import android.transition.Fade;
+import android.transition.PathMotion;
 import android.transition.Transition;
+import android.transition.TransitionPropagation;
 import android.transition.TransitionSet;
+import android.transition.TransitionValues;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -123,5 +134,141 @@
         assertEquals(1, transitionSet.getTransitionCount());
         assertSame(changeBounds, transitionSet.getTransitionAt(0));
     }
+
+    @Test
+    public void testSetTransferValuesDuringAdd() throws Throwable {
+        Fade fade = new Fade();
+        fade.setDuration(500);
+        fade.setPropagation(new TestPropagation());
+        fade.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return null;
+            }
+        });
+        fade.setInterpolator(new AccelerateDecelerateInterpolator());
+        fade.setPathMotion(new ArcMotion());
+
+        TransitionSet transitionSet = new TransitionSet();
+        int duration = 100;
+        TestPropagation propagation = new TestPropagation();
+        TimeInterpolator interpolator = new DecelerateInterpolator();
+        PathMotion pathMotion = new ArcMotion();
+        Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return null;
+            }
+        };
+        transitionSet.setDuration(duration);
+        transitionSet.setPropagation(propagation);
+        transitionSet.setInterpolator(interpolator);
+        transitionSet.setPathMotion(pathMotion);
+        transitionSet.setEpicenterCallback(epicenterCallback);
+
+        transitionSet.addTransition(fade);
+        assertEquals(duration, fade.getDuration());
+        assertSame(propagation, fade.getPropagation());
+        assertSame(interpolator, fade.getInterpolator());
+        assertSame(pathMotion, fade.getPathMotion());
+        assertSame(epicenterCallback, fade.getEpicenterCallback());
+    }
+
+    @Test
+    public void testSetTransferNullValuesDuringAdd() throws Throwable {
+        Fade fade = new Fade();
+        fade.setDuration(500);
+        fade.setPropagation(new TestPropagation());
+        fade.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return null;
+            }
+        });
+        fade.setInterpolator(new AccelerateDecelerateInterpolator());
+        fade.setPathMotion(new ArcMotion());
+
+        TransitionSet transitionSet = new TransitionSet();
+        transitionSet.setDuration(0);
+        transitionSet.setPropagation(null);
+        transitionSet.setInterpolator(null);
+        transitionSet.setPathMotion(null);
+        transitionSet.setEpicenterCallback(null);
+
+        transitionSet.addTransition(fade);
+        assertEquals(0, fade.getDuration());
+        assertNull(fade.getPropagation());
+        assertNull(fade.getInterpolator());
+        assertSame(transitionSet.getPathMotion(), fade.getPathMotion());
+        assertNull(fade.getEpicenterCallback());
+    }
+
+    @Test
+    public void testSetNoTransferValuesDuringAdd() throws Throwable {
+        Fade fade = new Fade();
+        int duration = 100;
+        TestPropagation propagation = new TestPropagation();
+        TimeInterpolator interpolator = new DecelerateInterpolator();
+        PathMotion pathMotion = new ArcMotion();
+        Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return null;
+            }
+        };
+        fade.setDuration(duration);
+        fade.setPropagation(propagation);
+        fade.setInterpolator(interpolator);
+        fade.setPathMotion(pathMotion);
+        fade.setEpicenterCallback(epicenterCallback);
+
+        TransitionSet transitionSet = new TransitionSet();
+
+        transitionSet.addTransition(fade);
+        assertEquals(duration, fade.getDuration());
+        assertSame(propagation, fade.getPropagation());
+        assertSame(interpolator, fade.getInterpolator());
+        assertSame(pathMotion, fade.getPathMotion());
+        assertSame(epicenterCallback, fade.getEpicenterCallback());
+    }
+
+    @Test
+    public void testSetAllowsChildrenToOverrideConfigs() {
+        TransitionSet transitionSet = new TransitionSet();
+        transitionSet.setDuration(100);
+        transitionSet.setInterpolator(new DecelerateInterpolator());
+
+        Fade fade = new Fade();
+        transitionSet.addTransition(fade); // here set's duration and interpolator are applied
+
+        int overriddenDuration = 200;
+        TimeInterpolator overriddenInterpolator = new AccelerateInterpolator();
+        fade.setDuration(overriddenDuration);
+        fade.setInterpolator(overriddenInterpolator);
+
+        // emulate beginDelayedTransition where we clone the provided transition
+        transitionSet = (TransitionSet) transitionSet.clone();
+        fade = (Fade) transitionSet.getTransitionAt(0);
+
+        assertEquals(overriddenDuration, fade.getDuration());
+        assertEquals(overriddenInterpolator, fade.getInterpolator());
+    }
+
+    private static class TestPropagation extends TransitionPropagation {
+        @Override
+        public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+                TransitionValues startValues, TransitionValues endValues) {
+            return 0;
+        }
+
+        @Override
+        public void captureValues(TransitionValues transitionValues) {
+        }
+
+        @Override
+        public String[] getPropagationProperties() {
+            return new String[] { };
+        }
+    }
 }
 
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionTest.java b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
index a323951..ca6d7dd 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
@@ -305,8 +305,10 @@
         View holder2 = layout2.findViewById(R.id.holder);
         mTransition.excludeChildren(holder2, true);
         startTransition(scene2);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         mTransition.excludeChildren(holder1, false); // remove it
         mTransition.excludeChildren(holder2, false); // remove it
@@ -321,8 +323,10 @@
         enterScene(R.layout.scene1);
         mTransition.excludeChildren(R.id.holder, true);
         startTransition(R.layout.scene2);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         resetListener();
         mTransition.excludeChildren(R.id.holder, false); // remove it
@@ -336,8 +340,10 @@
         enterScene(R.layout.scene1);
         mTransition.excludeChildren(RelativeLayout.class, true);
         startTransition(R.layout.scene2);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         resetListener();
         mTransition.excludeChildren(RelativeLayout.class, false); // remove it
@@ -368,8 +374,10 @@
         enterScene(R.layout.scene1);
         mTransition.excludeTarget(R.id.redSquare, true);
         startTransition(R.layout.scene7);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         resetListener();
         mTransition.excludeTarget(R.id.redSquare, false); // remove it
@@ -383,8 +391,10 @@
         enterScene(R.layout.scene1);
         mTransition.excludeTarget(TextView.class, true);
         startTransition(R.layout.scene3);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         resetListener();
         mTransition.excludeTarget(TextView.class, false); // remove it
@@ -398,8 +408,10 @@
         enterScene(R.layout.scene1);
         mTransition.excludeTarget("hello", true);
         startTransition(R.layout.scene3);
-        // Should already be ended, since no children are transitioning
-        verify(mListener, times(1)).onTransitionEnd(any());
+        mActivityRule.runOnUiThread(() -> {
+            // Should already be ended, since no children are transitioning
+            verify(mListener, times(1)).onTransitionEnd(any());
+        });
 
         resetListener();
         mTransition.excludeTarget("hello", false); // remove it
@@ -417,9 +429,14 @@
         DurationListener durationListener = new DurationListener();
         mTransition.addListener(durationListener);
         startTransition(R.layout.scene3);
-        waitForEnd(800);
+        waitForEnd(5000);
+        // We can't be certain that the onTransitionStart() and onTransitionEnd()
+        // are going to be called exactly 500ms apart. There could be more of a
+        // delay at the beginning than the end. So, we give it some room at the
+        // minimum. It can also take a lot longer on the larger side because of
+        // slow devices.
         assertThat(durationListener.getDuration(),
-                allOf(greaterThanOrEqualTo(500L), lessThan(900L)));
+                allOf(greaterThanOrEqualTo(400L), lessThan(900L)));
     }
 
     @Test
@@ -731,17 +748,17 @@
 
     private static class DurationListener extends TransitionListenerAdapter {
 
-        private long mUptimeMillisStart = -1;
+        private long mElapsedMillisStart = -1;
         private long mDuration = -1;
 
         @Override
         public void onTransitionStart(Transition transition) {
-            mUptimeMillisStart = SystemClock.uptimeMillis();
+            mElapsedMillisStart = SystemClock.elapsedRealtime();
         }
 
         @Override
         public void onTransitionEnd(Transition transition) {
-            mDuration = SystemClock.uptimeMillis() - mUptimeMillisStart;
+            mDuration = SystemClock.elapsedRealtime() - mElapsedMillisStart;
         }
 
         public long getDuration() {
diff --git a/tests/tests/transition/src/android/transition/cts/VisibilityTest.java b/tests/tests/transition/src/android/transition/cts/VisibilityTest.java
index 6b608e4..c74b5d1 100644
--- a/tests/tests/transition/src/android/transition/cts/VisibilityTest.java
+++ b/tests/tests/transition/src/android/transition/cts/VisibilityTest.java
@@ -26,9 +26,11 @@
 import static org.mockito.Mockito.verify;
 
 import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.transition.TransitionManager;
+import android.transition.TransitionSet;
 import android.transition.TransitionValues;
 import android.transition.Visibility;
 import android.view.View;
@@ -99,22 +101,19 @@
 
         enterScene(R.layout.scene1);
         final View redSquare = mActivity.findViewById(R.id.redSquare);
-        TransitionValues visibleValues = new TransitionValues();
-        visibleValues.view = redSquare;
+        TransitionValues visibleValues = new TransitionValues(redSquare);
         mTransition.captureStartValues(visibleValues);
 
         assertTrue(mVisibilityTransition.isVisible(visibleValues));
         mActivityRule.runOnUiThread(() -> redSquare.setVisibility(View.INVISIBLE));
         mInstrumentation.waitForIdleSync();
-        TransitionValues invisibleValues = new TransitionValues();
-        invisibleValues.view = redSquare;
+        TransitionValues invisibleValues = new TransitionValues(redSquare);
         mTransition.captureStartValues(invisibleValues);
         assertFalse(mVisibilityTransition.isVisible(invisibleValues));
 
         mActivityRule.runOnUiThread(() -> redSquare.setVisibility(View.GONE));
         mInstrumentation.waitForIdleSync();
-        TransitionValues goneValues = new TransitionValues();
-        goneValues.view = redSquare;
+        TransitionValues goneValues = new TransitionValues(redSquare);
         mTransition.captureStartValues(goneValues);
         assertFalse(mVisibilityTransition.isVisible(goneValues));
     }
@@ -158,6 +157,51 @@
         // any animators.
     }
 
+    @Test
+    public void testApplyingTwoVisibility() throws Throwable {
+        enterScene(R.layout.scene5);
+
+        final View[] views = new View[2];
+        // create fake transition and listener
+        final TransitionSet set = new TransitionSet();
+        set.addTransition(new Visibility() {
+            @Override
+            public Animator onDisappear(ViewGroup sceneRoot, View view,
+                    TransitionValues startValues, TransitionValues endValues) {
+                views[0] = view;
+                return ValueAnimator.ofFloat(0, 1);
+            }
+        });
+        set.addTransition(new Visibility() {
+            @Override
+            public Animator onDisappear(ViewGroup sceneRoot, View view,
+                    TransitionValues startValues, TransitionValues endValues) {
+                views[1] = view;
+                return ValueAnimator.ofFloat(0, 1);
+            }
+        });
+        mTransition = set;
+        resetListener();
+
+        // remove view
+        final ViewGroup holder = (ViewGroup) mActivity.findViewById(R.id.holder);
+        final View text = mActivity.findViewById(R.id.text);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mSceneRoot, set);
+                holder.removeView(text);
+            }
+        });
+        waitForStart();
+
+        // verify original view added to overlay
+        assertNotNull(text.getParent());
+        // verify both animations using the same original view
+        assertEquals(text, views[0]);
+        assertEquals(text, views[1]);
+    }
+
     static class AppearTransition extends Visibility {
         private View mExpectedView;
         public CountDownLatch onAppearCalled = new CountDownLatch(1);
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
index 0573a12..70c51ae 100644
--- a/tests/tests/uiautomation/AndroidManifest.xml
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
   <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
   <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+  <uses-permission android:name="android.permission.CAMERA" />
 
   <application android:theme="@android:style/Theme.Holo.NoActionBar" >
 
diff --git a/tests/tests/uiautomation/AndroidTest.xml b/tests/tests/uiautomation/AndroidTest.xml
index c4bcdd6..f5cf748 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -16,10 +16,15 @@
 <configuration description="Config for CTS UI Automation 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="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="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" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.uiautomation.cts" />
         <option name="runtime-hint" value="6m42s" />
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 55f8543..898f329 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -16,18 +16,30 @@
 
 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.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.os.ParcelFileDescriptor;
+import android.content.pm.PackageManager;
+import android.os.Process;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
-import android.test.InstrumentationTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.view.FrameStats;
 import android.view.WindowAnimationFrameStats;
 import android.view.WindowContentFrameStats;
@@ -36,6 +48,10 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.ListView;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -43,7 +59,8 @@
 /**
  * Tests for the UiAutomation APIs.
  */
-public class UiAutomationTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class UiAutomationTest {
     private static final long QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE = 1000;//ms
 
     private static final long TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE = 1000 * 10;//ms
@@ -52,9 +69,8 @@
     private static final String COMPONENT_NAME_SEPARATOR = ":";
     private static final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
@@ -62,6 +78,109 @@
         grantWriteSecureSettingsPermission(uiAutomation);
     }
 
+    @AppModeFull
+    @Test
+    public void testAdoptAllShellPermissions() {
+        final Context context = getInstrumentation().getContext();
+        final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+        final PackageManager packageManager = context.getPackageManager();
+
+        // Try to access APIs guarded by a platform defined signature permissions
+        try {
+            activityManager.getPackageImportance("foo.bar.baz");
+            fail("Should not be able to access APIs protected by a permission apps cannot get");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+        try {
+            packageManager.grantRuntimePermission(context.getPackageName(),
+                    Manifest.permission.CAMERA, Process.myUserHandle());
+            fail("Should not be able to access APIs protected by a permission apps cannot get");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+
+        // Access APIs guarded by a platform defined signature permissions
+        try {
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+
+            // Access APIs guarded by a platform defined signature permission
+            activityManager.getPackageImportance("foo.bar.baz");
+
+            // Grant ourselves a runtime permission (was granted at install)
+            assertSame(packageManager.checkPermission(Manifest.permission.CAMERA,
+                    context.getPackageName()), PackageManager.PERMISSION_DENIED);
+            packageManager.grantRuntimePermission(context.getPackageName(),
+                    Manifest.permission.CAMERA, 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,
+                context.getPackageName()), PackageManager.PERMISSION_GRANTED);
+
+
+        // Try to access APIs guarded by a platform defined signature permissions
+        try {
+            activityManager.getPackageImportance("foo.bar.baz");
+            fail("Should not be able to access APIs protected by a permission apps cannot get");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+        try {
+            packageManager.revokeRuntimePermission(context.getPackageName(),
+                    Manifest.permission.CAMERA, Process.myUserHandle());
+            fail("Should not be able to access APIs protected by a permission apps cannot get");
+        } catch (SecurityException e) {
+            /* expected */
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void testAdoptSomeShellPermissions() {
+        final Context context = getInstrumentation().getContext();
+
+        // Make sure we don't have any of the permissions
+        assertSame(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(
+                Manifest.permission.BATTERY_STATS));
+        assertSame(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(
+                Manifest.permission.PACKAGE_USAGE_STATS));
+
+        try {
+            // Adopt a permission
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                    Manifest.permission.BATTERY_STATS);
+            // Check one is granted and the other not
+            assertSame(PackageManager.PERMISSION_GRANTED, context.checkSelfPermission(
+                    Manifest.permission.BATTERY_STATS));
+            assertSame(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(
+                    Manifest.permission.PACKAGE_USAGE_STATS));
+
+            // Adopt all permissions
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+            // Check both permissions are granted
+            assertSame(PackageManager.PERMISSION_GRANTED, context.checkSelfPermission(
+                    Manifest.permission.BATTERY_STATS));
+            assertSame(PackageManager.PERMISSION_GRANTED, context.checkSelfPermission(
+                    Manifest.permission.PACKAGE_USAGE_STATS));
+
+            // Adopt a permission
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                    Manifest.permission.PACKAGE_USAGE_STATS);
+            // Check one is granted and the other not
+            assertSame(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(
+                    Manifest.permission.BATTERY_STATS));
+            assertSame(PackageManager.PERMISSION_GRANTED, context.checkSelfPermission(
+                    Manifest.permission.PACKAGE_USAGE_STATS));
+        } finally {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testWindowContentFrameStats() throws Exception {
         Activity activity = null;
         try {
@@ -123,6 +242,7 @@
         }
     }
 
+    @Test
     public void testWindowContentFrameStatsNoAnimation() throws Exception {
         Activity activity = null;
         try {
@@ -174,6 +294,7 @@
     }
 
     @Presubmit
+    @Test
     public void testWindowAnimationFrameStats() throws Exception {
         Activity firstActivity = null;
         Activity secondActivity = null;
@@ -241,6 +362,7 @@
         }
     }
 
+    @Test
     public void testWindowAnimationFrameStatsNoAnimation() throws Exception {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
 
@@ -271,6 +393,7 @@
     }
 
     @Presubmit
+    @Test
     public void testUsingUiAutomationAfterDestroy_shouldThrowException() {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         uiAutomation.destroy();
@@ -282,8 +405,8 @@
     }
 
     @AppModeFull
-    public void testDontSuppressAccessibility_canStartA11yService() throws IOException,
-            InterruptedException {
+    @Test
+    public void testDontSuppressAccessibility_canStartA11yService() throws Exception {
         turnAccessibilityOff();
         try {
             getInstrumentation()
@@ -296,7 +419,8 @@
     }
 
     @AppModeFull
-    public void testServiceWithNoFlags_shutsDownA11yService() throws IOException {
+    @Test
+    public void testServiceWithNoFlags_shutsDownA11yService() throws Exception {
         turnAccessibilityOff();
         try {
             UiAutomation uiAutomation = getInstrumentation()
@@ -313,8 +437,9 @@
     }
 
     @AppModeFull
+    @Test
     public void testServiceSupressingA11yServices_a11yServiceStartsWhenDestroyed()
-            throws IOException, InterruptedException {
+            throws Exception {
         turnAccessibilityOff();
         try {
             UiAutomation uiAutomation = getInstrumentation()
@@ -333,8 +458,9 @@
     }
 
     @AppModeFull
+    @Test
     public void testServiceSupressingA11yServices_a11yServiceStartsWhenFlagsChange()
-            throws IOException, InterruptedException {
+            throws Exception {
         turnAccessibilityOff();
         try {
             getInstrumentation()
@@ -379,7 +505,7 @@
                 QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE, TOTAL_TIME_TO_WAIT_FOR_IDLE_STATE);
     }
 
-    private void grantWriteSecureSettingsPermission(UiAutomation uiAutomation) throws IOException {
+    private void grantWriteSecureSettingsPermission(UiAutomation uiAutomation) {
         uiAutomation.grantRuntimePermission(getInstrumentation().getContext().getPackageName(),
                 android.Manifest.permission.WRITE_SECURE_SETTINGS);
     }
@@ -514,4 +640,8 @@
         }
         return -1;
     }
+
+    private Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
 }
diff --git a/tests/tests/uidisolation/AndroidTest.xml b/tests/tests/uidisolation/AndroidTest.xml
index 0a83f12..590627b 100644
--- a/tests/tests/uidisolation/AndroidTest.xml
+++ b/tests/tests/uidisolation/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS UID Isolation test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/uirendering/AndroidManifest.xml b/tests/tests/uirendering/AndroidManifest.xml
index c11c1dd..cbbcfb3 100644
--- a/tests/tests/uirendering/AndroidManifest.xml
+++ b/tests/tests/uirendering/AndroidManifest.xml
@@ -28,9 +28,10 @@
          * start up in multi-window mode.
          -->
         <activity android:name="android.uirendering.cts.testinfrastructure.DrawActivity"
-                  android:theme="@style/WhiteBackgroundTheme"
+                  android:theme="@style/DefaultTheme"
                   android:screenOrientation="locked"
-                  android:resizeableActivity="false" />
+                  android:resizeableActivity="false"
+                  android:configChanges="uiMode" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/uirendering/res/layout/force_dark_siblings.xml b/tests/tests/uirendering/res/layout/force_dark_siblings.xml
new file mode 100644
index 0000000..6e25452
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/force_dark_siblings.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="@dimen/test_width"
+             android:layout_height="@dimen/test_height">
+
+    <android.uirendering.cts.testinfrastructure.CanvasClientView
+        android:id="@+id/bg_canvas"
+        android:layout_width="@dimen/test_width"
+        android:layout_height="@dimen/test_height"/>
+
+    <android.uirendering.cts.testinfrastructure.CanvasClientView
+        android:id="@+id/fg_canvas"
+        android:layout_width="@dimen/test_width"
+        android:layout_height="@dimen/test_height"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/layout/simple_force_dark.xml b/tests/tests/uirendering/res/layout/simple_force_dark.xml
new file mode 100644
index 0000000..f042f87
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/simple_force_dark.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+<android.uirendering.cts.testinfrastructure.CanvasClientView xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/canvas"
+             android:layout_width="@dimen/test_width"
+             android:layout_height="@dimen/test_height"
+             android:background="@android:color/white" />
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/layout/skew_layout.xml b/tests/tests/uirendering/res/layout/skew_layout.xml
new file mode 100644
index 0000000..0b67a8f
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/skew_layout.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<android.uirendering.cts.testclasses.view.SkewLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height">
+    <View
+        android:id="@+id/view1"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:background="#F00"
+        android:elevation="8dp" />
+</android.uirendering.cts.testclasses.view.SkewLayout>
diff --git a/tests/tests/uirendering/res/layout/test_container.xml b/tests/tests/uirendering/res/layout/test_container.xml
index deff9de..fd7b4e2 100644
--- a/tests/tests/uirendering/res/layout/test_container.xml
+++ b/tests/tests/uirendering/res/layout/test_container.xml
@@ -20,9 +20,5 @@
         android:id="@+id/test_content_wrapper"
         android:layout_width="@dimen/test_width"
         android:layout_height="@dimen/test_height">
-        <ViewStub
-            android:id="@+id/test_content_stub"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
     </FrameLayout>
 </FrameLayout>
diff --git a/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml b/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml
deleted file mode 100644
index 9f8a139..0000000
--- a/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml
+++ /dev/null
@@ -1,18 +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.
-  -->
-<android.uirendering.cts.testinfrastructure.CanvasClientView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/test_width"
-    android:layout_height="@dimen/test_height"/>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/values/themes.xml b/tests/tests/uirendering/res/values/themes.xml
index c12ca95..e75376b 100644
--- a/tests/tests/uirendering/res/values/themes.xml
+++ b/tests/tests/uirendering/res/values/themes.xml
@@ -14,7 +14,7 @@
 limitations under the License.
 -->
 <resources>
-    <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
+    <style name="DefaultTheme" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowFullscreen">true</item>
         <item name="android:windowOverscan">true</item>
@@ -24,5 +24,10 @@
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:ambientShadowAlpha">0.039</item>
         <item name="android:spotShadowAlpha">0.19</item>
+        <item name="android:forceDarkAllowed">false</item>
+    </style>
+    <style name="AutoDarkTheme" parent="@style/DefaultTheme">
+        <item name="android:forceDarkAllowed">true</item>
+        <item name="android:isLightTheme">true</item>
     </style>
 </resources>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointWideGamutVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointWideGamutVerifier.java
index c075105..5cd019a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointWideGamutVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/SamplePointWideGamutVerifier.java
@@ -16,19 +16,16 @@
 
 package android.uirendering.cts.bitmapverifiers;
 
+import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.graphics.ColorSpace;
 import android.graphics.Point;
-import android.util.Half;
 import android.util.Log;
 
-import java.nio.ByteBuffer;
+import org.junit.Assert;
 
-public class SamplePointWideGamutVerifier extends WideGamutBitmapVerifier {
+public class SamplePointWideGamutVerifier extends BitmapVerifier {
     private static final String TAG = "SamplePointWideGamut";
 
-    private static final ColorSpace SCRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
-
     private final Point[] mPoints;
     private final Color[] mColors;
     private final float mEps;
@@ -40,31 +37,37 @@
     }
 
     @Override
-    public boolean verify(ByteBuffer bitmap, int offset, int stride, int width, int height) {
+    public boolean verify(Bitmap bitmap) {
+        Assert.assertTrue("You cannot use this verifier with an bitmap whose ColorSpace is not "
+                 + "wide gamut: " + bitmap.getColorSpace(), bitmap.getColorSpace().isWideGamut());
+
         boolean success = true;
         for (int i = 0; i < mPoints.length; i++) {
             Point p = mPoints[i];
-            Color c = mColors[i];
+            Color expected = mColors[i];
 
-            int index = p.y * stride + (p.x << 3);
-            float r = Half.toFloat(bitmap.getShort(index));
-            float g = Half.toFloat(bitmap.getShort(index + 2));
-            float b = Half.toFloat(bitmap.getShort(index + 4));
+            Color actual = bitmap.getColor(p.x, p.y).convert(expected.getColorSpace());
 
             boolean localSuccess = true;
-            if (!floatCompare(c.red(),   r, mEps)) localSuccess = false;
-            if (!floatCompare(c.green(), g, mEps)) localSuccess = false;
-            if (!floatCompare(c.blue(),  b, mEps)) localSuccess = false;
+            if (!floatCompare(expected.red(),   actual.red(),   mEps)) localSuccess = false;
+            if (!floatCompare(expected.green(), actual.green(), mEps)) localSuccess = false;
+            if (!floatCompare(expected.blue(),  actual.blue(),  mEps)) localSuccess = false;
+            if (!floatCompare(expected.alpha(), actual.alpha(), mEps)) localSuccess = false;
 
             if (!localSuccess) {
                 success = false;
-                Log.w(TAG, "Expected " + c.toString() + " at " + p.x + "x" + p.y
-                        + ", got " + Color.valueOf(r, g, b, 1.0f, SCRGB).toString());
+                Log.w(TAG, "Expected " + expected + " at " + p + ", got " + actual);
             }
         }
         return success;
     }
 
+    @Override
+    public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+        Assert.fail("This verifier requires more info than can be encoded in sRGB (int) values");
+        return false;
+    }
+
     private static boolean floatCompare(float a, float b, float eps) {
         return Float.compare(a, b) == 0 || Math.abs(a - b) <= eps;
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/WideGamutBitmapVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/WideGamutBitmapVerifier.java
deleted file mode 100644
index 0c11a064..0000000
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/WideGamutBitmapVerifier.java
+++ /dev/null
@@ -1,46 +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.uirendering.cts.bitmapverifiers;
-
-import android.graphics.Bitmap;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-public abstract class WideGamutBitmapVerifier extends BitmapVerifier {
-    @Override
-    public boolean verify(Bitmap bitmap) {
-        ByteBuffer dst = ByteBuffer.allocateDirect(bitmap.getAllocationByteCount());
-        bitmap.copyPixelsToBuffer(dst);
-        dst.rewind();
-        dst.order(ByteOrder.LITTLE_ENDIAN);
-
-        int width = bitmap.getWidth();
-        int height = bitmap.getHeight();
-        return verify(dst, 0, bitmap.getRowBytes(), width, height);
-    }
-
-    public abstract boolean verify(ByteBuffer bitmap, int offset, int stride,
-            int width, int height);
-
-    @Override
-    public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
-        // This method is never called, we use
-        // verify(ByteBuffer bitmap, int offset, int stride, int width, int height) instead
-        return false;
-    }
-}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeColorFilterTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeColorFilterTest.java
new file mode 100644
index 0000000..fe0292c
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeColorFilterTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.CanvasClient;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BlendModeColorFilterTest extends ActivityTestBase {
+    private static final int TOLERANCE = 5;
+
+    private static final int LEFT_X = TEST_WIDTH / 4;
+    private static final int RIGHT_X = TEST_WIDTH * 3 / 4;
+    private static final int TOP_Y = TEST_HEIGHT / 4;
+    private static final int BOTTOM_Y = TEST_HEIGHT * 3 / 4;
+
+    private static final int FILTER_COLOR = Color.argb(0x80, 0, 0xFF, 0);
+
+    private static final Point[] SAMPLE_POINTS = {
+            new Point(LEFT_X, TOP_Y),
+            new Point(LEFT_X, BOTTOM_Y),
+            new Point(RIGHT_X, BOTTOM_Y)
+    };
+
+    private static class BlendModeColorFilterClient implements CanvasClient {
+
+        private final Bitmap mB1;
+        private final Bitmap mB2;
+        private final BlendMode mMode;
+        private final int mFilterColor;
+
+        BlendModeColorFilterClient(int filterColor, BlendMode mode) {
+            final Bitmap b1 = Bitmap.createBitmap(TEST_WIDTH / 2,
+                            TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+            b1.eraseColor(Color.RED);
+            final Bitmap b2 = Bitmap.createBitmap(TEST_WIDTH,
+                            TEST_HEIGHT / 2, Bitmap.Config.ARGB_8888);
+            b2.eraseColor(Color.BLUE);
+            mB1 = b1;
+            mB2 = b2;
+            mMode = mode;
+            mFilterColor = filterColor;
+        }
+
+        @Override
+        public void draw(Canvas canvas, int width, int height) {
+            canvas.drawColor(Color.WHITE);
+
+            BlendModeColorFilter filter = new BlendModeColorFilter(mFilterColor, mMode);
+            Paint p = new Paint();
+            canvas.drawBitmap(mB1, 0, 0, p);
+            p.setColorFilter(filter);
+            canvas.drawBitmap(mB2, 0, height / 2, p);
+        }
+    }
+
+    private void testBlendModeColorFilter(int filterColor, BlendMode mode, int[] colors) {
+        createTest().addCanvasClient(new BlendModeColorFilterClient(filterColor, mode))
+                .runWithVerifier(new SamplePointVerifier(SAMPLE_POINTS, colors));
+    }
+
+    @Test
+    public void testBlendModeColorFilter_SRC() {
+        testBlendModeColorFilter(FILTER_COLOR, BlendMode.SRC,
+                new int[]{Color.RED, 0xFF7F8000, 0xFF7FFF7f});
+    }
+
+    @Test
+    public void testBlendModeColorFilter_DST() {
+        testBlendModeColorFilter(FILTER_COLOR, BlendMode.DST,
+                new int[]{Color.RED, Color.BLUE, Color.BLUE});
+    }
+
+    @Test
+    public void testBlendModeColorFilter_SCREEN() {
+        testBlendModeColorFilter(Color.GREEN, BlendMode.SCREEN,
+                new int[]{Color.RED, Color.CYAN, Color.CYAN});
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeTest.java
new file mode 100644
index 0000000..800fcdb
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BlendModeTest.java
@@ -0,0 +1,525 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.CanvasClient;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BlendModeTest extends ActivityTestBase {
+
+    private static final int BG_COLOR = Color.WHITE;
+    private static final int DST_COLOR = Color.RED;
+    private static final int SRC_COLOR = Color.BLUE;
+
+    private static final int LEFT_X = TEST_WIDTH / 4;
+    private static final int RIGHT_X = TEST_WIDTH * 3 / 4;
+    private static final int TOP_Y = TEST_HEIGHT / 4;
+    private static final int BOTTOM_Y = TEST_HEIGHT * 3 / 4;
+
+    private class BlendModeCanvasClient implements CanvasClient {
+        final Paint mPaint = new Paint();
+
+        private final Bitmap mSrcBitmap;
+        private final Bitmap mDstBitmap;
+        private final BlendMode mBlendmode;
+
+        BlendModeCanvasClient(BlendMode mode, Bitmap dstBitmap, Bitmap srcBitmap) {
+            mDstBitmap = dstBitmap;
+            mSrcBitmap = srcBitmap;
+            mBlendmode = mode;
+        }
+
+        @Override
+        public void draw(Canvas canvas, int width, int height) {
+            canvas.drawColor(Color.WHITE);
+
+            int sc = canvas.saveLayer(0, 0, TEST_WIDTH, TEST_HEIGHT, null);
+
+            canvas.drawBitmap(mDstBitmap, 0, 0, null);
+            mPaint.setBlendMode(mBlendmode);
+
+            canvas.drawBitmap(mSrcBitmap, 0, 0, mPaint);
+
+            canvas.restoreToCount(sc);
+        }
+    }
+
+    private static final Point[] SAMPLE_POINTS = {
+        new Point(LEFT_X, TOP_Y),
+        new Point(LEFT_X, BOTTOM_Y),
+        new Point(RIGHT_X, BOTTOM_Y)
+    };
+
+    private void testBlendMode(BlendMode mode,
+            int topLeftColor,
+            int bottomLeftColor,
+            int bottomRightColor) {
+
+        BlendModeCanvasClient client = new BlendModeCanvasClient(mode,
+                createBlendmodeDst(), createBlendmodeSrc());
+
+        int[] colors = { topLeftColor, bottomLeftColor, bottomRightColor };
+        createTest().addCanvasClient(client)
+                .runWithVerifier(new SamplePointVerifier(SAMPLE_POINTS, colors));
+    }
+
+    private Bitmap createBlendmodeDst() {
+        Bitmap srcB = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas srcCanvas = new Canvas(srcB);
+        Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        srcPaint.setColor(DST_COLOR);
+        srcCanvas.drawRect(0, 0, TEST_WIDTH / 2, TEST_HEIGHT, srcPaint);
+        return srcB;
+    }
+
+    private Bitmap createBlendmodeSrc() {
+        Bitmap dstB = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas dstCanvas = new Canvas(dstB);
+        Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        dstPaint.setColor(SRC_COLOR);
+        dstCanvas.drawRect(0, TEST_HEIGHT / 2, TEST_WIDTH, TEST_HEIGHT, dstPaint);
+        return dstB;
+    }
+
+    @Test
+    public void testBlendMode_CLEAR() {
+        testBlendMode(BlendMode.CLEAR, Color.WHITE, Color.WHITE, Color.WHITE);
+    }
+
+    @Test
+    public void testBlendMode_SRC() {
+        testBlendMode(BlendMode.SRC, BG_COLOR, SRC_COLOR, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_DST() {
+        testBlendMode(BlendMode.DST, DST_COLOR, DST_COLOR, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_SRC_OVER() {
+        testBlendMode(BlendMode.SRC_OVER, DST_COLOR, SRC_COLOR, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_DST_OVER() {
+        testBlendMode(BlendMode.DST_OVER, Color.RED, Color.RED, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_SRC_IN() {
+        testBlendMode(BlendMode.SRC_IN, BG_COLOR, Color.BLUE, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_DST_IN() {
+        testBlendMode(BlendMode.DST_IN, BG_COLOR, DST_COLOR, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_SRC_OUT() {
+        testBlendMode(BlendMode.SRC_OUT, BG_COLOR, BG_COLOR, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_DST_OUT() {
+        testBlendMode(BlendMode.DST_OUT, DST_COLOR, BG_COLOR, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_SRC_ATOP() {
+        testBlendMode(BlendMode.SRC_ATOP, DST_COLOR, Color.BLUE, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_DST_ATOP() {
+        testBlendMode(BlendMode.DST_ATOP, BG_COLOR, DST_COLOR, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_XOR() {
+        testBlendMode(BlendMode.XOR, DST_COLOR, BG_COLOR, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_PLUS() {
+        testBlendMode(BlendMode.PLUS, DST_COLOR, Color.MAGENTA, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_MODULATE() {
+        int alpha = (Color.alpha(DST_COLOR) * Color.alpha(Color.BLUE)) / 255;
+        int red = (Color.red(DST_COLOR) * Color.red(Color.BLUE)) / 255;
+        int green = (Color.green(Color.GREEN) * Color.green(Color.BLUE)) / 255;
+        int blue = (Color.blue(DST_COLOR) * Color.blue(Color.BLUE)) / 255;
+        int resultColor = Color.argb(alpha, red, green, blue);
+        testBlendMode(BlendMode.MODULATE, BG_COLOR, resultColor, BG_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_SCREEN() {
+        testBlendMode(BlendMode.SCREEN, DST_COLOR, Color.MAGENTA, Color.BLUE);
+    }
+
+    @Test
+    public void testBlendMode_OVERLAY() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alpha = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeOverlay(alphaSrc, alphaDst, redSrc, redDst);
+        int green = computeOverlay(alphaSrc, alphaDst, greenSrc, greenDst);
+        int blue = computeOverlay(alphaSrc, alphaDst, blueSrc, blueDst);
+        int result = Color.argb(alpha, red, green, blue);
+        testBlendMode(BlendMode.OVERLAY, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeOverlay(int alphaSrc, int alphaDst, int colorSrc, int colorDst) {
+        if (2 * colorDst < alphaDst) {
+            return (2 * colorSrc * colorDst) / 255;
+        } else {
+            return (alphaSrc * alphaDst) / 255
+                    - (2 * (alphaDst - colorSrc) * (alphaSrc - colorDst));
+        }
+    }
+
+    @Test
+    public void testBlendMode_DARKEN() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeDarken(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeDarken(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeDarken(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.DARKEN, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeDarken(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        return (((255 - alphaDst)) * (colorSrc)) / 255 + ((255 - alphaSrc) * colorDst) / 255
+                + Math.min(colorSrc, colorDst);
+    }
+
+    @Test
+    public void testBlendMode_LIGHTEN() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeLighten(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeLighten(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeLighten(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.LIGHTEN, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeLighten(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        return (((255 - alphaDst)) * (colorSrc)) / 255 + ((255 - alphaSrc) * colorDst) / 255
+                + Math.max(colorSrc, colorDst);
+    }
+
+    @Test
+    public void testBlendMode_COLOR_DODGE() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeColorDodge(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeColorDodge(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeColorDodge(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.COLOR_DODGE, DST_COLOR, result, SRC_COLOR);
+
+    }
+
+    private int computeColorDodge(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        if (colorDst == 0) {
+            return (colorSrc * (255 - alphaDst)) / 255;
+        } else if (colorSrc == alphaSrc) {
+            return colorSrc + alphaDst * (255 - alphaSrc) / 255;
+        } else {
+            float alphaRatio = (float) alphaSrc / ((float) alphaSrc - colorSrc);
+            return Math.round((alphaSrc * Math.min(alphaDst, colorDst * alphaRatio)) / 255
+                    + colorSrc * (255 - alphaDst) / 255.0f
+                    + alphaDst * (255 - alphaSrc) / 255.0f);
+        }
+    }
+
+    @Test
+    public void testBlendMode_COLOR_BURN() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeColorBurn(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeColorBurn(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeColorBurn(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.COLOR_BURN, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeColorBurn(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        if (colorDst == alphaDst) {
+            return colorDst + (colorSrc * (255 - alphaDst)) / 255;
+        } else if (colorSrc == 0) {
+            return alphaDst * (255 - alphaSrc) / 255;
+        } else {
+            return alphaSrc * (alphaDst - Math.min(alphaDst,
+                    (alphaDst - colorDst) * alphaSrc / colorSrc))
+                    + colorSrc * (255 - alphaDst) / 255 + alphaDst * (255 - alphaSrc) / 255;
+        }
+    }
+
+    @Test
+    public void testBlendMode_HARD_LIGHT() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeHardLight(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeHardLight(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeHardLight(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.HARD_LIGHT, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeHardLight(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        if (2 * colorSrc <= alphaSrc) {
+            return 2 * colorSrc * colorDst / 255 + colorSrc * (255 - alphaDst) / 255
+                    + colorDst * (255 - alphaSrc) / 255;
+        } else {
+            return colorSrc * (255 + alphaDst) / 255
+                    + colorDst * (255 + alphaSrc) / 255
+                    - (alphaSrc * alphaDst) / 255
+                    - 2 * colorSrc * colorDst / 255;
+        }
+    }
+
+    @Test
+    public void testBlendMode_SOFT_LIGHT() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeSoftLight(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeSoftLight(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeSoftLight(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.SOFT_LIGHT, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeSoftLight(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        float m = alphaDst > 0 ? (float) colorDst / alphaDst : 0.0f;
+        if (2 * colorSrc <= alphaSrc) {
+            return Math.round(
+                    colorDst * (alphaSrc + (2 * colorSrc - alphaSrc) * (1 - m)) / 255.0f
+                            + colorSrc * (255 - alphaDst) / 255.0f
+                            + (float) colorDst * (255 - alphaSrc) / 255.0f
+            );
+        } else if ((4 * colorDst) <= alphaDst) {
+            return Math.round(
+                    (alphaDst * (2 * colorSrc - alphaSrc)) / 255.0f
+                            * (16 * m * m * m - 12 * m * m - 3 * m)
+                            + colorSrc - (colorSrc * alphaDst) / 255.0f + colorDst
+            );
+
+        } else {
+            return (int) Math.round(
+                    (alphaDst * (2 * colorSrc - alphaSrc)) / 255.0f
+                            * (Math.sqrt(m) - m)
+                            + colorSrc - (colorSrc * alphaDst) / 255.0f + colorDst);
+        }
+    }
+
+    @Test
+    public void testBlendMode_DIFFERENCE() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeDifference(alphaDst, alphaSrc, redDst, redSrc);
+        int green = computeDifference(alphaDst, alphaSrc, greenDst, greenSrc);
+        int blue = computeDifference(alphaDst, alphaSrc, blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.DIFFERENCE, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeDifference(int alphaDst, int alphaSrc, int colorDst, int colorSrc) {
+        return colorSrc + colorDst - 2 * Math.min(colorSrc * alphaDst, colorDst * alphaSrc) / 255;
+    }
+
+    @Test
+    public void testBlendMode_EXCLUSION() {
+        int alphaDst = Color.alpha(DST_COLOR);
+        int alphaSrc = Color.alpha(SRC_COLOR);
+
+        int redDst = Color.red(DST_COLOR);
+        int redSrc = Color.red(SRC_COLOR);
+
+        int greenDst = Color.green(DST_COLOR);
+        int greenSrc = Color.green(SRC_COLOR);
+
+        int blueDst = Color.blue(DST_COLOR);
+        int blueSrc = Color.blue(SRC_COLOR);
+
+        int alphaOut = (alphaSrc + alphaDst) - (alphaSrc * alphaDst) / 255;
+        int red = computeExclusion(redDst, redSrc);
+        int green = computeExclusion(greenDst, greenSrc);
+        int blue = computeExclusion(blueDst, blueSrc);
+        int result = Color.argb(alphaOut, red, green, blue);
+        testBlendMode(BlendMode.EXCLUSION, DST_COLOR, result, SRC_COLOR);
+    }
+
+    private int computeExclusion(int colorDst, int colorSrc) {
+        return colorSrc + colorDst - (2 * colorSrc * colorDst) / 255;
+    }
+
+    int computeMultiply(int dstColor, int srcColor, int dstAlpha, int srcAlpha) {
+        return (srcColor * dstColor) / 255
+                + (srcColor * (255 - dstAlpha)) / 255
+                + (dstColor * (255 - srcAlpha)) / 255;
+    }
+
+    @Test
+    public void testBlendMode_MULTIPLY() {
+        int redAlpha = Color.alpha(Color.RED);
+        int blueAlpha = Color.alpha(Color.BLUE);
+        int alpha = (redAlpha + blueAlpha) - (redAlpha * blueAlpha) / 255;
+
+        int dstRed = Color.red(DST_COLOR);
+        int srcRed = Color.red(SRC_COLOR);
+
+        int dstGreen = Color.green(DST_COLOR);
+        int srcGreen = Color.green(SRC_COLOR);
+
+        int dstBlue = Color.blue(DST_COLOR);
+        int srcBlue = Color.blue(SRC_COLOR);
+
+        int red = computeMultiply(dstRed, srcRed, redAlpha, blueAlpha);
+        int green = computeMultiply(dstGreen, srcGreen, redAlpha, blueAlpha);
+        int blue = computeMultiply(dstBlue, srcBlue, redAlpha, blueAlpha);
+        int resultColor = Color.argb(alpha, red, green, blue);
+        testBlendMode(BlendMode.MULTIPLY, DST_COLOR, resultColor, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_COLOR() {
+        testBlendMode(BlendMode.COLOR, DST_COLOR, 0xFF3636FF, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_SATURATION() {
+        testBlendMode(BlendMode.SATURATION, DST_COLOR, DST_COLOR, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_LUMINOSITY() {
+        testBlendMode(BlendMode.LUMINOSITY, DST_COLOR, 0xFF5e0000, SRC_COLOR);
+    }
+
+    @Test
+    public void testBlendMode_HUE() {
+        testBlendMode(BlendMode.HUE, DST_COLOR, 0xFF3636FF, SRC_COLOR);
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CameraTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CameraTests.java
new file mode 100644
index 0000000..c46fbaf
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CameraTests.java
@@ -0,0 +1,49 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.graphics.Camera;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CameraTests extends ActivityTestBase {
+    @Test
+    public void testBasicTranslate() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setAntiAlias(false);
+                    paint.setColor(Color.BLUE);
+                    Camera camera = new Camera();
+                    camera.translate(0, 50, 0);
+                    camera.applyToCanvas(canvas);
+                    canvas.drawRect(0, 50, 100, 100, paint);
+                })
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE,
+                        new Rect(0, 0, 100, 50)));
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
new file mode 100644
index 0000000..9a252dd
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
@@ -0,0 +1,796 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import static org.junit.Assert.assertFalse;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CanvasTests extends ActivityTestBase {
+
+    private static final int PAINT_COLOR = 0xff00ff00;
+    private static final int BITMAP_WIDTH = 10;
+    private static final int BITMAP_HEIGHT = 28;
+
+    private Paint getPaint() {
+        Paint paint = new Paint();
+        paint.setColor(PAINT_COLOR);
+        return paint;
+    }
+
+    public Bitmap getImmutableBitmap() {
+        final Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        BitmapFactory.Options opt = new BitmapFactory.Options();
+        opt.inScaled = false; // bitmap will only be immutable if not scaled during load
+        Bitmap immutableBitmap = BitmapFactory.decodeResource(res, R.drawable.sunset1, opt);
+        assertFalse(immutableBitmap.isMutable());
+        return immutableBitmap;
+    }
+
+    public Bitmap getMutableBitmap() {
+        return Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
+    }
+
+    @Test
+    public void testDrawDoubleRoundRect() {
+        Point[] testPoints = {
+                new Point(0, 0),
+                new Point(89, 0),
+                new Point(89, 89),
+                new Point(0, 89),
+                new Point(10, 10),
+                new Point(79, 10),
+                new Point(79, 79),
+                new Point(10, 79)
+        };
+        int[] colors = {
+                Color.WHITE,
+                Color.WHITE,
+                Color.WHITE,
+                Color.WHITE,
+                Color.RED,
+                Color.RED,
+                Color.RED,
+                Color.RED
+        };
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setColor(Color.WHITE);
+                    canvas.drawRect(0, 0, width, height, paint);
+
+                    paint.setColor(Color.RED);
+                    int inset = 20;
+                    RectF outer = new RectF(0, 0, width, height);
+                    RectF inner = new RectF(inset,
+                            inset,
+                            width - inset,
+                            height - inset);
+                    canvas.drawDoubleRoundRect(outer, 5, 5,
+                            inner, 10, 10, paint);
+                })
+                .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+    }
+
+    @Test
+    public void testDrawDoubleRoundRectWithRadii() {
+        Point[] testPoints = {
+                new Point(0, 0),
+                new Point(89, 0),
+                new Point(89, 89),
+                new Point(0, 89),
+                new Point(9, 7),
+                new Point(81, 7),
+                new Point(75, 75),
+                new Point(21, 67)
+        };
+        int[] colors = {
+                Color.RED,
+                Color.WHITE,
+                Color.WHITE,
+                Color.WHITE,
+                Color.RED,
+                Color.RED,
+                Color.RED,
+                Color.RED
+        };
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setColor(Color.WHITE);
+                    canvas.drawRect(0, 0, width, height, paint);
+
+                    paint.setColor(Color.RED);
+                    int inset = 30;
+                    RectF outer = new RectF(0, 0, width, height);
+                    RectF inner = new RectF(inset,
+                            inset,
+                            width - inset,
+                            height - inset);
+
+                    float[] outerRadii = {
+                            0.0f, 0.0f, // top left
+                            5.0f, 5.0f, // top right
+                            10.0f, 10.0f, // bottom right
+                            20.0f, 20.0f // bottom left
+                    };
+
+                    float[] innerRadii = {
+                            20.0f, 20.0f,
+                            15.0f, 15.0f,
+                            8.0f, 8.0f,
+                            3.0f, 3.0f
+                    };
+                    canvas.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint);
+                })
+                .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawHwBitmap_inSwCanvas() {
+        Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
+        // we verify this specific call should IAE
+        new Canvas(getMutableBitmap()).drawBitmap(hwBitmap, 0, 0, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawHwBitmap_inPictureCanvas_inSwCanvas() {
+        Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
+        Picture picture = new Picture();
+        Canvas pictureCanvas = picture.beginRecording(100, 100);
+        pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
+        // we verify this specific call should IAE
+        new Canvas(getMutableBitmap()).drawPicture(picture);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawHwBitmap_inPictureCanvas_inPictureCanvas_inSwCanvas() {
+        Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
+        Picture innerPicture = new Picture();
+        Canvas pictureCanvas = innerPicture.beginRecording(100, 100);
+        pictureCanvas.drawBitmap(hwBitmap, 0, 0, null);
+
+        Picture outerPicture = new Picture();
+        Canvas outerPictureCanvas = outerPicture.beginRecording(100, 100);
+        outerPictureCanvas.drawPicture(innerPicture);
+        // we verify this specific call should IAE
+        new Canvas(getMutableBitmap()).drawPicture(outerPicture);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHwBitmapShaderInSwCanvas1() {
+        Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
+        BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
+                Shader.TileMode.REPEAT);
+        RadialGradient gradientShader = new RadialGradient(10, 10, 30,
+                Color.BLACK, Color.CYAN, Shader.TileMode.REPEAT);
+        Shader shader = new ComposeShader(gradientShader, bitmapShader, PorterDuff.Mode.OVERLAY);
+        Paint p = new Paint();
+        p.setShader(shader);
+        new Canvas(getMutableBitmap()).drawRect(0, 0, 10, 10, p);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHwBitmapShaderInSwCanvas2() {
+        Bitmap hwBitmap = getImmutableBitmap().copy(Bitmap.Config.HARDWARE, false);
+        BitmapShader bitmapShader = new BitmapShader(hwBitmap, Shader.TileMode.REPEAT,
+                Shader.TileMode.REPEAT);
+        RadialGradient gradientShader = new RadialGradient(10, 10, 30,
+                Color.BLACK, Color.CYAN, Shader.TileMode.REPEAT);
+        Shader shader = new ComposeShader(bitmapShader, gradientShader, PorterDuff.Mode.OVERLAY);
+        Paint p = new Paint();
+        p.setShader(shader);
+        new Canvas(getMutableBitmap()).drawRect(0, 0, 10, 10, p);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCanvasFromImmutableBitmap() {
+        // Should throw out IllegalStateException when creating Canvas with an ImmutableBitmap
+        new Canvas(getImmutableBitmap());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testCanvasFromRecycledBitmap() {
+        // Should throw out RuntimeException when creating Canvas with a MutableBitmap which
+        // is recycled
+        Bitmap bitmap = getMutableBitmap();
+        bitmap.recycle();
+        new Canvas(bitmap);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testSetBitmapToImmutableBitmap() {
+        // Should throw out IllegalStateException when setting an ImmutableBitmap to a Canvas
+        new Canvas(getMutableBitmap()).setBitmap(getImmutableBitmap());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testSetBitmapToRecycledBitmap() {
+        // Should throw out RuntimeException when setting Bitmap which is recycled to a Canvas
+        Bitmap bitmap = getMutableBitmap();
+        bitmap.recycle();
+        new Canvas(getMutableBitmap()).setBitmap(bitmap);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRestoreWithoutSave() {
+        // Should throw out IllegalStateException because cannot restore Canvas before save
+        new Canvas(getMutableBitmap()).restore();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRestoreToCountIllegalSaveCount() {
+        // Should throw out IllegalArgumentException because saveCount is less than 1
+        new Canvas(getMutableBitmap()).restoreToCount(0);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawPointsInvalidOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
+        new Canvas(getMutableBitmap()).drawPoints(new float[]{
+                10.0f, 29.0f
+        }, -1, 2, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawPointsInvalidCount() {
+        // Should throw out ArrayIndexOutOfBoundsException because of invalid count
+        new Canvas(getMutableBitmap()).drawPoints(new float[]{
+                10.0f, 29.0f
+        }, 0, 31, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawLinesInvalidOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because of invalid offset
+        new Canvas(getMutableBitmap()).drawLines(new float[]{
+                0, 0, 10, 31
+        }, 2, 4, new Paint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawLinesInvalidCount() {
+        // Should throw out ArrayIndexOutOfBoundsException because of invalid count
+        new Canvas(getMutableBitmap()).drawLines(new float[]{
+                0, 0, 10, 31
+        }, 0, 8, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawOvalNull() {
+        // Should throw out NullPointerException because oval is null
+        new Canvas(getMutableBitmap()).drawOval(null, getPaint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawArcNullOval() {
+        // Should throw NullPointerException because oval is null
+        new Canvas(getMutableBitmap()).drawArc(null, 10.0f, 29.0f,
+                true, getPaint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawRoundRectNull() {
+        // Should throw out NullPointerException because RoundRect is null
+        new Canvas(getMutableBitmap()).drawRoundRect(null, 10.0f, 29.0f, getPaint());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testDrawBitmapAtPointRecycled() {
+        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+        b.recycle();
+
+        // Should throw out RuntimeException because bitmap has been recycled
+        new Canvas(getMutableBitmap()).drawBitmap(b, 10.0f, 29.0f, getPaint());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testDrawBitmapSrcDstFloatRecycled() {
+        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+        b.recycle();
+
+        // Should throw out RuntimeException because bitmap has been recycled
+        new Canvas(getMutableBitmap()).drawBitmap(b, null, new RectF(), getPaint());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testDrawBitmapSrcDstIntRecycled() {
+        Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+        b.recycle();
+
+        // Should throw out RuntimeException because bitmap has been recycled
+        new Canvas(getMutableBitmap()).drawBitmap(b, null, new Rect(), getPaint());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapIntsNegativeWidth() {
+        // Should throw out IllegalArgumentException because width is less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10,
+                10, -1, 10, true, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapIntsNegativeHeight() {
+        // Should throw out IllegalArgumentException because height is less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10,
+                10, 10, -1, true, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapIntsBadStride() {
+        // Should throw out IllegalArgumentException because stride less than width and
+        // bigger than -width
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 5, 10,
+                10, 10, 10, true, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapIntsNegativeOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], -1, 10, 10,
+                10, 10, 10, true, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapIntsBadOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
+        // is bigger than colors' length
+        new Canvas(getMutableBitmap()).drawBitmap(new int[29], 10, 29, 10,
+                10, 20, 10, true, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapFloatsNegativeWidth() {
+        // Should throw out IllegalArgumentException because width is less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10.0f,
+                10.0f, -1, 10, true, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapFloatsNegativeHeight() {
+        // Should throw out IllegalArgumentException because height is less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 10, 10.0f,
+                10.0f, 10, -1, true, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDrawBitmapFloatsBadStride() {
+        // Should throw out IllegalArgumentException because stride less than width and
+        // bigger than -width
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], 10, 5, 10.0f,
+                10.0f, 10, 10, true, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapFloatsNegativeOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because offset less than 0
+        new Canvas(getMutableBitmap()).drawBitmap(new int[2008], -1, 10, 10.0f,
+                10.0f, 10, 10, true, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapFloatsBadOffset() {
+        // Should throw out ArrayIndexOutOfBoundsException because sum of offset and width
+        // is bigger than colors' length
+        new Canvas(getMutableBitmap()).drawBitmap(new int[29], 10, 29, 10.0f,
+                10.0f, 20, 10, true, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshNegativeWidth() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because meshWidth less than 0
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, -1, 10,
+                null, 0, null, 0, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshNegativeHeight() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because meshHeight is less than 0
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, -1,
+                null, 0, null, 0, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshNegativeVertOffset() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because vertOffset is less than 0
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
+                null, -1, null, 0, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshNegativeColorOffset() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because colorOffset is less than 0
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
+                null, 10, null, -1, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshTooFewVerts() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because verts' length is too short
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10,
+                new float[] { 10.0f, 29.0f}, 10, null, 10, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawBitmapMeshTooFewColors() {
+        final Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, 29, Bitmap.Config.ARGB_8888);
+
+        // Should throw out ArrayIndexOutOfBoundsException because colors' length is too short
+        // abnormal case: colors' length is too short
+        final float[] verts = new float[2008];
+        new Canvas(getMutableBitmap()).drawBitmapMesh(b, 10, 10, verts,
+                10, new int[] { 10, 29}, 10, null);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawVerticesTooFewVerts() {
+        final float[] verts = new float[10];
+        final float[] texs = new float[10];
+        final int[] colors = new int[10];
+        final short[] indices = {
+                0, 1, 2, 3, 4, 1
+        };
+
+        // Should throw out ArrayIndexOutOfBoundsException because sum of vertOffset and
+        // vertexCount is bigger than verts' length
+        new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
+                verts, 8, texs, 0, colors, 0, indices,
+                0, 4, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawVerticesTooFewTexs() {
+        final float[] verts = new float[10];
+        final float[] texs = new float[10];
+        final int[] colors = new int[10];
+        final short[] indices = {
+                0, 1, 2, 3, 4, 1
+        };
+
+        // Should throw out ArrayIndexOutOfBoundsException because sum of texOffset and
+        // vertexCount is bigger thatn texs' length
+        new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
+                verts, 0, texs, 30, colors, 0, indices,
+                0, 4, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawVerticesTooFewColors() {
+        final float[] verts = new float[10];
+        final float[] texs = new float[10];
+        final int[] colors = new int[10];
+        final short[] indices = {
+                0, 1, 2, 3, 4, 1
+        };
+
+        // Should throw out ArrayIndexOutOfBoundsException because sum of colorOffset and
+        // vertexCount is bigger than colors' length
+        new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
+                verts, 0, texs, 0, colors, 30, indices,
+                0, 4, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawVerticesTooFewIndices() {
+        final float[] verts = new float[10];
+        final float[] texs = new float[10];
+        final int[] colors = new int[10];
+        final short[] indices = {
+                0, 1, 2, 3, 4, 1
+        };
+
+        // Should throw out ArrayIndexOutOfBoundsException because sum of indexOffset and
+        // indexCount is bigger than indices' length
+        new Canvas(getMutableBitmap()).drawVertices(Canvas.VertexMode.TRIANGLES, 10,
+                verts, 0, texs, 0, colors, 0, indices,
+                10, 30, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawArrayTextNegativeIndex() {
+        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
+
+        // Should throw out IndexOutOfBoundsException because index is less than 0
+        new Canvas(getMutableBitmap()).drawText(text, -1, 7, 10, 10, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawArrayTextNegativeCount() {
+        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
+
+        // Should throw out IndexOutOfBoundsException because count is less than 0
+        new Canvas(getMutableBitmap()).drawText(text, 0, -1, 10, 10, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawArrayTextTextLengthTooSmall() {
+        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
+
+        // Should throw out IndexOutOfBoundsException because sum of index and count
+        // is bigger than text's length
+        new Canvas(getMutableBitmap()).drawText(text, 0, 10, 10, 10, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextTextAtPositionWithOffsetsNegativeStart() {
+        // Should throw out IndexOutOfBoundsException because start is less than 0
+        new Canvas(getMutableBitmap()).drawText("android", -1, 7, 10, 30,
+                getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextTextAtPositionWithOffsetsNegativeEnd() {
+        // Should throw out IndexOutOfBoundsException because end is less than 0
+        new Canvas(getMutableBitmap()).drawText("android", 0, -1, 10, 30,
+                getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextTextAtPositionWithOffsetsStartEndMismatch() {
+        // Should throw out IndexOutOfBoundsException because start is bigger than end
+        new Canvas(getMutableBitmap()).drawText("android", 3, 1, 10, 30,
+                getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextTextAtPositionWithOffsetsTextTooLong() {
+        // Should throw out IndexOutOfBoundsException because end subtracts start should
+        // bigger than text's length
+        new Canvas(getMutableBitmap()).drawText("android", 0, 10, 10, 30,
+                getPaint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawTextRunNullCharArray() {
+        // Should throw out NullPointerException because text is null
+        new Canvas(getMutableBitmap()).drawTextRun((char[]) null, 0, 0,
+                0, 0, 0.0f, 0.0f, false, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawTextRunNullCharSequence() {
+        // Should throw out NullPointerException because text is null
+        new Canvas(getMutableBitmap()).drawTextRun((CharSequence) null, 0, 0,
+                0, 0, 0.0f, 0.0f, false, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawTextRunCharArrayNullPaint() {
+        // Should throw out NullPointerException because paint is null
+        new Canvas(getMutableBitmap()).drawTextRun("android".toCharArray(), 0, 0,
+                0, 0, 0.0f, 0.0f, false, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testDrawTextRunCharSequenceNullPaint() {
+        // Should throw out NullPointerException because paint is null
+        new Canvas(getMutableBitmap()).drawTextRun("android", 0, 0, 0,
+                0, 0.0f, 0.0f, false, null);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunNegativeIndex() {
+        final String text = "android";
+        final Paint paint = new Paint();
+
+        // Should throw out IndexOutOfBoundsException because index is less than 0
+        new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), -1, text.length(),
+                0, text.length(), 0.0f, 0.0f,
+                false, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunNegativeCount() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because count is less than 0
+        new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0, -1,
+                0, text.length(), 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunContestIndexTooLarge() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because contextIndex is bigger than index
+        new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0, text.length(),
+                1, text.length(), 0.0f, 0.0f,
+                false, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunContestIndexTooSmall() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because contextIndex + contextCount
+        // is less than index + count
+        new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
+                text.length() - 1, 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunIndexTooLarge() {
+        final String text = "android";
+        final Paint paint = new Paint();
+
+        // Should throw out IndexOutOfBoundsException because index + count is bigger than
+        // text length
+        new Canvas(getMutableBitmap()).drawTextRun(text.toCharArray(), 0,
+                text.length() + 1, 0, text.length() + 1,
+                0.0f, 0.0f, false, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunNegativeContextStart() {
+        final String text = "android";
+        final Paint paint = new Paint();
+
+        // Should throw out IndexOutOfBoundsException because contextStart is less than 0
+        new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), -1,
+                text.length(), 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunStartLessThanContextStart() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because start is less than contextStart
+        new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 1,
+                text.length(), 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunEndLessThanStart() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because end is less than start
+        new Canvas(getMutableBitmap()).drawTextRun(text, 1, 0, 0,
+                text.length(), 0.0f, 0.0f, false, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunContextEndLessThanEnd() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because contextEnd is less than end
+        new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
+                text.length() - 1, 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawTextRunContextEndLargerThanTextLength() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because contextEnd is bigger than
+        // text length
+        new Canvas(getMutableBitmap()).drawTextRun(text, 0, text.length(), 0,
+                text.length() + 1, 0.0f, 0.0f, false,
+                new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawPosTextWithIndexAndCountNegativeIndex() {
+        final char[] text = {
+                'a', 'n', 'd', 'r', 'o', 'i', 'd'
+        };
+        final float[] pos = new float[]{
+                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
+                7.0f, 7.0f
+        };
+
+        // Should throw out IndexOutOfBoundsException because index is less than 0
+        new Canvas(getMutableBitmap()).drawPosText(text, -1, 7, pos, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawPosTextWithIndexAndCountTextTooShort() {
+        final char[] text = {
+                'a', 'n', 'd', 'r', 'o', 'i', 'd'
+        };
+        final float[] pos = new float[]{
+                0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f,
+                7.0f, 7.0f
+        };
+
+        // Should throw out IndexOutOfBoundsException because sum of index and count is
+        // bigger than text's length
+        new Canvas(getMutableBitmap()).drawPosText(text, 1, 10, pos, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawPosTextWithIndexAndCountCountTooLarge() {
+        final char[] text = {
+                'a', 'n', 'd', 'r', 'o', 'i', 'd'
+        };
+
+        // Should throw out IndexOutOfBoundsException because 2 times of count is
+        // bigger than pos' length
+        new Canvas(getMutableBitmap()).drawPosText(text, 1, 10, new float[] {
+                10.0f, 30.f
+        }, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawTextOnPathWithIndexAndCountNegativeIndex() {
+        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
+
+        // Should throw out ArrayIndexOutOfBoundsException because index is smaller than 0
+        new Canvas(getMutableBitmap()).drawTextOnPath(text, -1, 7, new Path(),
+                10.0f, 10.0f, getPaint());
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testDrawTextOnPathWithIndexAndCountTextTooShort() {
+        final char[] text = { 'a', 'n', 'd', 'r', 'o', 'i', 'd' };
+
+        // Should throw out ArrayIndexOutOfBoundsException because sum of index and
+        // count is bigger than text's length
+        new Canvas(getMutableBitmap()).drawTextOnPath(text, 0, 10, new Path(),
+                10.0f, 10.0f, getPaint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testDrawPosTextCountTooLarge() {
+        final String text = "android";
+
+        // Should throw out IndexOutOfBoundsException because 2 times of count is
+        // bigger than pos' length
+        new Canvas(getMutableBitmap()).drawPosText(text, new float[]{
+                10.0f, 30.f
+        }, getPaint());
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index cce9bb0..8d16d5e 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -16,8 +16,10 @@
 
 package android.uirendering.cts.testclasses;
 
+import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Picture;
@@ -196,6 +198,14 @@
     }
 
     @Test
+    public void testBasicColorBlendMode() {
+        createTest().addCanvasClient((canvas, width, height) -> {
+            canvas.drawColor(Color.GRAY);
+            canvas.drawColor(Color.BLUE, BlendMode.MULTIPLY);
+        }).runWithComparer(mExactComparer);
+    }
+
+    @Test
     public void testBluePaddedSquare() {
         final NinePatchDrawable ninePatchDrawable = (NinePatchDrawable)
             getActivity().getResources().getDrawable(R.drawable.blue_padded_square);
@@ -253,4 +263,39 @@
                 .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK,
                         new Rect(20, 20, 70, 70)));
     }
+
+    @Test
+    public void testBlackTriangleVertices() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    float[] vertices = new float[6];
+                    vertices[0] = width / 2.0f;
+                    vertices[1] = 0;
+                    vertices[2] = width;
+                    vertices[3] = height;
+                    vertices[4] = 0;
+                    vertices[5] = height;
+                    int[] colors = new int[] { Color.BLACK, Color.BLACK, Color.BLACK };
+                    canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
+                            null, 0, colors, 0, null, 0, 0,
+                            new Paint());
+                })
+                .runWithComparer(mExactComparer);
+    }
+
+    @Test
+    public void testColorLongs() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    canvas.drawColor(Color.pack(0.5f, 0.3f, 0.1f, 1.0f,
+                                ColorSpace.get(ColorSpace.Named.DISPLAY_P3)));
+                    canvas.drawColor(Color.pack(0.2f, 0.2f, 0.2f, 1.0f,
+                                ColorSpace.get(ColorSpace.Named.DISPLAY_P3)), BlendMode.PLUS);
+                    Paint p = new Paint();
+                    p.setColor(Color.pack(0.7f, 0.9f, 0.4f, 1.0f,
+                                ColorSpace.get(ColorSpace.Named.DISPLAY_P3)));
+                    canvas.drawRect(20, 20, 70, 70, p);
+                })
+                .runWithComparer(mExactComparer);
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ForceDarkTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ForceDarkTests.java
new file mode 100644
index 0000000..6d94262
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ForceDarkTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.CanvasClientView;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ForceDarkTests extends ActivityTestBase {
+    static boolean sWasEnabled = false;
+
+    @BeforeClass
+    public static void enableForceDark() {
+        // We need to ensure the value is set to the default, overriding any user preference
+        // temporarily
+        sWasEnabled = Boolean.parseBoolean(
+                SystemUtil.runShellCommand("getprop debug.hwui.force_dark_enabled"));
+        SystemUtil.runShellCommand("setprop debug.hwui.force_dark_enabled true");
+    }
+
+    @AfterClass
+    public static void restoreForceDarkSetting() {
+        if (!sWasEnabled) {
+            SystemUtil.runShellCommand("setprop debug.hwui.force_dark_enabled false");
+        }
+    }
+
+    @Override
+    protected boolean useForceDark() {
+        return true;
+    }
+
+    @Test
+    public void testFgRect() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        createTest()
+                .addLayout(R.layout.simple_force_dark, (ViewInitializer) view -> {
+                    Assert.assertTrue(view.isForceDarkAllowed());
+                    ((CanvasClientView) view).setCanvasClient((canvas, width, height) -> {
+                        Paint p = new Paint();
+                        p.setAntiAlias(false);
+                        p.setColor(Color.BLACK);
+                        canvas.drawRect(rect, p);
+                    });
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.BLACK, Color.WHITE, rect, 100));
+    }
+
+    @Test
+    public void testFgRectDisable() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        createTest()
+                .addLayout(R.layout.simple_force_dark, (ViewInitializer) view -> {
+                    view.setForceDarkAllowed(false);
+                    Assert.assertFalse(view.isForceDarkAllowed());
+                    ((CanvasClientView) view).setCanvasClient((canvas, width, height) -> {
+                        Paint p = new Paint();
+                        p.setAntiAlias(false);
+                        p.setColor(Color.BLACK);
+                        canvas.drawRect(rect, p);
+                    });
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK, rect, 0));
+    }
+
+    @Test
+    public void testSiblings() {
+        final Rect fgRect = new Rect(10, 10, 80, 80);
+        createTest()
+                .addLayout(R.layout.force_dark_siblings, (ViewInitializer) view -> {
+
+                    CanvasClientView bg = view.findViewById(R.id.bg_canvas);
+                    CanvasClientView fg = view.findViewById(R.id.fg_canvas);
+
+                    bg.setCanvasClient((canvas, width, height) -> {
+                        canvas.drawColor(Color.WHITE);
+                    });
+
+                    fg.setCanvasClient((canvas, width, height) -> {
+                        Paint p = new Paint();
+                        p.setAntiAlias(false);
+                        p.setColor(Color.BLACK);
+                        canvas.drawRect(fgRect, p);
+                    });
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.BLACK, Color.WHITE, fgRect, 100));
+    }
+}
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 3391bb9..fb07aa6 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -242,7 +242,7 @@
         createTest().addCanvasClient((canvas, width, height) -> {
             Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
                     HARDWARE_OPTIONS);
-            Bitmap scaled = Bitmap.createScaledBitmap(hardwareBitmap, 24, 24, false);
+            Bitmap scaled = Bitmap.createScaledBitmap(hardwareBitmap, 24, 24, true);
             assertEquals(Bitmap.Config.HARDWARE, scaled.getConfig());
             canvas.drawBitmap(scaled, 0, 0, null);
         }, true).runWithVerifier(new GoldenImageVerifier(getActivity(),
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
index abaf96e..7d4b403 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
@@ -16,7 +16,6 @@
 package android.uirendering.cts.testclasses;
 
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
@@ -36,14 +35,6 @@
 @RunWith(AndroidJUnit4.class)
 public class InfrastructureTests extends ActivityTestBase {
 
-    @Test
-    public void testScreenshot() {
-        for (int i = 0 ; i < 500 ; i ++) {
-            takeScreenshot(new TestPositionInfo(new Point(), new Point()));
-            System.gc();
-        }
-    }
-
     /**
      * Ensure that both render paths are producing independent output. We do this
      * by verifying that two paths that should render differently *do* render
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 b4c7798..49b6fc9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -49,7 +49,6 @@
 
 import androidx.annotation.ColorInt;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -193,7 +192,7 @@
     @Test
     public void testLayerClear() {
         ViewInitializer initializer = new ViewInitializer() {
-            ValueAnimator mAnimator;
+            ValueAnimator mAnimator = ValueAnimator.ofInt(0, 20);
             @Override
             public void initializeView(View view) {
                 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
@@ -207,7 +206,6 @@
                 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                 root.addView(child);
 
-                mAnimator = ValueAnimator.ofInt(0, 20);
                 mAnimator.setRepeatMode(ValueAnimator.REVERSE);
                 mAnimator.setRepeatCount(ValueAnimator.INFINITE);
                 mAnimator.setDuration(200);
@@ -539,12 +537,19 @@
 
     @LargeTest
     @Test
-    @Ignore // b/109839751
     public void testWebViewWithUnclippedLayer() {
         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
             return; // no WebView to run test on
         }
         CountDownLatch hwFence = new CountDownLatch(1);
+        Point[] testPoints = new Point[] {
+            // solid area
+            new Point(0, 0),
+            new Point(0, TEST_HEIGHT - 1),
+            // fade area
+            new Point(0, TEST_HEIGHT - 10),
+            new Point(0, TEST_HEIGHT - 5)
+        };
         createTest()
                 .addLayout(R.layout.test_content_webview, (ViewInitializer) view -> {
                     WebView webview = view.requireViewById(R.id.webview);
@@ -554,32 +559,39 @@
                     webview.setVerticalScrollBarEnabled(false);
                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
+                    // 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;
                 }, true, hwFence)
                 .runWithVerifier(new SamplePointVerifier(
-                        new Point[] {
-                                // solid area
-                                new Point(0, 0),
-                                new Point(0, TEST_HEIGHT - 1),
-                                // fade area
-                                new Point(0, TEST_HEIGHT - 10),
-                                new Point(0, TEST_HEIGHT - 5)
-                        },
+                        testPoints,
                         new int[] {
                                 Color.BLUE,
                                 Color.WHITE,
-                                0xffb3b3ff, // white blended with blue
+                                0xffc5c5ff, // white blended with blue
                                 0xffdbdbff  // white blended with blue
-                        }));
+                        }, 50));
     }
 
     @LargeTest
     @Test
-    @Ignore // b/109839751
     public void testWebViewWithUnclippedLayerAndComplexClip() {
         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
             return; // no WebView to run test on
         }
         CountDownLatch hwFence = new CountDownLatch(1);
+        Point[] testPoints = new Point[] {
+            // solid white area
+            new Point(0, 0),
+            new Point(0, TEST_HEIGHT - 1),
+            // solid blue area
+            new Point(TEST_WIDTH / 2 , 5),
+            // fade area
+            new Point(TEST_WIDTH / 2, TEST_HEIGHT - 10),
+            new Point(TEST_WIDTH / 2, TEST_HEIGHT - 5)
+        };
         createTest()
                 .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
                     WebView webview = view.requireViewById(R.id.webview);
@@ -588,26 +600,21 @@
                     webview.setVerticalFadingEdgeEnabled(true);
                     webview.setVerticalScrollBarEnabled(false);
                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-
+                    // 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;
                 }, true, hwFence)
                 .runWithVerifier(new SamplePointVerifier(
-                        new Point[] {
-                                // solid white area
-                                new Point(0, 0),
-                                new Point(0, TEST_HEIGHT - 1),
-                                // solid blue area
-                                new Point(TEST_WIDTH / 2 , 5),
-                                // fade area
-                                new Point(TEST_WIDTH / 2, TEST_HEIGHT - 10),
-                                new Point(TEST_WIDTH / 2, TEST_HEIGHT - 5)
-                        },
+                        testPoints,
                         new int[] {
                                 Color.WHITE,
                                 Color.WHITE,
                                 Color.BLUE,
-                                0xffb3b3ff, // white blended with blue
+                                0xffc5c5ff, // white blended with blue
                                 0xffdbdbff  // white blended with blue
-                        }));
+                        }, 50));
     }
 
     @LargeTest
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
index 3d1c10e..285db5a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
@@ -16,12 +16,14 @@
 package android.uirendering.cts.testclasses;
 
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
 import org.junit.Test;
@@ -41,5 +43,21 @@
         createTest().addLayout(R.layout.simple_rect_layout, null, false).runWithVerifier(
                 new RectVerifier(Color.WHITE, Color.BLUE, new Rect(5, 5, 85, 85)));
     }
+
+    @Test
+    public void testTransformChildViewLayout() {
+        createTest()
+                // Verify skew tranform matrix is applied for child view.
+                .addLayout(R.layout.skew_layout, null)
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] {
+                                new Point(20, 10),
+                                new Point(0, TEST_HEIGHT - 1)
+                        },
+                        new int[] {
+                                Color.RED,
+                                Color.WHITE
+                        }));
+    }
 }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
new file mode 100644
index 0000000..afd9a64
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -0,0 +1,366 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+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 android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RenderNodeTests extends ActivityTestBase {
+
+    @Test
+    public void testDefaults() {
+        final RenderNode renderNode = new RenderNode(null);
+        assertEquals(0, renderNode.getLeft());
+        assertEquals(0, renderNode.getRight());
+        assertEquals(0, renderNode.getTop());
+        assertEquals(0, renderNode.getBottom());
+        assertEquals(0, renderNode.getWidth());
+        assertEquals(0, renderNode.getHeight());
+
+        assertEquals(0, renderNode.getTranslationX(), 0.01f);
+        assertEquals(0, renderNode.getTranslationY(), 0.01f);
+        assertEquals(0, renderNode.getTranslationZ(), 0.01f);
+        assertEquals(0, renderNode.getElevation(), 0.01f);
+
+        assertEquals(0, renderNode.getRotationX(), 0.01f);
+        assertEquals(0, renderNode.getRotationY(), 0.01f);
+        assertEquals(0, renderNode.getRotation(), 0.01f);
+
+        assertEquals(1, renderNode.getScaleX(), 0.01f);
+        assertEquals(1, renderNode.getScaleY(), 0.01f);
+
+        assertEquals(1, renderNode.getAlpha(), 0.01f);
+
+        assertEquals(0, renderNode.getPivotX(), 0.01f);
+        assertEquals(0, renderNode.getPivotY(), 0.01f);
+
+        assertEquals(Color.BLACK, renderNode.getAmbientShadowColor());
+        assertEquals(Color.BLACK, renderNode.getSpotShadowColor());
+
+        assertEquals(8, renderNode.getCameraDistance(), 0.01f);
+
+        assertTrue(renderNode.isForceDarkAllowed());
+        assertTrue(renderNode.hasIdentityMatrix());
+        assertTrue(renderNode.getClipToBounds());
+        assertFalse(renderNode.getClipToOutline());
+        assertFalse(renderNode.isPivotExplicitlySet());
+        assertFalse(renderNode.hasDisplayList());
+        assertFalse(renderNode.hasOverlappingRendering());
+        assertFalse(renderNode.hasShadow());
+        assertFalse(renderNode.getUseCompositingLayer());
+    }
+
+    @Test
+    public void testBasicDraw() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+
+        final RenderNode renderNode = new RenderNode("Blue rect");
+        renderNode.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom);
+        assertEquals(rect.left, renderNode.getLeft());
+        assertEquals(rect.top, renderNode.getTop());
+        assertEquals(rect.right, renderNode.getRight());
+        assertEquals(rect.bottom, renderNode.getBottom());
+        renderNode.setClipToBounds(true);
+
+        {
+            Canvas canvas = renderNode.startRecording();
+            assertEquals(rect.width(), canvas.getWidth());
+            assertEquals(rect.height(), canvas.getHeight());
+            assertTrue(canvas.isHardwareAccelerated());
+            canvas.drawColor(Color.BLUE);
+            renderNode.endRecording();
+        }
+
+        assertTrue(renderNode.hasDisplayList());
+        assertTrue(renderNode.hasIdentityMatrix());
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE, rect));
+    }
+
+    @Test
+    public void testAlphaOverlappingRendering() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom);
+        renderNode.setHasOverlappingRendering(true);
+        assertTrue(renderNode.hasOverlappingRendering());
+        {
+            Canvas canvas = renderNode.startRecording();
+            canvas.drawColor(Color.RED);
+            canvas.drawColor(Color.BLUE);
+            renderNode.endRecording();
+        }
+        renderNode.setAlpha(.5f);
+        createTest()
+              .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                  canvas.drawRenderNode(renderNode);
+              }, true)
+              .runWithVerifier(new RectVerifier(Color.WHITE, 0xFF8080FF, rect));
+    }
+
+    @Test
+    public void testAlphaNonOverlappingRendering() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom);
+        renderNode.setHasOverlappingRendering(false);
+        assertFalse(renderNode.hasOverlappingRendering());
+        {
+            Canvas canvas = renderNode.startRecording();
+            canvas.drawColor(Color.RED);
+            canvas.drawColor(Color.BLUE);
+            renderNode.endRecording();
+        }
+        renderNode.setAlpha(.5f);
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, 0xFF8040BF, rect));
+    }
+
+    @Test
+    public void testUseCompositingLayer() {
+        final Rect rect = new Rect(10, 10, 80, 80);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom);
+        {
+            Canvas canvas = renderNode.startRecording();
+            canvas.drawColor(0xFF0000AF);
+            renderNode.endRecording();
+        }
+        // Construct & apply a Y'UV lightness invert color matrix to the layer paint
+        Paint paint = new Paint();
+        ColorMatrix matrix = new ColorMatrix();
+        ColorMatrix tmp = new ColorMatrix();
+        matrix.setRGB2YUV();
+        tmp.set(new float[] {
+                -1f, 0f, 0f, 0f, 255f,
+                0f, 1f, 0f, 0f, 0f,
+                0f, 0f, 1f, 0f, 0f,
+                0f, 0f, 0f, 1f, 0f,
+        });
+        matrix.postConcat(tmp);
+        tmp.setYUV2RGB();
+        matrix.postConcat(tmp);
+        paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+        renderNode.setUseCompositingLayer(true, paint);
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, 0xFFD7D7FF, rect));
+    }
+
+    @Test
+    public void testComputeApproximateMemoryUsage() {
+        final RenderNode renderNode = new RenderNode("sizeTest");
+        assertTrue(renderNode.computeApproximateMemoryUsage() > 500);
+        assertTrue(renderNode.computeApproximateMemoryUsage() < 1500);
+        int sizeBefore = renderNode.computeApproximateMemoryUsage();
+        {
+            Canvas canvas = renderNode.startRecording();
+            assertTrue(canvas.isHardwareAccelerated());
+            canvas.drawColor(Color.BLUE);
+            renderNode.endRecording();
+        }
+        int sizeAfter = renderNode.computeApproximateMemoryUsage();
+        assertTrue(sizeAfter > sizeBefore);
+        renderNode.discardDisplayList();
+        assertEquals(sizeBefore, renderNode.computeApproximateMemoryUsage());
+    }
+
+    @Test
+    public void testTranslationGetSet() {
+        final RenderNode renderNode = new RenderNode("translation");
+
+        assertTrue(renderNode.hasIdentityMatrix());
+
+        assertFalse(renderNode.setTranslationX(0.0f));
+        assertFalse(renderNode.setTranslationY(0.0f));
+        assertFalse(renderNode.setTranslationZ(0.0f));
+
+        assertTrue(renderNode.hasIdentityMatrix());
+
+        assertTrue(renderNode.setTranslationX(1.0f));
+        assertEquals(1.0f, renderNode.getTranslationX(), 0.0f);
+        assertTrue(renderNode.setTranslationY(1.0f));
+        assertEquals(1.0f, renderNode.getTranslationY(), 0.0f);
+        assertTrue(renderNode.setTranslationZ(1.0f));
+        assertEquals(1.0f, renderNode.getTranslationZ(), 0.0f);
+
+        assertFalse(renderNode.hasIdentityMatrix());
+
+        assertTrue(renderNode.setTranslationX(0.0f));
+        assertTrue(renderNode.setTranslationY(0.0f));
+        assertTrue(renderNode.setTranslationZ(0.0f));
+
+        assertTrue(renderNode.hasIdentityMatrix());
+    }
+
+    @Test
+    public void testAlphaGetSet() {
+        final RenderNode renderNode = new RenderNode("alpha");
+
+        assertFalse(renderNode.setAlpha(1.0f));
+        assertTrue(renderNode.setAlpha(.5f));
+        assertEquals(.5f, renderNode.getAlpha(), 0.0001f);
+        assertTrue(renderNode.setAlpha(1.0f));
+    }
+
+    @Test
+    public void testRotationGetSet() {
+        final RenderNode renderNode = new RenderNode("rotation");
+
+        assertFalse(renderNode.setRotationX(0.0f));
+        assertFalse(renderNode.setRotationY(0.0f));
+        assertFalse(renderNode.setRotation(0.0f));
+        assertTrue(renderNode.hasIdentityMatrix());
+
+        assertTrue(renderNode.setRotationX(1.0f));
+        assertEquals(1.0f, renderNode.getRotationX(), 0.0f);
+        assertTrue(renderNode.setRotationY(1.0f));
+        assertEquals(1.0f, renderNode.getRotationY(), 0.0f);
+        assertTrue(renderNode.setRotation(1.0f));
+        assertEquals(1.0f, renderNode.getRotation(), 0.0f);
+        assertFalse(renderNode.hasIdentityMatrix());
+
+        assertTrue(renderNode.setRotationX(0.0f));
+        assertTrue(renderNode.setRotationY(0.0f));
+        assertTrue(renderNode.setRotation(0.0f));
+        assertTrue(renderNode.hasIdentityMatrix());
+    }
+
+    @Test
+    public void testScaleGetSet() {
+        final RenderNode renderNode = new RenderNode("scale");
+
+        assertFalse(renderNode.setScaleX(1.0f));
+        assertFalse(renderNode.setScaleY(1.0f));
+
+        assertTrue(renderNode.setScaleX(2.0f));
+        assertEquals(2.0f, renderNode.getScaleX(), 0.0f);
+        assertTrue(renderNode.setScaleY(2.0f));
+        assertEquals(2.0f, renderNode.getScaleY(), 0.0f);
+
+        assertTrue(renderNode.setScaleX(1.0f));
+        assertTrue(renderNode.setScaleY(1.0f));
+    }
+
+    @Test
+    public void testStartEndRecordingEmpty() {
+        final RenderNode renderNode = new RenderNode(null);
+        assertEquals(0, renderNode.getWidth());
+        assertEquals(0, renderNode.getHeight());
+        RecordingCanvas canvas = renderNode.startRecording();
+        assertTrue(canvas.isHardwareAccelerated());
+        assertEquals(0, canvas.getWidth());
+        assertEquals(0, canvas.getHeight());
+        renderNode.endRecording();
+    }
+
+    @Test
+    public void testStartEndRecordingWithBounds() {
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setLeftTopRightBottom(10, 20, 30, 50);
+        assertEquals(20, renderNode.getWidth());
+        assertEquals(30, renderNode.getHeight());
+        RecordingCanvas canvas = renderNode.startRecording();
+        assertTrue(canvas.isHardwareAccelerated());
+        assertEquals(20, canvas.getWidth());
+        assertEquals(30, canvas.getHeight());
+        renderNode.endRecording();
+    }
+
+    @Test
+    public void testStartEndRecordingEmptyWithSize() {
+        final RenderNode renderNode = new RenderNode(null);
+        assertEquals(0, renderNode.getWidth());
+        assertEquals(0, renderNode.getHeight());
+        RecordingCanvas canvas = renderNode.startRecording(5, 10);
+        assertTrue(canvas.isHardwareAccelerated());
+        assertEquals(5, canvas.getWidth());
+        assertEquals(10, canvas.getHeight());
+        renderNode.endRecording();
+    }
+
+    @Test
+    public void testStartEndRecordingWithBoundsWithSize() {
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setLeftTopRightBottom(10, 20, 30, 50);
+        assertEquals(20, renderNode.getWidth());
+        assertEquals(30, renderNode.getHeight());
+        RecordingCanvas canvas = renderNode.startRecording(5, 10);
+        assertTrue(canvas.isHardwareAccelerated());
+        assertEquals(5, canvas.getWidth());
+        assertEquals(10, canvas.getHeight());
+        renderNode.endRecording();
+    }
+
+    @Test
+    public void testGetUniqueId() {
+        final RenderNode r1 = new RenderNode(null);
+        final RenderNode r2 = new RenderNode(null);
+        assertNotEquals(r1.getUniqueId(), r2.getUniqueId());
+        final Set<Long> usedIds = new HashSet<>();
+        assertTrue(usedIds.add(r1.getUniqueId()));
+        assertTrue(usedIds.add(r2.getUniqueId()));
+        for (int i = 0; i < 100; i++) {
+            assertTrue(usedIds.add(new RenderNode(null).getUniqueId()));
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidCameraDistance() {
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setCameraDistance(-1f);
+    }
+
+    @Test
+    public void testCameraDistanceSetGet() {
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setCameraDistance(100f);
+        assertEquals(100f, renderNode.getCameraDistance(), 0.0f);
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/TextureViewTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/TextureViewTests.java
index ad20dd7..e25260b 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/TextureViewTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/TextureViewTests.java
@@ -25,8 +25,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.SurfaceTexture;
-import androidx.annotation.ColorInt;
-import android.support.test.filters.MediumTest;
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
@@ -38,6 +37,8 @@
 import android.view.TextureView.SurfaceTextureListener;
 import android.view.ViewGroup;
 
+import androidx.annotation.ColorInt;
+
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -47,7 +48,8 @@
 
 import java.util.concurrent.CountDownLatch;
 
-@MediumTest
+// Temporarily mark @LargeTest to surpress it from presubmit b/37773896
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TextureViewTests extends ActivityTestBase {
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
index f4db101..1368420 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
@@ -16,9 +16,6 @@
 
 package android.uirendering.cts.testclasses;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -31,14 +28,15 @@
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
-
-import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.Gravity;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
+
 import java.util.concurrent.CountDownLatch;
-import android.animation.Animator;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -82,68 +80,51 @@
      */
     @Test
     public void testInvalidateCache() {
-        CountDownLatch testFinishedFence = new CountDownLatch(1);
-
-        ViewInitializer initializer = new ViewInitializer() {
-            ValueAnimator mAnimator;
-
-            @Override
-            public void initializeView(View view) {
-                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
-                root.setBackgroundColor(Color.BLUE);
-
-                final VectorDrawableView child = new VectorDrawableView(view.getContext());
-
-                child.setLayoutParams(new FrameLayout.LayoutParams(ActivityTestBase.TEST_WIDTH,
-                    ActivityTestBase.TEST_HEIGHT));
-                // VectorDrawable is a red circle drawn on top of a blue background.
-                // The first frame has VectorDrawable size set to 1x1 pixels, which deforms
-                // the red circle into a 1x1 red-ish square.
-                // An animation grows VectorDrawable bounds from 0x0 to 90x90. If VD cache is
-                // refreshed, then we should see a red circle on top of a blue background.
-                // If VD cache is stale, then VD will upscale the original 1x1 cached image to
-                // 90x90 red-ish square.
-                // At the end of the animation, we verify the color of top left pixel, which should
-                // be a blue background pixel.
-                child.setVDSize(new Rect(0, 0, 2, 2)); //first draw with VD size set to 1x1 pixels.
-                root.addView(child);
-
-                mAnimator = ValueAnimator.ofFloat(0, 1);
-                mAnimator.setRepeatCount(0);
-                mAnimator.setDuration(400);
-                mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        float progress = (float) mAnimator.getAnimatedValue();
-                        child.setVDSize(new Rect(0, 0, (int)(progress*child.getWidth()),
-                                (int)(progress*child.getHeight())));
-                        child.invalidate();
-                    }
-                });
-                mAnimator.addListener(
-                    new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            super.onAnimationEnd(animation);
-                            testFinishedFence.countDown();
-                        }
-                    });
-
-                mAnimator.start();
-            }
-
-            @Override
-            public void teardownView() {
-              mAnimator.cancel();
-            }
-        };
-
+        final CountDownLatch fence = new CountDownLatch(1);
         createTest()
-            .addLayout(R.layout.frame_layout, initializer, true, testFinishedFence)
-            .runWithVerifier(new SamplePointVerifier(
-                new Point[] { new Point(0, 0) },
-                new int[] { 0xff0000ff }
-            ));
+                .addLayout(R.layout.frame_layout, view -> {
+                    FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                    root.setBackgroundColor(Color.BLUE);
+                    final VectorDrawableView child = new VectorDrawableView(view.getContext());
+                    // VectorDrawable is a red circle drawn on top of a blue background.
+                    // The first frame has VectorDrawable size set to 1x1 pixels, which deforms
+                    // the red circle into a 1x1 red-ish square.
+                    // After first draw we grow VectorDrawable bounds from 0x0 to 90x90. If VD cache
+                    // is refreshed, then we should see a red circle on top of a blue background.
+                    // If VD cache is stale, then VD will upscale the original 1x1 cached image to
+                    // 90x90 red-ish square.
+                    // At the end we verify the color of top left pixel, which should be a blue
+                    // background pixel.
+                    child.setVDSize(new Rect(0, 0, 2, 2));
+
+                    root.addView(child, new FrameLayout.LayoutParams(TEST_WIDTH, TEST_HEIGHT,
+                              Gravity.TOP | Gravity.LEFT));
+
+                    // Post a new VD size a few frames in, so that the initial draw completes.
+                    root.getViewTreeObserver().addOnPreDrawListener(
+                            new ViewTreeObserver.OnPreDrawListener() {
+                                int mDrawCount = 0;
+                                @Override
+                                public boolean onPreDraw() {
+                                    if (mDrawCount++ == 5) {
+                                        child.setVDSize(new Rect(0, 0,
+                                                (int) (child.getWidth()),
+                                                (int) (child.getHeight())));
+                                        child.invalidate();
+
+                                        root.getViewTreeObserver().removeOnPreDrawListener(this);
+                                        root.post(fence::countDown);
+                                    } else {
+                                        root.postInvalidate();
+                                    }
+                                    return true;
+                                }
+                            });
+                }, true, fence)
+                .runWithVerifier(new SamplePointVerifier(
+                    new Point[] { new Point(0, 0) },
+                    new int[] { 0xff0000ff }
+                ));
     }
 }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/WideColorGamutTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/WideColorGamutTests.java
index 9f46e36..57446b0 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/WideColorGamutTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/WideColorGamutTests.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
 import android.graphics.Color;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
@@ -30,6 +31,7 @@
 import android.uirendering.cts.testclasses.view.BitmapView;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.view.View;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -129,4 +131,33 @@
                 }, true)
                 .runWithVerifier(getVerifier(POINTS, COLORS, 1e-2f));
     }
+
+    @Test
+    public void testCanvasDrawColorLong() {
+        final Color greenP3 = Color.valueOf(0, 1.0f, 0, 1.0f, DISPLAY_P3);
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    canvas.drawColor(greenP3.pack());
+                })
+                .runWithVerifier(getVerifier(
+                            new Point[] { new Point(0, 0), new Point(50, 50) },
+                            new Color[] { greenP3, greenP3 },
+                            0));
+    }
+
+    @Test
+    public void testCanvasDrawColorLongBlendMode() {
+        final Color greenP3 = Color.valueOf(0, 1.0f, 0, 1.0f, DISPLAY_P3);
+        final Color redP3 = Color.valueOf(1.0f, 0, 0, 1.0f, DISPLAY_P3);
+        final Color expected = Color.valueOf(1.0f, 1.0f, 0, 1.0f, DISPLAY_P3);
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    canvas.drawColor(greenP3.pack());
+                    canvas.drawColor(redP3.pack(), BlendMode.PLUS);
+                })
+                .runWithVerifier(getVerifier(
+                            new Point[] { new Point(0, 0), new Point(50, 50) },
+                            new Color[] { expected, expected },
+                            0));
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/SkewLayout.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/SkewLayout.java
new file mode 100644
index 0000000..f9b8a3c
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/SkewLayout.java
@@ -0,0 +1,46 @@
+/*
+ * 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.uirendering.cts.testclasses.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class SkewLayout extends FrameLayout {
+
+    public SkewLayout(Context context) {
+        super(context);
+    }
+
+    public SkewLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SkewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        canvas.save();
+        canvas.skew(0.5f, 0f);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return result;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 6f2ac81..b1bf6ed 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -21,7 +21,6 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.Point;
 import android.graphics.Rect;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.uirendering.cts.bitmapcomparers.BitmapComparer;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
@@ -29,6 +28,8 @@
 import android.util.Log;
 import android.view.PixelCopy;
 
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.SynchronousPixelCopy;
 
 import org.junit.After;
@@ -81,6 +82,7 @@
             intent.setClass(instrumentation.getTargetContext(), DrawActivity.class);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             intent.putExtra(DrawActivity.EXTRA_WIDE_COLOR_GAMUT, isWideColorGamut());
+            intent.putExtra(DrawActivity.EXTRA_USE_FORCE_DARK, useForceDark());
             sActivity = (DrawActivity) instrumentation.startActivitySync(intent);
         }
         return sActivity;
@@ -90,6 +92,10 @@
         return false;
     }
 
+    protected boolean useForceDark() {
+        return false;
+    }
+
     @AfterClass
     public static void tearDownClass() {
         if (sActivity != null) {
@@ -118,7 +124,7 @@
         }
     }
 
-    public Bitmap takeScreenshot(TestPositionInfo testPositionInfo) {
+    private Bitmap takeScreenshot(TestPositionInfo testPositionInfo) {
         if (mScreenshotter == null) {
             SynchronousPixelCopy copy = new SynchronousPixelCopy();
             Bitmap dest = Bitmap.createBitmap(
@@ -135,7 +141,8 @@
             return mScreenshotter.takeScreenshot(testPositionInfo);
         }
     }
-    protected TestPositionInfo runRenderSpec(TestCase testCase) {
+
+    private TestPositionInfo runRenderSpec(TestCase testCase) {
         TestPositionInfo testPositionInfo = getActivity().enqueueRenderSpecAndWait(
                 testCase.layoutID, testCase.canvasClient,
                 testCase.viewInitializer, testCase.useHardware, testCase.usePicture);
@@ -146,6 +153,9 @@
             } catch (InterruptedException e) {
                 throw new RuntimeException("readyFence didn't signal within 5 seconds");
             }
+            // The fence setup may have (and probably did) changed things that we need to wait
+            // have been drawn. So force an invalidate() and wait for it to finish
+            getActivity().waitForRedraw();
         }
         return testPositionInfo;
     }
@@ -153,7 +163,7 @@
     /**
      * Used to execute a specific part of a test and get the resultant bitmap
      */
-    protected Bitmap captureRenderSpec(TestCase testCase) {
+    private Bitmap captureRenderSpec(TestCase testCase) {
         return takeScreenshot(runRenderSpec(testCase));
     }
 
@@ -289,7 +299,7 @@
         }
 
         public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer,
-                                         boolean useHardware) {
+                boolean useHardware) {
             mTestCases.add(new TestCase(layoutId, viewInitializer, useHardware));
             return this;
         }
@@ -316,7 +326,7 @@
         }
 
         public TestCaseBuilder addCanvasClient(String debugString,
-                    CanvasClient canvasClient, boolean useHardware) {
+                CanvasClient canvasClient, boolean useHardware) {
             return addCanvasClientInternal(debugString, canvasClient, useHardware, false)
                     .addCanvasClientInternal(debugString, canvasClient, useHardware, true);
         }
@@ -350,9 +360,11 @@
     private class TestCase {
         public int layoutID;
         public ViewInitializer viewInitializer;
-        /** After launching the test case this fence is used to signal when
+        /**
+         * After launching the test case this fence is used to signal when
          * to proceed with capture & verification. If this is null the test
-         * proceeds immediately to verification */
+         * proceeds immediately to verification
+         */
         @Nullable
         public CountDownLatch readyFence;
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
deleted file mode 100644
index d876c6c..0000000
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.uirendering.cts.testinfrastructure;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import androidx.annotation.Nullable;
-import android.uirendering.cts.R;
-import android.util.Log;
-import android.util.Pair;
-import android.view.FrameMetrics;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A generic activity that uses a view specified by the user.
- */
-public class DrawActivity extends Activity {
-    static final String EXTRA_WIDE_COLOR_GAMUT = "DrawActivity.WIDE_COLOR_GAMUT";
-
-    private final static long TIME_OUT_MS = 10000;
-    private final Object mLock = new Object();
-    private ActivityTestBase.TestPositionInfo mPositionInfo;
-
-    private Handler mHandler;
-    private View mView;
-    private View mViewWrapper;
-    private boolean mOnTv;
-    private DrawMonitor mDrawMonitor;
-
-    public void onCreate(Bundle bundle){
-        super.onCreate(bundle);
-        getWindow().getDecorView().setSystemUiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
-        if (getIntent().getBooleanExtra(EXTRA_WIDE_COLOR_GAMUT, false)) {
-            getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
-        }
-        mHandler = new RenderSpecHandler();
-        int uiMode = getResources().getConfiguration().uiMode;
-        mOnTv = (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
-        mDrawMonitor = new DrawMonitor(getWindow());
-    }
-
-    public boolean getOnTv() {
-        return mOnTv;
-    }
-
-    public ActivityTestBase.TestPositionInfo enqueueRenderSpecAndWait(int layoutId,
-            CanvasClient canvasClient, @Nullable ViewInitializer viewInitializer,
-            boolean useHardware, boolean usePicture) {
-        ((RenderSpecHandler) mHandler).setViewInitializer(viewInitializer);
-        int arg2 = (useHardware ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE);
-        synchronized (mLock) {
-            if (canvasClient != null) {
-                mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, usePicture ? 1 : 0,
-                        arg2, canvasClient).sendToTarget();
-            } else {
-                mHandler.obtainMessage(RenderSpecHandler.LAYOUT_MSG, layoutId, arg2).sendToTarget();
-            }
-
-            try {
-                mLock.wait(TIME_OUT_MS);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-        assertNotNull("Timeout waiting for draw", mPositionInfo);
-        return mPositionInfo;
-    }
-
-    public void reset() {
-        CountDownLatch fence = new CountDownLatch(1);
-        mHandler.obtainMessage(RenderSpecHandler.RESET_MSG, fence).sendToTarget();
-        try {
-            if (!fence.await(10, TimeUnit.SECONDS)) {
-                fail("Timeout exception");
-            }
-        } catch (InterruptedException ex) {
-            fail(ex.getMessage());
-        }
-    }
-
-    private ViewInitializer mViewInitializer;
-
-    private void notifyOnDrawCompleted() {
-        DrawCounterListener onDrawListener = new DrawCounterListener();
-        mView.getViewTreeObserver().addOnDrawListener(onDrawListener);
-        mView.invalidate();
-    }
-
-    private class RenderSpecHandler extends Handler {
-        public static final int RESET_MSG = 0;
-        public static final int LAYOUT_MSG = 1;
-        public static final int CANVAS_MSG = 2;
-
-
-        public void setViewInitializer(ViewInitializer viewInitializer) {
-            mViewInitializer = viewInitializer;
-        }
-
-        public void handleMessage(Message message) {
-            Log.d("UiRendering", "message of type " + message.what);
-            if (message.what == RESET_MSG) {
-                ((ViewGroup)findViewById(android.R.id.content)).removeAllViews();
-                ((CountDownLatch)message.obj).countDown();
-                return;
-            }
-            setContentView(R.layout.test_container);
-            ViewStub stub = (ViewStub) findViewById(R.id.test_content_stub);
-            mViewWrapper = findViewById(R.id.test_content_wrapper);
-            switch (message.what) {
-                case LAYOUT_MSG: {
-                    stub.setLayoutResource(message.arg1);
-                    mView = stub.inflate();
-                } break;
-
-                case CANVAS_MSG: {
-                    stub.setLayoutResource(R.layout.test_content_canvasclientview);
-                    mView = stub.inflate();
-                    ((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
-                    if (message.arg1 != 0) {
-                        ((CanvasClientView) mView).setUsePicture(true);
-                    }
-                } break;
-            }
-
-            if (mView == null) {
-                throw new IllegalStateException("failed to inflate test content");
-            }
-
-            if (mViewInitializer != null) {
-                mViewInitializer.initializeView(mView);
-            }
-
-            // set layer on wrapper parent of view, so view initializer
-            // can control layer type of View under test.
-            mViewWrapper.setLayerType(message.arg2, null);
-
-            notifyOnDrawCompleted();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        if (mViewInitializer != null) {
-            mViewInitializer.teardownView();
-        }
-    }
-
-    @Override
-    public void finish() {
-        // Ignore
-    }
-
-    /** Call this when all the tests that use this activity have completed.
-     * This will then clean up any internal state and finish the activity. */
-    public void allTestsFinished() {
-        super.finish();
-    }
-
-    private class DrawCounterListener implements ViewTreeObserver.OnDrawListener {
-        private static final int DEBUG_REQUIRE_EXTRA_FRAMES = 1;
-        private int mDrawCount = 0;
-
-        @Override
-        public void onDraw() {
-            if (++mDrawCount <= DEBUG_REQUIRE_EXTRA_FRAMES) {
-                mView.postInvalidate();
-                return;
-            }
-
-            long vsyncMillis = mView.getDrawingTime();
-
-            mView.post(() -> mView.getViewTreeObserver().removeOnDrawListener(this));
-
-            mDrawMonitor.notifyWhenDrawn(vsyncMillis, () -> {
-                final int[] location = new int[2];
-                mViewWrapper.getLocationInWindow(location);
-                Point surfaceOffset = new Point(location[0], location[1]);
-                mViewWrapper.getLocationOnScreen(location);
-                Point screenOffset = new Point(location[0], location[1]);
-                synchronized (mLock) {
-                    mPositionInfo = new ActivityTestBase.TestPositionInfo(
-                            surfaceOffset, screenOffset);
-                    mLock.notify();
-                }
-            });
-        }
-    }
-
-    private static class DrawMonitor {
-
-        private ArrayList<Pair<Long, Runnable>> mListeners = new ArrayList<>();
-
-        private DrawMonitor(Window window) {
-            window.addOnFrameMetricsAvailableListener(this::onFrameMetricsAvailable,
-                    new Handler());
-        }
-
-        private void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
-                /* This isn't actually unused as it's necessary for the method handle */
-                @SuppressWarnings("unused") int dropCountSinceLastInvocation) {
-            ArrayList<Runnable> toInvoke = null;
-            synchronized (mListeners) {
-                if (mListeners.size() == 0) {
-                    return;
-                }
-
-                long vsyncAtMillis = TimeUnit.NANOSECONDS.convert(frameMetrics
-                        .getMetric(FrameMetrics.VSYNC_TIMESTAMP), TimeUnit.MILLISECONDS);
-                boolean isUiThreadDraw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION) > 0;
-
-                Iterator<Pair<Long, Runnable>> iter = mListeners.iterator();
-                while (iter.hasNext()) {
-                    Pair<Long, Runnable> listener = iter.next();
-                    if ((listener.first == vsyncAtMillis && isUiThreadDraw)
-                            || (listener.first < vsyncAtMillis)) {
-                        if (toInvoke == null) {
-                            toInvoke = new ArrayList<>();
-                        }
-                        Log.d("UiRendering", "adding listener for vsync " + listener.first
-                                + "; got vsync timestamp: " + vsyncAtMillis
-                                + " with isUiThreadDraw " + isUiThreadDraw);
-                        toInvoke.add(listener.second);
-                        iter.remove();
-                    } else if (listener.first == vsyncAtMillis && !isUiThreadDraw) {
-                        Log.d("UiRendering",
-                                "Ignoring draw that's not from the UI thread at vsync: "
-                                        + vsyncAtMillis);
-                    }
-                }
-            }
-
-            if (toInvoke != null && toInvoke.size() > 0) {
-                for (Runnable run : toInvoke) {
-                    run.run();
-                }
-            }
-
-        }
-
-        public void notifyWhenDrawn(long contentVsyncMillis, Runnable runWhenDrawn) {
-            synchronized (mListeners) {
-                mListeners.add(new Pair<>(contentVsyncMillis, runWhenDrawn));
-            }
-        }
-    }
-}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
new file mode 100644
index 0000000..8176ba9
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.uirendering.cts.testinfrastructure
+
+import android.app.Activity
+import android.app.UiModeManager
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.graphics.Point
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.uirendering.cts.R
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.Nullable
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+class Offset(val dx: Float, val dy: Float) {
+    override fun equals(other: Any?): Boolean {
+        return this === other || (other as? Offset)?.let {
+            dx == other.dx && dy == other.dy
+        } ?: false
+    }
+
+    override fun hashCode(): Int = dx.hashCode() xor dy.hashCode()
+}
+
+/**
+ * A generic activity that uses a view specified by the user.
+ */
+class DrawActivity : Activity() {
+    companion object {
+        internal const val EXTRA_WIDE_COLOR_GAMUT = "DrawActivity.WIDE_COLOR_GAMUT"
+        internal const val EXTRA_USE_FORCE_DARK = "DrawActivity.USE_FORCE_DARK"
+
+        private const val TIME_OUT_MS: Long = 10000
+
+        private const val LAYOUT_MSG = 1
+        private const val CANVAS_MSG = 2
+    }
+
+    private val mLock = java.lang.Object()
+    private var mPositionInfo: ActivityTestBase.TestPositionInfo? = null
+
+    private lateinit var mHandler: Handler
+    private lateinit var mTestContainer: ViewGroup
+
+    private var mView: View? = null
+
+    private var mViewInitializer: ViewInitializer? = null
+
+    public override fun onCreate(bundle: Bundle?) {
+        super.onCreate(bundle)
+
+        if (intent.getBooleanExtra(EXTRA_USE_FORCE_DARK, false)) {
+            forceUiMode(Configuration.UI_MODE_NIGHT_YES)
+            setTheme(R.style.AutoDarkTheme)
+        } else {
+            forceUiMode(Configuration.UI_MODE_NIGHT_NO)
+        }
+
+        if (intent.getBooleanExtra(EXTRA_WIDE_COLOR_GAMUT, false)) {
+            window.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+        }
+
+        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_FULLSCREEN
+        mHandler = RenderSpecHandler()
+
+        setContentView(R.layout.test_container)
+        mTestContainer = findViewById(R.id.test_content_wrapper)
+    }
+
+    private fun forceUiMode(mode: Int) {
+        if (resources.configuration.uiMode != mode) {
+            val newConfig = Configuration(resources.configuration)
+            newConfig.uiMode = mode
+            resources.updateConfiguration(newConfig, resources.displayMetrics)
+            onConfigurationChanged(newConfig)
+        }
+    }
+
+    fun enqueueRenderSpecAndWait(
+        layoutId: Int,
+        canvasClient: CanvasClient?, @Nullable viewInitializer: ViewInitializer?,
+        useHardware: Boolean, usePicture: Boolean
+    ): ActivityTestBase.TestPositionInfo {
+        (mHandler as RenderSpecHandler).setViewInitializer(viewInitializer)
+        val arg2 = if (useHardware) View.LAYER_TYPE_NONE else View.LAYER_TYPE_SOFTWARE
+        synchronized(mLock) {
+            if (canvasClient != null) {
+                mHandler.obtainMessage(CANVAS_MSG, if (usePicture) 1 else 0,
+                    arg2, canvasClient
+                ).sendToTarget()
+            } else {
+                mHandler.obtainMessage(LAYOUT_MSG, layoutId, arg2)
+                    .sendToTarget()
+            }
+
+            try {
+                mLock.wait(TIME_OUT_MS)
+            } catch (e: InterruptedException) {
+                throw AssertionError(e)
+            }
+
+        }
+        assertNotNull("Timeout waiting for draw", mPositionInfo)
+        return mPositionInfo!!
+    }
+
+    fun waitForRedraw() {
+        synchronized(mLock) {
+            mHandler.post {
+                mTestContainer.invalidate()
+                notifyOnDrawCompleted()
+            }
+            try {
+                mLock.wait(TIME_OUT_MS)
+            } catch (e: InterruptedException) {
+                throw AssertionError(e)
+            }
+        }
+    }
+
+    private fun notifyOnDrawCompleted() {
+        mTestContainer.viewTreeObserver.registerFrameCommitCallback {
+            val location = IntArray(2)
+            mTestContainer.getLocationInWindow(location)
+            val surfaceOffset = Point(location[0], location[1])
+            mTestContainer.getLocationOnScreen(location)
+            val screenOffset = Point(location[0], location[1])
+            synchronized(mLock) {
+                mPositionInfo = ActivityTestBase.TestPositionInfo(
+                    surfaceOffset, screenOffset
+                )
+                mLock.notify()
+            }
+        }
+    }
+
+    fun reset() {
+        val fence = CountDownLatch(1)
+        mHandler.post {
+            mViewInitializer?.teardownView()
+            mViewInitializer = null
+            mView = null
+            mTestContainer.removeAllViews()
+            fence.countDown()
+        }
+        assertTrue(fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS))
+    }
+
+    private inner class RenderSpecHandler : Handler() {
+
+        fun setViewInitializer(viewInitializer: ViewInitializer?) {
+            mViewInitializer = viewInitializer
+        }
+
+        override fun handleMessage(message: Message) {
+            mTestContainer.removeAllViews()
+            when (message.what) {
+                LAYOUT_MSG -> {
+                    mView = LayoutInflater.from(this@DrawActivity).inflate(
+                        message.arg1, mTestContainer, false
+                    )
+                }
+
+                CANVAS_MSG -> {
+                    val canvasClientView = CanvasClientView(this@DrawActivity)
+                    canvasClientView.setCanvasClient(message.obj as CanvasClient)
+                    if (message.arg1 != 0) {
+                        canvasClientView.setUsePicture(true)
+                    }
+                    mView = canvasClientView
+                }
+            }
+
+            if (mView == null) {
+                throw IllegalStateException("failed to inflate test content")
+            }
+
+            mTestContainer.addView(
+                mView, ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+
+            if (mViewInitializer != null) {
+                mViewInitializer!!.initializeView(mView)
+            }
+
+            // set layer on wrapper parent of view, so view initializer
+            // can control layer type of View under test.
+            mTestContainer.setLayerType(message.arg2, null)
+
+            notifyOnDrawCompleted()
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        mViewInitializer?.run {
+            teardownView()
+        }
+    }
+
+    override fun finish() {
+        // Ignore
+    }
+
+    /** Call this when all the tests that use this activity have completed.
+     * This will then clean up any internal state and finish the activity.  */
+    fun allTestsFinished() {
+        super.finish()
+    }
+}
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
index f8ff037..0c23468 100644
--- a/tests/tests/util/src/android/util/cts/ArraySetTest.java
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -36,6 +36,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.function.Predicate;
 
 // As is the case with ArraySet itself, ArraySetTest borrows heavily from ArrayMapTest.
 
@@ -214,6 +215,18 @@
         }
     }
 
+    /** Confirms that all the invalid indices [mSize, internalArray.length) are null. */
+    private static <E> void confirmNullOutOfBounds(ArraySet<E> array) {
+        for (int i = array.size(); ; ++i) {
+            try {
+                assertNull(array.valueAtUnchecked(i));
+            } catch (ArrayIndexOutOfBoundsException e) {
+                // Finally reached the end of the internal array.
+                break;
+            }
+        }
+    }
+
     @Test
     public void testTest() {
         assertEquals("OPS and KEYS must be equal length", OPS.length, KEYS.length);
@@ -359,6 +372,8 @@
         assertEquals(indexToDelete * 10, set.removeAt(indexToDelete).intValue());
         assertEquals(9, set.size());
 
+        confirmNullOutOfBounds(set);
+
         for (int i = 0; i < 9; ++i) {
             int expectedValue = ((i >= indexToDelete) ? (i + 1) : i) * 10;
             assertEquals(expectedValue, set.valueAt(i).intValue());
@@ -366,10 +381,12 @@
 
         for (int i = 9; i > 0; --i) {
             set.removeAt(0);
+            confirmNullOutOfBounds(set);
             assertEquals(i - 1, set.size());
         }
 
         assertTrue(set.isEmpty());
+        confirmNullOutOfBounds(set);
 
         try {
             set.removeAt(0);
@@ -380,6 +397,21 @@
     }
 
     @Test
+    public void testValueAt_OutOfBounds() {
+        ArraySet<Integer> set = new ArraySet<>();
+
+        for (int i = 0; i < 10; ++i) {
+            try {
+                set.valueAt(i);
+                fail("Expected ArrayIndexOutOfBoundsException");
+            } catch (ArrayIndexOutOfBoundsException expected) {
+                // expected
+            }
+            set.add(i);
+        }
+    }
+
+    @Test
     public void testIndexOf() {
         ArraySet<Integer> set = new ArraySet<>();
 
@@ -462,6 +494,58 @@
     }
 
     @Test
+    public void testRemoveIf() {
+        ArraySet<Integer> set = new ArraySet<>();
+
+        for (int i = 0; i < 10; ++i) {
+            // Array starts with a value it should discard.
+            set.add(i * 10);
+            // Make sure there are alternating keep/discard elements.
+            set.add(i);
+            set.add(i);
+            set.add(i * -10);
+            // Array ends with a value it should keep.
+            set.add(i * 100);
+        }
+
+        Predicate<Integer> predicate = (i) -> {
+            return i < 50;
+        };
+        set.removeIf(predicate);
+
+        // Kept values: 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900
+        assertEquals(14, set.size());
+        for (int i = 0; i < set.size(); ++i) {
+            Integer val = set.valueAt(i);
+            assertTrue("Value " + val + " should have been removed.", val >= 50);
+            assertEquals(i, set.indexOf(val));
+        }
+        confirmNullOutOfBounds(set);
+
+        ArraySet<Integer> setDecreasing = new ArraySet<>();
+        for (int i = 10; i >= 0; --i) {
+            // Array starts with a value it should keep.
+            setDecreasing.add(i * 100);
+            setDecreasing.add(i * 10);
+            setDecreasing.add(i);
+            setDecreasing.add(i);
+            // Array ends with a value it should discard.
+            setDecreasing.add(i * -10);
+        }
+
+        setDecreasing.removeIf(predicate);
+
+        // Kept values: 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90, 80, 70, 60, 50
+        assertEquals(15, setDecreasing.size());
+        for (int i = 0; i < setDecreasing.size(); ++i) {
+            Integer val = setDecreasing.valueAt(i);
+            assertTrue("Value " + val + " should have been removed.", val >= 50);
+            assertEquals(i, setDecreasing.indexOf(val));
+        }
+        confirmNullOutOfBounds(set);
+    }
+
+    @Test
     public void testRetainAll() {
         ArraySet<Integer> arraySet = new ArraySet<>();
         ArrayList<Integer> arrayListToRetain = new ArrayList<>();
diff --git a/tests/tests/util/src/android/util/cts/TypedValueTest.java b/tests/tests/util/src/android/util/cts/TypedValueTest.java
index 9c454e4..fb036b4 100644
--- a/tests/tests/util/src/android/util/cts/TypedValueTest.java
+++ b/tests/tests/util/src/android/util/cts/TypedValueTest.java
@@ -17,8 +17,10 @@
 package android.util.cts;
 
 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 android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -245,4 +247,26 @@
         tv.data = 21474865;
         assertEquals(TypedValue.COMPLEX_UNIT_FRACTION_PARENT, tv.getComplexUnit());
     }
+
+    @Test
+    public void testIsColorType() {
+        TypedValue tv = new TypedValue();
+        tv.type = TypedValue.TYPE_INT_COLOR_ARGB8;
+        assertTrue(tv.isColorType());
+        tv.type = TypedValue.TYPE_INT_COLOR_RGB8;
+        assertTrue(tv.isColorType());
+        tv.type = TypedValue.TYPE_INT_COLOR_ARGB4;
+        assertTrue(tv.isColorType());
+        tv.type = TypedValue.TYPE_INT_COLOR_RGB4;
+        assertTrue(tv.isColorType());
+
+        tv.type = TypedValue.TYPE_INT_HEX;
+        assertFalse(tv.isColorType());
+        tv.type = TypedValue.TYPE_FLOAT;
+        assertFalse(tv.isColorType());
+        tv.type = TypedValue.TYPE_ATTRIBUTE;
+        assertFalse(tv.isColorType());
+        tv.type = TypedValue.TYPE_NULL;
+        assertFalse(tv.isColorType());
+    }
 }
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 6174a73..fb7b019 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -35,14 +35,16 @@
     ctstestrunner \
     mockito-target-minus-junit4 \
     platform-test-annotations \
-    ub-uiautomator
+    ub-uiautomator \
+    truth-prebuilt
+
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsViewTestCases
-LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_SDK_VERSION := test_current
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 4217959..dabbc1a 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -136,6 +136,7 @@
         </activity>
 
         <activity android:name="android.view.cts.TextureViewCtsActivity"
+                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
                   android:screenOrientation="locked"
                   android:label="TextureViewCtsActivity">
             <intent-filter>
@@ -358,6 +359,12 @@
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
+
+        <activity android:name="android.view.cts.ViewSourceLayoutTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/AndroidTest.xml b/tests/tests/view/AndroidTest.xml
index ac1a731..27e8c1e 100644
--- a/tests/tests/view/AndroidTest.xml
+++ b/tests/tests/view/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS View test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsViewTestCases.apk" />
diff --git a/tests/tests/view/jni/Android.mk b/tests/tests/view/jni/Android.mk
index f1aad68..85c073b 100644
--- a/tests/tests/view/jni/Android.mk
+++ b/tests/tests/view/jni/Android.mk
@@ -24,12 +24,15 @@
 
 LOCAL_SRC_FILES := \
 		CtsViewJniOnLoad.cpp \
+		android_view_cts_ASurfaceControlTest.cpp \
 		android_view_cts_ChoreographerNativeTest.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
-LOCAL_SHARED_LIBRARIES := libandroid libnativehelper_compat_libc++ liblog
+LOCAL_SHARED_LIBRARIES := libandroid libnativehelper_compat_libc++ liblog libsync
 
-LOCAL_CXX_STL := libc++_static
+LOCAL_NDK_STL_VARIANT := c++_static
+
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/view/jni/CtsViewJniOnLoad.cpp b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
index 1a7ef3c..a6f50ca 100644
--- a/tests/tests/view/jni/CtsViewJniOnLoad.cpp
+++ b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
@@ -17,8 +17,8 @@
 #include <jni.h>
 
 #define LOG_TAG "CtsViewJniOnLoad"
-#include <utils/Log.h>
 
+extern int register_android_view_cts_ASurfaceControlTest(JNIEnv *);
 extern int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env);
 
 jint JNI_OnLoad(JavaVM *vm, void *) {
@@ -26,6 +26,9 @@
     if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
         return JNI_ERR;
     }
+    if (register_android_view_cts_ASurfaceControlTest(env)) {
+      return JNI_ERR;
+    }
     if (register_android_view_cts_ChoreographerNativeTest(env)) {
         return JNI_ERR;
     }
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
new file mode 100644
index 0000000..dd6a186
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -0,0 +1,461 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ASurfaceControlTest"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <array>
+#include <cinttypes>
+#include <string>
+
+#include <android/hardware_buffer.h>
+#include <android/log.h>
+#include <android/native_window_jni.h>
+#include <android/surface_control.h>
+#include <android/sync.h>
+
+#include <jni.h>
+
+namespace {
+
+// Raises a java exception
+static void fail(JNIEnv* env, const char* format, ...) {
+    va_list args;
+
+    va_start(args, format);
+    char* msg;
+    vasprintf(&msg, format, args);
+    va_end(args);
+
+    jclass exClass;
+    const char* className = "java/lang/AssertionError";
+    exClass = env->FindClass(className);
+    env->ThrowNew(exClass, msg);
+    free(msg);
+}
+
+#define ASSERT(condition, format, args...) \
+    if (!(condition)) {                    \
+        fail(env, format, ##args);         \
+        return;                            \
+    }
+
+static AHardwareBuffer* allocateBuffer(int32_t width, int32_t height) {
+    AHardwareBuffer* buffer = nullptr;
+    AHardwareBuffer_Desc desc = {};
+    desc.width = width;
+    desc.height = height;
+    desc.layers = 1;
+    desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+    desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+
+    AHardwareBuffer_allocate(&desc, &buffer);
+
+    return buffer;
+}
+
+static void fillRegion(void* data, int32_t left, int32_t top, int32_t right,
+                       int32_t bottom, uint32_t color, uint32_t stride) {
+    uint32_t* ptr = static_cast<uint32_t*>(data);
+
+    ptr += stride * top;
+
+    for (uint32_t y = top; y < bottom; y++) {
+        for (uint32_t x = left; x < right; x++) {
+            ptr[x] = color;
+        }
+        ptr += stride;
+    }
+}
+
+static bool getSolidBuffer(int32_t width, int32_t height, uint32_t color,
+                           AHardwareBuffer** outHardwareBuffer,
+                           int* outFence) {
+    AHardwareBuffer* buffer = allocateBuffer(width, height);
+    if (!buffer) {
+        return true;
+    }
+
+    AHardwareBuffer_Desc desc = {};
+    AHardwareBuffer_describe(buffer, &desc);
+
+    void* data = nullptr;
+    const ARect rect{0, 0, width, height};
+    AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect,
+                                             &data);
+    if (!data) {
+        return true;
+    }
+
+    fillRegion(data, 0, 0, width, height, color, desc.stride);
+
+    AHardwareBuffer_unlock(buffer, outFence);
+
+    *outHardwareBuffer = buffer;
+    return false;
+}
+
+static bool getQuadrantBuffer(int32_t width, int32_t height, jint colorTopLeft,
+                              jint colorTopRight, jint colorBottomRight,
+                              jint colorBottomLeft,
+                              AHardwareBuffer** outHardwareBuffer,
+                              int* outFence) {
+    AHardwareBuffer* buffer = allocateBuffer(width, height);
+    if (!buffer) {
+        return true;
+    }
+
+    AHardwareBuffer_Desc desc = {};
+    AHardwareBuffer_describe(buffer, &desc);
+
+    void* data = nullptr;
+    const ARect rect{0, 0, width, height};
+    AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect,
+                                             &data);
+    if (!data) {
+        return true;
+    }
+
+    fillRegion(data, 0, 0, width / 2, height / 2, colorTopLeft, desc.stride);
+    fillRegion(data, width / 2, 0, width, height / 2, colorTopRight, desc.stride);
+    fillRegion(data, 0, height / 2, width / 2, height, colorBottomLeft,
+                         desc.stride);
+    fillRegion(data, width / 2, height / 2, width, height, colorBottomRight,
+                         desc.stride);
+
+    AHardwareBuffer_unlock(buffer, outFence);
+
+    *outHardwareBuffer = buffer;
+    return false;
+}
+
+jlong SurfaceTransaction_create(JNIEnv* /*env*/, jclass) {
+    return reinterpret_cast<jlong>(ASurfaceTransaction_create());
+}
+
+void SurfaceTransaction_delete(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) {
+    ASurfaceTransaction_delete(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction));
+}
+
+void SurfaceTransaction_apply(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) {
+    ASurfaceTransaction_apply(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction));
+}
+
+long SurfaceControl_createFromWindow(JNIEnv* env, jclass, jobject jSurface) {
+    if (!jSurface) {
+        return 0;
+    }
+
+    ANativeWindow* window = ANativeWindow_fromSurface(env, jSurface);
+    if (!window) {
+        return 0;
+    }
+
+    const std::string debugName = "SurfaceControl_createFromWindowLayer";
+    ASurfaceControl* surfaceControl =
+            ASurfaceControl_createFromWindow(window, debugName.c_str());
+    if (!surfaceControl) {
+        return 0;
+    }
+
+    ANativeWindow_release(window);
+
+    return reinterpret_cast<jlong>(surfaceControl);
+}
+
+jlong SurfaceControl_create(JNIEnv* /*env*/, jclass, jlong parentSurfaceControlId) {
+    ASurfaceControl* surfaceControl = nullptr;
+    const std::string debugName = "SurfaceControl_create";
+
+    surfaceControl = ASurfaceControl_create(
+            reinterpret_cast<ASurfaceControl*>(parentSurfaceControlId),
+            debugName.c_str());
+
+    return reinterpret_cast<jlong>(surfaceControl);
+}
+
+void SurfaceControl_release(JNIEnv* /*env*/, jclass, jlong surfaceControl) {
+    ASurfaceControl_release(reinterpret_cast<ASurfaceControl*>(surfaceControl));
+}
+
+jlong SurfaceTransaction_setSolidBuffer(JNIEnv* /*env*/, jclass,
+                                        jlong surfaceControl,
+                                        jlong surfaceTransaction, jint width,
+                                        jint height, jint color) {
+    AHardwareBuffer* buffer = nullptr;
+    int fence = -1;
+
+    bool err = getSolidBuffer(width, height, color, &buffer, &fence);
+    if (err) {
+        return 0;
+    }
+
+    ASurfaceTransaction_setBuffer(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), buffer, fence);
+
+    return reinterpret_cast<jlong>(buffer);
+}
+
+jlong SurfaceTransaction_setQuadrantBuffer(
+        JNIEnv* /*env*/, jclass, jlong surfaceControl, jlong surfaceTransaction,
+        jint width, jint height, jint colorTopLeft, jint colorTopRight,
+        jint colorBottomRight, jint colorBottomLeft) {
+    AHardwareBuffer* buffer = nullptr;
+    int fence = -1;
+
+    bool err =
+            getQuadrantBuffer(width, height, colorTopLeft, colorTopRight,
+                              colorBottomRight, colorBottomLeft, &buffer, &fence);
+    if (err) {
+        return 0;
+    }
+
+    ASurfaceTransaction_setBuffer(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), buffer, fence);
+
+    return reinterpret_cast<jlong>(buffer);
+}
+
+void SurfaceTransaction_releaseBuffer(JNIEnv* /*env*/, jclass, jlong buffer) {
+    AHardwareBuffer_release(reinterpret_cast<AHardwareBuffer*>(buffer));
+}
+
+void SurfaceTransaction_setVisibility(JNIEnv* /*env*/, jclass,
+                                      jlong surfaceControl,
+                                      jlong surfaceTransaction, jboolean show) {
+    int8_t visibility = (show) ? ASURFACE_TRANSACTION_VISIBILITY_SHOW :
+                                 ASURFACE_TRANSACTION_VISIBILITY_HIDE;
+    ASurfaceTransaction_setVisibility(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), visibility);
+}
+
+void SurfaceTransaction_setBufferOpaque(JNIEnv* /*env*/, jclass,
+                                        jlong surfaceControl,
+                                        jlong surfaceTransaction,
+                                        jboolean opaque) {
+    int8_t transparency = (opaque) ? ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE :
+                                   ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT;
+    ASurfaceTransaction_setBufferTransparency(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), transparency);
+}
+
+void SurfaceTransaction_setGeometry(JNIEnv* /*env*/, jclass,
+                                    jlong surfaceControl,
+                                    jlong surfaceTransaction,
+                                    jint srcLeft, jint srcTop, jint srcRight, jint srcBottom,
+                                    jint dstLeft, jint dstTop, jint dstRight, jint dstBottom,
+                                    jint transform) {
+    const ARect src{srcLeft, srcTop, srcRight, srcBottom};
+    const ARect dst{dstLeft, dstTop, dstRight, dstBottom};
+    ASurfaceTransaction_setGeometry(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), src, dst, transform);
+}
+
+void SurfaceTransaction_setDamageRegion(JNIEnv* /*env*/, jclass,
+                                        jlong surfaceControl,
+                                        jlong surfaceTransaction, jint left,
+                                        jint top, jint right, jint bottom) {
+    const ARect rect[] = {{left, top, right, bottom}};
+    ASurfaceTransaction_setDamageRegion(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), rect, 1);
+}
+
+void SurfaceTransaction_setZOrder(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                  jlong surfaceTransaction, jint z) {
+    ASurfaceTransaction_setZOrder(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), z);
+}
+
+static void onComplete(void* context, ASurfaceTransactionStats* stats) {
+    if (!stats) {
+        return;
+    }
+
+    int64_t latchTime = ASurfaceTransactionStats_getLatchTime(stats);
+    if (latchTime < 0) {
+        return;
+    }
+
+    ASurfaceControl** surfaceControls = nullptr;
+    size_t surfaceControlsSize = 0;
+    ASurfaceTransactionStats_getASurfaceControls(stats, &surfaceControls, &surfaceControlsSize);
+
+    for (int i = 0; i < surfaceControlsSize; i++) {
+        ASurfaceControl* surfaceControl = surfaceControls[i];
+
+        int64_t acquireTime = ASurfaceTransactionStats_getAcquireTime(stats, surfaceControl);
+        if (acquireTime < -1) {
+            return;
+        }
+
+        int previousReleaseFence = ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+                    stats, surfaceControl);
+        close(previousReleaseFence);
+    }
+
+    int presentFence = ASurfaceTransactionStats_getPresentFenceFd(stats);
+
+    if (!context) {
+        close(presentFence);
+        return;
+    }
+
+    int* contextIntPtr = reinterpret_cast<int*>(context);
+    contextIntPtr[0]++;
+    contextIntPtr[1] = presentFence;
+}
+
+jlong SurfaceTransaction_setOnComplete(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) {
+    int* context = new int[2];
+    context[0] = 0;
+    context[1] = -1;
+
+    ASurfaceTransaction_setOnComplete(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<void*>(context), onComplete);
+    return reinterpret_cast<jlong>(context);
+}
+
+void SurfaceTransaction_checkOnComplete(JNIEnv* env, jclass, jlong context,
+                                        jlong desiredPresentTime) {
+    ASSERT(context != 0, "invalid context")
+
+    int* contextPtr = reinterpret_cast<int*>(context);
+    int data = contextPtr[0];
+    int presentFence = contextPtr[1];
+
+    delete[] contextPtr;
+
+    if (desiredPresentTime < 0) {
+        close(presentFence);
+        ASSERT(data >= 1, "did not receive a callback")
+        ASSERT(data <= 1, "received too many callbacks")
+        return;
+    }
+
+    struct sync_file_info* syncFileInfo = sync_file_info(presentFence);
+    ASSERT(syncFileInfo, "invalid fence")
+
+    if (syncFileInfo->status != 1) {
+        sync_file_info_free(syncFileInfo);
+        ASSERT(syncFileInfo->status == 1, "fence did not signal")
+    }
+
+    uint64_t presentTime = 0;
+    struct sync_fence_info* syncFenceInfo = sync_get_fence_info(syncFileInfo);
+    for (size_t i = 0; i < syncFileInfo->num_fences; i++) {
+        if (syncFenceInfo[i].timestamp_ns > presentTime) {
+            presentTime = syncFenceInfo[i].timestamp_ns;
+        }
+    }
+
+    sync_file_info_free(syncFileInfo);
+    close(presentFence);
+
+    // In the worst case the worst present time should be no more than three frames off from the
+    // desired present time. Since the test case is using a virtual display and there are no
+    // requirements for virtual display refresh rate timing, lets assume a refresh rate of 16fps.
+    ASSERT(presentTime < desiredPresentTime + 0.188 * 1e9, "transaction was presented too late");
+    ASSERT(presentTime >= desiredPresentTime, "transaction was presented too early");
+
+    ASSERT(data >= 1, "did not receive a callback")
+    ASSERT(data <= 1, "received too many callbacks")
+}
+
+jlong SurfaceTransaction_setDesiredPresentTime(JNIEnv* /*env*/, jclass, jlong surfaceTransaction,
+                                              jlong desiredPresentTimeOffset) {
+    struct timespec t;
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    int64_t currentTime = ((int64_t) t.tv_sec)*1000000000LL + t.tv_nsec;
+
+    int64_t desiredPresentTime = currentTime + desiredPresentTimeOffset;
+
+    ASurfaceTransaction_setDesiredPresentTime(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction), desiredPresentTime);
+
+    return desiredPresentTime;
+}
+
+void SurfaceTransaction_setBufferAlpha(JNIEnv* /*env*/, jclass,
+                                      jlong surfaceControl,
+                                      jlong surfaceTransaction, jdouble alpha) {
+    ASurfaceTransaction_setBufferAlpha(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl), alpha);
+}
+
+void SurfaceTransaction_reparent(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                 jlong newParentSurfaceControl, jlong surfaceTransaction) {
+    ASurfaceTransaction_reparent(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl),
+            reinterpret_cast<ASurfaceControl*>(newParentSurfaceControl));
+}
+
+void SurfaceTransaction_setColor(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                 jlong surfaceTransaction, jfloat r,
+                                 jfloat g, jfloat b, jfloat alpha) {
+    ASurfaceTransaction_setColor(
+            reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+            reinterpret_cast<ASurfaceControl*>(surfaceControl),
+            r, g, b, alpha, ADATASPACE_UNKNOWN);
+}
+
+const std::array<JNINativeMethod, 20> JNI_METHODS = {{
+    {"nSurfaceTransaction_create", "()J", (void*)SurfaceTransaction_create},
+    {"nSurfaceTransaction_delete", "(J)V", (void*)SurfaceTransaction_delete},
+    {"nSurfaceTransaction_apply", "(J)V", (void*)SurfaceTransaction_apply},
+    {"nSurfaceControl_createFromWindow", "(Landroid/view/Surface;)J",
+                                            (void*)SurfaceControl_createFromWindow},
+    {"nSurfaceControl_create", "(J)J", (void*)SurfaceControl_create},
+    {"nSurfaceControl_release", "(J)V", (void*)SurfaceControl_release},
+    {"nSurfaceTransaction_setSolidBuffer", "(JJIII)J", (void*)SurfaceTransaction_setSolidBuffer},
+    {"nSurfaceTransaction_setQuadrantBuffer", "(JJIIIIII)J",
+                                            (void*)SurfaceTransaction_setQuadrantBuffer},
+    {"nSurfaceTransaction_releaseBuffer", "(J)V", (void*)SurfaceTransaction_releaseBuffer},
+    {"nSurfaceTransaction_setVisibility", "(JJZ)V", (void*)SurfaceTransaction_setVisibility},
+    {"nSurfaceTransaction_setBufferOpaque", "(JJZ)V", (void*)SurfaceTransaction_setBufferOpaque},
+    {"nSurfaceTransaction_setGeometry", "(JJIIIIIIIII)V", (void*)SurfaceTransaction_setGeometry},
+    {"nSurfaceTransaction_setDamageRegion", "(JJIIII)V", (void*)SurfaceTransaction_setDamageRegion},
+    {"nSurfaceTransaction_setZOrder", "(JJI)V", (void*)SurfaceTransaction_setZOrder},
+    {"nSurfaceTransaction_setOnComplete", "(J)J", (void*)SurfaceTransaction_setOnComplete},
+    {"nSurfaceTransaction_checkOnComplete", "(JJ)V", (void*)SurfaceTransaction_checkOnComplete},
+    {"nSurfaceTransaction_setDesiredPresentTime", "(JJ)J",
+                                            (void*)SurfaceTransaction_setDesiredPresentTime},
+    {"nSurfaceTransaction_setBufferAlpha", "(JJD)V", (void*)SurfaceTransaction_setBufferAlpha},
+    {"nSurfaceTransaction_reparent", "(JJJ)V", (void*)SurfaceTransaction_reparent},
+    {"nSurfaceTransaction_setColor", "(JJFFFF)V", (void*)SurfaceTransaction_setColor},
+}};
+
+}  // anonymous namespace
+
+jint register_android_view_cts_ASurfaceControlTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/view/cts/ASurfaceControlTest");
+    return env->RegisterNatives(clazz, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/view/res/layout/view_layout.xml b/tests/tests/view/res/layout/view_layout.xml
index 31d8918..ab0bd04 100644
--- a/tests/tests/view/res/layout/view_layout.xml
+++ b/tests/tests/view/res/layout/view_layout.xml
@@ -202,18 +202,26 @@
     </FrameLayout>
 
     <View
-            android:id="@+id/overlapping_rendering_unset"
-            android:layout_width="10px"
-            android:layout_height="10px" />
+        android:id="@+id/overlapping_rendering_unset"
+        android:layout_width="10px"
+        android:layout_height="10px" />
     <View
-            android:id="@+id/overlapping_rendering_false"
-            android:forceHasOverlappingRendering="false"
-            android:layout_width="10px"
-            android:layout_height="10px" />
+        android:id="@+id/overlapping_rendering_false"
+        android:forceHasOverlappingRendering="false"
+        android:layout_width="10px"
+        android:layout_height="10px" />
     <View
-            android:id="@+id/overlapping_rendering_true"
-            android:forceHasOverlappingRendering="true"
-            android:layout_width="10px"
-            android:layout_height="10px" />
+        android:id="@+id/overlapping_rendering_true"
+        android:forceHasOverlappingRendering="true"
+        android:layout_width="10px"
+        android:layout_height="10px" />
+    <View
+        android:id="@+id/transform_matrix_view"
+        android:layout_width="10px"
+        android:layout_height="10px" />
+    <View
+        android:id="@+id/transform_matrix_view_2"
+        android:layout_width="10px"
+        android:layout_height="10px" />
 
 </LinearLayout>
diff --git a/tests/tests/view/res/layout/view_source_layout_test_include_layout.xml b/tests/tests/view/res/layout/view_source_layout_test_include_layout.xml
new file mode 100644
index 0000000..9421131
--- /dev/null
+++ b/tests/tests/view/res/layout/view_source_layout_test_include_layout.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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <View
+        android:id="@+id/view2"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/view_source_layout_test_layout.xml b/tests/tests/view/res/layout/view_source_layout_test_layout.xml
new file mode 100644
index 0000000..049a4de
--- /dev/null
+++ b/tests/tests/view/res/layout/view_source_layout_test_layout.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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/view_root"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <View
+        android:id="@+id/view1"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <include layout="@layout/view_source_layout_test_include_layout" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/view/sdk28/Android.mk b/tests/tests/view/sdk28/Android.mk
new file mode 100644
index 0000000..7d4f806
--- /dev/null
+++ b/tests/tests/view/sdk28/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests cts_instant
+
+LOCAL_MULTILIB := both
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    compatibility-device-util \
+    ctsdeviceutillegacy \
+    ctstestrunner \
+    mockito-target-minus-junit4 \
+    platform-test-annotations \
+    ub-uiautomator \
+    truth-prebuilt
+
+
+LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsViewTestCasesSdk28
+LOCAL_SDK_VERSION := 28
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/view/sdk28/AndroidManifest.xml b/tests/tests/view/sdk28/AndroidManifest.xml
new file mode 100644
index 0000000..44c2142
--- /dev/null
+++ b/tests/tests/view/sdk28/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.view.cts.sdk28"
+    android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-feature android:name="android.hardware.camera" />
+
+    <application android:label="Android TestCase"
+                android:maxRecents="1"
+                android:multiArch="true"
+                android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+
+
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.view.cts.sdk28"
+                     android:label="CTS tests of android.view">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/view/sdk28/AndroidTest.xml b/tests/tests/view/sdk28/AndroidTest.xml
new file mode 100644
index 0000000..1a64e98
--- /dev/null
+++ b/tests/tests/view/sdk28/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C)9 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 View test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
+        <option name="test-file-name" value="CtsViewTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.view.cts" />
+        <option name="runtime-hint" value="6m47s" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/view/sdk28/src/android/view/cts/sdk28/ViewGroupTest.java b/tests/tests/view/sdk28/src/android/view/cts/sdk28/ViewGroupTest.java
new file mode 100644
index 0000000..be1b197
--- /dev/null
+++ b/tests/tests/view/sdk28/src/android/view/cts/sdk28/ViewGroupTest.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.view.cts.sdk28;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewGroupTest {
+
+    private Context mContext;
+    private MockViewGroup mViewGroup;
+
+    @UiThreadTest
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mViewGroup = new MockViewGroup(mContext);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testFitSystemWindows() {
+        Rect rect = new Rect(1, 1, 100, 100);
+        assertFalse(mViewGroup.fitSystemWindows(rect));
+
+        mViewGroup = new MockViewGroup(mContext);
+        MockView mv = new MockView(mContext);
+        mViewGroup.addView(mv);
+        assertTrue(mViewGroup.fitSystemWindows(rect));
+    }
+
+    private class MockViewGroup extends ViewGroup {
+
+        public MockViewGroup(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        }
+
+        @Override
+        protected boolean fitSystemWindows(Rect insets) {
+            return super.fitSystemWindows(insets);
+        }
+    }
+
+    private class MockView extends View {
+
+        public MockView(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected boolean fitSystemWindows(Rect insets) {
+            return true;
+        }
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/animation/cts/AnimationTest.java b/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
index d3bc8e0..fb9e27d 100644
--- a/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
+++ b/tests/tests/view/src/android/view/animation/cts/AnimationTest.java
@@ -579,7 +579,7 @@
         anim.setRepeatMode(Animation.REVERSE);
 
         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim,
-                3000);
+                3 * ACCELERATE_ALPHA_DURATION);
         verify(listener, times(1)).onAnimationStart(anim);
         verify(listener, times(2)).onAnimationRepeat(anim);
         verify(listener, times(1)).onAnimationEnd(anim);
@@ -599,6 +599,90 @@
     }
 
     @Test
+    public void testAddRemoveAnimationListener() throws Throwable {
+        final View animWindow = mActivity.findViewById(R.id.anim_window);
+
+        // XML file of R.anim.accelerate_alpha
+        // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        //      android:interpolator="@android:anim/accelerate_interpolator"
+        //      android:fromAlpha="0.1"
+        //      android:toAlpha="0.9"
+        //      android:duration="1000" />
+        final Animation anim = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
+        // Speed things up a little
+        anim.setDuration(10);
+
+        final AnimationListener l1 = mock(AnimationListener.class);
+        final AnimationListener l2 = mock(AnimationListener.class);
+        final AnimationListener l3 = mock(AnimationListener.class);
+        final AnimationListener l4 = mock(AnimationListener.class);
+
+        // Test that adding listeners does not remove the set listener
+        anim.setAnimationListener(l1);
+        anim.addAnimationListener(l2);
+        anim.addAnimationListener(l3);
+        verifyZeroInteractions(l1, l2, l3, l4);
+
+        // set: l1
+        // added: l2, l3
+        AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
+        verifyListeners(anim, l1, l2, l3);
+        verifyZeroInteractions(l4);
+
+        // Test that setting a listener does not remove the added listeners
+        reset(l1, l2, l3, l4);
+        anim.setAnimationListener(l4);
+        verifyZeroInteractions(l1, l2, l3, l4);
+
+        // set: l4
+        // added: l2, l3
+        AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
+        verifyListeners(anim, l2, l3, l4);
+        verifyZeroInteractions(l1);
+
+        // Test that removing a listener does not affect the set listener
+        reset(l1, l2, l3, l4);
+        anim.removeAnimationListener(l2);
+        verifyZeroInteractions(l1, l2, l3, l4);
+
+        // set: l4
+        // added: l3
+        AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
+        verifyListeners(anim, l3, l4);
+        verifyZeroInteractions(l1, l2);
+
+        // Test that removing the set listener does not affect the set listener
+        reset(l1, l2, l3, l4);
+        anim.removeAnimationListener(l4);
+        verifyZeroInteractions(l1, l2, l3, l4);
+
+        // set: l4
+        // added: l3
+        AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
+        verifyListeners(anim, l3, l4);
+        verifyZeroInteractions(l1, l2);
+
+        // Test that setting the set listener to null does not remove the added listeners
+        reset(l1, l2, l3, l4);
+        anim.setAnimationListener(null);
+        verifyZeroInteractions(l1, l2, l3, l4);
+
+        // set: null
+        // added: l3
+        AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
+        verifyListeners(anim, l3);
+        verifyZeroInteractions(l1, l2, l4);
+    }
+
+    private void verifyListeners(Animation anim, AnimationListener... listeners) {
+        for (AnimationListener listener : listeners) {
+            verify(listener, times(1)).onAnimationStart(anim);
+            verify(listener, times(1)).onAnimationEnd(anim);
+            verify(listener, never()).onAnimationRepeat(anim);
+        }
+    }
+
+    @Test
     public void testStart() {
         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
         animation.setStartTime(0);
@@ -700,7 +784,7 @@
         // whether it is still animating.
         final View view = mActivity.findViewById(R.id.anim_window);
         mActivityRule.runOnUiThread(() -> {
-            anim.setDuration(delayed ? 150 : 100);
+            anim.setDuration(delayed ? 300 : 200);
             if (repeating) {
                 anim.setRepeatCount(Animation.INFINITE);
             }
@@ -708,12 +792,12 @@
             if (!delayed) {
                 anim.cancel();
             } else {
-                view.postDelayed(anim::cancel, 50);
+                view.postDelayed(anim::cancel, 100);
             }
             view.postDelayed(() -> {
                 anim.setStillAnimating(false);
-                view.postDelayed(latch::countDown, 50);
-            }, delayed ? 100 : 50);
+                view.postDelayed(latch::countDown, 200);
+            }, delayed ? 300 : 200);
         });
     }
 
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
new file mode 100644
index 0000000..7bc8155
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -0,0 +1,1582 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+import android.view.cts.surfacevalidator.AnimationFactory;
+import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.PixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+import android.view.cts.surfacevalidator.SurfaceControlTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ASurfaceControlTest {
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    private static final String TAG = ASurfaceControlTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_LAYOUT_WIDTH = 100;
+    private static final int DEFAULT_LAYOUT_HEIGHT = 100;
+    private static final int DEFAULT_BUFFER_WIDTH = 640;
+    private static final int DEFAULT_BUFFER_HEIGHT = 480;
+
+    @Rule
+    public ActivityTestRule<CapturedActivity> mActivityRule =
+            new ActivityTestRule<>(CapturedActivity.class);
+
+    @Rule
+    public TestName mName = new TestName();
+
+    private CapturedActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    /**
+     * Want to be especially sure we don't leave up the permission dialog, so try and dismiss
+     * after test.
+     */
+    @After
+    public void tearDown() throws UiObjectNotFoundException {
+        mActivity.dismissPermissionDialog();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SurfaceHolder.Callbacks
+    ///////////////////////////////////////////////////////////////////////////
+
+    private abstract class BasicSurfaceHolderCallback implements SurfaceHolder.Callback {
+        private Set<Long> mSurfaceControls = new HashSet<Long>();
+        private Set<Long> mBuffers = new HashSet<Long>();
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            Canvas canvas = holder.lockCanvas();
+            canvas.drawColor(Color.YELLOW);
+            holder.unlockCanvasAndPost(canvas);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            for (Long surfaceControl : mSurfaceControls) {
+                reparent(surfaceControl, 0);
+                nSurfaceControl_release(surfaceControl);
+            }
+            mSurfaceControls.clear();
+
+            for (Long buffer : mBuffers) {
+                nSurfaceTransaction_releaseBuffer(buffer);
+            }
+            mBuffers.clear();
+        }
+
+        public long createSurfaceTransaction() {
+            long surfaceTransaction = nSurfaceTransaction_create();
+            assertTrue("failed to create surface transaction", surfaceTransaction != 0);
+            return surfaceTransaction;
+        }
+
+        public void applySurfaceTransaction(long surfaceTransaction) {
+            nSurfaceTransaction_apply(surfaceTransaction);
+        }
+
+        public void deleteSurfaceTransaction(long surfaceTransaction) {
+            nSurfaceTransaction_delete(surfaceTransaction);
+        }
+
+        public void applyAndDeleteSurfaceTransaction(long surfaceTransaction) {
+            nSurfaceTransaction_apply(surfaceTransaction);
+            nSurfaceTransaction_delete(surfaceTransaction);
+        }
+
+        public long createFromWindow(Surface surface) {
+            long surfaceControl = nSurfaceControl_createFromWindow(surface);
+            assertTrue("failed to create surface control", surfaceControl != 0);
+
+            mSurfaceControls.add(surfaceControl);
+            return surfaceControl;
+        }
+
+        public long create(long parentSurfaceControl) {
+            long childSurfaceControl = nSurfaceControl_create(parentSurfaceControl);
+            assertTrue("failed to create child surface control", childSurfaceControl != 0);
+
+            mSurfaceControls.add(childSurfaceControl);
+            return childSurfaceControl;
+        }
+
+        public void setSolidBuffer(
+                long surfaceControl, long surfaceTransaction, int width, int height, int color) {
+            long buffer = nSurfaceTransaction_setSolidBuffer(
+                    surfaceControl, surfaceTransaction, width, height, color);
+            assertTrue("failed to set buffer", buffer != 0);
+            mBuffers.add(buffer);
+        }
+
+        public void setSolidBuffer(long surfaceControl, int width, int height, int color) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setSolidBuffer(surfaceControl, surfaceTransaction, width, height, color);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setQuadrantBuffer(long surfaceControl, long surfaceTransaction, int width,
+                int height, int colorTopLeft, int colorTopRight, int colorBottomRight,
+                int colorBottomLeft) {
+            long buffer = nSurfaceTransaction_setQuadrantBuffer(surfaceControl, surfaceTransaction,
+                    width, height, colorTopLeft, colorTopRight, colorBottomRight, colorBottomLeft);
+            assertTrue("failed to set buffer", buffer != 0);
+            mBuffers.add(buffer);
+        }
+
+        public void setQuadrantBuffer(long surfaceControl, int width, int height, int colorTopLeft,
+                int colorTopRight, int colorBottomRight, int colorBottomLeft) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setQuadrantBuffer(surfaceControl, surfaceTransaction, width, height, colorTopLeft,
+                    colorTopRight, colorBottomRight, colorBottomLeft);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setVisibility(long surfaceControl, long surfaceTransaction, boolean visible) {
+            nSurfaceTransaction_setVisibility(surfaceControl, surfaceTransaction, visible);
+        }
+
+        public void setVisibility(long surfaceControl, boolean visible) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setVisibility(surfaceControl, surfaceTransaction, visible);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setBufferOpaque(long surfaceControl, long surfaceTransaction, boolean opaque) {
+            nSurfaceTransaction_setBufferOpaque(surfaceControl, surfaceTransaction, opaque);
+        }
+
+        public void setBufferOpaque(long surfaceControl, boolean opaque) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setBufferOpaque(surfaceControl, surfaceTransaction, opaque);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setGeometry(long surfaceControl, long surfaceTransaction, int srcLeft,
+                int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight,
+                int dstBottom, int transform) {
+            nSurfaceTransaction_setGeometry(
+                    surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom,
+                    dstLeft, dstTop, dstRight, dstBottom, transform);
+        }
+
+        public void setGeometry(long surfaceControl, int srcLeft, int srcTop, int srcRight,
+                int srcBottom, int dstLeft, int dstTop, int dstRight, int dstBottom,
+                int transform) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setGeometry(surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom,
+                    dstLeft, dstTop, dstRight, dstBottom, transform);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setDamageRegion(long surfaceControl, long surfaceTransaction, int left, int top,
+                int right, int bottom) {
+            nSurfaceTransaction_setDamageRegion(
+                    surfaceControl, surfaceTransaction, left, top, right, bottom);
+        }
+
+        public void setDamageRegion(long surfaceControl, int left, int top, int right, int bottom) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setDamageRegion(surfaceControl, surfaceTransaction, left, top, right, bottom);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setZOrder(long surfaceControl, long surfaceTransaction, int z) {
+            nSurfaceTransaction_setZOrder(surfaceControl, surfaceTransaction, z);
+        }
+
+        public void setZOrder(long surfaceControl, int z) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setZOrder(surfaceControl, surfaceTransaction, z);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setBufferAlpha(long surfaceControl, long surfaceTransaction, double alpha) {
+            nSurfaceTransaction_setBufferAlpha(surfaceControl, surfaceTransaction, alpha);
+        }
+
+        public void setBufferAlpha(long surfaceControl, double alpha) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setBufferAlpha(surfaceControl, surfaceTransaction, alpha);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void reparent(long surfaceControl, long newParentSurfaceControl,
+                             long surfaceTransaction) {
+            nSurfaceTransaction_reparent(surfaceControl, newParentSurfaceControl,
+                                         surfaceTransaction);
+        }
+
+        public void reparent(long surfaceControl, long newParentSurfaceControl) {
+            long surfaceTransaction = createSurfaceTransaction();
+            reparent(surfaceControl, newParentSurfaceControl, surfaceTransaction);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+
+        public void setColor(long surfaceControl, long surfaceTransaction, float red, float green,
+                float blue, float alpha) {
+            nSurfaceTransaction_setColor(surfaceControl, surfaceTransaction, red, green, blue,
+                    alpha);
+        }
+
+        public void setColor(long surfaceControl, float red, float green, float blue, float alpha) {
+            long surfaceTransaction = createSurfaceTransaction();
+            setColor(surfaceControl, surfaceTransaction, red, green, blue, alpha);
+            applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AnimationFactories
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static ValueAnimator makeInfinite(ValueAnimator a) {
+        a.setRepeatMode(ObjectAnimator.REVERSE);
+        a.setRepeatCount(ObjectAnimator.INFINITE);
+        a.setDuration(200);
+        a.setInterpolator(new LinearInterpolator());
+        return a;
+    }
+
+    private static AnimationFactory sTranslateAnimationFactory = view -> {
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests
+    ///////////////////////////////////////////////////////////////////////////
+
+    private void verifyTest(SurfaceHolder.Callback callback, PixelChecker pixelChecker)
+                throws Throwable {
+        mActivity.verifyTest(new SurfaceControlTestCase(callback, sTranslateAnimationFactory,
+                                                 pixelChecker,
+                                                 DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                                 DEFAULT_BUFFER_WIDTH, DEFAULT_BUFFER_HEIGHT),
+                mName);
+    }
+
+    @Test
+    public void testSurfaceTransaction_create() {
+        mActivity.dismissPermissionDialog();
+
+        long surfaceTransaction = nSurfaceTransaction_create();
+        assertTrue("failed to create surface transaction", surfaceTransaction != 0);
+
+        nSurfaceTransaction_delete(surfaceTransaction);
+    }
+
+    @Test
+    public void testSurfaceTransaction_apply() {
+        mActivity.dismissPermissionDialog();
+
+        long surfaceTransaction = nSurfaceTransaction_create();
+        assertTrue("failed to create surface transaction", surfaceTransaction != 0);
+
+        Log.e("Transaction", "created: " + surfaceTransaction);
+
+        nSurfaceTransaction_apply(surfaceTransaction);
+        nSurfaceTransaction_delete(surfaceTransaction);
+    }
+
+    // INTRO: The following tests run a series of commands and verify the
+    // output based on the number of pixels with a certain color on the display.
+    //
+    // The interface being tested is a NDK api but the only way to record the display
+    // through public apis is in through the SDK. So the test logic and test verification
+    // is in Java but the hooks that call into the NDK api are jni code.
+    //
+    // The set up is done during the surfaceCreated callback. In most cases, the
+    // test uses the opportunity to create a child layer through createFromWindow and
+    // performs operations on the child layer.
+    //
+    // When there is no visible buffer for the layer(s) the color defaults to black.
+    // The test cases allow a +/- 10% error rate. This is based on the error
+    // rate allowed in the SurfaceViewSyncTests
+
+    @Test
+    public void testSurfaceControl_createFromWindow() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceControl_create() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer_parentAndChild() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(parentSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer_childOnly() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setVisibility_show() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setVisibility(surfaceControl, true);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setVisibility_hide() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setVisibility(surfaceControl, false);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferOpaque_opaque() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setBufferOpaque(surfaceControl, true);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferOpaque_transparent() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.TRANSPARENT_RED);
+                        setBufferOpaque(surfaceControl, false);
+                    }
+                },
+                // setBufferOpaque is an optimization that can be used by SurfaceFlinger.
+                // It isn't required to affect SurfaceFlinger's behavior.
+                //
+                // Ideally we would check for a specific blending of red with a layer below
+                // it. Unfortunately we don't know what blending the layer will use and
+                // we don't know what variation the GPU/DPU/blitter might have. Although
+                // we don't know what shade of red might be present, we can at least check
+                // that the optimization doesn't cause the framework to drop the buffer entirely.
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, 0, 0, 640, 480, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_small() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, 64, 48, 320, 240, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //1600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 1440 && pixelCount < 1760;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_childSmall() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        setGeometry(childSurfaceControl, 0, 0, 100, 100, 64, 48, 320, 240, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //1600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 1440 && pixelCount < 1760;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_extraLarge() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, -100, -100, 740, 580, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_childExtraLarge() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        setGeometry(childSurfaceControl, 0, 0, 100, 100, -100, -100, 740, 580, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_negativeOffset() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, -32, -24, 320, 240, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_outOfParentBounds() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, 320, 240, 704, 504, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_twoLayers() throws Throwable {
+        BasicSurfaceHolderCallback callback = new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.BLUE);
+                        setGeometry(surfaceControl1, 0, 0, 100, 100, 64, 48, 192, 192, 0);
+                        setGeometry(surfaceControl2, 0, 0, 100, 100, 448, 96, 576, 240, 0);
+                    }
+                };
+        verifyTest(callback,
+                new PixelChecker(PixelColor.RED) { //600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 540 && pixelCount < 660;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.BLUE) { //600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 540 && pixelCount < 660;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect() throws Throwable {
+        BasicSurfaceHolderCallback callback = new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, 0, 0, 100, 100, 0, 0, 640, 480, 0);
+                    }
+                };
+        verifyTest(callback,
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.BLUE) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.MAGENTA) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.GREEN) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_smallCentered() throws Throwable {
+        BasicSurfaceHolderCallback callback = new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, 10, 10, 90, 90, 0, 0, 640, 480, 0);
+                    }
+                };
+        verifyTest(callback,
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.BLUE) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.MAGENTA) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.GREEN) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_small() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, 60, 10, 90, 90, 0, 0, 640, 480, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.MAGENTA) { //5000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 4500 && pixelCount < 5500;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_extraLarge() throws Throwable {
+        BasicSurfaceHolderCallback callback = new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, -50, -50, 150, 150, 0, 0, 640, 480, 0);
+                    }
+                };
+        verifyTest(callback,
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.BLUE) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.MAGENTA) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.GREEN) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_badOffset() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, -50, -50, 50, 50, 0, 0, 640, 480, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setTransform_flipH() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, 60, 10, 90, 90, 0, 0, 640, 480,
+                                    /*NATIVE_WINDOW_TRANSFORM_FLIP_H*/ 1);
+                    }
+                },
+                new PixelChecker(PixelColor.BLUE) { //5000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 4500 && pixelCount < 5500;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setTransform_rotate180() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setGeometry(surfaceControl, 60, 10, 90, 90, 0, 0, 640, 480,
+                                    /*NATIVE_WINDOW_TRANSFORM_ROT_180*/ 3);
+                    }
+                },
+                new PixelChecker(PixelColor.BLUE) { //5000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 4500 && pixelCount < 5500;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDamageRegion_all() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+
+                        long surfaceTransaction = createSurfaceTransaction();
+                        setSolidBuffer(surfaceControl, surfaceTransaction, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+                        setDamageRegion(surfaceControl, surfaceTransaction, 0, 0, 100, 100);
+                        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+                    }
+                },
+                new PixelChecker(PixelColor.BLUE) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_zero() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        setZOrder(surfaceControl1, 1);
+                        setZOrder(surfaceControl2, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_positive() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        setZOrder(surfaceControl1, 1);
+                        setZOrder(surfaceControl2, 5);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_negative() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        setZOrder(surfaceControl1, 1);
+                        setZOrder(surfaceControl2, -15);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_max() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        setZOrder(surfaceControl1, 1);
+                        setZOrder(surfaceControl2, Integer.MAX_VALUE);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_min() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl1 = createFromWindow(holder.getSurface());
+                        long surfaceControl2 = createFromWindow(holder.getSurface());
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        setZOrder(surfaceControl1, 1);
+                        setZOrder(surfaceControl2, Integer.MIN_VALUE);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setOnComplete() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    private long mContext;
+
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        long surfaceTransaction = createSurfaceTransaction();
+                        setSolidBuffer(surfaceControl, surfaceTransaction, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        mContext = nSurfaceTransaction_setOnComplete(surfaceTransaction);
+                        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+                    }
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {
+                        super.surfaceDestroyed(holder);
+                        nSurfaceTransaction_checkOnComplete(mContext, -1);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDesiredPresentTime_now() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    private long mContext;
+                    private long mDesiredPresentTime;
+
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        long surfaceTransaction = createSurfaceTransaction();
+                        setSolidBuffer(surfaceControl, surfaceTransaction, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        mDesiredPresentTime = nSurfaceTransaction_setDesiredPresentTime(
+                                surfaceTransaction, 0);
+                        mContext = nSurfaceTransaction_setOnComplete(surfaceTransaction);
+                        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+                    }
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {
+                        super.surfaceDestroyed(holder);
+                        nSurfaceTransaction_checkOnComplete(mContext, mDesiredPresentTime);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDesiredPresentTime_30ms() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    private long mContext;
+                    private long mDesiredPresentTime;
+
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        long surfaceTransaction = createSurfaceTransaction();
+                        setSolidBuffer(surfaceControl, surfaceTransaction, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        mDesiredPresentTime = nSurfaceTransaction_setDesiredPresentTime(
+                                surfaceTransaction, 30000000);
+                        mContext = nSurfaceTransaction_setOnComplete(surfaceTransaction);
+                        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+                    }
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {
+                        super.surfaceDestroyed(holder);
+                        nSurfaceTransaction_checkOnComplete(mContext, mDesiredPresentTime);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDesiredPresentTime_100ms() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    private long mContext;
+                    private long mDesiredPresentTime;
+
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        long surfaceTransaction = createSurfaceTransaction();
+                        setSolidBuffer(surfaceControl, surfaceTransaction, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        mDesiredPresentTime = nSurfaceTransaction_setDesiredPresentTime(
+                                surfaceTransaction, 100000000);
+                        mContext = nSurfaceTransaction_setOnComplete(surfaceTransaction);
+                        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+                    }
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {
+                        super.surfaceDestroyed(holder);
+                        nSurfaceTransaction_checkOnComplete(mContext, mDesiredPresentTime);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferAlpha_1_0() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setBufferAlpha(surfaceControl, 1.0);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferAlpha_0_5() throws Throwable {
+        BasicSurfaceHolderCallback callback = new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setBufferAlpha(surfaceControl, 0.5);
+                    }
+                };
+        verifyTest(callback,
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+        verifyTest(callback,
+                new PixelChecker(PixelColor.RED) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferAlpha_0_0() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setBufferAlpha(surfaceControl, 0.0);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_reparent() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl1 = createFromWindow(holder.getSurface());
+                        long parentSurfaceControl2 = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl1);
+
+                        setGeometry(parentSurfaceControl1, 0, 0, 100, 100, 0, 0, 160, 480, 0);
+                        setGeometry(parentSurfaceControl2, 0, 0, 100, 100, 160, 0, 640, 480, 0);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+
+                        reparent(childSurfaceControl, parentSurfaceControl2);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //7500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 6750 && pixelCount < 8250;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_reparent_null() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long parentSurfaceControl = createFromWindow(holder.getSurface());
+                        long childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+
+                        reparent(childSurfaceControl, 0);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setColor() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long surfaceControl = createFromWindow(holder.getSurface());
+
+                    setColor(surfaceControl, 0, 1.0f, 0, 1.0f);
+                }
+            },
+                new PixelChecker(PixelColor.GREEN) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_noColorNoBuffer() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long parentSurfaceControl = createFromWindow(holder.getSurface());
+                    long childSurfaceControl = create(parentSurfaceControl);
+
+                    setColor(parentSurfaceControl, 0, 1.0f, 0, 1.0f);
+                }
+            },
+                new PixelChecker(PixelColor.GREEN) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setColorAlpha() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long parentSurfaceControl = createFromWindow(holder.getSurface());
+                    setColor(parentSurfaceControl, 0, 0, 1.0f, 0);
+                }
+            },
+                new PixelChecker(PixelColor.YELLOW) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setColorAndBuffer() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long surfaceControl = createFromWindow(holder.getSurface());
+
+                    setSolidBuffer(
+                            surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                            DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    setColor(surfaceControl, 0, 1.0f, 0, 1.0f);
+                }
+            },
+                new PixelChecker(PixelColor.RED) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setColorAndBuffer_bufferAlpha_0_5() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long surfaceControl = createFromWindow(holder.getSurface());
+
+                    setSolidBuffer(
+                            surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                            PixelColor.RED);
+                    setBufferAlpha(surfaceControl, 0.5);
+                    setColor(surfaceControl, 0, 0, 1.0f, 1.0f);
+                }
+            },
+                new PixelChecker(PixelColor.RED) {
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount == 0;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferNoColor_bufferAlpha_0() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long surfaceControlA = createFromWindow(holder.getSurface());
+                    long surfaceControlB = createFromWindow(holder.getSurface());
+
+                    setColor(surfaceControlA, 1.0f, 0, 0, 1.0f);
+                    setSolidBuffer(surfaceControlB, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                            PixelColor.TRANSPARENT);
+
+                    setZOrder(surfaceControlA, 1);
+                    setZOrder(surfaceControlB, 2);
+                }
+            },
+                new PixelChecker(PixelColor.RED) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setColorAndBuffer_hide() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long parentSurfaceControl = createFromWindow(holder.getSurface());
+                    long childSurfaceControl = create(parentSurfaceControl);
+
+                    setColor(parentSurfaceControl, 0, 1.0f, 0, 1.0f);
+
+                    setSolidBuffer(
+                            childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                            DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    setColor(childSurfaceControl, 0, 0, 1.0f, 1.0f);
+                    setVisibility(childSurfaceControl, false);
+                }
+            },
+                new PixelChecker(PixelColor.GREEN) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_zOrderMultipleSurfaces() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long surfaceControlA = createFromWindow(holder.getSurface());
+                    long surfaceControlB = createFromWindow(holder.getSurface());
+
+                    // blue color layer of A is above the green buffer and red color layer
+                    // of B
+                    setColor(surfaceControlA, 0, 0, 1.0f, 1.0f);
+                    setSolidBuffer(
+                            surfaceControlB, DEFAULT_LAYOUT_WIDTH,
+                            DEFAULT_LAYOUT_HEIGHT, PixelColor.GREEN);
+                    setColor(surfaceControlB, 1.0f, 0, 0, 1.0f);
+                    setZOrder(surfaceControlA, 5);
+                    setZOrder(surfaceControlB, 4);
+                }
+            },
+                new PixelChecker(PixelColor.BLUE) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    @Test
+    public void testSurfaceTransaction_zOrderMultipleSurfacesWithParent() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    long parentSurfaceControl = createFromWindow(holder.getSurface());
+                    long surfaceControlA = create(parentSurfaceControl);
+                    long surfaceControlB = create(parentSurfaceControl);
+
+                    setColor(surfaceControlA, 0, 1.0f, 0, 1.0f);
+                    setSolidBuffer(
+                            surfaceControlA, DEFAULT_LAYOUT_WIDTH,
+                            DEFAULT_LAYOUT_HEIGHT, PixelColor.GREEN);
+                    setColor(surfaceControlB, 1.0f, 0, 0, 1.0f);
+                    setZOrder(surfaceControlA, 3);
+                    setZOrder(surfaceControlB, 4);
+                }
+            },
+                new PixelChecker(PixelColor.RED) { // 10000
+                @Override
+                public boolean checkPixels(int pixelCount, int width, int height) {
+                    return pixelCount > 9000 && pixelCount < 11000;
+                }
+            });
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Native function prototypes
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static native long nSurfaceTransaction_create();
+    private static native void nSurfaceTransaction_delete(long surfaceTransaction);
+    private static native void nSurfaceTransaction_apply(long surfaceTransaction);
+    private static native long nSurfaceControl_createFromWindow(Surface surface);
+    private static native long nSurfaceControl_create(long surfaceControl);
+    private static native void nSurfaceControl_release(long surfaceControl);
+    private static native long nSurfaceTransaction_setSolidBuffer(
+            long surfaceControl, long surfaceTransaction, int width, int height, int color);
+    private static native long nSurfaceTransaction_setQuadrantBuffer(long surfaceControl,
+            long surfaceTransaction, int width, int height, int colorTopLeft, int colorTopRight,
+            int colorBottomRight, int colorBottomLeft);
+    private static native void nSurfaceTransaction_releaseBuffer(long buffer);
+    private static native void nSurfaceTransaction_setVisibility(
+            long surfaceControl, long surfaceTransaction, boolean show);
+    private static native void nSurfaceTransaction_setBufferOpaque(
+            long surfaceControl, long surfaceTransaction, boolean opaque);
+    private static native void nSurfaceTransaction_setGeometry(
+            long surfaceControl, long surfaceTransaction, int srcRight, int srcTop, int srcLeft,
+            int srcBottom, int dstRight, int dstTop, int dstLeft, int dstBottom, int transform);
+    private static native void nSurfaceTransaction_setDamageRegion(
+            long surfaceControl, long surfaceTransaction, int right, int top, int left, int bottom);
+    private static native void nSurfaceTransaction_setZOrder(
+            long surfaceControl, long surfaceTransaction, int z);
+    private static native long nSurfaceTransaction_setOnComplete(long surfaceTransaction);
+    private static native void nSurfaceTransaction_checkOnComplete(long context,
+            long desiredPresentTime);
+    private static native long nSurfaceTransaction_setDesiredPresentTime(long surfaceTransaction,
+            long desiredPresentTimeOffset);
+    private static native void nSurfaceTransaction_setBufferAlpha(long surfaceControl,
+            long surfaceTransaction, double alpha);
+    private static native void nSurfaceTransaction_reparent(long surfaceControl,
+            long newParentSurfaceControl, long surfaceTransaction);
+    private static native void nSurfaceTransaction_setColor(long surfaceControl,
+            long surfaceTransaction, float r, float g, float b, float alpha);
+}
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
index 598552b..7f86484 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
@@ -16,6 +16,7 @@
 
 package android.view.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
@@ -34,16 +35,23 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
-
 
 /**
  * Certain KeyEvents should never be delivered to apps. These keys are:
  *      KEYCODE_ASSIST
  *      KEYCODE_VOICE_ASSIST
  *      KEYCODE_HOME
- * This test launches an Activity and inject KeyEvents with the corresponding key codes.
+ * This test launches an Activity and injects KeyEvents with the corresponding key codes.
  * The test will fail if any of these keys are received by the activity.
+ * Note: The ASSIST tests were removed because they caused a side-effect of launching the
+ * assistant asynchronously (as intended), which causes problems with tests which happen to
+ * be running later and lose focus/visibility because of that extra window.
+ *
+ * Certain combinations of keys should be treated as shortcuts. Those are:
+ *      KEYCODE_META_* + KEYCODE_ENTER --> KEYCODE_BACK
+ *      KEYCODE_META_* + KEYCODE_DEL --> KEYCODE_HOME
+ * For those combinations, we make sure that they are either delivered to the app
+ * as the desired key (KEYCODE_BACK), or not delivered to the app (KEYCODE_HOME).
  */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -63,20 +71,52 @@
     }
 
     @Test
-    public void testKeyCodeAssist() {
-        testKey(KeyEvent.KEYCODE_ASSIST);
-    }
-
-    @Test
-    public void testKeyCodeVoiceAssist() {
-        testKey(KeyEvent.KEYCODE_VOICE_ASSIST);
-    }
-
-    @Test
     public void testKeyCodeHome() {
         testKey(KeyEvent.KEYCODE_HOME);
     }
 
+    @Test
+    public void testKeyCodeHomeShortcutLeftMeta() {
+        testKeyCodeHomeShortcut(KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_ON);
+    }
+
+    @Test
+    public void testKeyCodeHomeShortcutRightMeta() {
+        testKeyCodeHomeShortcut(KeyEvent.META_META_RIGHT_ON | KeyEvent.META_META_ON);
+    }
+
+    @Test
+    public void testKeyCodeBackShortcutLeftMeta() {
+        testKeyCodeBackShortcut(KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_ON);
+    }
+
+    @Test
+    public void testKeyCodeBackShortcutRightMeta() {
+        testKeyCodeBackShortcut(KeyEvent.META_META_RIGHT_ON | KeyEvent.META_META_ON);
+    }
+
+    private void testKeyCodeHomeShortcut(int metaState) {
+        long downTime = SystemClock.uptimeMillis();
+        injectEvent(new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_ENTER, 0, metaState));
+        injectEvent(new KeyEvent(downTime, downTime + 1, KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_ENTER, 0, metaState));
+
+        assertKeyNotReceived();
+    }
+
+    private void testKeyCodeBackShortcut(int metaState) {
+        long downTime = SystemClock.uptimeMillis();
+        injectEvent(new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_DEL, 0, metaState));
+        injectEvent(new KeyEvent(downTime, downTime + 1, KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_DEL, 0, metaState));
+
+        assertKeyReceived(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_DOWN);
+        assertKeyReceived(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_UP);
+        assertKeyNotReceived();
+    }
+
     private void testKey(int keyCode) {
         sendKey(keyCode);
         assertKeyNotReceived();
@@ -93,18 +133,22 @@
     private void injectEvent(KeyEvent event) {
         final UiAutomation automation = mInstrumentation.getUiAutomation();
         automation.injectInputEvent(event, true);
-        event.recycle();
     }
 
     private void assertKeyNotReceived() {
-        try {
-            KeyEvent keyEvent = mActivity.mKeyEvents.poll(1, TimeUnit.SECONDS);
-            if (keyEvent == null) {
-                return;
-            }
-            fail("Should not have received " + KeyEvent.keyCodeToString(keyEvent.getKeyCode()));
-        } catch (InterruptedException ex) {
-            fail("BlockingQueue.poll(..) was unexpectedly interrupted");
+        KeyEvent keyEvent = mActivity.mKeyEvents.poll();
+        if (keyEvent == null) {
+            return;
         }
+        fail("Should not have received " + KeyEvent.keyCodeToString(keyEvent.getKeyCode()));
+    }
+
+    private void assertKeyReceived(int keyCode, int action) {
+        KeyEvent keyEvent = mActivity.mKeyEvents.poll();
+        if (keyEvent == null) {
+            fail("Did not receive " + KeyEvent.keyCodeToString(keyCode) + ", queue is empty");
+        }
+        assertEquals(keyCode, keyEvent.getKeyCode());
+        assertEquals(action, keyEvent.getAction());
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
index 18e0713..2422a61 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
@@ -19,21 +19,14 @@
 import android.app.Activity;
 import android.view.KeyEvent;
 
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingDeque;
+import java.util.LinkedList;
+import java.util.Queue;
 
 public class KeyEventInterceptTestActivity extends Activity {
-    final BlockingQueue<KeyEvent> mKeyEvents = new LinkedBlockingDeque<>();
+    final Queue<KeyEvent> mKeyEvents = new LinkedList<>();
 
     @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        mKeyEvents.add(event);
-        return true;
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        // Check this in case some spurious event with ACTION_UP is received
+    public boolean dispatchKeyEvent(KeyEvent event) {
         mKeyEvents.add(event);
         return true;
     }
diff --git a/tests/tests/view/src/android/view/cts/KeyEventTest.java b/tests/tests/view/src/android/view/cts/KeyEventTest.java
index fde3f81..0e69bc3 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventTest.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventTest.java
@@ -96,6 +96,10 @@
         assertEquals(KeyEvent.KEYCODE_UNKNOWN, mKeyEvent.getKeyCode());
         assertEquals(characters, mKeyEvent.getCharacters());
 
+        // Make sure mCharacters survives after serialization / deserialization
+        KeyEvent keyEvent = parcelUnparcel(mKeyEvent);
+        assertEquals(mKeyEvent.getCharacters(), keyEvent.getCharacters());
+
         mKeyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0);
         assertNull(mKeyEvent.getCharacters());
     }
@@ -618,12 +622,7 @@
 
     @Test
     public void testWriteToParcel() {
-        Parcel parcel = Parcel.obtain();
-        mKeyEvent.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-        parcel.setDataPosition(0);
-
-        KeyEvent keyEvent = KeyEvent.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
+        KeyEvent keyEvent = parcelUnparcel(mKeyEvent);
 
         assertEquals(mKeyEvent.getAction(), keyEvent.getAction());
         assertEquals(mKeyEvent.getKeyCode(), keyEvent.getKeyCode());
@@ -634,6 +633,7 @@
         assertEquals(mKeyEvent.getFlags(), keyEvent.getFlags());
         assertEquals(mKeyEvent.getDownTime(), keyEvent.getDownTime());
         assertEquals(mKeyEvent.getEventTime(), keyEvent.getEventTime());
+        assertEquals(mKeyEvent.getCharacters(), keyEvent.getCharacters());
     }
 
     @Test
@@ -760,4 +760,48 @@
                 1, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_TOUCHSCREEN);
         assertFalse(mKeyEvent.isLongPress());
     }
+
+    @Test
+    public void testKeyCodeFromString() {
+        assertEquals(KeyEvent.KEYCODE_A, KeyEvent.keyCodeFromString("KEYCODE_A"));
+        assertEquals(KeyEvent.KEYCODE_A, KeyEvent.keyCodeFromString("A"));
+        assertEquals(KeyEvent.KEYCODE_A,
+                KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_A)));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("keycode_a"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("a"));
+        assertEquals(0, KeyEvent.keyCodeFromString("0"));
+        assertEquals(1, KeyEvent.keyCodeFromString("1"));
+        assertEquals(KeyEvent.KEYCODE_HOME, KeyEvent.keyCodeFromString("3"));
+        assertEquals(KeyEvent.KEYCODE_POWER,
+                KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_POWER)));
+        assertEquals(KeyEvent.KEYCODE_MENU,
+                KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.KEYCODE_MENU)));
+        assertEquals(KeyEvent.KEYCODE_BACK, KeyEvent.keyCodeFromString("BACK"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("back"));
+
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN,
+                KeyEvent.keyCodeFromString("KEYCODE_NOT_A_REAL_KEYCODE"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("NOT_A_REAL_KEYCODE"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("-1"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("1001"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE_123"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString("KEYCODE_"));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.keyCodeFromString(""));
+        assertEquals(KeyEvent.LAST_KEYCODE,
+                KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.LAST_KEYCODE)));
+        assertEquals(KeyEvent.KEYCODE_UNKNOWN,
+                KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.LAST_KEYCODE + 1)));
+    }
+
+    // Parcel a KeyEvent, then create a new KeyEvent from this parcel. Return the new KeyEvent
+    private KeyEvent parcelUnparcel(KeyEvent keyEvent) {
+        Parcel parcel = Parcel.obtain();
+        keyEvent.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        parcel.setDataPosition(0);
+
+        KeyEvent keyEventFromParcel = KeyEvent.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return keyEventFromParcel;
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTest.java b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
index e5f6560..4c3fe0e 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTest.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
@@ -272,7 +272,7 @@
     private Window waitForWindowProducerActivity() {
         PixelCopyViewProducerActivity activity =
                 mWindowSourceActivityRule.launchActivity(null);
-        activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
         return activity.getWindow();
     }
 
@@ -382,7 +382,7 @@
     private Window waitForWideGamutWindowProducerActivity() {
         PixelCopyWideGamutViewProducerActivity activity =
                 mWideGamutWindowSourceActivityRule.launchActivity(null);
-        activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
         return activity.getWindow();
     }
 
@@ -465,7 +465,7 @@
     private Window waitForDialogProducerActivity() {
         PixelCopyViewProducerActivity activity =
                 mDialogSourceActivityRule.launchActivity(null);
-        activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
+        activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
         return activity.getWindow();
     }
 
diff --git a/tests/tests/view/src/android/view/cts/SurfaceControlTest.java b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
new file mode 100644
index 0000000..e02b286
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.PixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+import android.view.cts.surfacevalidator.SurfaceControlTestCase;
+import android.view.SurfaceControl;
+import android.view.Surface;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+@LargeTest
+public class SurfaceControlTest {
+    private static final int DEFAULT_LAYOUT_WIDTH = 100;
+    private static final int DEFAULT_LAYOUT_HEIGHT = 100;
+    private static final int DEFAULT_BUFFER_WIDTH = 640;
+    private static final int DEFAULT_BUFFER_HEIGHT = 480;
+
+    @Rule
+    public ActivityTestRule<CapturedActivity> mActivityRule =
+            new ActivityTestRule<>(CapturedActivity.class);
+
+    @Rule
+    public TestName mName = new TestName();
+    private CapturedActivity mActivity;
+
+    private void verifyTest(SurfaceControlTestCase.ParentSurfaceConsumer psc,
+            PixelChecker pixelChecker) throws Throwable {
+        mActivity.verifyTest(new SurfaceControlTestCase(psc, null,
+                        pixelChecker, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                        DEFAULT_BUFFER_WIDTH, DEFAULT_BUFFER_HEIGHT),
+                mName);
+    }
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+        mActivity.dismissPermissionDialog();
+    }
+
+    /**
+     * Want to be especially sure we don't leave up the permission dialog, so try and dismiss
+     * after test.
+     */
+    @After
+    public void tearDown() throws UiObjectNotFoundException {
+        mActivity.dismissPermissionDialog();
+    }
+
+    @Test
+    public void testLifecycle() {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder();
+        final SurfaceControl sc = b.build();
+
+        assertTrue("Failed to build SurfaceControl", sc != null);
+        assertTrue(sc.isValid());
+        sc.release();
+        assertFalse(sc.isValid());
+    }
+
+    private SurfaceControl buildDefaultSurface(SurfaceControl parent) {
+        return new SurfaceControl.Builder()
+            .setBufferSize(DEFAULT_BUFFER_WIDTH, DEFAULT_BUFFER_HEIGHT)
+            .setParent(parent)
+            .build();
+
+    }
+
+    void fillWithColor(SurfaceControl sc, int color) {
+        Surface s = new Surface(sc);
+
+        Canvas c = s.lockHardwareCanvas();
+        c.drawColor(color);
+        s.unlockCanvasAndPost(c);
+    }
+
+    private SurfaceControl buildDefaultRedSurface(SurfaceControl parent) {
+        final SurfaceControl sc = buildDefaultSurface(parent);
+        fillWithColor(sc, Color.RED);
+        return sc;
+    }
+
+    /**
+     * Verify that showing a 100x100 surface filled with RED produces roughly 10,000 red pixels.
+     */
+    @Test
+    public void testShow() throws Throwable {
+        verifyTest(
+                new SurfaceControlTestCase.ParentSurfaceConsumer () {
+                    @Override
+                    public void addChildren(SurfaceControl parent) {
+                        final SurfaceControl sc = buildDefaultRedSurface(parent);
+
+                        new SurfaceControl.Transaction().setVisibility(sc, true).apply();
+
+                        sc.release();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    /**
+     * The same setup as testShow, however we hide the surface and verify that we don't see Red.
+     */
+    @Test
+    public void testHide() throws Throwable {
+        verifyTest(
+                new SurfaceControlTestCase.ParentSurfaceConsumer () {
+                    @Override
+                    public void addChildren(SurfaceControl parent) {
+                        final SurfaceControl sc = buildDefaultRedSurface(parent);
+
+                        new SurfaceControl.Transaction().setVisibility(sc, false).apply();
+
+                        sc.release();
+                    }
+                },
+                new PixelChecker(PixelColor.BLACK) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    /**
+     * Here we use the same red-surface set up but construct it off-screen and then re-parent it.
+     */
+    @Test
+    public void testReparent() throws Throwable {
+        verifyTest(
+                new SurfaceControlTestCase.ParentSurfaceConsumer () {
+                    @Override
+                    public void addChildren(SurfaceControl parent) {
+                        final SurfaceControl sc = buildDefaultRedSurface(null);
+
+                        new SurfaceControl.Transaction().setVisibility(sc, true)
+                            .reparent(sc, parent)
+                            .apply();
+
+                        sc.release();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
index 2a14a8d..de73a79 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
@@ -15,24 +15,19 @@
  */
 package android.view.cts;
 
-import static org.junit.Assert.assertTrue;
-
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.media.MediaPlayer;
-import android.os.Environment;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.RequiresDevice;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.util.Log;
-import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -42,6 +37,7 @@
 import android.view.cts.surfacevalidator.AnimationFactory;
 import android.view.cts.surfacevalidator.AnimationTestCase;
 import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.PixelChecker;
 import android.view.cts.surfacevalidator.ViewFactory;
 import android.widget.FrameLayout;
 
@@ -52,14 +48,10 @@
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 @SuppressLint("RtlHardcoded")
+@RequiresDevice
 public class SurfaceViewSyncTest {
     private static final String TAG = "SurfaceViewSyncTests";
 
@@ -188,71 +180,13 @@
     };
 
     ///////////////////////////////////////////////////////////////////////////
-    // Bad frame capture
-    ///////////////////////////////////////////////////////////////////////////
-
-    private void saveFailureCaptures(SparseArray<Bitmap> failFrames) {
-        if (failFrames.size() == 0) return;
-
-        String directoryName = Environment.getExternalStorageDirectory()
-                + "/" + getClass().getSimpleName()
-                + "/" + mName.getMethodName();
-        File testDirectory = new File(directoryName);
-        if (testDirectory.exists()) {
-            String[] children = testDirectory.list();
-            if (children == null) {
-                return;
-            }
-            for (String file : children) {
-                new File(testDirectory, file).delete();
-            }
-        } else {
-            testDirectory.mkdirs();
-        }
-
-        for (int i = 0; i < failFrames.size(); i++) {
-            int frameNr = failFrames.keyAt(i);
-            Bitmap bitmap = failFrames.valueAt(i);
-
-            String bitmapName =  "frame_" + frameNr + ".png";
-            Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
-
-            File file = new File(directoryName, bitmapName);
-            try (FileOutputStream fileStream = new FileOutputStream(file)) {
-                bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
-                fileStream.flush();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////
     // Tests
     ///////////////////////////////////////////////////////////////////////////
 
-    private void verifyTest(AnimationTestCase testCase) throws Throwable {
-        CapturedActivity.TestResult result = mActivity.runTest(testCase);
-        saveFailureCaptures(result.failures);
-
-        float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
-        assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
-                failRatio < 0.95f);
-        assertTrue("Error: " + result.failFrames
-                + " incorrect frames observed - incorrect positioning",
-                result.failFrames == 0);
-        float framesPerSecond = 1.0f * result.passFrames
-                / TimeUnit.MILLISECONDS.toSeconds(mActivity.getCaptureDurationMs());
-        assertTrue("Error, only " + result.passFrames
-                + " frames observed, virtual display only capturing at "
-                + framesPerSecond + " frames per second",
-                result.passFrames > 100);
-    }
-
     /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
     @Test
     public void testSmallRect() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 context -> new View(context) {
                     // draw a single pixel
                     final Paint sBlackPaint = new Paint();
@@ -271,8 +205,12 @@
                 },
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
-                (blackishPixelCount, width, height) ->
-                        blackishPixelCount >= 90 && blackishPixelCount <= 110));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount >= 90 && blackishPixelCount <= 110;
+                    }
+                }), mName);
     }
 
     /**
@@ -281,56 +219,80 @@
      */
     @Test
     public void testEmptySurfaceView() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sEmptySurfaceViewFactory,
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 sTranslateAnimationFactory,
-                (blackishPixelCount, width, height) ->
-                        blackishPixelCount > 9000 && blackishPixelCount < 11000));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount > 9000 && blackishPixelCount < 11000;
+                    }
+                }), mName);
     }
 
     @Test
     public void testSurfaceViewSmallScale() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sGreenSurfaceViewFactory,
                 new FrameLayout.LayoutParams(320, 240, Gravity.LEFT | Gravity.TOP),
                 sSmallScaleAnimationFactory,
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 
     @Test
     public void testSurfaceViewBigScale() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sGreenSurfaceViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
                 sBigScaleAnimationFactory,
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 
     @Test
     public void testVideoSurfaceViewTranslate() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
                 sTranslateAnimationFactory,
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 
     @Test
     public void testVideoSurfaceViewRotated() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
                         PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f),
                         PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
                         PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 
     @Test
     public void testVideoSurfaceViewEdgeCoverage() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -343,12 +305,17 @@
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, 0, x, 0, -x),
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
                 },
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 
     @Test
     public void testVideoSurfaceViewCornerCoverage() throws Throwable {
-        verifyTest(new AnimationTestCase(
+        mActivity.verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -361,6 +328,11 @@
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, x, x, -x, -x),
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
                 },
-                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
index 8b0d4c7..66b1539 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
@@ -355,12 +355,24 @@
                     case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_EXT:
                         eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3";
                         break;
+                    case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT:
+                        eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3_linear";
+                        break;
+                    case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT:
+                        eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3_passthrough";
+                        break;
                     case TextureViewTest.EGL_GL_COLORSPACE_SRGB_KHR:
                         eglColorSpaceString = "EGL_KHR_gl_colorspace";
                         break;
+                    case TextureViewTest.EGL_GL_COLORSPACE_SCRGB_EXT:
+                        eglColorSpaceString = "EGL_EXT_gl_colorspace_scrgb";
+                        break;
                     case TextureViewTest.EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
                         eglColorSpaceString = "EGL_EXT_gl_colorspace_scrgb_linear";
                         break;
+                    case TextureViewTest.EGL_GL_COLORSPACE_LINEAR_KHR:
+                        eglColorSpaceString = "EGL_KHR_gl_colorspace";
+                        break;
                     default:
                         throw new RuntimeException("Unknown eglColorSpace: " + mEglColorSpace);
                 }
@@ -431,6 +443,7 @@
                     EGL10.EGL_ALPHA_SIZE, 16,
                     EGL10.EGL_DEPTH_SIZE, 0,
                     EGL10.EGL_STENCIL_SIZE, 0,
+                    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
                     EGL10.EGL_NONE
             };
         } else {
@@ -442,6 +455,7 @@
                     EGL10.EGL_ALPHA_SIZE, 8,
                     EGL10.EGL_DEPTH_SIZE, 0,
                     EGL10.EGL_STENCIL_SIZE, 0,
+                    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
                     EGL10.EGL_NONE
             };
         }
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index 431b27d..b3b8744 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewTest.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewTest.java
@@ -33,6 +33,7 @@
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -45,7 +46,6 @@
 import com.android.compatibility.common.util.SynchronousPixelCopy;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,7 +59,11 @@
 public class TextureViewTest {
 
     static final int EGL_GL_COLORSPACE_SRGB_KHR = 0x3089;
+    static final int EGL_GL_COLORSPACE_LINEAR_KHR = 0x308A;
     static final int EGL_GL_COLORSPACE_DISPLAY_P3_EXT = 0x3363;
+    static final int EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT = 0x3362;
+    static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
+    static final int EGL_GL_COLORSPACE_SCRGB_EXT = 0x3351;
     static final int EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT = 0x3350;
 
     @Rule
@@ -119,7 +123,6 @@
     }
 
     @Test
-    @Ignore // Temporary, see b/111801626
     public void testRotateScale() throws Throwable {
         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
         final TextureView textureView = activity.getTextureView();
@@ -159,7 +162,6 @@
     }
 
     @Test
-    @Ignore // Temporary, see b/111801626
     public void testTransformScale() throws Throwable {
         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
         final TextureView textureView = activity.getTextureView();
@@ -220,38 +222,170 @@
 
     @Test
     public void testGetBitmap_8888_P3() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.Named.DISPLAY_P3, false,
+        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.get(ColorSpace.Named.DISPLAY_P3),
+                false, false, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
+    }
+
+    @Test
+    public void testGetBitmap_8888_PassthroughP3() throws Throwable {
+        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT,
+                ColorSpace.get(ColorSpace.Named.DISPLAY_P3), false, true,
                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
     @Test
-    public void testGetBitmap_FP16_P3() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.Named.DISPLAY_P3, true,
+    public void testGetBitmap_FP16_PassthroughP3() throws Throwable {
+        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT,
+                ColorSpace.get(ColorSpace.Named.DISPLAY_P3), true, true,
                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
     @Test
+    public void testGetBitmap_FP16_LinearP3() throws Throwable {
+        ColorSpace.Rgb displayP3 = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+        ColorSpace.Rgb linearDisplayP3 = new ColorSpace.Rgb(
+                "Display P3 Linear",
+                displayP3.getTransform(),
+                displayP3.getWhitePoint(),
+                x -> x,
+                x -> x,
+                0.0f, 1.0f
+        );
+
+        testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT, linearDisplayP3, true,
+                true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
+    }
+
+    @Test
+    public void testGetBitmap_FP16_ExtendedSRGB() throws Throwable {
+        // isLinear is "true", because the spec says
+        // GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_LINEAR for EGL_GL_COLORSPACE_SCRGB_EXT.
+        // See https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_scrgb.txt.
+        testGetBitmap(EGL_GL_COLORSPACE_SCRGB_EXT,
+                ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), true,
+                true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
+    }
+
+    @Test
     public void testGetBitmap_FP16_LinearExtendedSRGB() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT, ColorSpace.Named.LINEAR_EXTENDED_SRGB,
+        testGetBitmap(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT,
+                ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), true,
                 true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
     }
 
     @Test
     public void testGet565Bitmap_SRGB() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, true,
-                new SRGBCompare(Bitmap.Config.RGB_565));
+        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.get(ColorSpace.Named.SRGB),
+                false, false, new SRGBCompare(Bitmap.Config.RGB_565));
     }
 
     @Test
     public void testGetBitmap_SRGB() throws Throwable {
-        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.Named.SRGB, true,
-                new SRGBCompare(Bitmap.Config.ARGB_8888));
+        testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.get(ColorSpace.Named.SRGB),
+                false, false, new SRGBCompare(Bitmap.Config.ARGB_8888));
+    }
+
+    @Test
+    public void testGetBitmap_SRGBLinear() throws Throwable {
+        testGetBitmap(EGL_GL_COLORSPACE_LINEAR_KHR, ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
+                false, true, new SRGBCompare(Bitmap.Config.ARGB_8888));
+    }
+
+    /**
+     *  Test that verifies TextureView is drawn with bilerp sampling, when the matrix is not
+     *  an integer translate or identity.
+     */
+    @Test
+    public void testSamplingWithTransform() throws Throwable {
+        final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
+        final TextureView textureView = activity.getTextureView();
+        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
+        };
+        boolean[] nearestSampling = {
+            true,  // nearest sampling for identity
+            false, // bilerp sampling for fractional translate
+            false, // bilerp sampling for scaling
+            true   // nearest sampling for integer translate
+        };
+        for (int i = 0; i < nearestSampling.length; i++) {
+
+            Matrix transform = new Matrix();
+            transform.setValues(matrices[i]);
+
+            // Test draws a set of black & white alternating lines.
+            activity.drawFrame(transform, TextureViewTest::drawGlBlackWhiteLines);
+
+            final Rect viewPos = new Rect();
+            mActivityRule.runOnUiThread(() -> {
+                int[] outLocation = new int[2];
+                textureView.getLocationOnScreen(outLocation);
+                viewPos.left = outLocation[0];
+                viewPos.top = outLocation[1];
+                viewPos.right = viewPos.left + textureView.getWidth();
+                viewPos.bottom = viewPos.top + textureView.getHeight();
+            });
+
+            // Capture the portion of the screen that contains the texture view only.
+            Window window = activity.getWindow();
+            Bitmap screenshot = Bitmap.createBitmap(viewPos.width(), viewPos.height(),
+                    Bitmap.Config.ARGB_8888);
+            int result = new SynchronousPixelCopy().request(window, viewPos, screenshot);
+            assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
+
+            // "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());
+            Rect texturePos = new Rect((int) Math.ceil(texturePosF.left),
+                    (int) Math.ceil(texturePosF.top), (int) Math.floor(texturePosF.right),
+                    (int) Math.floor(texturePosF.bottom));
+
+            int[] pixels = new int[texturePos.width() * texturePos.height()];
+            screenshot.getPixels(pixels, 0, texturePos.width(), texturePos.left, texturePos.top,
+                    texturePos.width(), texturePos.height());
+
+            boolean success = true;
+            int failPosition = 0;
+            if (nearestSampling[i]) {
+                // Check all pixels are either black or white.
+                for (int j = 0; j < pixels.length; j++) {
+                    if (pixels[j] != Color.BLACK && pixels[j] != Color.WHITE) {
+                        success = false;
+                        failPosition = j;
+                        break;
+                    }
+                }
+            } else {
+                // Check there are no black nor white pixels, because bilerp sampling changed
+                // pure black/white to a variety of gray intermediates.
+                for (int j = 0; j < pixels.length; j++) {
+                    if (pixels[j] == Color.BLACK || pixels[j] == Color.WHITE) {
+                        success = false;
+                        failPosition = j;
+                        break;
+                    }
+                }
+            }
+            assertTrue("Unexpected color at position " + failPosition + " = "
+                    + Integer.toHexString(pixels[failPosition]) + " " + transform.toString(),
+                    success);
+        }
     }
 
     interface CompareFunction {
         Bitmap.Config getConfig();
         ColorSpace getColorSpace();
-        void verify(float[] srcColor, ColorSpace.Named srcColorSpace, Bitmap dstBitmap);
+        void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap);
     }
 
     private class FP16Compare implements CompareFunction {
@@ -269,7 +403,7 @@
             return mDstColorSpace;
         }
 
-        public void verify(float[] srcColor, ColorSpace.Named srcColorSpace, Bitmap dstBitmap) {
+        public void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap) {
             // read pixels into buffer and compare using colorspace connector
             ByteBuffer buffer = ByteBuffer.allocate(dstBitmap.getAllocationByteCount());
             buffer.order(ByteOrder.LITTLE_ENDIAN);
@@ -277,9 +411,8 @@
             Half alpha = Half.valueOf(buffer.getShort(6));
             assertEquals(1.0f, alpha.floatValue(), 0.0f);
 
-            final ColorSpace srcSpace = ColorSpace.get(srcColorSpace);
             final ColorSpace dstSpace = getColorSpace();
-            float[] expectedColor = ColorSpace.connect(srcSpace, dstSpace).transform(srcColor);
+            float[] expectedColor = ColorSpace.connect(srcColorSpace, dstSpace).transform(srcColor);
             float[] outputColor = {
                     Half.valueOf(buffer.getShort(0)).floatValue(),
                     Half.valueOf(buffer.getShort(2)).floatValue(),
@@ -306,7 +439,7 @@
             return ColorSpace.get(ColorSpace.Named.SRGB);
         }
 
-        public void verify(float[] srcColor, ColorSpace.Named srcColorSpace, Bitmap dstBitmap) {
+        public void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap) {
             int color = dstBitmap.getPixel(0, 0);
             assertEquals(1.0f, Color.alpha(color) / 255.0f, 0.0f);
             assertEquals(srcColor[0], Color.red(color) / 255.0f, 0.01f);
@@ -315,8 +448,11 @@
         }
     }
 
-    private void testGetBitmap(int eglColorSpace, ColorSpace.Named colorSpace,
-            boolean useHalfFloat, CompareFunction compareFunction) throws Throwable {
+    // isFramebufferLinear is true, when GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_LINEAR.
+    // It is false, when GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_SRGB.
+    private void testGetBitmap(int eglColorSpace, ColorSpace colorSpace,
+            boolean useHalfFloat, boolean isFramebufferLinear,
+            CompareFunction compareFunction) throws Throwable {
         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
         activity.waitForSurface();
 
@@ -342,7 +478,18 @@
 
         final Bitmap bitmap = activity.getContents(compareFunction.getConfig(),
                 compareFunction.getColorSpace());
-        compareFunction.verify(inputColor, colorSpace, bitmap);
+
+        // If GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_SRGB, then glClear will treat the input
+        // color as linear and write a converted sRGB color into the framebuffer.
+        if (isFramebufferLinear) {
+            compareFunction.verify(inputColor, colorSpace, bitmap);
+        } else {
+            ColorSpace.Connector connector;
+            connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
+                    ColorSpace.get(ColorSpace.Named.SRGB));
+            float[] outputColor = connector.transform(inputColor);
+            compareFunction.verify(outputColor, colorSpace, bitmap);
+        }
     }
 
     private static void drawGlQuad(int width, int height) {
@@ -364,6 +511,15 @@
         clearColor(Color.BLACK);
     }
 
+    private static void drawGlBlackWhiteLines(int width, int height) {
+        final int lineHeight = 1;
+        glEnable(GL_SCISSOR_TEST);
+        for (int y = 0; y < height / lineHeight; y++) {
+            glScissor(0, lineHeight * y, width, lineHeight);
+            clearColor((y % 2 == 0) ? Color.BLACK : Color.WHITE);
+        }
+    }
+
     private static void clearColor(int color) {
         glClearColor(Color.red(color) / 255.0f,
                 Color.green(color) / 255.0f,
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
index 9c94d4c..18fba70 100644
--- a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
@@ -17,10 +17,12 @@
 package android.view.cts;
 
 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 android.app.Instrumentation;
+import android.content.Context;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -28,7 +30,9 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.view.InputDevice;
 import android.view.MotionEvent;
-
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -78,9 +82,9 @@
     public void testCancelEvent() {
         // Ensure events with ACTION_CANCEL are received by the TouchDelegate
         final long downTime = SystemClock.uptimeMillis();
-        dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY,
+        dispatchTouchEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY,
                 downTime);
-        dispatchMotionEventToActivity(MotionEvent.ACTION_CANCEL, mActivity.touchDelegateY,
+        dispatchTouchEventToActivity(MotionEvent.ACTION_CANCEL, mActivity.touchDelegateY,
                 downTime);
         mInstrumentation.waitForIdleSync();
 
@@ -97,8 +101,8 @@
         // Ensure ACTION_POINTER_DOWN and ACTION_POINTER_UP are forwarded to the target view
         // by the TouchDelegate
         final long downTime = SystemClock.uptimeMillis();
-        dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY, downTime);
-        dispatchMotionEventToActivity(MotionEvent.ACTION_MOVE, mActivity.touchDelegateY, downTime);
+        dispatchTouchEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY, downTime);
+        dispatchTouchEventToActivity(MotionEvent.ACTION_MOVE, mActivity.touchDelegateY, downTime);
         int actionPointer1Down =
                 (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_DOWN;
         dispatchMultiTouchMotionEventToActivity(actionPointer1Down, 2,
@@ -109,7 +113,7 @@
                 (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_UP;
         dispatchMultiTouchMotionEventToActivity(actionPointer1Up, 2,
                 mActivity.touchDelegateY, downTime);
-        dispatchMotionEventToActivity(MotionEvent.ACTION_UP, mActivity.touchDelegateY, downTime);
+        dispatchTouchEventToActivity(MotionEvent.ACTION_UP, mActivity.touchDelegateY, downTime);
         mInstrumentation.waitForIdleSync();
 
         ensureOldestActionEquals(MotionEvent.ACTION_DOWN);
@@ -120,6 +124,42 @@
         ensureOldestActionEquals(MotionEvent.ACTION_UP);
     }
 
+    @Test
+    public void testGetTouchDelegateInfo_withNullBounds_noException() {
+        final View view = mActivity.findViewById(R.id.layout);
+        final TouchDelegate touchDelegate = new TouchDelegate(null, view);
+        touchDelegate.getTouchDelegateInfo();
+    }
+
+    @Test
+    public void testOnTouchExplorationHoverEvent_withNullBounds_noException() {
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent event = MotionEvent.obtain(downTime, downTime,
+                MotionEvent.ACTION_HOVER_MOVE, mActivity.x, mActivity.touchDelegateY, 0);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        final View view = mActivity.findViewById(R.id.button);
+        final TouchDelegate touchDelegate = new TouchDelegate(null, view);
+
+        touchDelegate.onTouchExplorationHoverEvent(event);
+    }
+
+    @Test
+    public void testOnTouchExplorationHoverEvent_whenA11yEbtDisabled_receiveNoEvent() {
+        final long downTime = SystemClock.uptimeMillis();
+        final AccessibilityManager manager = (AccessibilityManager) mInstrumentation.getContext()
+                .getSystemService(Context.ACCESSIBILITY_SERVICE);
+        assertFalse("Touch exploration should not be enabled",
+                manager.isEnabled() && manager.isTouchExplorationEnabled());
+
+        dispatchHoverEventToActivity(MotionEvent.ACTION_HOVER_MOVE, mActivity.touchDelegateY,
+                downTime);
+        dispatchHoverEventToActivity(MotionEvent.ACTION_HOVER_MOVE, mActivity.parentViewY,
+                downTime);
+        mInstrumentation.waitForIdleSync();
+
+        assertNull(mActivity.removeOldestButtonEvent());
+    }
+
     private void ensureOldestActionEquals(int action) {
         MotionEvent event = mActivity.removeOldestButtonEvent();
         assertNotNull(event);
@@ -139,18 +179,29 @@
 
     private void click(int y) {
         final long downTime = SystemClock.uptimeMillis();
-        dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, y, downTime);
-        dispatchMotionEventToActivity(MotionEvent.ACTION_UP, y, downTime);
+        dispatchTouchEventToActivity(MotionEvent.ACTION_DOWN, y, downTime);
+        dispatchTouchEventToActivity(MotionEvent.ACTION_UP, y, downTime);
         mInstrumentation.waitForIdleSync();
     }
 
-    private void dispatchMotionEventToActivity(int action, int y, long downTime) {
+    private void dispatchTouchEventToActivity(int action, int y, long downTime) {
+        dispatchMotionEventToActivity(action, y, downTime, InputDevice.SOURCE_TOUCHSCREEN,
+                mActivity::dispatchTouchEvent);
+    }
+
+    private void dispatchHoverEventToActivity(int action, int y, long downTime) {
+        dispatchMotionEventToActivity(action, y, downTime, InputDevice.SOURCE_MOUSE,
+                mActivity::dispatchGenericMotionEvent);
+    }
+
+    private void dispatchMotionEventToActivity(int action, int y, long downTime, int source,
+            Dispatcher dispatcher) {
         mActivity.runOnUiThread(() -> {
             final long eventTime = SystemClock.uptimeMillis();
             final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
                     mActivity.x, y, 0);
-            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-            mActivity.dispatchTouchEvent(event);
+            event.setSource(source);
+            dispatcher.dispatchMotionEvent(event);
             event.recycle();
         });
     }
@@ -180,4 +231,8 @@
             event.recycle();
         });
     }
+
+    interface Dispatcher {
+        void dispatchMotionEvent(MotionEvent event);
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
index 0f01a95..49f9eb7 100644
--- a/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
@@ -91,6 +91,10 @@
             mButtonEvents.add(MotionEvent.obtain(event));
             return TouchDelegateTestActivity.super.onTouchEvent(event);
         });
+        button.setOnHoverListener((v, event) -> {
+            mButtonEvents.add(MotionEvent.obtain(event));
+            return TouchDelegateTestActivity.super.onGenericMotionEvent(event);
+        });
     }
 
     void resetCounters() {
diff --git a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
index 955cf7c..82c62ec 100644
--- a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
+++ b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.fail;
 
+import android.os.SystemClock;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
@@ -156,6 +157,28 @@
                 "Expect weak bound when there is changing acceleration.");
     }
 
+    @Test
+    public void testUsesRawCoordinates() {
+        VelocityTracker vt = VelocityTracker.obtain();
+        final int numevents = 5;
+
+        final long downTime = SystemClock.uptimeMillis();
+        for (int i = 0; i < numevents; i++) {
+            final long eventTime = downTime + i * 10;
+            int action = i == 0 ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_MOVE;
+            MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 0, 0, 0);
+            event.offsetLocation(i * 10, i * 10);
+            vt.addMovement(event);
+        }
+        vt.computeCurrentVelocity(1000);
+        float xVelocity = vt.getXVelocity();
+        float yVelocity = vt.getYVelocity();
+        if (xVelocity == 0 || yVelocity == 0) {
+            fail("VelocityTracker is using raw coordinates,"
+                    + " but it should be using adjusted coordinates");
+        }
+    }
+
     private void move(long duration, long step) {
         addMovement();
         while (duration > 0) {
diff --git a/tests/tests/view/src/android/view/cts/ViewGroupTest.java b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
index fef5eba..42935b4 100644
--- a/tests/tests/view/src/android/view/cts/ViewGroupTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -23,11 +23,14 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.content.Context;
@@ -37,13 +40,13 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import androidx.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.LargeTest;
@@ -60,11 +63,14 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.View;
 import android.view.View.BaseSavedState;
 import android.view.View.MeasureSpec;
+import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -72,13 +78,18 @@
 import android.view.animation.LayoutAnimationController;
 import android.view.animation.RotateAnimation;
 import android.view.animation.Transformation;
+import android.view.cts.util.EventUtils;
+import android.view.cts.util.ScrollBarUtils;
 import android.view.cts.util.XmlUtils;
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.compatibility.common.util.CTSResult;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -759,18 +770,6 @@
         assertSame(mMockViewGroup, mMockViewGroup.findFocus());
     }
 
-    @UiThreadTest
-    @Test
-    public void testFitSystemWindows() {
-        Rect rect = new Rect(1, 1, 100, 100);
-        assertFalse(mMockViewGroup.fitSystemWindows(rect));
-
-        mMockViewGroup = new MockViewGroup(mContext, null, 0);
-        MockView mv = new MockView(mContext);
-        mMockViewGroup.addView(mv);
-        assertTrue(mMockViewGroup.fitSystemWindows(rect));
-    }
-
     static class MockView extends ViewGroup {
 
         public int mWidthMeasureSpec;
@@ -785,11 +784,6 @@
         }
 
         @Override
-        public boolean fitSystemWindows(Rect insets) {
-            return true;
-        }
-
-        @Override
         public void onMeasure(int widthMeasureSpec,
                 int heightMeasureSpec) {
             mWidthMeasureSpec = widthMeasureSpec;
@@ -1006,6 +1000,180 @@
     }
 
     @Test
+    public void onInterceptHoverEvent_verticalCanScroll_intercepts() {
+        onInterceptHoverEvent_scrollabilityAffectsResult(true, true, true);
+    }
+
+    @Test
+    public void onInterceptHoverEvent_verticalCantScroll_doesntIntercept() {
+        onInterceptHoverEvent_scrollabilityAffectsResult(true, false, false);
+    }
+
+    @Test
+    public void onInterceptHoverEvent_horizontalCanScroll_intercepts() {
+        onInterceptHoverEvent_scrollabilityAffectsResult(false, true, true);
+    }
+
+    @Test
+    public void onInterceptHoverEvent_horizontalCantScroll_doesntIntercept() {
+        onInterceptHoverEvent_scrollabilityAffectsResult(false, false, false);
+    }
+
+    private void onInterceptHoverEvent_scrollabilityAffectsResult(boolean vertical,
+            boolean canScroll, boolean intercepts) {
+
+        // Arrange
+
+        int range = canScroll ? 101 : 100;
+
+        final ScrollTestView viewGroup = spy(new ScrollTestView(mContext));
+        viewGroup.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        viewGroup.setHorizontalScrollBarEnabled(true);
+        viewGroup.setVerticalScrollBarEnabled(true);
+        viewGroup.setScrollBarSize(10);
+        viewGroup.layout(0, 0, 100, 100);
+
+        when(viewGroup.computeVerticalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeVerticalScrollRange()).thenReturn(range);
+        when(viewGroup.computeHorizontalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeHorizontalScrollRange()).thenReturn(range);
+
+        int touchX = vertical ? 95 : 50;
+        int touchY = vertical ? 50 : 95;
+        MotionEvent event =
+                EventUtils.generateMouseEvent(touchX, touchY, MotionEvent.ACTION_HOVER_ENTER, 0);
+
+        // Act
+
+        boolean actualResult = viewGroup.onInterceptHoverEvent(event);
+        event.recycle();
+
+        // Assert
+
+        assertEquals(actualResult, intercepts);
+    }
+
+    @Test
+    public void onInterceptTouchEvent_verticalCanScroll_intercepts() {
+        onInterceptTouchEvent_scrollabilityAffectsResult(true, true, true);
+    }
+
+    @Test
+    public void onInterceptTouchEvent_verticalCantScroll_doesntIntercept() {
+        onInterceptTouchEvent_scrollabilityAffectsResult(true, false, false);
+    }
+
+    @Test
+    public void onInterceptTouchEvent_horizontalCanScroll_intercepts() {
+        onInterceptTouchEvent_scrollabilityAffectsResult(false, true, true);
+    }
+
+    @Test
+    public void onInterceptTouchEvent_horizontalCantScroll_doesntIntercept() {
+        onInterceptTouchEvent_scrollabilityAffectsResult(false, false, false);
+    }
+
+    private void onInterceptTouchEvent_scrollabilityAffectsResult(boolean vertical,
+            boolean canScroll, boolean intercepts) {
+        int range = canScroll ? 101 : 100;
+        int thumbLength = ScrollBarUtils.getThumbLength(1, 10, 100, range);
+
+        PointerIcon expectedPointerIcon = PointerIcon.getSystemIcon(mContext,
+                PointerIcon.TYPE_HAND);
+
+        final ScrollTestView viewGroup = spy(new ScrollTestView(mContext));
+        viewGroup.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        viewGroup.setHorizontalScrollBarEnabled(true);
+        viewGroup.setVerticalScrollBarEnabled(true);
+        viewGroup.setScrollBarSize(10);
+        viewGroup.setPointerIcon(expectedPointerIcon);
+        viewGroup.layout(0, 0, 100, 100);
+
+        when(viewGroup.computeVerticalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeVerticalScrollRange()).thenReturn(range);
+        when(viewGroup.computeHorizontalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeHorizontalScrollRange()).thenReturn(range);
+
+        int touchX = vertical ? 95 : thumbLength / 2;
+        int touchY = vertical ? thumbLength / 2 : 95;
+        MotionEvent event = EventUtils.generateMouseEvent(touchX, touchY, MotionEvent.ACTION_DOWN,
+                MotionEvent.BUTTON_PRIMARY);
+
+        // Act
+
+        boolean actualResult = viewGroup.onInterceptTouchEvent(event);
+        event.recycle();
+
+        // Assert
+
+        assertEquals(intercepts, actualResult);
+    }
+
+    @Test
+    public void onResolvePointerIcon_verticalCanScroll_pointerIsArrow() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(true, true, true);
+    }
+
+    @Test
+    public void onResolvePointerIcon_verticalCantScroll_pointerIsProperty() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(true, false, false);
+    }
+
+    @Test
+    public void onResolvePointerIcon_horizontalCanScroll_pointerIsArrow() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(false, true, true);
+    }
+
+    @Test
+    public void onResolvePointerIcon_horizontalCantScroll_pointerIsProperty() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(false, false, false);
+    }
+
+    private void onResolvePointerIcon_scrollabilityAffectsPointerIcon(boolean vertical,
+            boolean canScroll, boolean pointerIsSystemArrow) {
+
+        // Arrange
+
+        int range = canScroll ? 101 : 100;
+        int thumbLength = ScrollBarUtils.getThumbLength(1, 10, 100, range);
+
+        PointerIcon expectedPointerIcon = PointerIcon.getSystemIcon(mContext,
+                PointerIcon.TYPE_HAND);
+
+        final ScrollTestView viewGroup = spy(new ScrollTestView(mContext));
+        viewGroup.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        viewGroup.setHorizontalScrollBarEnabled(true);
+        viewGroup.setVerticalScrollBarEnabled(true);
+        viewGroup.setScrollBarSize(10);
+        viewGroup.setPointerIcon(expectedPointerIcon);
+        viewGroup.layout(0, 0, 100, 100);
+
+        when(viewGroup.computeVerticalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeVerticalScrollRange()).thenReturn(range);
+        when(viewGroup.computeHorizontalScrollExtent()).thenReturn(100);
+        when(viewGroup.computeHorizontalScrollRange()).thenReturn(range);
+
+        int touchX = vertical ? 95 : thumbLength / 2;
+        int touchY = vertical ? thumbLength / 2 : 95;
+        MotionEvent event =
+                EventUtils.generateMouseEvent(touchX, touchY, MotionEvent.ACTION_HOVER_ENTER, 0);
+
+        // Act
+
+        PointerIcon actualResult = viewGroup.onResolvePointerIcon(event, 0);
+        event.recycle();
+
+        // Assert
+
+        if (pointerIsSystemArrow) {
+            assertEquals(PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW), actualResult);
+        } else {
+            assertEquals(expectedPointerIcon, actualResult);
+        }
+    }
+
+
+    @Test
     public void testOnDescendantInvalidated() throws Throwable {
         Activity activity = null;
         try {
@@ -1926,7 +2094,6 @@
 
     class MockCanvas extends Canvas {
 
-        public boolean mIsSaveCalled;
         public int mLeft;
         public int mTop;
         public int mRight;
@@ -1948,18 +2115,6 @@
         }
 
         @Override
-        public int save() {
-            mIsSaveCalled = true;
-            return super.save();
-        }
-
-        @Override
-        public int save(int saveFlags) {
-            mIsSaveCalled = true;
-            return super.save(saveFlags);
-        }
-
-        @Override
         public boolean clipRect(int left, int top, int right, int bottom) {
             mLeft = left;
             mTop = top;
@@ -1987,7 +2142,6 @@
         mMockViewGroup.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
         mMockViewGroup.dispatchDraw(canvas);
         //check that the clip region does not contain the padding area
-        assertTrue(canvas.mIsSaveCalled);
         assertEquals(10, canvas.mLeft);
         assertEquals(20, canvas.mTop);
         assertEquals(-frameLeft, canvas.mRight);
@@ -1996,7 +2150,6 @@
         mMockViewGroup.setClipToPadding(false);
         canvas = new MockCanvas();
         mMockViewGroup.dispatchDraw(canvas);
-        assertFalse(canvas.mIsSaveCalled);
         assertEquals(0, canvas.mLeft);
         assertEquals(0, canvas.mTop);
         assertEquals(0, canvas.mRight);
@@ -2613,6 +2766,128 @@
         assertEquals(5, resetResolvedDrawablesCount);
     }
 
+    @UiThreadTest
+    @Test
+    public void testLayoutNotCalledWithSuppressLayoutTrue() {
+        mMockViewGroup.isRequestLayoutCalled = false;
+        mMockViewGroup.suppressLayout(true);
+        mMockViewGroup.layout(0, 0, 100, 100);
+
+        assertTrue(mMockViewGroup.isLayoutSuppressed());
+        assertFalse(mMockViewGroup.isOnLayoutCalled);
+        assertFalse(mMockViewGroup.isRequestLayoutCalled);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testLayoutCalledAfterSettingBackSuppressLayoutToFalseTrue() {
+        mMockViewGroup.suppressLayout(true);
+        mMockViewGroup.suppressLayout(false);
+        mMockViewGroup.layout(0, 0, 100, 100);
+
+        assertFalse(mMockViewGroup.isLayoutSuppressed());
+        assertTrue(mMockViewGroup.isOnLayoutCalled);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testRequestLayoutCalledAfterSettingSuppressToFalseWhenItWasCalledWithTrue() {
+        mMockViewGroup.isRequestLayoutCalled = false;
+        mMockViewGroup.suppressLayout(true);
+        // now we call layout while in suppressed state
+        mMockViewGroup.layout(0, 0, 100, 100);
+        // then we undo suppressing. it should call requestLayout as we swallowed one layout call
+        mMockViewGroup.suppressLayout(false);
+
+        assertTrue(mMockViewGroup.isRequestLayoutCalled);
+    }
+
+    @UiThreadTest
+    @Ignore("Turn on once ViewRootImpl.USE_NEW_INSETS is switched to true")
+    @Test
+    public void testDispatchInsets_affectsChildren() {
+        View v1 = new View(mContext);
+        mMockViewGroup.addView(v1);
+
+        mMockViewGroup.setOnApplyWindowInsetsListener((v, insets) -> insets.inset(0, 0, 0, 10));
+
+        OnApplyWindowInsetsListener listenerMock = mock(OnApplyWindowInsetsListener.class);
+        v1.setOnApplyWindowInsetsListener(listenerMock);
+
+        WindowInsets insets = new WindowInsets.Builder().setSystemWindowInsets(
+                Insets.of(10, 10, 10, 10)).build();
+        mMockViewGroup.dispatchApplyWindowInsets(insets);
+        verify(listenerMock).onApplyWindowInsets(any(),
+                eq(new WindowInsets.Builder()
+                        .setSystemWindowInsets(Insets.of(10, 10, 10, 0)).build()));
+    }
+
+    @UiThreadTest
+    @Ignore("Turn on once ViewRootImpl.USE_NEW_INSETS is switched to true")
+    @Test
+    public void testDispatchInsets_doesntAffectSiblings() {
+        View v1 = new View(mContext);
+        View v2 = new View(mContext);
+        mMockViewGroup.addView(v1);
+        mMockViewGroup.addView(v2);
+
+        v1.setOnApplyWindowInsetsListener((v, insets) -> insets.inset(0, 0, 0, 10));
+
+        OnApplyWindowInsetsListener listenerMock = mock(OnApplyWindowInsetsListener.class);
+        v2.setOnApplyWindowInsetsListener(listenerMock);
+
+        WindowInsets insets = new WindowInsets.Builder().setSystemWindowInsets(
+                Insets.of(10, 10, 10, 10)).build();
+        mMockViewGroup.dispatchApplyWindowInsets(insets);
+        verify(listenerMock).onApplyWindowInsets(any(),
+                eq(new WindowInsets.Builder()
+                        .setSystemWindowInsets(Insets.of(10, 10, 10, 10)).build()));
+    }
+
+    @UiThreadTest
+    @Ignore("Turn on once ViewRootImpl.USE_NEW_INSETS is switched to true")
+    @Test
+    public void testDispatchInsets_doesntAffectParentSiblings() {
+        ViewGroup v1 = new MockViewGroup(mContext);
+        View v11 = new View(mContext);
+        View v2 = new View(mContext);
+        mMockViewGroup.addView(v1);
+        v1.addView(v11);
+        mMockViewGroup.addView(v2);
+
+        v11.setOnApplyWindowInsetsListener((v, insets) -> insets.inset(0, 0, 0, 10));
+
+        OnApplyWindowInsetsListener listenerMock = mock(OnApplyWindowInsetsListener.class);
+        v2.setOnApplyWindowInsetsListener(listenerMock);
+
+        WindowInsets insets = new WindowInsets.Builder().setSystemWindowInsets(
+                Insets.of(10, 10, 10, 10)).build();
+        mMockViewGroup.dispatchApplyWindowInsets(insets);
+        verify(listenerMock).onApplyWindowInsets(any(),
+                eq(new WindowInsets.Builder()
+                        .setSystemWindowInsets(Insets.of(10, 10, 10, 10)).build()));
+    }
+
+    @UiThreadTest
+    @Ignore("Turn on once ViewRootImpl.USE_NEW_INSETS is switched to true")
+    @Test
+    public void testDispatchInsets_consumeDoesntStopDispatch() {
+        View v1 = new View(mContext);
+        mMockViewGroup.addView(v1);
+
+        mMockViewGroup.setOnApplyWindowInsetsListener(
+                (v, insets) -> insets.consumeSystemWindowInsets());
+
+        OnApplyWindowInsetsListener listenerMock = mock(OnApplyWindowInsetsListener.class);
+        v1.setOnApplyWindowInsetsListener(listenerMock);
+
+        WindowInsets insets = new WindowInsets.Builder().setSystemWindowInsets(
+                Insets.of(10, 10, 10, 10)).build();
+        mMockViewGroup.dispatchApplyWindowInsets(insets);
+        verify(listenerMock).onApplyWindowInsets(any(),
+                eq(new WindowInsets.Builder().build()));
+    }
+
     static class MockTextView extends TextView {
 
         public boolean isClearFocusCalled;
@@ -3062,8 +3337,9 @@
         }
 
         @Override
-        public boolean setFrame(int left, int top, int right, int bottom) {
-            return super.setFrame(left, top, right, bottom);
+        public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+            isOnDescendantInvalidatedCalled = true;
+            super.onDescendantInvalidated(child, target);
         }
 
         @Override
@@ -3075,12 +3351,6 @@
         public boolean isChildrenDrawnWithCacheEnabled() {
             return super.isChildrenDrawnWithCacheEnabled();
         }
-
-        @Override
-        public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
-            isOnDescendantInvalidatedCalled = true;
-            super.onDescendantInvalidated(child, target);
-        }
     }
 
     static class MockView2 extends View {
@@ -3185,6 +3455,47 @@
         }
     }
 
+    public static class ScrollTestView extends ViewGroup {
+        public ScrollTestView(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+
+        }
+
+        @Override
+        public boolean awakenScrollBars() {
+            return super.awakenScrollBars();
+        }
+
+        @Override
+        public int computeHorizontalScrollRange() {
+            return super.computeHorizontalScrollRange();
+        }
+
+        @Override
+        public int computeHorizontalScrollExtent() {
+            return super.computeHorizontalScrollExtent();
+        }
+
+        @Override
+        public int computeVerticalScrollRange() {
+            return super.computeVerticalScrollRange();
+        }
+
+        @Override
+        public int computeVerticalScrollExtent() {
+            return super.computeVerticalScrollExtent();
+        }
+
+        @Override
+        protected int getHorizontalScrollbarHeight() {
+            return super.getHorizontalScrollbarHeight();
+        }
+    }
+
     @Override
     public void setResult(int resultCode) {
         synchronized (mSync) {
diff --git a/tests/tests/view/src/android/view/cts/ViewSourceLayoutTest.java b/tests/tests/view/src/android/view/cts/ViewSourceLayoutTest.java
new file mode 100644
index 0000000..3a6aa4c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/ViewSourceLayoutTest.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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Resources;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ViewSourceLayoutTest {
+    @Rule
+    public ActivityTestRule<ViewSourceLayoutTestActivity> mActivityRule =
+            new ActivityTestRule<>(ViewSourceLayoutTestActivity.class);
+
+    @Test
+    public void testGetSourceLayout() {
+        ViewSourceLayoutTestActivity activity = mActivityRule.getActivity();
+        View rootView = activity.findViewById(R.id.view_root);
+        assertEquals(R.layout.view_source_layout_test_layout, rootView.getSourceLayoutResId());
+
+        View view1 = activity.findViewById(R.id.view1);
+        assertEquals(R.layout.view_source_layout_test_layout, view1.getSourceLayoutResId());
+
+        View view2 = activity.findViewById(R.id.view2);
+        assertEquals(R.layout.view_source_layout_test_include_layout, view2.getSourceLayoutResId());
+
+        View view3 = new View(activity);
+        assertEquals(Resources.ID_NULL, view3.getSourceLayoutResId());
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewSourceLayoutTestActivity.java b/tests/tests/view/src/android/view/cts/ViewSourceLayoutTestActivity.java
new file mode 100644
index 0000000..efb6f02
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/ViewSourceLayoutTestActivity.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.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class ViewSourceLayoutTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.view_source_layout_test_layout);
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 346a513..3168c60 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -18,6 +18,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.assertNotSame;
 import static org.junit.Assert.assertNull;
@@ -49,6 +50,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -96,6 +98,8 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
+import android.view.cts.util.EventUtils;
+import android.view.cts.util.ScrollBarUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
@@ -118,6 +122,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -406,12 +412,72 @@
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, view);
         assertTrue(view.hasCalledOnTouchEvent());
         verify(delegate, times(1)).onTouchEvent(any());
+        CtsMouseUtil.emulateHoverOnView(mInstrumentation, view, view.getWidth() / 2,
+                view.getHeight() / 2);
+        assertTrue(view.hasCalledOnHoverEvent());
+        verifyZeroInteractions(delegate);
 
         view.setTouchDelegate(null);
         assertNull(view.getTouchDelegate());
     }
 
     @Test
+    public void onHoverEvent_verticalCanScroll_awakenScrollBarsCalled() {
+        onHoverEvent_awakensScrollBars(true, true, true);
+    }
+
+    @Test
+    public void onHoverEvent_verticalCantScroll_awakenScrollBarsNotCalled() {
+        onHoverEvent_awakensScrollBars(true, false, false);
+    }
+
+    @Test
+    public void onHoverEvent_horizontalCanScroll_awakenScrollBarsCalled() {
+        onHoverEvent_awakensScrollBars(false, true, true);
+    }
+
+    @Test
+    public void onHoverEvent_horizontalCantScroll_awakenScrollBarsNotCalled() {
+        onHoverEvent_awakensScrollBars(false, false, false);
+    }
+
+    private void onHoverEvent_awakensScrollBars(boolean vertical, boolean canScroll,
+            boolean awakenScrollBarsCalled) {
+
+        // Arrange
+
+        final ScrollTestView view = spy(new ScrollTestView(mContext));
+        view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        view.setHorizontalScrollBarEnabled(true);
+        view.setVerticalScrollBarEnabled(true);
+        view.setScrollBarSize(10);
+        view.layout(0, 0, 100, 100);
+
+        when(view.computeVerticalScrollExtent()).thenReturn(100);
+        when(view.computeVerticalScrollRange()).thenReturn(canScroll ? 101 : 100);
+        when(view.computeHorizontalScrollExtent()).thenReturn(100);
+        when(view.computeHorizontalScrollRange()).thenReturn(canScroll ? 101 : 100);
+
+        int x = vertical ? 95 : 50;
+        int y = vertical ? 50 : 95;
+
+        MotionEvent event = EventUtils.generateMouseEvent(x, y, MotionEvent.ACTION_HOVER_ENTER, 0);
+
+        // Act
+
+        view.onHoverEvent(event);
+        event.recycle();
+
+        // Assert
+
+        if (awakenScrollBarsCalled) {
+            verify(view).awakenScrollBars();
+        } else {
+            verify(view, never()).awakenScrollBars();
+        }
+    }
+
+    @Test
     public void testMouseEventCallsGetPointerIcon() {
         final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
 
@@ -510,6 +576,69 @@
     }
 
     @Test
+    public void onResolvePointerIcon_verticalCanScroll_pointerIsArrow() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(true, true, true);
+    }
+
+    @Test
+    public void onResolvePointerIcon_verticalCantScroll_pointerIsProperty() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(true, false, false);
+    }
+
+    @Test
+    public void onResolvePointerIcon_horizontalCanScroll_pointerIsArrow() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(false, true, true);
+    }
+
+    @Test
+    public void onResolvePointerIcon_horizontalCantScroll_pointerIsProperty() {
+        onResolvePointerIcon_scrollabilityAffectsPointerIcon(false, false, false);
+    }
+
+    private void onResolvePointerIcon_scrollabilityAffectsPointerIcon(boolean vertical,
+            boolean canScroll, boolean pointerIsSystemArrow) {
+
+        // Arrange
+
+        int range = canScroll ? 101 : 100;
+        int thumbLength = ScrollBarUtils.getThumbLength(1, 10, 100, range);
+
+        PointerIcon expectedPointerIcon = PointerIcon.getSystemIcon(mContext,
+                PointerIcon.TYPE_HAND);
+
+        final ScrollTestView view = spy(new ScrollTestView(mContext));
+        view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        view.setHorizontalScrollBarEnabled(true);
+        view.setVerticalScrollBarEnabled(true);
+        view.setScrollBarSize(10);
+        view.setPointerIcon(expectedPointerIcon);
+        view.layout(0, 0, 100, 100);
+
+        when(view.computeVerticalScrollExtent()).thenReturn(100);
+        when(view.computeVerticalScrollRange()).thenReturn(range);
+        when(view.computeHorizontalScrollExtent()).thenReturn(100);
+        when(view.computeHorizontalScrollRange()).thenReturn(range);
+
+        int touchX = vertical ? 95 : thumbLength / 2;
+        int touchY = vertical ? thumbLength / 2 : 95;
+        MotionEvent event =
+                EventUtils.generateMouseEvent(touchX, touchY, MotionEvent.ACTION_HOVER_ENTER, 0);
+
+        // Act
+
+        PointerIcon actualResult = view.onResolvePointerIcon(event, 0);
+        event.recycle();
+
+        // Assert
+
+        if (pointerIsSystemArrow) {
+            assertEquals(PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW), actualResult);
+        } else {
+            assertEquals(expectedPointerIcon, actualResult);
+        }
+    }
+
+    @Test
     public void testCreatePointerIcons() {
         assertSystemPointerIcon(PointerIcon.TYPE_NULL);
         assertSystemPointerIcon(PointerIcon.TYPE_DEFAULT);
@@ -4298,7 +4427,7 @@
 
     private boolean startDragAndDrop(View view, View.DragShadowBuilder shadowBuilder) {
         final Point size = new Point();
-        mActivity.getDisplay().getSize(size);
+        mActivity.getWindowManager().getDefaultDisplay().getSize(size);
         final MotionEvent event = MotionEvent.obtain(
                 SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_DOWN, size.x / 2, size.y / 2, 1);
@@ -4581,6 +4710,16 @@
     }
 
     @Test
+    public void testTransitionAlpha() {
+        View view = new View(mContext);
+        view.setAlpha(1f);
+        view.setTransitionAlpha(0.5f);
+
+        assertEquals(1f, view.getAlpha(), 0.0001f);
+        assertEquals(0.5f, view.getTransitionAlpha(), 0.0001f);
+    }
+
+    @Test
     public void testSetGetOutlineShadowColor() {
         ViewGroup group = (ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.view_outlineshadowcolor, null);
@@ -4606,6 +4745,60 @@
     }
 
     @Test
+    public void testTransformMatrixToGlobal() {
+        final View view = mActivity.findViewById(R.id.transform_matrix_view);
+        final Matrix initialMatrix = view.getMatrix();
+        assertNotNull(initialMatrix);
+
+        final Matrix newMatrix = new Matrix(initialMatrix);
+        float[] initialValues = new float[9];
+        newMatrix.getValues(initialValues);
+
+        view.transformMatrixToGlobal(newMatrix);
+        float[] newValues = new float[9];
+        newMatrix.getValues(newValues);
+        int[] location = new int[2];
+        view.getLocationInWindow(location);
+        boolean hasChanged = false;
+        for (int i = 0; i < 9; ++i) {
+            if (initialValues[i] != newValues[i]) {
+                hasChanged = true;
+            }
+        }
+        assertTrue("Matrix should be changed", hasChanged);
+        assertEquals("Matrix should reflect position in window",
+                location[1], newValues[5], 0.001);
+    }
+
+    @Test
+    public void testTransformMatrixToLocal() {
+        final View view1 = mActivity.findViewById(R.id.transform_matrix_view);
+        final View view2 = mActivity.findViewById(R.id.transform_matrix_view_2);
+        final Matrix initialMatrix = view1.getMatrix();
+        assertNotNull(initialMatrix);
+
+        final Matrix globalMatrix = new Matrix(initialMatrix);
+
+        view1.transformMatrixToGlobal(globalMatrix);
+        float[] globalValues = new float[9];
+        globalMatrix.getValues(globalValues);
+
+        view2.transformMatrixToLocal(globalMatrix);
+        float[] localValues = new float[9];
+        globalMatrix.getValues(localValues);
+
+        boolean hasChanged = false;
+        for (int i = 0; i < 9; ++i) {
+            if (globalValues[i] != localValues[i]) {
+                hasChanged = true;
+            }
+        }
+        assertTrue("Matrix should be changed", hasChanged);
+        assertEquals("The first view should be 10px above the second view",
+                -10, localValues[5], 0.001);
+    }
+
+    @Test
     public void testPivot() {
         View view = new View(mContext);
         int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
@@ -4638,6 +4831,30 @@
         assertFalse(view.isPivotSet());
     }
 
+    @Test
+    public void testSetLeftTopRightBottom() {
+        View view = new View(mContext);
+        view.setLeftTopRightBottom(1, 2, 3, 4);
+
+        assertEquals(1, view.getLeft());
+        assertEquals(2, view.getTop());
+        assertEquals(3, view.getRight());
+        assertEquals(4, view.getBottom());
+    }
+
+    @Test
+    public void testGetUniqueDrawingId() {
+        View view1 = new View(mContext);
+        View view2 = new View(mContext);
+        Set<Long> idSet = new HashSet<>(50);
+
+        assertNotEquals(view1.getUniqueDrawingId(), view2.getUniqueDrawingId());
+
+        for (int i = 0; i < 50; i++) {
+            assertTrue(idSet.add(new View(mContext).getUniqueDrawingId()));
+        }
+    }
+
     private static class MockDrawable extends Drawable {
         private boolean mCalledSetTint = false;
 
@@ -5012,6 +5229,42 @@
         }
     }
 
+    public static class ScrollTestView extends View {
+        public ScrollTestView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean awakenScrollBars() {
+            return super.awakenScrollBars();
+        }
+
+        @Override
+        public int computeHorizontalScrollRange() {
+            return super.computeHorizontalScrollRange();
+        }
+
+        @Override
+        public int computeHorizontalScrollExtent() {
+            return super.computeHorizontalScrollExtent();
+        }
+
+        @Override
+        public int computeVerticalScrollRange() {
+            return super.computeVerticalScrollRange();
+        }
+
+        @Override
+        public int computeVerticalScrollExtent() {
+            return super.computeVerticalScrollExtent();
+        }
+
+        @Override
+        protected int getHorizontalScrollbarHeight() {
+            return super.getHorizontalScrollbarHeight();
+        }
+    }
+
     private static final Class<?> ASYNC_INFLATE_VIEWS[] = {
         android.app.FragmentBreadCrumbs.class,
 // DISABLED because it doesn't have a AppWidgetHostView(Context, AttributeSet)
diff --git a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
index 4aa7377..4865dfc 100644
--- a/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTreeObserverTest.java
@@ -42,6 +42,7 @@
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -130,6 +131,21 @@
         verify(listener, times(1)).onDraw();
     }
 
+    @Test
+    public void testFrameCommitListener() throws Throwable {
+        mViewTreeObserver = mLinearLayout.getViewTreeObserver();
+
+        final Runnable activeListener = mock(Runnable.class);
+        final Runnable removedListener = mock(Runnable.class);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mLinearLayout, () -> {
+            mViewTreeObserver.registerFrameCommitCallback(activeListener);
+            mViewTreeObserver.registerFrameCommitCallback(removedListener);
+            mViewTreeObserver.unregisterFrameCommitCallback(removedListener);
+        });
+        verify(activeListener, within(TIMEOUT_MS)).run();
+        verifyZeroInteractions(removedListener);
+    }
+
     @Test(expected=IllegalStateException.class)
     public void testRemoveOnDrawListenerInDispatch() {
         final View view = new View(mActivity);
diff --git a/tests/tests/view/src/android/view/cts/WindowInsetsTest.java b/tests/tests/view/src/android/view/cts/WindowInsetsTest.java
new file mode 100644
index 0000000..d9c6bba
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/WindowInsetsTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * Test {@link WindowInsets}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WindowInsetsTest {
+
+    private static final DisplayCutout CUTOUT = new DisplayCutout(new Rect(0, 10, 0, 0),
+            Collections.singletonList(new Rect(5, 0, 15, 10)));
+    private static final DisplayCutout CUTOUT2 = new DisplayCutout(new Rect(0, 15, 0, 0),
+            Collections.singletonList(new Rect(5, 0, 15, 15)));
+    private static final int INSET_LEFT = 1;
+    private static final int INSET_TOP = 2;
+    private static final int INSET_RIGHT = 3;
+    private static final int INSET_BOTTOM = 4;
+
+    @Test
+    public void testBuilder() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT)
+                .build();
+
+        assertEquals(Insets.of(1, 2, 3, 4), insets.getSystemWindowInsets());
+        assertEquals(Insets.of(5, 6, 7, 8), insets.getStableInsets());
+        assertSame(CUTOUT, insets.getDisplayCutout());
+    }
+
+    @Test
+    public void testBuilder_copy() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT)
+                .build();
+        final WindowInsets copy = new WindowInsets.Builder(insets).build();
+
+        assertEquals(insets, copy);
+    }
+
+    @Test
+    public void testBuilder_consumed() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .build();
+
+        assertFalse(insets.hasSystemWindowInsets());
+        assertFalse(insets.hasStableInsets());
+        assertNull(insets.getDisplayCutout());
+        assertTrue(insets.isConsumed());
+    }
+
+    @Test
+    public void testBuilder_emptyCutout() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setDisplayCutout(null)
+                .build();
+
+        assertFalse(insets.hasSystemWindowInsets());
+        assertFalse(insets.hasStableInsets());
+
+        assertNull(insets.getDisplayCutout());
+        assertFalse(insets.isConsumed());
+        assertTrue(insets.consumeDisplayCutout().isConsumed());
+    }
+
+    @Test
+    public void testBuilder_producesImmutableWindowInsets() {
+        final WindowInsets.Builder builder = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT);
+        final WindowInsets insets = builder.build();
+
+        builder.setSystemWindowInsets(Insets.NONE);
+        builder.setStableInsets(Insets.NONE);
+        builder.setDisplayCutout(null);
+
+        assertEquals(Insets.of(1, 2, 3, 4), insets.getSystemWindowInsets());
+        assertEquals(Insets.of(5, 6, 7, 8), insets.getStableInsets());
+        assertSame(CUTOUT, insets.getDisplayCutout());
+    }
+
+    @Test
+    public void testEquality() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insets2 = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        assertNotSame("Test setup failed, insets and insets2 should not be identical",
+                insets, insets2);
+
+        assertEquals(insets, insets2);
+        assertEquals(insets.hashCode(), insets2.hashCode());
+    }
+
+    @Test
+    public void testInEquality_consuming() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        assertNotEquals(insets, insets.consumeSystemWindowInsets());
+        assertNotEquals(insets, insets.consumeStableInsets());
+        assertNotEquals(insets, insets.consumeDisplayCutout());
+    }
+
+    @Test
+    public void testConsume_systemWindowInsets() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets consumed = insets.consumeSystemWindowInsets();
+
+        assertEquals(Insets.NONE, consumed.getSystemWindowInsets());
+        assertEquals(insets.getStableInsets(), consumed.getStableInsets());
+        assertEquals(insets.getDisplayCutout(), consumed.getDisplayCutout());
+    }
+
+    @Test
+    public void testConsume_stableInsets() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets consumed = insets.consumeStableInsets();
+
+        assertEquals(insets.getSystemWindowInsets(), consumed.getSystemWindowInsets());
+        assertEquals(Insets.NONE, consumed.getStableInsets());
+        assertEquals(insets.getDisplayCutout(), consumed.getDisplayCutout());
+    }
+
+    @Test
+    public void testConsume_displayCutout() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets consumed = insets.consumeDisplayCutout();
+
+        assertEquals(insets.getSystemWindowInsets(), consumed.getSystemWindowInsets());
+        assertEquals(insets.getStableInsets(), consumed.getStableInsets());
+        assertNull(consumed.getDisplayCutout());
+    }
+
+    @Test
+    public void testConsistency_individualSides() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        assertEquals(insets.getSystemWindowInsets(), Insets.of(
+                insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
+        assertEquals(insets.getStableInsets(), Insets.of(
+                insets.getStableInsetLeft(), insets.getStableInsetTop(),
+                insets.getStableInsetRight(), insets.getStableInsetBottom()));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testReplacingConsumedSystemWindowInset_staysZeroAndConsumed() {
+        final WindowInsets consumed = new WindowInsets.Builder().build();
+        final WindowInsets replaced = consumed.replaceSystemWindowInsets(new Rect(1, 2, 3, 4));
+
+        assertEquals(Insets.NONE, replaced.getSystemWindowInsets());
+        assertTrue(replaced.isConsumed());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testReplacingSystemWindowInsets_works() {
+        final WindowInsets replaced = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build()
+                .replaceSystemWindowInsets(new Rect(9, 10, 11, 12));
+        final WindowInsets expected = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(9, 10, 11, 12))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        assertEquals(expected, replaced);
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testReplacingSystemWindowInsets_consistencyAcrossOverloads() {
+        final Rect newInsets = new Rect(9, 10, 11, 12);
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        assertEquals(insets.replaceSystemWindowInsets(newInsets),
+                insets.replaceSystemWindowInsets(newInsets.left, newInsets.top, newInsets.right,
+                        newInsets.bottom));
+    }
+
+    @Test
+    public void testInEquality_difference() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insetsChangedSysWindowInsets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(10, 20, 30, 40))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insetsChangedStableInsets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(50, 60, 70, 80))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insetsChangedCutout = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(1, 2, 3, 4))
+                .setStableInsets(Insets.of(5, 6, 7, 8))
+                .setDisplayCutout(CUTOUT2).build();
+
+        assertNotEquals(insets, insetsChangedSysWindowInsets);
+        assertNotEquals(insets, insetsChangedStableInsets);
+        assertNotEquals(insets, insetsChangedCutout);
+    }
+
+    @Test
+    public void testInset() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(10, 20, 30, 40))
+                .setStableInsets(Insets.of(50, 60, 70, 80))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insetInsets = insets.inset(
+                INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM);
+
+        assertEquals(applyInset(insets.getSystemWindowInsets()),
+                insetInsets.getSystemWindowInsets());
+        assertEquals(applyInset(insets.getStableInsets()), insetInsets.getStableInsets());
+        assertEquals(applyInset(getCutoutSafeInsets(insets)), getCutoutSafeInsets(insetInsets));
+    }
+
+    @Test
+    public void testInset_clipsToZero() {
+        final WindowInsets insets = new WindowInsets.Builder()
+                .setSystemWindowInsets(Insets.of(10, 20, 30, 40))
+                .setStableInsets(Insets.of(50, 60, 70, 80))
+                .setDisplayCutout(CUTOUT).build();
+
+        final WindowInsets insetInsets = insets.inset(1000, 1000, 1000, 1000);
+
+        assertEquals(Insets.NONE, insetInsets.getSystemWindowInsets());
+        assertEquals(Insets.NONE, insetInsets.getStableInsets());
+        assertNull(insetInsets.getDisplayCutout());
+    }
+
+    private static Insets applyInset(Insets res) {
+        return Insets.of(Math.max(0, res.left - INSET_LEFT),
+                Math.max(0, res.top - INSET_TOP),
+                Math.max(0, res.right - INSET_RIGHT),
+                Math.max(0, res.bottom - INSET_BOTTOM));
+    }
+
+    private static Insets getCutoutSafeInsets(WindowInsets insets) {
+        final DisplayCutout dc = insets.getDisplayCutout();
+        return Insets.of(dc.getSafeInsetLeft(), dc.getSafeInsetTop(), dc.getSafeInsetRight(),
+                dc.getSafeInsetBottom());
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/WindowTest.java b/tests/tests/view/src/android/view/cts/WindowTest.java
index 94b3ceb..ecbb973 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -978,17 +978,5 @@
         public int getNavigationBarColor() {
             return 0;
         }
-
-        @Override
-        public void onMultiWindowModeChanged() {
-        }
-
-        @Override
-        public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        }
-
-        @Override
-        public void reportActivityRelaunched() {
-        }
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
index 74ee5ee..3ebfc79 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
@@ -20,7 +20,7 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-public class AnimationTestCase {
+public class AnimationTestCase implements ISurfaceValidatorTestCase {
     private final ViewFactory mViewFactory;
     private final FrameLayout.LayoutParams mLayoutParams;
     private final AnimationFactory mAnimationFactory;
@@ -39,7 +39,7 @@
         mPixelChecker = pixelChecker;
     }
 
-    PixelChecker getChecker() {
+    public PixelChecker getChecker() {
         return mPixelChecker;
     }
 
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 4fc5e04..4d77ae4 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -31,6 +31,7 @@
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
@@ -47,6 +48,11 @@
 import android.view.cts.R;
 import android.widget.FrameLayout;
 
+import org.junit.rules.TestName;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -160,7 +166,7 @@
         return mOnEmbedded ? 100000 : 10000;
     }
 
-    public TestResult runTest(AnimationTestCase animationTestCase) throws Throwable {
+    public TestResult runTest(ISurfaceValidatorTestCase animationTestCase) throws Throwable {
         TestResult testResult = new TestResult();
         if (mOnWatch) {
             /**
@@ -214,7 +220,8 @@
 
             View decorView = getWindow().getDecorView();
             Rect boundsToCheck = new Rect(0, 0, decorView.getWidth(), decorView.getHeight());
-            int[] topLeft = decorView.getLocationOnScreen();
+            int[] topLeft = new int[2];
+            decorView.getLocationOnScreen(topLeft);
             boundsToCheck.offset(topLeft[0], topLeft[1]);
 
             if (boundsToCheck.width() < 90 || boundsToCheck.height() < 90) {
@@ -259,6 +266,61 @@
         return testResult;
     }
 
+    private void saveFailureCaptures(SparseArray<Bitmap> failFrames, TestName name) {
+        if (failFrames.size() == 0) return;
+
+        String directoryName = Environment.getExternalStorageDirectory()
+                + "/" + getClass().getSimpleName()
+                + "/" + name.getMethodName();
+        File testDirectory = new File(directoryName);
+        if (testDirectory.exists()) {
+            String[] children = testDirectory.list();
+            if (children == null) {
+                return;
+            }
+            for (String file : children) {
+                new File(testDirectory, file).delete();
+            }
+        } else {
+            testDirectory.mkdirs();
+        }
+
+        for (int i = 0; i < failFrames.size(); i++) {
+            int frameNr = failFrames.keyAt(i);
+            Bitmap bitmap = failFrames.valueAt(i);
+
+            String bitmapName =  "frame_" + frameNr + ".png";
+            Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
+
+            File file = new File(directoryName, bitmapName);
+            try (FileOutputStream fileStream = new FileOutputStream(file)) {
+                bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
+                fileStream.flush();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void verifyTest(ISurfaceValidatorTestCase testCase, TestName name) throws Throwable {
+        CapturedActivity.TestResult result = runTest(testCase);
+        saveFailureCaptures(result.failures, name);
+
+        float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
+        assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
+                failRatio < 0.95f);
+        assertTrue("Error: " + result.failFrames
+                        + " incorrect frames observed - incorrect positioning",
+                result.failFrames == 0);
+
+        float framesPerSecond = 1.0f * result.passFrames
+                / TimeUnit.MILLISECONDS.toSeconds(getCaptureDurationMs());
+        assertTrue("Error, only " + result.passFrames
+                        + " frames observed, virtual display only capturing at "
+                        + framesPerSecond + " frames per second",
+                result.passFrames > 100);
+    }
+
     private class MediaProjectionCallback extends MediaProjection.Callback {
         @Override
         public void onStop() {
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/ISurfaceValidatorTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/ISurfaceValidatorTestCase.java
new file mode 100644
index 0000000..3b74958
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/ISurfaceValidatorTestCase.java
@@ -0,0 +1,27 @@
+/*
+ * 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.view.cts.surfacevalidator;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+public interface ISurfaceValidatorTestCase {
+    PixelChecker getChecker();
+
+    void start(Context context, FrameLayout parent);
+
+    void end();
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
index 76f0adc..860d11b 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
@@ -15,6 +15,20 @@
  */
 package android.view.cts.surfacevalidator;
 
-public interface PixelChecker {
-    boolean checkPixels(int blackishPixelCount, int width, int height);
-}
\ No newline at end of file
+public abstract class PixelChecker {
+    private PixelColor mPixelColor;
+
+    public PixelChecker() {
+        mPixelColor = new PixelColor();
+    }
+
+    public PixelChecker(int color) {
+        mPixelColor = new PixelColor(color);
+    }
+
+    PixelColor getColor() {
+        return mPixelColor;
+    }
+
+    public abstract boolean checkPixels(int matchingPixelCount, int width, int height);
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelColor.java b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelColor.java
new file mode 100644
index 0000000..c4d4b95
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelColor.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+public class PixelColor {
+    public static final int BLACK = 0xFF000000;
+    public static final int RED = 0xFF0000FF;
+    public static final int GREEN = 0xFF00FF00;
+    public static final int BLUE = 0xFFFF0000;
+    public static final int YELLOW = 0xFF00FFFF;
+    public static final int MAGENTA = 0xFFFF00FF;
+
+    public static final int TRANSPARENT_RED = 0x7F0000FF;
+    public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+    public static final int TRANSPARENT = 0x00000000;
+
+    // Default to black
+    public short mMinAlpha;
+    public short mMaxAlpha;
+    public short mMinRed;
+    public short mMaxRed;
+    public short mMinBlue;
+    public short mMaxBlue;
+    public short mMinGreen;
+    public short mMaxGreen;
+
+    public PixelColor(int color) {
+        short alpha = (short) ((color >> 24) & 0xFF);
+        short blue = (short) ((color >> 16) & 0xFF);
+        short green = (short) ((color >> 8) & 0xFF);
+        short red = (short) (color & 0xFF);
+
+        mMinAlpha = (short) getMinValue(alpha);
+        mMaxAlpha = (short) getMaxValue(alpha);
+        mMinRed = (short) getMinValue(red);
+        mMaxRed = (short) getMaxValue(red);
+        mMinBlue = (short) getMinValue(blue);
+        mMaxBlue = (short) getMaxValue(blue);
+        mMinGreen = (short) getMinValue(green);
+        mMaxGreen = (short) getMaxValue(green);
+    }
+
+    public PixelColor() {
+        this(BLACK);
+    }
+
+    private int getMinValue(short color) {
+        if (color - 4 > 0) {
+            return color - 4;
+        }
+        return 0;
+    }
+
+    private int getMaxValue(short color) {
+        if (color + 4 < 0xFF) {
+            return color + 4;
+        }
+        return 0xFF;
+    }
+
+    public void addToPixelCounter(ScriptC_PixelCounter script) {
+        script.set_MIN_ALPHA(mMinAlpha);
+        script.set_MAX_ALPHA(mMaxAlpha);
+        script.set_MIN_RED(mMinRed);
+        script.set_MAX_RED(mMaxRed);
+        script.set_MIN_BLUE(mMinBlue);
+        script.set_MAX_BLUE(mMaxBlue);
+        script.set_MIN_GREEN(mMinGreen);
+        script.set_MAX_GREEN(mMaxGreen);
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
index 12e024c..b4fe3be 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -17,14 +17,26 @@
 #pragma rs java_package_name(android.view.cts.surfacevalidator)
 #pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
 
-uchar THRESHOLD;
+uchar MIN_ALPHA;
+uchar MAX_ALPHA;
+uchar MIN_RED;
+uchar MAX_RED;
+uchar MIN_GREEN;
+uchar MAX_GREEN;
+uchar MIN_BLUE;
+uchar MAX_BLUE;
 int BOUNDS[4];
 
 static void countBlackishPixelsAccum(int *accum, uchar4 pixel, uint32_t x, uint32_t y) {
 
-    if (pixel.r < THRESHOLD
-            && pixel.g < THRESHOLD
-            && pixel.b < THRESHOLD
+    if (pixel.a <= MAX_ALPHA
+            && pixel.a >= MIN_ALPHA
+            && pixel.r <= MAX_RED
+            && pixel.r >= MIN_RED
+            && pixel.g <= MAX_GREEN
+            && pixel.g >= MIN_GREEN
+            && pixel.b <= MAX_BLUE
+            && pixel.b >= MIN_BLUE
             && x >= BOUNDS[0]
             && x < BOUNDS[2]
             && y >= BOUNDS[1]
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfaceControlTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfaceControlTestCase.java
new file mode 100644
index 0000000..9c8c871
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfaceControlTestCase.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view.cts.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class SurfaceControlTestCase implements ISurfaceValidatorTestCase {
+    private final SurfaceViewFactory mViewFactory;
+    private final FrameLayout.LayoutParams mLayoutParams;
+    private final AnimationFactory mAnimationFactory;
+    private final PixelChecker mPixelChecker;
+    
+    private final int mBufferWidth;
+    private final int mBufferHeight;
+
+    private FrameLayout mParent;
+    private ValueAnimator mAnimator;
+
+    public static abstract class ParentSurfaceConsumer {
+        abstract public void addChildren(SurfaceControl parent);
+    };
+
+    private static class ParentSurfaceHolder implements SurfaceHolder.Callback {
+        ParentSurfaceConsumer mPsc;
+        SurfaceView mSurfaceView;
+
+        ParentSurfaceHolder(ParentSurfaceConsumer psc) {
+            mPsc = psc;
+        }
+
+        public void surfaceCreated(SurfaceHolder holder) {
+            mPsc.addChildren(mSurfaceView.getSurfaceControl());
+        }
+        public void surfaceDestroyed(SurfaceHolder holder) {
+        }
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+    };
+
+    public SurfaceControlTestCase(SurfaceHolder.Callback callback,
+            AnimationFactory animationFactory, PixelChecker pixelChecker,
+            int layoutWidth, int layoutHeight, int bufferWidth, int bufferHeight) {
+        mViewFactory = new SurfaceViewFactory(callback);
+        mLayoutParams =
+                new FrameLayout.LayoutParams(layoutWidth, layoutHeight, Gravity.LEFT | Gravity.TOP);
+        mAnimationFactory = animationFactory;
+        mPixelChecker = pixelChecker;
+        mBufferWidth = bufferWidth;
+        mBufferHeight = bufferHeight;
+    }
+
+    public SurfaceControlTestCase(ParentSurfaceConsumer psc,
+            AnimationFactory animationFactory, PixelChecker pixelChecker,
+            int layoutWidth, int layoutHeight, int bufferWidth, int bufferHeight) {
+        this(new ParentSurfaceHolder(psc), animationFactory, pixelChecker,
+                layoutWidth, layoutHeight, bufferWidth, bufferHeight);
+    }
+
+    public PixelChecker getChecker() {
+        return mPixelChecker;
+    }
+
+    public void start(Context context, FrameLayout parent) {
+        View view = mViewFactory.createView(context);
+        if (mViewFactory.mCallback instanceof ParentSurfaceHolder) {
+            ParentSurfaceHolder psh = (ParentSurfaceHolder) mViewFactory.mCallback;
+            psh.mSurfaceView = (SurfaceView) view;
+        }
+
+        mParent = parent;
+        mParent.addView(view, mLayoutParams);
+
+        if (mAnimationFactory != null) {
+            mAnimator = mAnimationFactory.createAnimator(view);
+            mAnimator.start();
+        }
+    }
+
+    public void end() {
+        if (mAnimator != null) {
+            mAnimator.end();
+            mAnimator = null;
+        }
+        mParent.removeAllViews();
+    }
+
+    private class SurfaceViewFactory implements ViewFactory {
+        private SurfaceHolder.Callback mCallback;
+
+        SurfaceViewFactory(SurfaceHolder.Callback callback) {
+            mCallback = callback;
+        }
+
+        public View createView(Context context) {
+            SurfaceView surfaceView = new SurfaceView(context);
+
+            // prevent transparent region optimization, which is invalid for a SurfaceView moving
+            // around
+            surfaceView.setWillNotDraw(false);
+
+            surfaceView.getHolder().setFixedSize(mBufferWidth, mBufferHeight);
+            surfaceView.getHolder().addCallback(mCallback);
+
+            return surfaceView;
+        }
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
index 1a8bc0d..c2fb007 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -39,9 +39,6 @@
      */
     private static final int NUM_FIRST_FRAMES_SKIPPED = 8;
 
-    // If no channel is greater than this value, pixel will be considered 'blackish'.
-    private static final short PIXEL_CHANNEL_THRESHOLD = 4;
-
     private static final int MAX_CAPTURED_FAILURES = 5;
 
     private final int mWidth;
@@ -120,7 +117,7 @@
         mInPixelsAllocation = createBufferQueueAllocation();
         mScript.set_BOUNDS(new int[] {boundsToCheck.left, boundsToCheck.top,
                 boundsToCheck.right, boundsToCheck.bottom});
-        mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
+        pixelChecker.getColor().addToPixelCounter(mScript);
 
         mInPixelsAllocation.setOnBufferAvailableListener(
                 allocation -> mWorkerHandler.post(mConsumeRunnable));
diff --git a/tests/tests/view/src/android/view/cts/util/EventUtils.java b/tests/tests/view/src/android/view/cts/util/EventUtils.java
new file mode 100644
index 0000000..a3ff488
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/EventUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view.cts.util;
+
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+public class EventUtils {
+
+    public static MotionEvent generateMouseEvent(int x, int y, int eventType, int buttonState) {
+
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
+        pointerCoords[0] = new MotionEvent.PointerCoords();
+        pointerCoords[0].x = x;
+        pointerCoords[0].y = y;
+
+        MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[1];
+        pp[0] = new MotionEvent.PointerProperties();
+        pp[0].id = 0;
+
+        return MotionEvent.obtain(0, SystemClock.uptimeMillis(), eventType, 1, pp,
+                pointerCoords, 0, buttonState, 0, 0, 0, 0, InputDevice.SOURCE_MOUSE, 0);
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/util/ScrollBarUtils.java b/tests/tests/view/src/android/view/cts/util/ScrollBarUtils.java
new file mode 100644
index 0000000..00ecfb1
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/ScrollBarUtils.java
@@ -0,0 +1,32 @@
+/*
+ * 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.view.cts.util;
+
+/**
+ * Copy of com.android.internal.widget.ScrollBarUtils
+ */
+public class ScrollBarUtils {
+    public static int getThumbLength(int size, int thickness, int extent, int range) {
+        // Avoid the tiny thumb.
+        final int minLength = thickness * 2;
+        int length = Math.round((float) size * extent / range);
+        if (length < minLength) {
+            length = minLength;
+        }
+        return length;
+    }
+}
diff --git a/tests/tests/view/src/android/view/inspector/cts/IntEnumMappingTest.java b/tests/tests/view/src/android/view/inspector/cts/IntEnumMappingTest.java
new file mode 100644
index 0000000..98c8dd7
--- /dev/null
+++ b/tests/tests/view/src/android/view/inspector/cts/IntEnumMappingTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.view.inspector.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inspector.IntEnumMapping;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link IntEnumMapping}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IntEnumMappingTest {
+    @Test
+    public void testSimpleValue() {
+        IntEnumMapping mapping = new IntEnumMapping.Builder()
+                .addValue("ZERO", 0)
+                .build();
+        assertEquals("ZERO", mapping.get(0));
+    }
+
+    @Test
+    public void testMissingValue() {
+        IntEnumMapping mapping = new IntEnumMapping.Builder()
+                .addValue("ZERO", 0)
+                .build();
+        assertNull(mapping.get(1));
+    }
+
+    @Test
+    public void testBuilderReuse() {
+        IntEnumMapping.Builder builder = new IntEnumMapping.Builder();
+
+        builder.addValue("ONE", 1);
+        IntEnumMapping mapping1 = builder.build();
+
+        assertEquals("ONE", mapping1.get(1));
+
+        builder.addValue("TWO", 2);
+        IntEnumMapping mapping2 = builder.build();
+
+        assertEquals("ONE", mapping2.get(1));
+        assertEquals("TWO", mapping2.get(2));
+        assertNull(mapping1.get(2));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullName() {
+        new IntEnumMapping.Builder().addValue(null, 0);
+    }
+}
diff --git a/tests/tests/view/src/android/view/inspector/cts/IntFlagMappingTest.java b/tests/tests/view/src/android/view/inspector/cts/IntFlagMappingTest.java
new file mode 100644
index 0000000..ce93d30
--- /dev/null
+++ b/tests/tests/view/src/android/view/inspector/cts/IntFlagMappingTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.view.inspector.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inspector.IntFlagMapping;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for {@link IntFlagMapping}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class IntFlagMappingTest {
+    @Test
+    public void testNonExclusiveFlags() {
+        IntFlagMapping mapping = new IntFlagMapping.Builder()
+                .addFlag("ONE", 1)
+                .addFlag("TWO", 2)
+                .build();
+
+        assertEmpty(mapping.get(0));
+        assertEquals(setOf("ONE"), mapping.get(1));
+        assertEquals(setOf("TWO"), mapping.get(2));
+        assertEquals(setOf("ONE", "TWO"), mapping.get(3));
+        assertEmpty(mapping.get(4));
+    }
+
+    @Test
+    public void testMutuallyExclusiveFlags() {
+        IntFlagMapping mapping = new IntFlagMapping.Builder()
+                .addFlag("ONE", 1, 3)
+                .addFlag("TWO", 2, 3)
+                .build();
+
+
+        assertEmpty(mapping.get(0));
+        assertEquals(setOf("ONE"), mapping.get(1));
+        assertEquals(setOf("TWO"), mapping.get(2));
+        assertEmpty(mapping.get(3));
+        assertEmpty(mapping.get(4));
+    }
+
+    @Test
+    public void testMixedFlags() {
+        IntFlagMapping mapping = new IntFlagMapping.Builder()
+                .addFlag("ONE", 1, 3)
+                .addFlag("TWO", 2, 3)
+                .addFlag("FOUR", 4)
+                .build();
+
+
+        assertEmpty(mapping.get(0));
+        assertEquals(setOf("ONE"), mapping.get(1));
+        assertEquals(setOf("TWO"), mapping.get(2));
+        assertEmpty(mapping.get(3));
+        assertEquals(setOf("FOUR"), mapping.get(4));
+        assertEquals(setOf("ONE", "FOUR"), mapping.get(5));
+        assertEquals(setOf("TWO", "FOUR"), mapping.get(6));
+        assertEquals(setOf("FOUR"), mapping.get(7));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullName() {
+        new IntFlagMapping.Builder().addFlag(null, 0);
+    }
+
+    private static Set<String> setOf(String... values) {
+        final Set<String> set = new HashSet<>(values.length);
+        set.addAll(Arrays.asList(values));
+        return Collections.unmodifiableSet(set);
+    }
+
+    private static void assertEmpty(Collection collection) {
+        assertTrue(collection.isEmpty());
+    }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
new file mode 100644
index 0000000..8e34571
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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.view.textclassifier.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.app.Person;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.ConversationAction;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.TextClassifier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ConversationActionsTest {
+    private static final String ID = "ID";
+    private static final String CONVERSATION_ID = "conversation_id";
+    private static final String TEXT = "TEXT";
+    private static final Person PERSON = new Person.Builder().setKey(TEXT).build();
+    private static final ZonedDateTime TIME =
+            ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
+    private static final float FLOAT_TOLERANCE = 0.01f;
+
+    private static final Bundle EXTRAS = new Bundle();
+    private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+
+    private static final RemoteAction REMOTE_ACTION = new RemoteAction(
+            Icon.createWithData(new byte[0], 0, 0),
+            TEXT,
+            TEXT,
+            PENDING_INTENT);
+
+    static {
+        EXTRAS.putString(TEXT, TEXT);
+    }
+
+    @Test
+    public void testMessage_full() {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(PERSON)
+                        .setText(TEXT)
+                        .setExtras(EXTRAS)
+                        .setReferenceTime(TIME)
+                        .build();
+
+        ConversationActions.Message recovered = parcelizeDeparcelize(message,
+                ConversationActions.Message.CREATOR);
+
+        assertFullMessage(message);
+        assertFullMessage(recovered);
+    }
+
+    @Test
+    public void testMessage_minimal() {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(PERSON).build();
+
+        ConversationActions.Message recovered = parcelizeDeparcelize(message,
+                ConversationActions.Message.CREATOR);
+
+        assertMinimalMessage(message);
+        assertMinimalMessage(recovered);
+    }
+
+    @Test
+    public void testTypeConfig_full() {
+        TextClassifier.EntityConfig typeConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullTypeConfig(typeConfig);
+        assertFullTypeConfig(recovered);
+    }
+
+    @Test
+    public void testTypeConfig_full_notIncludeTypesFromTextClassifier() {
+        TextClassifier.EntityConfig typeConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullTypeConfig_notIncludeTypesFromTextClassifier(typeConfig);
+        assertFullTypeConfig_notIncludeTypesFromTextClassifier(recovered);
+    }
+
+    @Test
+    public void testTypeConfig_minimal() {
+        TextClassifier.EntityConfig typeConfig =
+                new TextClassifier.EntityConfig.Builder().build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertMinimalTypeConfig(typeConfig);
+        assertMinimalTypeConfig(recovered);
+    }
+
+    @Test
+    public void testRequest_minimal() {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(PERSON)
+                        .setText(TEXT)
+                        .build();
+
+        ConversationActions.Request request =
+                new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .build();
+
+        ConversationActions.Request recovered =
+                parcelizeDeparcelize(request, ConversationActions.Request.CREATOR);
+
+        assertMinimalRequest(request);
+        assertMinimalRequest(recovered);
+    }
+
+    @Test
+    public void testRequest_full() {
+        ConversationActions.Message message =
+                new ConversationActions.Message.Builder(PERSON)
+                        .setText(TEXT)
+                        .build();
+        TextClassifier.EntityConfig typeConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .build();
+        ConversationActions.Request request =
+                new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .setConversationId(CONVERSATION_ID)
+                        .setHints(
+                                Collections.singletonList(
+                                        ConversationActions.Request.HINT_FOR_IN_APP))
+                        .setMaxSuggestions(10)
+                        .setTypeConfig(typeConfig)
+                        .build();
+
+        ConversationActions.Request recovered =
+                parcelizeDeparcelize(request, ConversationActions.Request.CREATOR);
+
+        assertFullRequest(request);
+        assertFullRequest(recovered);
+    }
+
+    @Test
+    public void testConversationAction_minimal() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertMinimalConversationAction(conversationAction);
+        assertMinimalConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationAction_full() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .setConfidenceScore(1.0f)
+                        .setTextReply(TEXT)
+                        .setAction(REMOTE_ACTION)
+                        .setExtras(EXTRAS)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertFullConversationAction(conversationAction);
+        assertFullConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationActions_full() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationActions conversationActions =
+                new ConversationActions(Arrays.asList(conversationAction), ID);
+
+        ConversationActions recovered =
+                parcelizeDeparcelize(conversationActions, ConversationActions.CREATOR);
+
+        assertFullConversationActions(conversationActions);
+        assertFullConversationActions(recovered);
+    }
+
+    @Test
+    public void testConversationActions_minimal() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationActions conversationActions =
+                new ConversationActions(Arrays.asList(conversationAction), null);
+
+        ConversationActions recovered =
+                parcelizeDeparcelize(conversationActions, ConversationActions.CREATOR);
+
+        assertMinimalConversationActions(conversationActions);
+        assertMinimalConversationActions(recovered);
+    }
+
+    private void assertFullMessage(ConversationActions.Message message) {
+        assertThat(message.getText().toString()).isEqualTo(TEXT);
+        assertThat(message.getAuthor()).isEqualTo(PERSON);
+        assertThat(message.getExtras().keySet()).containsExactly(TEXT);
+        assertThat(message.getReferenceTime()).isEqualTo(TIME);
+    }
+
+    private void assertMinimalMessage(ConversationActions.Message message) {
+        assertThat(message.getAuthor()).isEqualTo(PERSON);
+        assertThat(message.getExtras().isEmpty()).isTrue();
+        assertThat(message.getReferenceTime()).isNull();
+    }
+
+    private void assertFullTypeConfig(TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(
+                ConversationAction.TYPE_OPEN_URL, ConversationAction.TYPE_CREATE_REMINDER);
+    }
+
+    private void assertFullTypeConfig_notIncludeTypesFromTextClassifier(
+            TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private void assertMinimalTypeConfig(TextClassifier.EntityConfig typeConfig) {
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList())).isEmpty();
+        assertThat(typeConfig.resolveEntityListModifications(
+                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))).containsExactly(
+                ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private void assertMinimalRequest(ConversationActions.Request request) {
+        assertThat(request.getConversation()).hasSize(1);
+        assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
+        assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
+        assertThat(request.getHints()).isEmpty();
+        assertThat(request.getMaxSuggestions()).isEqualTo(0);
+        assertThat(request.getTypeConfig()).isNotNull();
+        assertThat(request.getConversationId()).isNull();
+    }
+
+    private void assertFullRequest(ConversationActions.Request request) {
+        assertThat(request.getConversation()).hasSize(1);
+        assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
+        assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
+        assertThat(request.getHints()).containsExactly(ConversationActions.Request.HINT_FOR_IN_APP);
+        assertThat(request.getMaxSuggestions()).isEqualTo(10);
+        assertThat(request.getTypeConfig().shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(request.getConversationId()).isEqualTo(CONVERSATION_ID);
+    }
+
+    private void assertMinimalConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction()).isNull();
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+    }
+
+    private void assertFullConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat(conversationAction.getTextReply()).isEqualTo(TEXT);
+        assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
+    }
+
+    private void assertMinimalConversationActions(ConversationActions conversationActions) {
+        assertThat(conversationActions.getConversationActions()).hasSize(1);
+        assertThat(conversationActions.getConversationActions().get(0).getType())
+                .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat(conversationActions.getId()).isNull();
+    }
+
+    private void assertFullConversationActions(ConversationActions conversationActions) {
+        assertThat(conversationActions.getConversationActions()).hasSize(1);
+        assertThat(conversationActions.getConversationActions().get(0).getType())
+                .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat(conversationActions.getId()).isEqualTo(ID);
+    }
+
+    private <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index 83481ee..3a33b86 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -16,21 +16,29 @@
 
 package android.view.textclassifier.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 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.mockito.Mockito.mock;
 
+import android.icu.util.ULocale;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.ConversationAction;
+import android.view.textclassifier.ConversationActions;
 import android.view.textclassifier.SelectionEvent;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
+import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
 
@@ -42,11 +50,18 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
 
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
     private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
     private static final int START = 1;
     private static final int END = 3;
@@ -62,6 +77,21 @@
             new TextClassification.Request.Builder(TEXT, START, END)
                     .setDefaultLocales(LOCALES)
                     .build();
+    private static final TextLanguage.Request TEXT_LANGUAGE_REQUEST =
+            new TextLanguage.Request.Builder(TEXT)
+                    .setExtras(BUNDLE)
+                    .build();
+    private static final ConversationActions.Message FIRST_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_SELF)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Message SECOND_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
+            new ConversationActions.Request.Builder(
+                    Arrays.asList(FIRST_MESSAGE, SECOND_MESSAGE)).build();
 
     private TextClassificationManager mManager;
     private TextClassifier mClassifier;
@@ -129,6 +159,14 @@
     }
 
     @Test
+    public void testSuggestConversationActions() {
+        ConversationActions conversationActions =
+                mClassifier.suggestConversationActions(CONVERSATION_ACTIONS_REQUEST);
+
+        assertValidResult(conversationActions);
+    }
+
+    @Test
     public void testResolveEntityListModifications_only_hints() {
         TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.createWithHints(
                 Arrays.asList("some_hint"));
@@ -167,6 +205,22 @@
                 SelectionEvent.createSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0));
     }
 
+    @Test
+    public void testOnTextClassifierEvent() {
+        // Doesn't crash.
+        mClassifier.onTextClassifierEvent(new TextClassifierEvent.Builder(0, 0).build());
+    }
+
+    @Test
+    public void testLanguageDetection() {
+        assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testLanguageDetection_nullRequest() {
+        assertValidResult(mClassifier.detectLanguage(null));
+    }
+
     private static void assertValidResult(TextSelection selection) {
         assertNotNull(selection);
         assertTrue(selection.getSelectionStartIndex() >= 0);
@@ -209,4 +263,30 @@
             }
         }
     }
+
+    private static void assertValidResult(TextLanguage language) {
+        assertNotNull(language);
+        assertNotNull(language.getExtras());
+        assertTrue(language.getLocaleHypothesisCount() >= 0);
+        for (int i = 0; i < language.getLocaleHypothesisCount(); i++) {
+            final ULocale locale = language.getLocale(i);
+            assertNotNull(locale);
+            final float confidenceScore = language.getConfidenceScore(locale);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+    }
+
+    private static void assertValidResult(ConversationActions conversationActions) {
+        assertNotNull(conversationActions);
+        List<ConversationAction> conversationActionsList =
+                conversationActions.getConversationActions();
+        assertNotNull(conversationActionsList);
+        for (ConversationAction conversationAction : conversationActionsList) {
+            assertThat(conversationAction.getAction()).isNotNull();
+            assertThat(conversationAction.getType()).isNotNull();
+            assertThat(conversationAction.getConfidenceScore()).isGreaterThan(0f);
+            assertThat(conversationAction.getConfidenceScore()).isLessThan(1.0f);
+        }
+    }
 }
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java
new file mode 100644
index 0000000..f84f2bf
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierEventTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierEventTest {
+    private static final float TOLERANCE = 0.000001f;
+
+    @Test
+    public void testMinimumEvent() {
+        final TextClassifierEvent event = createMinimalTextClassifierEvent();
+
+        assertMinimalTextClassifierEvent(event);
+    }
+
+    @Test
+    public void testParcelUnparcel_minimumEvent() {
+        final TextClassifierEvent event = createMinimalTextClassifierEvent();
+
+        final Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, event.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassifierEvent result = TextClassifierEvent.CREATOR.createFromParcel(parcel);
+
+        assertMinimalTextClassifierEvent(result);
+    }
+
+    @Test
+    public void testFullEvent() {
+        final long eventTime = System.currentTimeMillis();
+        final TextClassifierEvent event = createFullTextClassifierEvent(eventTime);
+
+        assertFullEvent(event, eventTime);
+    }
+
+    @Test
+    public void testParcelUnparcel_fullEvent() {
+        final long eventTime = System.currentTimeMillis();
+        final TextClassifierEvent event = createFullTextClassifierEvent(eventTime);
+
+        final Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, event.describeContents());
+        parcel.setDataPosition(0);
+        final TextClassifierEvent result = TextClassifierEvent.CREATOR.createFromParcel(parcel);
+
+        assertFullEvent(result, eventTime);
+    }
+
+    private TextClassifierEvent createFullTextClassifierEvent(long eventTime) {
+        final Bundle extra = new Bundle();
+        extra.putString("key", "value");
+        return new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_LINKIFY,
+                TextClassifierEvent.TYPE_LINK_CLICKED)
+                .setEventIndex(2)
+                .setEventTime(eventTime)
+                .setEntityTypes(TextClassifier.TYPE_ADDRESS)
+                .setRelativeWordStartIndex(1)
+                .setRelativeWordEndIndex(2)
+                .setRelativeSuggestedWordStartIndex(-1)
+                .setRelativeSuggestedWordEndIndex(3)
+                .setLanguage("en")
+                .setResultId("androidtc-en-v606-1234")
+                .setActionIndices(1, 2, 5)
+                .setExtras(extra)
+                .setEventContext(new TextClassificationContext.Builder(
+                        "pkg", TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                        .setWidgetVersion(TextView.class.getName())
+                        .build())
+                .setScore(0.5f)
+                .setEntityTypes(TextClassifier.TYPE_ADDRESS, TextClassifier.TYPE_DATE)
+                .build();
+    }
+
+    private void assertFullEvent(TextClassifierEvent event, long expectedEventTime) {
+        assertThat(event.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_LINKIFY);
+        assertThat(event.getEventType()).isEqualTo(TextClassifierEvent.TYPE_LINK_CLICKED);
+        assertThat(event.getEventIndex()).isEqualTo(2);
+        assertThat(event.getEventTime()).isEqualTo(expectedEventTime);
+        assertThat(event.getEntityTypes()).asList()
+                .containsExactly(TextClassifier.TYPE_ADDRESS, TextClassifier.TYPE_DATE);
+        assertThat(event.getRelativeWordStartIndex()).isEqualTo(1);
+        assertThat(event.getRelativeWordEndIndex()).isEqualTo(2);
+        assertThat(event.getRelativeSuggestedWordStartIndex()).isEqualTo(-1);
+        assertThat(event.getRelativeSuggestedWordEndIndex()).isEqualTo(3);
+        assertThat(event.getLanguage()).isEqualTo("en");
+        assertThat(event.getResultId()).isEqualTo("androidtc-en-v606-1234");
+        assertThat(event.getActionIndices()).asList().containsExactly(1, 2, 5);
+        assertThat(event.getExtras().get("key")).isEqualTo("value");
+        assertThat(event.getEventContext().getPackageName()).isEqualTo("pkg");
+        assertThat(event.getEventContext().getWidgetType())
+                .isEqualTo(TextClassifier.WIDGET_TYPE_TEXTVIEW);
+        assertThat(event.getEventContext().getWidgetVersion()).isEqualTo(TextView.class.getName());
+        assertThat(event.getScore()).isWithin(TOLERANCE).of(0.5f);
+    }
+
+    private TextClassifierEvent createMinimalTextClassifierEvent() {
+        return new TextClassifierEvent.Builder(
+                TextClassifierEvent.CATEGORY_UNDEFINED, TextClassifierEvent.TYPE_UNDEFINED)
+                .build();
+    }
+
+    private void assertMinimalTextClassifierEvent(TextClassifierEvent event) {
+        assertThat(event.getEventCategory()).isEqualTo(TextClassifierEvent.CATEGORY_UNDEFINED);
+        assertThat(event.getEventType()).isEqualTo(TextClassifierEvent.TYPE_UNDEFINED);
+        assertThat(event.getEventIndex()).isEqualTo(0);
+        assertThat(event.getEventTime()).isEqualTo(0);
+        assertThat(event.getEntityTypes()).isEmpty();
+        assertThat(event.getRelativeWordStartIndex()).isEqualTo(0);
+        assertThat(event.getRelativeWordEndIndex()).isEqualTo(0);
+        assertThat(event.getRelativeSuggestedWordStartIndex()).isEqualTo(0);
+        assertThat(event.getRelativeSuggestedWordEndIndex()).isEqualTo(0);
+        assertThat(event.getLanguage()).isNull();
+        assertThat(event.getResultId()).isNull();
+        assertThat(event.getActionIndices()).isEmpty();
+        assertThat(event.getExtras().size()).isEqualTo(0);
+        assertThat(event.getEventContext()).isNull();
+        assertThat(event.getEntityTypes()).isEmpty();
+    }
+}
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
index fda0f90..03e080e 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
@@ -19,6 +19,7 @@
 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.app.PendingIntent;
 import android.app.RemoteAction;
@@ -28,6 +29,8 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.icu.util.ULocale;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -35,11 +38,15 @@
 import android.view.View;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Collections;
+
 /**
  * TextClassifier value objects tests.
  *
@@ -49,6 +56,13 @@
 @RunWith(AndroidJUnit4.class)
 public class TextClassifierValueObjectsTest {
 
+    private static final float EPSILON = 0.000001f;
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
     private static final double ACCEPTED_DELTA = 0.0000001;
     private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
     private static final int START = 5;
@@ -65,6 +79,7 @@
                 .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
                 .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
                 .setId(ID)
+                .setExtras(BUNDLE)
                 .build();
 
         assertEquals(START, selection.getSelectionStartIndex());
@@ -78,6 +93,7 @@
                 ACCEPTED_DELTA);
         assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
         assertEquals(ID, selection.getId());
+        assertEquals(BUNDLE_VALUE, selection.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -107,6 +123,7 @@
         TextSelection selection = new TextSelection.Builder(START, END).build();
         assertEquals(0, selection.getEntityCount());
         assertNull(selection.getId());
+        assertTrue(selection.getExtras().isEmpty());
     }
 
     @Test
@@ -151,7 +168,7 @@
         final TextSelection.Request request = new TextSelection.Request.Builder(TEXT, START, END)
                 .setDefaultLocales(LOCALES)
                 .build();
-        assertEquals(TEXT, request.getText());
+        assertEquals(TEXT, request.getText().toString());
         assertEquals(START, request.getStartIndex());
         assertEquals(END, request.getEndIndex());
         assertEquals(LOCALES, request.getDefaultLocales());
@@ -171,6 +188,7 @@
         final TextSelection.Request request =
                 new TextSelection.Request.Builder(TEXT, START, END).build();
         assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
     }
 
     @Test
@@ -195,6 +213,7 @@
                 .addAction(new RemoteAction(icon1, label1, description1, intent1))
                 .addAction(new RemoteAction(icon2, label2, description2, intent2))
                 .setId(ID)
+                .setExtras(BUNDLE)
                 .build();
 
         assertEquals(TEXT, classification.getText());
@@ -223,6 +242,7 @@
         assertEquals(intent2, classification.getActions().get(1).getActionIntent());
         assertNotNull(classification.getActions().get(1).getIcon());
         assertEquals(ID, classification.getId());
+        assertEquals(BUNDLE_VALUE, classification.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -274,6 +294,7 @@
         assertEquals(null, classification.getOnClickListener());
         assertEquals(0, classification.getActions().size());
         assertNull(classification.getId());
+        assertTrue(classification.getExtras().isEmpty());
     }
 
     @Test
@@ -281,8 +302,11 @@
         final TextClassification.Request request =
                 new TextClassification.Request.Builder(TEXT, START, END)
                         .setDefaultLocales(LOCALES)
+                        .setExtras(BUNDLE)
                         .build();
+
         assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -291,7 +315,9 @@
                 new TextClassification.Request.Builder(TEXT, START, END)
                         .setDefaultLocales(null)
                         .build();
+
         assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
     }
 
     @Test
@@ -299,6 +325,101 @@
         final TextClassification.Request request =
                 new TextClassification.Request.Builder(TEXT, START, END).build();
         assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextLinks_defaultValues() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
+
+        assertTrue(textLinks.getExtras().isEmpty());
+        assertTrue(textLinks.getLinks().isEmpty());
+    }
+
+    @Test
+    public void testTextLinks_full() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
+        assertEquals(1, textLinks.getLinks().size());
+        TextLinks.TextLink textLink = textLinks.getLinks().iterator().next();
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
+        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS), EPSILON);
+    }
+
+    @Test
+    public void testTextLinksRequest_defaultValues() {
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT).build();
+
+        assertEquals(TEXT, request.getText());
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+        assertNull(request.getEntityConfig());
+    }
+
+    @Test
+    public void testTextLinksRequest_full() {
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT)
+                .setDefaultLocales(LOCALES)
+                .setExtras(BUNDLE)
+                .setEntityConfig(TextClassifier.EntityConfig.createWithHints(
+                        Collections.singletonList(TextClassifier.HINT_TEXT_IS_EDITABLE)))
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertEquals(1, request.getEntityConfig().getHints().size());
+        assertEquals(
+                TextClassifier.HINT_TEXT_IS_EDITABLE,
+                request.getEntityConfig().getHints().iterator().next());
+    }
+
+    @Test
+    public void testTextLanguage() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .setId(ID)
+                .putLocale(ULocale.ENGLISH, 0.6f)
+                .putLocale(ULocale.CHINESE, 0.3f)
+                .putLocale(ULocale.JAPANESE, 0.1f)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(ID, language.getId());
+        assertEquals(3, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(ULocale.CHINESE, language.getLocale(1));
+        assertEquals(ULocale.JAPANESE, language.getLocale(2));
+        assertEquals(0.6f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertEquals(0.3f, language.getConfidenceScore(ULocale.CHINESE), EPSILON);
+        assertEquals(0.1f, language.getConfidenceScore(ULocale.JAPANESE), EPSILON);
+        assertEquals(BUNDLE_VALUE, language.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextLanguage_clippedScore() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .putLocale(ULocale.ENGLISH, 2f)
+                .putLocale(ULocale.CHINESE, -2f)
+                .build();
+
+        assertEquals(1, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(1f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertNull(language.getId());
+    }
+
+    @Test
+    public void testTextLanguageRequest() {
+        final TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
     }
 
     // TODO: Add more tests.
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 203be4d..6f82d6f 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -17,15 +17,12 @@
 package android.voiceinteraction.service;
 
 import android.app.VoiceInteractor;
-import android.app.VoiceInteractor.Prompt;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.service.voice.VoiceInteractionSession;
-import android.service.voice.VoiceInteractionSession.ConfirmationRequest;
-import android.service.voice.VoiceInteractionSession.PickOptionRequest;
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
 
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestLocalInteractionActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestLocalInteractionActivity.java
index 4999fbe..b5d5938 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestLocalInteractionActivity.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestLocalInteractionActivity.java
@@ -17,9 +17,6 @@
 package android.voiceinteraction.cts;
 
 import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index 1abd00f..732cff2 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -43,6 +43,7 @@
     private TestResultsReceiver mReceiver;
     private Bundle mResults;
     private final CountDownLatch mLatch = new CountDownLatch(1);
+    // TODO: convert test to JUnit4 so we can use @RequiredFeatureRule instead
     protected boolean mHasFeature;
     protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
 
@@ -53,13 +54,14 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        startTestActivity();
+
         mContext = getInstrumentation().getTargetContext();
         mHasFeature = mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS);
-        if (mHasFeature) {
-            mReceiver = new TestResultsReceiver();
-            mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT));
-        }
+        if (!mHasFeature) return;
+
+        mReceiver = new TestResultsReceiver();
+        mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT));
+        startTestActivity();
     }
 
     @Override
@@ -87,12 +89,13 @@
     }
 
     public void testAll() throws Exception {
-        VoiceInteractionTestReceiver.waitSessionStarted(this, 5, TimeUnit.SECONDS);
-
         if (!mHasFeature) {
             Log.i(TAG, "The device doesn't support feature: " + FEATURE_VOICE_RECOGNIZERS);
             return;
         }
+
+        VoiceInteractionTestReceiver.waitSessionStarted(this, 5, TimeUnit.SECONDS);
+
         if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
             return;
@@ -116,7 +119,7 @@
     }
 
     private void verifySingleTestcaseResult(Utils.TestCaseType testCaseType, String result) {
-        Log.i(TAG, "Recevied testresult: " + result + " for " + testCaseType);
+        Log.i(TAG, "Received testresult: " + result + " for " + testCaseType);
         switch (testCaseType) {
           case ABORT_REQUEST_CANCEL_TEST:
               assertTrue(result.equals(Utils.ABORT_REQUEST_CANCEL_SUCCESS));
diff --git a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
index 7c6cf53..11f330e 100644
--- a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -26,11 +26,9 @@
 import android.app.VoiceInteractor.PickOptionRequest.Option;
 import android.app.VoiceInteractor.Prompt;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -134,10 +132,10 @@
     }
 
     private void broadcastResults() {
-        Intent intent = new Intent(Utils.BROADCAST_INTENT);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.putExtras(mTotalInfo);
-        Log.i(TAG, "broadcasting: " + intent.toString() + ", Bundle = " + mTotalInfo.toString());
+        Intent intent = new Intent(Utils.BROADCAST_INTENT)
+            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+            .putExtras(mTotalInfo);
+        Log.i(TAG, "broadcasting: " + intent + ", Bundle = " + mTotalInfo);
         sendOrderedBroadcast(intent, null, new DoneReceiver(),
                              null, Activity.RESULT_OK, null, null);
     }
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 21e116d..6e3ba4e 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -20,6 +20,8 @@
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+    <!-- Note: we must provide INTERNET permission for
+     ServiceWorkerWebSettingsTest#testBlockNetworkLoads -->
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <application android:maxRecents="1" android:usesCleartextTraffic="true">
diff --git a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
index ca61b24..5435cf8 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ChromeClient.java
@@ -16,7 +16,7 @@
 package android.webkit.cts;
 
 import android.webkit.ConsoleMessage;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 
 // A chrome client for listening webview chrome events.
 class ChromeClient extends WaitForProgressClient {
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 799545a..843e81e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -51,7 +51,7 @@
         super.setUp();
         mWebView = getActivity().getWebView();
         if (mWebView != null) {
-            mOnUiThread = new WebViewOnUiThread(this, mWebView);
+            mOnUiThread = new WebViewOnUiThread(mWebView);
 
             mCookieManager = CookieManager.getInstance();
             assertNotNull(mCookieManager);
@@ -412,41 +412,17 @@
     }
 
     private void removeAllCookiesOnUiThread(final ValueCallback<Boolean> callback) {
-        runTestOnUiThreadAndCatch(new Runnable() {
-            @Override
-            public void run() {
-                mCookieManager.removeAllCookies(callback);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mCookieManager.removeAllCookies(callback);
         });
     }
 
     private void removeSessionCookiesOnUiThread(final ValueCallback<Boolean> callback) {
-        runTestOnUiThreadAndCatch(new Runnable() {
-            @Override
-            public void run() {
-                mCookieManager.removeSessionCookies(callback);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mCookieManager.removeSessionCookies(callback);
         });
     }
 
-    private void setCookieOnUiThread(final String url, final String cookie,
-            final ValueCallback<Boolean> callback) {
-        runTestOnUiThreadAndCatch(new Runnable() {
-            @Override
-            public void run() {
-                mCookieManager.setCookie(url, cookie, callback);
-            }
-        });
-    }
-
-    private void runTestOnUiThreadAndCatch(Runnable runnable) {
-        try {
-            runTestOnUiThread(runnable);
-        } catch (Throwable t) {
-            fail("Unexpected error while running on UI thread: " + t.getMessage());
-        }
-    }
-
     /**
      * Makes a url look as if it comes from a different host.
      * @param url the url to fake.
diff --git a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
index 9384f8a..e240978 100644
--- a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
@@ -35,8 +35,8 @@
 import android.webkit.WebResourceResponse;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 
 import com.android.compatibility.common.util.LocationUtils;
 import com.android.compatibility.common.util.NullWebViewUtils;
@@ -146,7 +146,7 @@
         if (webview != null) {
             // Set up a WebView with JavaScript and Geolocation enabled
             final String GEO_DIR = "geo_test";
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
             mOnUiThread.getSettings().setJavaScriptEnabled(true);
             mOnUiThread.getSettings().setGeolocationEnabled(true);
             mOnUiThread.getSettings().setGeolocationDatabasePath(
diff --git a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
index dc9bf3c..f4724cf 100644
--- a/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/HttpAuthHandlerTest.java
@@ -19,7 +19,7 @@
 import android.test.ActivityInstrumentationTestCase2;
 import android.webkit.HttpAuthHandler;
 import android.webkit.WebView;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 
@@ -46,7 +46,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview != null) {
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
         }
     }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/OWNERS b/tests/tests/webkit/src/android/webkit/cts/OWNERS
new file mode 100644
index 0000000..8cd41c7
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/OWNERS
@@ -0,0 +1,4 @@
+changwan@google.com
+tobiasjs@google.com
+torne@google.com
+ntfschr@google.com
diff --git a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
index 0fa239d..06789ac 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -16,19 +16,24 @@
 
 package android.webkit.cts;
 
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
 import android.net.Uri;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
 import android.webkit.WebMessage;
 import android.webkit.WebMessagePort;
 import android.webkit.WebView;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.google.common.util.concurrent.SettableFuture;
 
-import java.util.concurrent.CountDownLatch;
 import junit.framework.Assert;
 
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
 public class PostMessageTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
     public static final long TIMEOUT = 20000L;
 
@@ -48,7 +53,7 @@
         final WebViewCtsActivity activity = getActivity();
         mWebView = activity.getWebView();
         if (mWebView != null) {
-            mOnUiThread = new WebViewOnUiThread(this, mWebView);
+            mOnUiThread = new WebViewOnUiThread(mWebView);
             mOnUiThread.getSettings().setJavaScriptEnabled(true);
         }
     }
@@ -99,16 +104,31 @@
         }.run();
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testSimpleMessageToMainFrame. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Post a string message to main frame and make sure it is received.
     public void testSimpleMessageToMainFrame() throws Throwable {
         verifyPostMessageToOrigin(Uri.parse(BASE_URI));
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testWildcardOriginMatchesAnything. Modifications to this
+     * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Post a string message to main frame passing a wildcard as target origin
     public void testWildcardOriginMatchesAnything() throws Throwable {
         verifyPostMessageToOrigin(Uri.parse("*"));
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testEmptyStringOriginMatchesAnything. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Post a string message to main frame passing an empty string as target origin
     public void testEmptyStringOriginMatchesAnything() throws Throwable {
         verifyPostMessageToOrigin(Uri.parse(""));
@@ -124,6 +144,11 @@
         waitForTitle(WEBVIEW_MESSAGE);
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testMultipleMessagesToMainFrame. Modifications to this
+     * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Post multiple messages to main frame and make sure they are received in
     // correct order.
     public void testMultipleMessagesToMainFrame() throws Throwable {
@@ -138,6 +163,11 @@
         waitForTitle("0123456789");
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testMessageChannel. Modifications to this test should be
+     * reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Create a message channel and make sure it can be used for data transfer to/from js.
     public void testMessageChannel() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -148,27 +178,34 @@
         WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
         mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
         final int messageCount = 3;
-        final CountDownLatch latch = new CountDownLatch(messageCount);
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < messageCount; i++) {
-                    channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE + i));
-                }
-                channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
-                    @Override
-                    public void onMessage(WebMessagePort port, WebMessage message) {
-                        int i = messageCount - (int)latch.getCount();
-                        assertEquals(WEBVIEW_MESSAGE + i + i, message.getData());
-                        latch.countDown();
-                    }
-                });
+        final BlockingQueue<String> queue = new ArrayBlockingQueue<>(messageCount);
+        WebkitUtils.onMainThreadSync(() -> {
+            for (int i = 0; i < messageCount; i++) {
+                channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE + i));
             }
+            channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
+                @Override
+                public void onMessage(WebMessagePort port, WebMessage message) {
+                    queue.add(message.getData());
+                }
+            });
         });
+
         // Wait for all the responses to arrive.
-        boolean ignore = latch.await(TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+        for (int i = 0; i < messageCount; i++) {
+            // The JavaScript code simply appends an integer counter to the end of the message it
+            // receives, which is why we have a second i on the end.
+            String expectedMessageFromJavascript = WEBVIEW_MESSAGE + i + "" + i;
+            assertEquals(expectedMessageFromJavascript,
+                    WebkitUtils.waitForNextQueueElement(queue));
+        }
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testClose. Modifications to this test should be reflected in
+     * that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Test that a message port that is closed cannot used to send a message
     public void testClose() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -178,19 +215,16 @@
         final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel();
         WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
         mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    channel[0].close();
-                    channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE));
-                } catch (IllegalStateException ex) {
-                    // expect to receive an exception
-                    return;
-                }
-                Assert.fail("A closed port cannot be used to transfer messages");
+        WebkitUtils.onMainThreadSync(() -> {
+            try {
+                channel[0].close();
+                channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE));
+            } catch (IllegalStateException ex) {
+                // expect to receive an exception
+                return;
             }
-         });
+            Assert.fail("A closed port cannot be used to transfer messages");
+        });
     }
 
     // Sends a new message channel from JS to Java.
@@ -211,6 +245,11 @@
             + "   </script>"
             + "</body></html>";
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testReceiveMessagePort. Modifications to this test should
+     * be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Test a message port created in JS can be received and used for message transfer.
     public void testReceiveMessagePort() throws Throwable {
         final String hello = "HELLO";
@@ -221,17 +260,80 @@
         final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel();
         WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
         mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
-                    @Override
-                    public void onMessage(WebMessagePort port, WebMessage message) {
-                        message.getPorts()[0].postMessage(new WebMessage(hello));
-                    }
-                });
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
+                @Override
+                public void onMessage(WebMessagePort port, WebMessage message) {
+                    message.getPorts()[0].postMessage(new WebMessage(hello));
+                }
+            });
         });
         waitForTitle(hello);
     }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testWebMessageHandler. Modifications to this test should
+     * be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    // Ensure the callback is invoked on the correct Handler.
+    public void testWebMessageHandler() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        loadPage(CHANNEL_MESSAGE);
+        final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel();
+        WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
+        mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
+        final int messageCount = 1;
+        final SettableFuture<Boolean> messageHandlerThreadFuture = SettableFuture.create();
+
+        // Create a new thread for the WebMessageCallback.
+        final HandlerThread messageHandlerThread = new HandlerThread("POST_MESSAGE_THREAD");
+        messageHandlerThread.start();
+        final Handler messageHandler = new Handler(messageHandlerThread.getLooper());
+
+        WebkitUtils.onMainThreadSync(() -> {
+            channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE));
+            channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
+                @Override
+                public void onMessage(WebMessagePort port, WebMessage message) {
+                    messageHandlerThreadFuture.set(
+                            messageHandlerThread.getLooper().isCurrentThread());
+                }
+            }, messageHandler);
+        });
+        // Wait for all the responses to arrive and assert correct thread.
+        assertTrue(WebkitUtils.waitForFuture(messageHandlerThreadFuture));
+    }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.PostMessageTest#testWebMessageDefaultHandler. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    // Ensure the callback is invoked on the MainLooper by default.
+    public void testWebMessageDefaultHandler() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        loadPage(CHANNEL_MESSAGE);
+        final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel();
+        WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
+        mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
+        final int messageCount = 1;
+        final SettableFuture<Boolean> messageMainLooperFuture = SettableFuture.create();
+
+        WebkitUtils.onMainThreadSync(() -> {
+            channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE));
+            channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
+                @Override
+                public void onMessage(WebMessagePort port, WebMessage message) {
+                    messageMainLooperFuture.set(Looper.getMainLooper().isCurrentThread());
+                }
+            });
+        });
+        // Wait for all the responses to arrive and assert correct thread.
+        assertTrue(WebkitUtils.waitForFuture(messageMainLooperFuture));
+    }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
index ac736cf..2b9e8e5 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -25,7 +25,7 @@
 import android.webkit.WebResourceRequest;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -129,7 +129,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview == null) return;
-        mOnUiThread = new WebViewOnUiThread(this, webview);
+        mOnUiThread = new WebViewOnUiThread(webview);
         mOnUiThread.getSettings().setJavaScriptEnabled(true);
 
         mJavascriptStatusReceiver = new JavascriptStatusReceiver();
@@ -145,6 +145,12 @@
         super.tearDown();
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerClientCompatTest#testServiceWorkerClientInterceptCallback.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
     // Test correct invocation of shouldInterceptRequest for Service Workers.
     public void testServiceWorkerClientInterceptCallback() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerWebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerWebSettingsTest.java
new file mode 100644
index 0000000..8c28890
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerWebSettingsTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.webkit.cts;
+
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.test.ActivityInstrumentationTestCase2;
+import android.webkit.ServiceWorkerController;
+import android.webkit.ServiceWorkerWebSettings;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import com.android.compatibility.common.util.NullWebViewUtils;
+
+
+public class ServiceWorkerWebSettingsTest extends
+        ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+
+    private ServiceWorkerWebSettings mSettings;
+    private WebViewOnUiThread mOnUiThread;
+
+    public ServiceWorkerWebSettingsTest() {
+        super("android.webkit.cts", WebViewCtsActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        WebView webview = getActivity().getWebView();
+        if (webview != null) {
+            mOnUiThread = new WebViewOnUiThread(webview);
+            mSettings = ServiceWorkerController.getInstance().getServiceWorkerWebSettings();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
+        }
+        super.tearDown();
+    }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerWebSettingsCompatTest#testCacheMode. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    public void testCacheMode() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        int i = WebSettings.LOAD_DEFAULT;
+        assertEquals(i, mSettings.getCacheMode());
+        for (; i <= WebSettings.LOAD_CACHE_ONLY; i++) {
+            mSettings.setCacheMode(i);
+            assertEquals(i, mSettings.getCacheMode());
+        }
+    }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerWebSettingsCompatTest#testAllowContentAccess. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    public void testAllowContentAccess() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertEquals(mSettings.getAllowContentAccess(), true);
+        for (boolean b : new boolean[]{false, true}) {
+            mSettings.setAllowContentAccess(b);
+            assertEquals(b, mSettings.getAllowContentAccess());
+        }
+    }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerWebSettingsCompatTest#testAllowFileAccess. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    public void testAllowFileAccess() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertEquals(mSettings.getAllowFileAccess(), true);
+        for (boolean b : new boolean[]{false, true}) {
+            mSettings.setAllowFileAccess(b);
+            assertEquals(b, mSettings.getAllowFileAccess());
+        }
+    }
+
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerWebSettingsCompatTest#testBlockNetworkLoads. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    public void testBlockNetworkLoads() {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        // Note: we cannot test this setter unless we provide the INTERNET permission, otherwise we
+        // get a SecurityException when we pass 'false'.
+        final boolean hasInternetPermission = true;
+
+        assertEquals(mSettings.getBlockNetworkLoads(), !hasInternetPermission);
+        for (boolean b : new boolean[]{false, true}) {
+            mSettings.setBlockNetworkLoads(b);
+            assertEquals(b, mSettings.getBlockNetworkLoads());
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java b/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
index db1be3a..32f2df0 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
@@ -17,11 +17,10 @@
 package android.webkit.cts;
 
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
 import android.webkit.TracingConfig;
 import android.webkit.TracingController;
 import android.webkit.WebView;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -115,7 +114,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview == null) return;
-        mOnUiThread = new WebViewOnUiThread(this, webview);
+        mOnUiThread = new WebViewOnUiThread(webview);
         singleThreadExecutor = Executors.newSingleThreadExecutor(getCustomThreadFactory());
     }
 
@@ -170,11 +169,8 @@
         }
         final TracingReceiver tracingReceiver = new TracingReceiver();
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                runTracingTestWithCallbacks(tracingReceiver, singleThreadExecutor);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            runTracingTestWithCallbacks(tracingReceiver, singleThreadExecutor);
         });
         PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, tracingReceiver.getCompleteCallable());
         assertTrue(tracingReceiver.getNbChunks() > 0);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
index eb5b413..b70e1a3 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
@@ -39,7 +39,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview != null) {
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
         }
     }
 
@@ -108,7 +108,4 @@
         }.run();
     }
 
-    public void testClone() {
-    }
-
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index 92573d8..c881de4 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -27,7 +27,7 @@
 import android.webkit.WebIconDatabase;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -50,7 +50,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview != null) {
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
         }
         mWebServer = new CtsTestServer(getActivity());
     }
@@ -117,14 +117,11 @@
         final MockWebChromeClient webChromeClient = new MockWebChromeClient();
         mOnUiThread.setWebChromeClient(webChromeClient);
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // getInstance must run on the UI thread
-                mIconDb = WebIconDatabase.getInstance();
-                String dbPath = getActivity().getFilesDir().toString() + "/icons";
-                mIconDb.open(dbPath);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            // getInstance must run on the UI thread
+            mIconDb = WebIconDatabase.getInstance();
+            String dbPath = getActivity().getFilesDir().toString() + "/icons";
+            mIconDb.open(dbPath);
         });
         getInstrumentation().waitForIdleSync();
         Thread.sleep(100); // Wait for open to be received on the icon db thread.
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index d04fd69..d2c54f0 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -19,7 +19,7 @@
 import android.graphics.Bitmap;
 import android.test.ActivityInstrumentationTestCase2;
 import android.webkit.WebBackForwardList;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 import android.webkit.WebHistoryItem;
 import android.webkit.WebIconDatabase;
 import android.webkit.WebView;
@@ -58,7 +58,7 @@
         mWebServer = new CtsTestServer(getActivity());
         WebView webview = getActivity().getWebView();
         if (webview != null) {
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
         }
     }
 
@@ -81,14 +81,11 @@
         }
         final WaitForIconClient waitForIconClient = new WaitForIconClient(mOnUiThread);
         mOnUiThread.setWebChromeClient(waitForIconClient);
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // getInstance must run on the UI thread
-                mIconDb = WebIconDatabase.getInstance();
-                String dbPath = getActivity().getFilesDir().toString() + "/icons";
-                mIconDb.open(dbPath);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            // getInstance must run on the UI thread
+            mIconDb = WebIconDatabase.getInstance();
+            String dbPath = getActivity().getFilesDir().toString() + "/icons";
+            mIconDb.open(dbPath);
         });
 
         WebBackForwardList list = mOnUiThread.copyBackForwardList();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 8cfaec3..eac21a9 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -34,18 +34,18 @@
 import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.google.common.util.concurrent.SettableFuture;
 
 import java.io.ByteArrayInputStream;
 import java.io.FileOutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -83,7 +83,7 @@
         super.setUp();
         WebView webview = getActivity().getWebView();
         if (webview != null) {
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
             mSettings = mOnUiThread.getSettings();
         }
         mContext = getInstrumentation().getTargetContext();
@@ -218,14 +218,11 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // getInstance must run on the UI thread
-                WebIconDatabase iconDb = WebIconDatabase.getInstance();
-                String dbPath = getActivity().getFilesDir().toString() + "/icons";
-                iconDb.open(dbPath);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            // getInstance must run on the UI thread
+            WebIconDatabase iconDb = WebIconDatabase.getInstance();
+            String dbPath = getActivity().getFilesDir().toString() + "/icons";
+            iconDb.open(dbPath);
         });
         getInstrumentation().waitForIdleSync();
         Thread.sleep(100); // Wait for open to be received on the icon db thread.
@@ -417,15 +414,15 @@
         startWebServer();
 
         final WebView childWebView = mOnUiThread.createWebView();
-        final CountDownLatch latch = new CountDownLatch(1);
+        final SettableFuture<Void> createWindowFuture = SettableFuture.create();
         mOnUiThread.setWebChromeClient(new WebChromeClient() {
             @Override
             public boolean onCreateWindow(
-                WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
+                    WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
                 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
                 transport.setWebView(childWebView);
                 resultMsg.sendToTarget();
-                latch.countDown();
+                createWindowFuture.set(null);
                 return true;
             }
         });
@@ -439,12 +436,12 @@
                 return "Popup blocked".equals(mOnUiThread.getTitle());
             }
         }.run();
-        assertEquals(1, latch.getCount());
+        assertFalse("onCreateWindow should not have been called yet", createWindowFuture.isDone());
 
         mSettings.setJavaScriptCanOpenWindowsAutomatically(true);
         assertTrue(mSettings.getJavaScriptCanOpenWindowsAutomatically());
         mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL));
-        assertTrue(latch.await(WEBVIEW_TIMEOUT, TimeUnit.MILLISECONDS));
+        WebkitUtils.waitForFuture(createWindowFuture);
     }
 
     public void testAccessJavaScriptEnabled() throws Exception {
@@ -528,6 +525,11 @@
         assertTrue(mSettings.getPluginsEnabled());
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebSettingsCompatTest#testOffscreenPreRaster. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testOffscreenPreRaster() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -627,12 +629,7 @@
         }
         assertTrue(mSettings.supportZoom());
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mSettings.setSupportZoom(false);
-            }
-        });
+        mSettings.setSupportZoom(false);
         assertFalse(mSettings.supportZoom());
     }
 
@@ -642,12 +639,7 @@
         }
         assertFalse(mSettings.getBuiltInZoomControls());
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mSettings.setBuiltInZoomControls(true);
-            }
-        });
+        mSettings.setBuiltInZoomControls(true);
         assertTrue(mSettings.getBuiltInZoomControls());
     }
 
@@ -742,6 +734,26 @@
         assertEquals("No database", mOnUiThread.getTitle());
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebSettingsCompatTest#testDisabledActionModeMenuItems. Modifications to this
+     * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
+    public void testDisabledActionModeMenuItems() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        assertEquals(WebSettings.MENU_ITEM_NONE, mSettings.getDisabledActionModeMenuItems());
+
+        int allDisabledFlags = WebSettings.MENU_ITEM_NONE | WebSettings.MENU_ITEM_SHARE |
+                WebSettings.MENU_ITEM_WEB_SEARCH | WebSettings.MENU_ITEM_PROCESS_TEXT;
+        for (int i = WebSettings.MENU_ITEM_NONE; i <= allDisabledFlags; i++) {
+            mSettings.setDisabledActionModeMenuItems(i);
+            assertEquals(i, mSettings.getDisabledActionModeMenuItems());
+        }
+    }
+
     public void testLoadsImagesAutomatically() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -1043,6 +1055,11 @@
         }
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebSettingsCompatTest#testEnableSafeBrowsing. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testEnableSafeBrowsing() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 3ce66ac..1146e25 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -34,12 +34,12 @@
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 import android.util.Pair;
 
-import com.android.compatibility.common.util.EvaluateJsResultPollingCheck;
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.google.common.util.concurrent.SettableFuture;
 
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
@@ -75,7 +75,7 @@
                 }
             }.run();
 
-            mOnUiThread = new WebViewOnUiThread(this, webview);
+            mOnUiThread = new WebViewOnUiThread(webview);
         }
     }
 
@@ -90,6 +90,12 @@
         super.tearDown();
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoadingDefault. Modifications
+     * to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
     // Verify that the shouldoverrideurlloading is false by default
     public void testShouldOverrideUrlLoadingDefault() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -99,6 +105,11 @@
         assertFalse(webViewClient.shouldOverrideUrlLoading(mOnUiThread.getWebView(), new String()));
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoading. Modifications to this
+     * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     // Verify shouldoverrideurlloading called on top level navigation
     public void testShouldOverrideUrlLoading() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -137,7 +148,7 @@
 
         final WebView childWebView = mOnUiThread.createWebView();
 
-        WebViewOnUiThread childWebViewOnUiThread = new WebViewOnUiThread(this, childWebView);
+        WebViewOnUiThread childWebViewOnUiThread = new WebViewOnUiThread(childWebView);
         mOnUiThread.setWebChromeClient(new WebChromeClient() {
             @Override
             public boolean onCreateWindow(
@@ -188,11 +199,10 @@
     }
 
     private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) {
-        EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("null");
-        webViewOnUiThread.evaluateJavascript(
-                "document.getElementById('" + linkId + "').click();" +
-                "console.log('element with id [" + linkId + "] clicked');", jsResult);
-        jsResult.run();
+        assertEquals("null", WebkitUtils.waitForFuture(
+                webViewOnUiThread.evaluateJavascript(
+                        "document.getElementById('" + linkId + "').click();" +
+                        "console.log('element with id [" + linkId + "] clicked');")));
     }
 
     public void testLoadPage() throws Exception {
@@ -265,6 +275,11 @@
             testServer.shutdown();
         }
     }
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testOnReceivedError. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testOnReceivedError() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -279,6 +294,11 @@
                 webViewClient.hasOnReceivedErrorCode());
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testOnReceivedErrorForSubresource. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testOnReceivedErrorForSubresource() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -552,28 +572,23 @@
             String mainUrl = server.setResponse(mainPath, mainPage, null);
             mOnUiThread.loadUrlAndWaitForCompletion(mainUrl, null);
 
-            EvaluateJsResultPollingCheck jsResult;
-
             // Test a nonexistent page
             client.interceptResponse = new WebResourceResponse("text/html", "UTF-8", null);
-            jsResult = new EvaluateJsResultPollingCheck("\"[404][Not Found]\"");
-            mOnUiThread.evaluateJavascript(js, jsResult);
-            jsResult.run();
+            assertEquals("\"[404][Not Found]\"", WebkitUtils.waitForFuture(
+                    mOnUiThread.evaluateJavascript(js)));
 
             // Test an empty page
             client.interceptResponse = new WebResourceResponse("text/html", "UTF-8",
                 new ByteArrayInputStream(new byte[0]));
-            jsResult = new EvaluateJsResultPollingCheck("\"[200][OK]\"");
-            mOnUiThread.evaluateJavascript(js, jsResult);
-            jsResult.run();
+            assertEquals("\"[200][OK]\"", WebkitUtils.waitForFuture(
+                    mOnUiThread.evaluateJavascript(js)));
 
             // Test a nonempty page with unusual response code/text
             client.interceptResponse =
                 new WebResourceResponse("text/html", "UTF-8", 123, "unusual", null,
                     new ByteArrayInputStream("nonempty page".getBytes(StandardCharsets.UTF_8)));
-            jsResult = new EvaluateJsResultPollingCheck("\"[123][unusual]\"");
-            mOnUiThread.evaluateJavascript(js, jsResult);
-            jsResult.run();
+            assertEquals("\"[123][unusual]\"", WebkitUtils.waitForFuture(
+                    mOnUiThread.evaluateJavascript(js)));
         } finally {
             server.shutdown();
         }
@@ -592,10 +607,7 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        if (Build.SUPPORTED_64_BIT_ABIS.length == 0 &&
-            getActivity().getSystemService(ActivityManager.class).isLowRamDevice()) {
-            // Renderer process crashes can only be handled when multiprocess is enabled,
-            // which is not the case for 32-bit lowram devices.
+        if (!getActivity().isMultiprocessMode()) {
             return;
         }
         final MockWebViewClient webViewClient = new MockWebViewClient();
@@ -610,20 +622,29 @@
         assertFalse(webViewClient.didRenderProcessCrash());
     }
 
-    public void testOnSafeBrowsingHit() throws Throwable {
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitBackToSafety.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
+    public void testOnSafeBrowsingHitBackToSafety() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient =
-                new SafeBrowsingBackToSafetyClient();
-        mOnUiThread.setWebViewClient(backToSafetyWebViewClient);
-        mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
-
         mWebServer = new CtsTestServer(getActivity());
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         final String ORIGINAL_URL = mOnUiThread.getUrl();
 
+        final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient =
+                new SafeBrowsingBackToSafetyClient();
+        mOnUiThread.setWebViewClient(backToSafetyWebViewClient);
+        mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
+
+        // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually
+        // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is
+        // enabled.
         if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) {
             assertEquals(0, backToSafetyWebViewClient.hasOnReceivedErrorCode());
             mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_URL);
@@ -639,11 +660,31 @@
             // Check that we actually navigated backward
             assertEquals(ORIGINAL_URL, mOnUiThread.getUrl());
         }
+    }
 
-        final SafeBrowsingProceedClient proceedWebViewClient = new SafeBrowsingProceedClient();
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitProceed.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
+    public void testOnSafeBrowsingHitProceed() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        mWebServer = new CtsTestServer(getActivity());
+        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        final String ORIGINAL_URL = mOnUiThread.getUrl();
+
+        final SafeBrowsingProceedClient proceedWebViewClient =
+                new SafeBrowsingProceedClient();
         mOnUiThread.setWebViewClient(proceedWebViewClient);
-
         mOnUiThread.getSettings().setSafeBrowsingEnabled(true);
+
+        // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually
+        // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is
+        // enabled.
         if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) {
             assertEquals(0, proceedWebViewClient.hasOnReceivedErrorCode());
             mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_URL);
@@ -664,6 +705,31 @@
         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewClientCompatTest#testOnPageCommitVisibleCalled.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
+    public void testOnPageCommitVisibleCalled() throws Exception {
+        // Check that the onPageCommitVisible callback is called
+        // correctly.
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        final SettableFuture<String> pageCommitVisibleFuture = SettableFuture.create();
+        mOnUiThread.setWebViewClient(new WebViewClient() {
+            public void onPageCommitVisible(WebView view, String url) {
+                pageCommitVisibleFuture.set(url);
+            }
+        });
+
+        final String url = "about:blank";
+        mOnUiThread.loadUrl(url);
+        assertEquals(url, WebkitUtils.waitForFuture(pageCommitVisibleFuture));
+    }
+
     private class MockWebViewClient extends WaitForLoadedClient {
         private boolean mOnPageStartedCalled;
         private boolean mOnPageFinishedCalled;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java b/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
index 9875662..6e656cc 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewCtsActivity.java
@@ -19,6 +19,8 @@
 import android.webkit.cts.R;
 
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -40,6 +42,13 @@
         }
     }
 
+    public boolean isMultiprocessMode() {
+        // Currently multiprocess is disabled on low RAM 32 bit devices.
+        return
+            Build.SUPPORTED_64_BIT_ABIS.length > 0 ||
+            !getSystemService(ActivityManager.class).isLowRamDevice();
+    }
+
     public WebView getWebView() {
         return mWebView;
     }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewRendererClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewRendererClientTest.java
new file mode 100644
index 0000000..dfdbcee
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewRendererClientTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.webkit.cts;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+import android.webkit.WebViewRenderer;
+import android.webkit.WebViewRendererClient;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class WebViewRendererClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+    private WebViewOnUiThread mOnUiThread;
+
+    public WebViewRendererClientTest() {
+        super("com.android.cts.webkit", WebViewCtsActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final WebViewCtsActivity activity = getActivity();
+        WebView webView = activity.getWebView();
+        mOnUiThread = new WebViewOnUiThread(webView);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
+        }
+        super.tearDown();
+    }
+
+    private static class JSBlocker {
+        // A CoundDownLatch is used here, instead of a Future, because that makes it
+        // easier to support requiring variable numbers of releaseBlock() calls
+        // to unblock.
+        private CountDownLatch mLatch;
+        private SettableFuture<Void> mIsBlocked;
+
+        JSBlocker(int requiredReleaseCount) {
+            mLatch = new CountDownLatch(requiredReleaseCount);
+            mIsBlocked = SettableFuture.create();
+        }
+
+        JSBlocker() {
+            this(1);
+        }
+
+        public void releaseBlock() {
+            mLatch.countDown();
+        }
+
+        @JavascriptInterface
+        public void block() throws Exception {
+            // This blocks indefinitely (until signalled) on a background thread.
+            // The actual test timeout is not determined by this wait, but by other
+            // code waiting for the onRendererUnresponsive() call.
+            mIsBlocked.set(null);
+            mLatch.await();
+        }
+
+        public void waitForBlocked() {
+            WebkitUtils.waitForFuture(mIsBlocked);
+        }
+    }
+
+    private void blockRenderer(final JSBlocker blocker) {
+        WebkitUtils.onMainThreadSync(new Runnable() {
+            @Override
+            public void run() {
+                WebView webView = mOnUiThread.getWebView();
+                webView.getSettings().setJavaScriptEnabled(true);
+                webView.addJavascriptInterface(blocker, "blocker");
+                webView.evaluateJavascript("blocker.block();", null);
+                blocker.waitForBlocked();
+                // Sending an input event that does not get acknowledged will cause
+                // the unresponsive renderer event to fire.
+                webView.dispatchKeyEvent(
+                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
+            }
+        });
+    }
+
+    private void testWebViewRendererClientOnExecutor(Executor executor) throws Throwable {
+        final JSBlocker blocker = new JSBlocker();
+        final SettableFuture<Void> rendererUnblocked = SettableFuture.create();
+
+        WebViewRendererClient client = new WebViewRendererClient() {
+            @Override
+            public void onRendererUnresponsive(WebView view, WebViewRenderer renderer) {
+                // Let the renderer unblock.
+                blocker.releaseBlock();
+            }
+
+            @Override
+            public void onRendererResponsive(WebView view, WebViewRenderer renderer) {
+                // Notify that the renderer has been unblocked.
+                rendererUnblocked.set(null);
+            }
+        };
+        if (executor == null) {
+            mOnUiThread.setWebViewRendererClient(client);
+        } else {
+            mOnUiThread.setWebViewRendererClient(executor, client);
+        }
+
+        mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
+        blockRenderer(blocker);
+        WebkitUtils.waitForFuture(rendererUnblocked);
+    }
+
+    public void testWebViewRendererClientWithoutExecutor() throws Throwable {
+        testWebViewRendererClientOnExecutor(null);
+    }
+
+    public void testWebViewRendererClientWithExecutor() throws Throwable {
+        final AtomicInteger executorCount = new AtomicInteger();
+        testWebViewRendererClientOnExecutor(new Executor() {
+            @Override
+            public void execute(Runnable r) {
+                executorCount.incrementAndGet();
+                r.run();
+            }
+        });
+        assertEquals(2, executorCount.get());
+    }
+
+    public void testSetWebViewRendererClient() throws Throwable {
+        assertNull("Initially the renderer client should be null",
+                mOnUiThread.getWebViewRendererClient());
+
+        final WebViewRendererClient webViewRendererClient = new WebViewRendererClient() {
+            @Override
+            public void onRendererUnresponsive(WebView view, WebViewRenderer renderer) {}
+
+            @Override
+            public void onRendererResponsive(WebView view, WebViewRenderer renderer) {}
+        };
+        mOnUiThread.setWebViewRendererClient(webViewRendererClient);
+
+        assertSame(
+                "After the renderer client is set, getting it should return the same object",
+                webViewRendererClient, mOnUiThread.getWebViewRendererClient());
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewRendererTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewRendererTest.java
new file mode 100644
index 0000000..e209d87
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewRendererTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.webkit.cts;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.test.ActivityInstrumentationTestCase2;
+import android.webkit.RenderProcessGoneDetail;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.WebViewRenderer;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.concurrent.Future;
+
+public class WebViewRendererTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+    private WebViewOnUiThread mOnUiThread;
+
+    public WebViewRendererTest() {
+        super("com.android.cts.webkit", WebViewCtsActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final WebViewCtsActivity activity = getActivity();
+        WebView webView = activity.getWebView();
+        mOnUiThread = new WebViewOnUiThread(webView);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
+        }
+        super.tearDown();
+    }
+
+    private boolean terminateRendererOnUiThread(
+            final WebViewRenderer renderer) {
+        return WebkitUtils.onMainThreadSync(() -> {
+            return renderer.terminate();
+        });
+    }
+
+    WebViewRenderer getRendererOnUiThread(final WebView webView) {
+        return WebkitUtils.onMainThreadSync(() -> {
+            return webView.getWebViewRenderer();
+        });
+    }
+
+    private Future<WebViewRenderer> startAndGetRenderer(
+            final WebView webView) throws Throwable {
+        final SettableFuture<WebViewRenderer> future = SettableFuture.create();
+
+        WebkitUtils.onMainThread(() -> {
+            webView.setWebViewClient(new WebViewClient() {
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    WebViewRenderer result = webView.getWebViewRenderer();
+                    future.set(result);
+                }
+            });
+            webView.loadUrl("about:blank");
+        });
+
+        return future;
+    }
+
+    Future<Boolean> catchRendererTermination(final WebView webView) {
+        final SettableFuture<Boolean> future = SettableFuture.create();
+
+        WebkitUtils.onMainThread(() -> {
+            webView.setWebViewClient(new WebViewClient() {
+                @Override
+                public boolean onRenderProcessGone(
+                        WebView view,
+                        RenderProcessGoneDetail detail) {
+                    view.destroy();
+                    future.set(true);
+                    return true;
+                }
+            });
+        });
+
+        return future;
+    }
+
+    public void testGetWebViewRenderer() throws Throwable {
+        final WebView webView = mOnUiThread.getWebView();
+        final WebViewRenderer preStartRenderer = getRendererOnUiThread(webView);
+
+        if (!getActivity().isMultiprocessMode()) {
+            assertNull(
+                    "getWebViewRenderer should return null is multiprocess is off.",
+                    preStartRenderer);
+            return;
+        }
+
+        assertNotNull(
+                "Should be possible to obtain a renderer handle before the renderer has started.",
+                preStartRenderer);
+        assertFalse(
+                "Should not be able to terminate an unstarted renderer.",
+                terminateRendererOnUiThread(preStartRenderer));
+
+        final WebViewRenderer renderer = WebkitUtils.waitForFuture(startAndGetRenderer(webView));
+        assertSame(
+                "The pre- and post-start renderer handles should be the same object.",
+                renderer, preStartRenderer);
+
+        assertSame(
+                "When getWebViewRender is called a second time, it should return the same object.",
+                renderer, WebkitUtils.waitForFuture(startAndGetRenderer(webView)));
+
+        Future<Boolean> terminationFuture = catchRendererTermination(webView);
+        assertTrue(
+                "A started renderer should be able to be terminated.",
+                terminateRendererOnUiThread(renderer));
+        assertTrue(
+                "Terminating a renderer should result in onRenderProcessGone being called.",
+                WebkitUtils.waitForFuture(terminationFuture));
+
+        assertFalse(
+                "It should not be possible to terminate a renderer that has already terminated.",
+                terminateRendererOnUiThread(renderer));
+
+        WebView webView2 = mOnUiThread.createWebView();
+        try {
+            assertNotSame(
+                    "After a renderer restart, the new renderer handle object should be different.",
+                    renderer, WebkitUtils.waitForFuture(startAndGetRenderer(webView2)));
+        } finally {
+            // Ensure that we clean up webView2. webView has been destroyed by the WebViewClient
+            // installed by catchRendererTermination
+            WebkitUtils.onMainThreadSync(() -> webView2.destroy());
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index 08de524..7fd8fc8 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -22,7 +22,6 @@
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
 import android.util.Log;
 import android.webkit.ClientCertRequest;
 import android.webkit.SslErrorHandler;
@@ -30,7 +29,7 @@
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -451,7 +450,7 @@
                 f.delete();
             }
 
-            mOnUiThread = new WebViewOnUiThread(this, mWebView);
+            mOnUiThread = new WebViewOnUiThread(mWebView);
         }
     }
 
@@ -483,7 +482,6 @@
         StrictMode.setThreadPolicy(oldPolicy);
     }
 
-    @UiThreadTest
     public void testInsecureSiteClearsCertificate() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -502,7 +500,7 @@
         mOnUiThread.setWebViewClient(new MockWebViewClient());
         mOnUiThread.loadUrlAndWaitForCompletion(
                 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        SslCertificate cert = mWebView.getCertificate();
+        SslCertificate cert = mOnUiThread.getCertificate();
         assertNotNull(cert);
         assertEquals("Android", cert.getIssuedTo().getUName());
 
@@ -511,10 +509,9 @@
         startWebServer(false);
         mOnUiThread.loadUrlAndWaitForCompletion(
                 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        assertNull(mWebView.getCertificate());
+        assertNull(mOnUiThread.getCertificate());
     }
 
-    @UiThreadTest
     public void testSecureSiteSetsCertificate() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -532,7 +529,7 @@
         startWebServer(false);
         mOnUiThread.loadUrlAndWaitForCompletion(
                 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        assertNull(mWebView.getCertificate());
+        assertNull(mOnUiThread.getCertificate());
 
         stopWebServer();
 
@@ -540,12 +537,11 @@
         mOnUiThread.setWebViewClient(new MockWebViewClient());
         mOnUiThread.loadUrlAndWaitForCompletion(
                 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        SslCertificate cert = mWebView.getCertificate();
+        SslCertificate cert = mOnUiThread.getCertificate();
         assertNotNull(cert);
         assertEquals("Android", cert.getIssuedTo().getUName());
     }
 
-    @UiThreadTest
     public void testClearSslPreferences() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index f5f3139..2f95f82 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -74,15 +74,13 @@
 import android.webkit.WebView.VisualStateCallback;
 import android.webkit.WebViewClient;
 import android.webkit.WebViewDatabase;
-import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
-import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
+import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 import android.widget.LinearLayout;
 
-import com.android.compatibility.common.util.EvaluateJsResultPollingCheck;
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
-
-import junit.framework.Assert;
+import com.google.common.util.concurrent.SettableFuture;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -100,9 +98,12 @@
 import java.util.Date;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
 import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.ArrayList;
@@ -177,7 +178,7 @@
                 f.delete();
             }
 
-            mOnUiThread = new WebViewOnUiThread(this, mWebView);
+            mOnUiThread = new WebViewOnUiThread(mWebView);
         }
     }
 
@@ -238,7 +239,7 @@
             return;
         }
 
-        Assert.fail("WebView should have thrown exception when creating with a device " +
+        fail("WebView should have thrown exception when creating with a device " +
                 "protected storage context");
     }
 
@@ -261,7 +262,7 @@
             return;
         }
 
-        Assert.fail("WebView should have thrown exception when creating with a device " +
+        fail("WebView should have thrown exception when creating with a device " +
                 "protected storage context");
     }
 
@@ -349,132 +350,66 @@
 
         // can zoom in or out although zoom support is disabled in web settings
         assertTrue(mOnUiThread.zoomIn());
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale > previousScale);
+        currScale = webViewClient.expectZoomIn(currScale);
 
         assertTrue(mOnUiThread.zoomOut());
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale < previousScale);
+        currScale = webViewClient.expectZoomOut(currScale);
 
         mOnUiThread.zoomBy(1.25f); // zoom in
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale > previousScale);
+        currScale = webViewClient.expectZoomBy(currScale, 1.25f);
 
         mOnUiThread.zoomBy(0.8f); // zoom out
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale < previousScale);
+        currScale = webViewClient.expectZoomBy(currScale, 0.8f);
 
         // enable zoom support
         settings.setSupportZoom(true);
         assertTrue(settings.supportZoom());
-        previousScale = mOnUiThread.getScale();
 
-        assertTrue(mOnUiThread.zoomIn());
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale > previousScale);
-
-        // zoom in until it reaches maximum scale
+        // Zoom in until maximum scale, in default increments.
         while (mOnUiThread.zoomIn()) {
-            previousScale = currScale;
-            webViewClient.waitForScaleChanged();
-            currScale = mOnUiThread.getScale();
-            assertTrue(currScale > previousScale);
+            currScale = webViewClient.expectZoomIn(currScale);
         }
 
-        previousScale = currScale;
-        // can not zoom in further
         assertFalse(mOnUiThread.zoomIn());
+        assertNoScaleChange(webViewClient, currScale);
 
-        // We sleep to assert to the best of our ability
-        // that a scale change does *not* happen.
-        Thread.sleep(500);
-        currScale = mOnUiThread.getScale();
-        assertEquals(currScale, previousScale, PAGE_SCALE_EPSILON);
-
-        assertTrue(mOnUiThread.zoomOut());
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale < previousScale);
-
-        // zoom out until it reaches minimum scale
+        // Zoom out until minimum scale, in default increments.
         while (mOnUiThread.zoomOut()) {
-            previousScale = currScale;
-            webViewClient.waitForScaleChanged();
-            currScale = mOnUiThread.getScale();
-            assertTrue(currScale < previousScale);
+            currScale = webViewClient.expectZoomOut(currScale);
         }
 
-        previousScale = currScale;
         assertFalse(mOnUiThread.zoomOut());
+        assertNoScaleChange(webViewClient, currScale);
 
-        // We sleep to assert to the best of our ability
-        // that a scale change does *not* happen.
-        Thread.sleep(500);
-        currScale = mOnUiThread.getScale();
-        assertEquals(currScale, previousScale, PAGE_SCALE_EPSILON);
-
-        mOnUiThread.zoomBy(1.25f);
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale > previousScale);
-
-        // zoom in until it reaches maximum scale
+        // Zoom in until maximum scale, in specified increments designed so that the last zoom will
+        // be less than expected.
         while (mOnUiThread.canZoomIn()) {
-            previousScale = currScale;
-            mOnUiThread.zoomBy(1.25f);
-            webViewClient.waitForScaleChanged();
-            currScale = mOnUiThread.getScale();
-            assertTrue(currScale > previousScale);
+            mOnUiThread.zoomBy(1.7f);
+            currScale = webViewClient.expectZoomBy(currScale, 1.7f);
         }
 
-        previousScale = currScale;
+        // At this point, zooming in should do nothing.
+        mOnUiThread.zoomBy(1.7f);
+        assertNoScaleChange(webViewClient, currScale);
 
-        // We sleep to assert to the best of our ability
-        // that a scale change does *not* happen.
-        Thread.sleep(500);
-        currScale = mOnUiThread.getScale();
-        assertEquals(currScale, previousScale, PAGE_SCALE_EPSILON);
-
-        mOnUiThread.zoomBy(0.8f);
-        previousScale = currScale;
-        webViewClient.waitForScaleChanged();
-
-        currScale = mOnUiThread.getScale();
-        assertTrue(currScale < previousScale);
-
-        // zoom out until it reaches minimum scale
+        // Zoom out until minimum scale, in specified increments designed so that the last zoom will
+        // be less than requested.
         while (mOnUiThread.canZoomOut()) {
-            previousScale = currScale;
-            mOnUiThread.zoomBy(0.8f);
-            webViewClient.waitForScaleChanged();
-            currScale = mOnUiThread.getScale();
-            assertTrue(currScale < previousScale);
+            mOnUiThread.zoomBy(0.7f);
+            currScale = webViewClient.expectZoomBy(currScale, 0.7f);
         }
 
-        previousScale = currScale;
+        // At this point, zooming out should do nothing.
+        mOnUiThread.zoomBy(0.7f);
+        assertNoScaleChange(webViewClient, currScale);
+    }
 
+    private void assertNoScaleChange(ScaleChangedWebViewClient webViewClient, float currScale) throws InterruptedException {
         // We sleep to assert to the best of our ability
         // that a scale change does *not* happen.
         Thread.sleep(500);
-        currScale = mOnUiThread.getScale();
-        assertEquals(currScale, previousScale, PAGE_SCALE_EPSILON);
+        assertFalse(webViewClient.onScaleChangedCalled());
+        assertEquals(currScale, mOnUiThread.getScale());
     }
 
     @UiThreadTest
@@ -674,7 +609,7 @@
         // handle loading a new URL. We set a WebViewClient as
         // WebViewClient.shouldOverrideUrlLoading() returns false, so
         // the WebView will load the new URL.
-        mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread));
+        mWebView.setWebViewClient(new WaitForLoadedClient(mOnUiThread));
         mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl);
 
         assertEquals(finalUrl, mWebView.getUrl());
@@ -769,15 +704,13 @@
         assertGoBackOrForwardBySteps(false, 1);
     }
 
-    @UiThreadTest
     public void testAddJavascriptInterface() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
-        WebSettings settings = mWebView.getSettings();
-        settings.setJavaScriptEnabled(true);
-        settings.setJavaScriptCanOpenWindowsAutomatically(true);
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
 
         final class DummyJavaScriptInterface {
             private boolean mWasProvideResultCalled;
@@ -791,7 +724,7 @@
                         continue;
                     }
                     if (!mWasProvideResultCalled) {
-                        Assert.fail("Unexpected timeout");
+                        fail("Unexpected timeout");
                     }
                 }
                 return mResult;
@@ -810,7 +743,7 @@
         }
 
         final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
-        mWebView.addJavascriptInterface(obj, "dummy");
+        mOnUiThread.addJavascriptInterface(obj, "dummy");
         assertFalse(obj.wasProvideResultCalled());
 
         startWebServer(false);
@@ -820,87 +753,75 @@
 
         // Verify that only methods annotated with @JavascriptInterface are exposed
         // on the JavaScript interface object.
-        mOnUiThread.evaluateJavascript("typeof dummy.provideResult",
-                new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(String result) {
-                        assertEquals("\"function\"", result);
-                    }
-                });
-        mOnUiThread.evaluateJavascript("typeof dummy.wasProvideResultCalled",
-                new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(String result) {
-                        assertEquals("\"undefined\"", result);
-                    }
-                });
-        mOnUiThread.evaluateJavascript("typeof dummy.getClass",
-                new ValueCallback<String>() {
-                    @Override
-                    public void onReceiveValue(String result) {
-                        assertEquals("\"undefined\"", result);
-                    }
-                });
+        final BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
+        mOnUiThread.evaluateJavascript("typeof dummy.provideResult", result -> {
+            queue.add(result);
+        });
+        mOnUiThread.evaluateJavascript("typeof dummy.wasProvideResultCalled", result -> {
+            queue.add(result);
+        });
+        mOnUiThread.evaluateJavascript("typeof dummy.getClass", result -> {
+            queue.add(result);
+        });
+        assertEquals("\"function\"", WebkitUtils.waitForNextQueueElement(queue));
+        assertEquals("\"undefined\"", WebkitUtils.waitForNextQueueElement(queue));
+        assertEquals("\"undefined\"", WebkitUtils.waitForNextQueueElement(queue));
     }
 
-    @UiThreadTest
     public void testAddJavascriptInterfaceNullObject() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
-        WebSettings settings = mWebView.getSettings();
-        settings.setJavaScriptEnabled(true);
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
         String setTitleToPropertyTypeHtml = "<html><head></head>" +
                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
 
         // Test that the property is initially undefined.
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("undefined", mWebView.getTitle());
+        assertEquals("undefined", mOnUiThread.getTitle());
 
         // Test that adding a null object has no effect.
-        mWebView.addJavascriptInterface(null, "injectedObject");
+        mOnUiThread.addJavascriptInterface(null, "injectedObject");
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("undefined", mWebView.getTitle());
+        assertEquals("undefined", mOnUiThread.getTitle());
 
         // Test that adding an object gives an object type.
         final Object obj = new Object();
-        mWebView.addJavascriptInterface(obj, "injectedObject");
+        mOnUiThread.addJavascriptInterface(obj, "injectedObject");
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("object", mWebView.getTitle());
+        assertEquals("object", mOnUiThread.getTitle());
 
         // Test that trying to replace with a null object has no effect.
-        mWebView.addJavascriptInterface(null, "injectedObject");
+        mOnUiThread.addJavascriptInterface(null, "injectedObject");
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("object", mWebView.getTitle());
+        assertEquals("object", mOnUiThread.getTitle());
     }
 
-    @UiThreadTest
     public void testRemoveJavascriptInterface() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
-        WebSettings settings = mWebView.getSettings();
-        settings.setJavaScriptEnabled(true);
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
         String setTitleToPropertyTypeHtml = "<html><head></head>" +
                 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
 
         // Test that adding an object gives an object type.
-        mWebView.addJavascriptInterface(new Object(), "injectedObject");
+        mOnUiThread.addJavascriptInterface(new Object(), "injectedObject");
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("object", mWebView.getTitle());
+        assertEquals("object", mOnUiThread.getTitle());
 
         // Test that reloading the page after removing the object leaves the property undefined.
-        mWebView.removeJavascriptInterface("injectedObject");
+        mOnUiThread.removeJavascriptInterface("injectedObject");
         mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
                 "text/html", null);
-        assertEquals("undefined", mWebView.getTitle());
+        assertEquals("undefined", mOnUiThread.getTitle());
     }
 
     public void testUseRemovedJavascriptInterface() throws Throwable {
@@ -979,18 +900,9 @@
 
         assertFalse(mJsInterfaceWasCalled.get());
 
-        final CountDownLatch resultLatch = new CountDownLatch(1);
-        mOnUiThread.evaluateJavascript(
-                "try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ",
-                new ValueCallback<String>() {
-                        @Override
-                        public void onReceiveValue(String result) {
-                            assertEquals("\"pass\"", result);
-                            resultLatch.countDown();
-                        }
-                    });
-
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertEquals("\"pass\"", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript(
+                        "try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ")));
         assertTrue(mJsInterfaceWasCalled.get());
     }
 
@@ -1007,19 +919,16 @@
         mOnUiThread.addJavascriptInterface(obj, "dummy");
         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
 
-        EvaluateJsResultPollingCheck jsResult;
-        jsResult = new EvaluateJsResultPollingCheck("42");
-        mOnUiThread.evaluateJavascript("dummy.custom_property = 42", jsResult);
-        jsResult.run();
-        jsResult = new EvaluateJsResultPollingCheck("true");
-        mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult);
-        jsResult.run();
+        assertEquals("42", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript("dummy.custom_property = 42")));
+
+        assertEquals("true", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript("'custom_property' in dummy")));
 
         mOnUiThread.reloadAndWaitForCompletion();
 
-        jsResult = new EvaluateJsResultPollingCheck("false");
-        mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult);
-        jsResult.run();
+        assertEquals("false", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript("'custom_property' in dummy")));
     }
 
     public void testJavascriptInterfaceForClientPopup() throws Exception {
@@ -1040,13 +949,12 @@
         final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
 
         final WebView childWebView = mOnUiThread.createWebView();
-        WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(this, childWebView);
+        WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(childWebView);
         childOnUiThread.getSettings().setJavaScriptEnabled(true);
         childOnUiThread.addJavascriptInterface(obj, "dummy");
 
-        final boolean[] hadOnCreateWindow = new boolean[1];
-        hadOnCreateWindow[0] = false;
-        mOnUiThread.setWebChromeClient(new WebViewOnUiThread.WaitForProgressClient(mOnUiThread) {
+        final SettableFuture<Void> onCreateWindowFuture = SettableFuture.create();
+        mOnUiThread.setWebChromeClient(new WebViewSyncLoader.WaitForProgressClient(mOnUiThread) {
             @Override
             public boolean onCreateWindow(
                 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
@@ -1056,7 +964,7 @@
                 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
                 transport.setWebView(childWebView);
                 resultMsg.sendToTarget();
-                hadOnCreateWindow[0] = true;
+                onCreateWindowFuture.set(null);
                 return true;
             }
         });
@@ -1064,22 +972,16 @@
         startWebServer(false);
         mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
                 getAssetUrl(TestHtmlConstants.POPUP_URL));
-        new PollingCheck(TEST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return hadOnCreateWindow[0];
-            }
-        }.run();
+        WebkitUtils.waitForFuture(onCreateWindowFuture);
 
         childOnUiThread.loadUrlAndWaitForCompletion("about:blank");
-        EvaluateJsResultPollingCheck jsResult;
-        jsResult = new EvaluateJsResultPollingCheck("true");
-        childOnUiThread.evaluateJavascript("'dummy' in window", jsResult);
-        jsResult.run();
+
+        assertEquals("true", WebkitUtils.waitForFuture(
+                childOnUiThread.evaluateJavascript("'dummy' in window")));
+
         // Verify that the injected object is functional.
-        jsResult = new EvaluateJsResultPollingCheck("42");
-        childOnUiThread.evaluateJavascript("dummy.test()", jsResult);
-        jsResult.run();
+        assertEquals("42", WebkitUtils.waitForFuture(
+                childOnUiThread.evaluateJavascript("dummy.test()")));
     }
 
     private final class TestPictureListener implements PictureListener {
@@ -1100,11 +1002,8 @@
         final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>();
         for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) {
             final int oldCallCount = listener.callCount;
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    pictureRef.set(mWebView.capturePicture());
-                }
+            WebkitUtils.onMainThreadSync(() -> {
+                pictureRef.set(mWebView.capturePicture());
             });
             if (isPictureFilledWithColor(pictureRef.get(), color))
                 break;
@@ -1132,11 +1031,8 @@
         // The default background color is white.
         Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener);
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.setBackgroundColor(Color.CYAN);
-            }
+        WebkitUtils.onMainThread(() -> {
+            mWebView.setBackgroundColor(Color.CYAN);
         });
         mOnUiThread.reloadAndWaitForCompletion();
         waitForPictureToHaveColor(Color.CYAN, listener);
@@ -1337,18 +1233,15 @@
         startWebServer(false);
         final ChromeClient webChromeClient = new ChromeClient(mOnUiThread);
         final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.getSettings().setJavaScriptEnabled(true);
-                mOnUiThread.setWebChromeClient(webChromeClient);
-                mOnUiThread.loadDataAndWaitForCompletion(
-                        "<html><head></head><body onload=\"" +
-                        "document.title = " +
-                        "document.getElementById('frame').contentWindow.location.href;" +
-                        "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
-                        "text/html", null);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.getSettings().setJavaScriptEnabled(true);
+            mWebView.setWebChromeClient(webChromeClient);
+            mOnUiThread.loadDataAndWaitForCompletion(
+                    "<html><head></head><body onload=\"" +
+                    "document.title = " +
+                    "document.getElementById('frame').contentWindow.location.href;" +
+                    "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
+                    "text/html", null);
         });
         assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000));
     }
@@ -1410,18 +1303,18 @@
         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com",
                 HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>",
                 "text/html", "UTF-8", null);
-        assertEquals("Hello World%21", mOnUiThread.getTitle());
+        assertEquals("Hello World%21", mWebView.getTitle());
 
         // Check that when a data: base URL is used, we treat the String to load as a data: URL
         // and run load steps such as decoding URL entities (i.e., contrary to the test case
         // above.)
         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
                 HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null);
-        assertEquals("Hello World!", mOnUiThread.getTitle());
+        assertEquals("Hello World!", mWebView.getTitle());
 
         // Check the method is null input safe.
         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null);
-        assertEquals("about:blank", mOnUiThread.getUrl());
+        assertEquals("about:blank", mWebView.getUrl());
     }
 
     private void deleteIfExists(File file) throws IOException {
@@ -1512,8 +1405,9 @@
         }
     }
 
-    private static class WaitForFindResultsListener extends FutureTask<Integer>
+    private static class WaitForFindResultsListener
             implements WebView.FindListener {
+        private final SettableFuture<Integer> mFuture;
         private final WebViewOnUiThread mWebViewOnUiThread;
         private final int mMatchesWanted;
         private final String mStringWanted;
@@ -1521,16 +1415,17 @@
 
         public WaitForFindResultsListener(
                 WebViewOnUiThread wv, String wanted, int matches, boolean retry) {
-            super(new Runnable() {
-                @Override
-                public void run() { }
-            }, null);
+            mFuture = SettableFuture.create();
             mWebViewOnUiThread = wv;
             mMatchesWanted = matches;
             mStringWanted = wanted;
             mRetry = retry;
         }
 
+        public Future<Integer> future() {
+            return mFuture;
+        }
+
         @Override
         public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
                 boolean isDoneCounting) {
@@ -1540,7 +1435,7 @@
                     mWebViewOnUiThread.findAll(mStringWanted);
                 }
                 else {
-                    set(numberOfMatches);
+                    mFuture.set(numberOfMatches);
                 }
             }
         }
@@ -1565,7 +1460,7 @@
         WaitForFindResultsListener l = new WaitForFindResultsListener(mOnUiThread, "find", 2, true);
         mOnUiThread.setFindListener(l);
         mOnUiThread.findAll("find");
-        assertEquals(2, l.get(MIN_FIND_WAIT_MS, TimeUnit.MILLISECONDS).intValue());
+        assertEquals(2, (int)WebkitUtils.waitForFuture(l.future()));
     }
 
     public void testFindNext() throws Throwable {
@@ -1587,7 +1482,7 @@
 
         // highlight all the strings found and wait for all the matches to be found
         mOnUiThread.findAll("all");
-        l.get(MIN_FIND_WAIT_MS, TimeUnit.MILLISECONDS);
+        WebkitUtils.waitForFuture(l.future());
         mOnUiThread.setFindListener(null);
 
         int previousScrollY = mOnUiThread.getScrollY();
@@ -1635,23 +1530,17 @@
             return;
         }
         final class DocumentHasImageCheckHandler extends Handler {
-            private boolean mReceived;
-            private int mMsgArg1;
+            private SettableFuture<Integer> mFuture;
             public DocumentHasImageCheckHandler(Looper looper) {
                 super(looper);
+                mFuture = SettableFuture.create();
             }
             @Override
             public void handleMessage(Message msg) {
-                synchronized(this) {
-                    mReceived = true;
-                    mMsgArg1 = msg.arg1;
-                }
+                mFuture.set(msg.arg1);
             }
-            public synchronized boolean hasCalledHandleMessage() {
-                return mReceived;
-            }
-            public synchronized int getMsgArg1() {
-                return mMsgArg1;
+            public Future<Integer> future() {
+                return mFuture;
             }
         }
 
@@ -1662,24 +1551,15 @@
         final DocumentHasImageCheckHandler handler =
             new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper());
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\""
-                        + imgUrl + "\"/></body></html>", "text/html", null);
-                Message response = new Message();
-                response.setTarget(handler);
-                assertFalse(handler.hasCalledHandleMessage());
-                mWebView.documentHasImages(response);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\""
+                    + imgUrl + "\"/></body></html>", "text/html", null);
+            Message response = new Message();
+            response.setTarget(handler);
+            assertFalse(handler.future().isDone());
+            mWebView.documentHasImages(response);
         });
-        new PollingCheck() {
-            @Override
-            protected boolean check() {
-                return handler.hasCalledHandleMessage();
-            }
-        }.run();
-        assertEquals(1, handler.getMsgArg1());
+        assertEquals(1, (int)WebkitUtils.waitForFuture(handler.future()));
     }
 
     private static void waitForFlingDone(WebViewOnUiThread webview) {
@@ -1945,19 +1825,19 @@
             return;
         }
         final class ImageLoaded {
-            public boolean mImageLoaded;
+            public SettableFuture<Void> mImageLoaded = SettableFuture.create();
 
             @JavascriptInterface
             public void loaded() {
-                mImageLoaded = true;
+                mImageLoaded.set(null);
+            }
+
+            public Future<Void> future() {
+                return mImageLoaded;
             }
         }
         final ImageLoaded imageLoaded = new ImageLoaded();
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                mOnUiThread.getSettings().setJavaScriptEnabled(true);
-            }
-        });
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
         mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded");
         AssetManager assets = getActivity().getAssets();
         Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL));
@@ -1979,12 +1859,7 @@
                 + "</head><body><img id=\"imgElement\" src=\"" + imgUrl
                 + "\" width=\"" + imgWidth + "\" height=\"" + imgHeight
                 + "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null);
-        new PollingCheck() {
-            @Override
-            protected boolean check() {
-                return imageLoaded.mImageLoaded;
-            }
-        }.run();
+        WebkitUtils.waitForFuture(imageLoaded.future());
         getInstrumentation().waitForIdleSync();
 
         final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
@@ -2252,7 +2127,31 @@
         pollingCheckForCanZoomIn();
 
         assertTrue(mOnUiThread.zoomIn());
-        webViewClient.waitForScaleChanged();
+        webViewClient.waitForNextScaleChange();
+    }
+
+    public void testScaleChangeCallbackMatchesGetScale() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        // wrap_parent layout causes extra onScaleChanged ecallbacks, so set to match_parent to
+        // suppress them.
+        mOnUiThread.setLayoutHeightToMatchParent();
+        final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient();
+        mOnUiThread.setWebViewClient(webViewClient);
+        startWebServer(false);
+
+        assertFalse(webViewClient.onScaleChangedCalled());
+        String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+        mOnUiThread.loadUrlAndWaitForCompletion(url1);
+        pollingCheckForCanZoomIn();
+
+        assertFalse(webViewClient.onScaleChangedCalled());
+        assertTrue(mOnUiThread.zoomIn());
+        ScaleChangedState state = webViewClient.waitForNextScaleChange();
+        assertEquals(
+                "Expected onScaleChanged arg 2 (new scale) to equal view.getScale()",
+                state.mNewScale, mOnUiThread.getScale());
     }
 
     public void testRequestChildRectangleOnScreen() throws Throwable {
@@ -2290,7 +2189,7 @@
             return;
         }
 
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<Void> downloadStartFuture = SettableFuture.create();
         final class MyDownloadListener implements DownloadListener {
             public String url;
             public String mimeType;
@@ -2304,7 +2203,7 @@
                 this.mimeType = mimetype;
                 this.contentLength = contentLength;
                 this.contentDisposition = contentDisposition;
-                resultLatch.countDown();
+                downloadStartFuture.set(null);
             }
         }
 
@@ -2327,7 +2226,7 @@
         // Wait for layout to complete before setting focus.
         getInstrumentation().waitForIdleSync();
 
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        WebkitUtils.waitForFuture(downloadStartFuture);
         assertEquals(url, listener.url);
         assertTrue(listener.contentDisposition.contains("test.bin"));
         assertEquals(length, listener.contentLength);
@@ -2389,40 +2288,24 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        final class MockWebChromeClient extends WaitForProgressClient {
-            private boolean mOnProgressChanged = false;
 
-            public MockWebChromeClient() {
-                super(mOnUiThread);
-            }
-
+        final SettableFuture<Void> future = SettableFuture.create();
+        mOnUiThread.setWebChromeClient(new WaitForProgressClient(mOnUiThread) {
             @Override
             public void onProgressChanged(WebView view, int newProgress) {
                 super.onProgressChanged(view, newProgress);
-                mOnProgressChanged = true;
+                future.set(null);
             }
-            public boolean onProgressChangedCalled() {
-                return mOnProgressChanged;
-            }
-        }
-
-        final MockWebChromeClient webChromeClient = new MockWebChromeClient();
-
-        mOnUiThread.setWebChromeClient(webChromeClient);
+        });
         getInstrumentation().waitForIdleSync();
-        assertFalse(webChromeClient.onProgressChangedCalled());
+        assertFalse(future.isDone());
 
         startWebServer(false);
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         getInstrumentation().waitForIdleSync();
 
-        new PollingCheck(TEST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return webChromeClient.onProgressChangedCalled();
-            }
-        }.run();
+        WebkitUtils.waitForFuture(future);
     }
 
     public void testPauseResumeTimers() throws Throwable {
@@ -2459,15 +2342,12 @@
                 "<body onload=\"monitor.update();\"></body></html>";
 
         // Test that JavaScript is executed even with timers paused.
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mWebView.getSettings().setJavaScriptEnabled(true);
-                mWebView.addJavascriptInterface(monitor, "monitor");
-                mWebView.pauseTimers();
-                mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml,
-                        "text/html", null);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            mWebView.getSettings().setJavaScriptEnabled(true);
+            mWebView.addJavascriptInterface(monitor, "monitor");
+            mWebView.pauseTimers();
+            mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml,
+                    "text/html", null);
         });
         assertTrue(monitor.waitForUpdate());
 
@@ -2517,13 +2397,11 @@
         mOnUiThread.getSettings().setJavaScriptEnabled(true);
         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
 
-        EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("2");
-        mOnUiThread.evaluateJavascript("1+1", jsResult);
-        jsResult.run();
+        assertEquals("2", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript("1+1")));
 
-        jsResult = new EvaluateJsResultPollingCheck("9");
-        mOnUiThread.evaluateJavascript("1+1; 4+5", jsResult);
-        jsResult.run();
+        assertEquals("9", WebkitUtils.waitForFuture(
+                mOnUiThread.evaluateJavascript("1+1; 4+5")));
 
         final String EXPECTED_TITLE = "test";
         mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
@@ -2554,12 +2432,7 @@
         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
                 ParcelFileDescriptor.parseMode("w"));
-        final FutureTask<Boolean> result =
-                new FutureTask<Boolean>(new Callable<Boolean>() {
-                            public Boolean call() {
-                                return true;
-                            }
-                        });
+        final SettableFuture<Void> result = SettableFuture.create();
         printDocumentLayout(adapter, null, attributes,
                 new LayoutResultCallback() {
                     // Called on UI thread
@@ -2570,7 +2443,7 @@
                     }
                 });
         try {
-            result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+            WebkitUtils.waitForFuture(result);
             assertTrue(file.length() > 0);
             FileInputStream in = new FileInputStream(file);
             byte[] b = new byte[PDF_PREAMBLE.length()];
@@ -2606,12 +2479,7 @@
         final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
         final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
                 ParcelFileDescriptor.parseMode("w"));
-        final FutureTask<Boolean> result =
-                new FutureTask<Boolean>(new Callable<Boolean>() {
-                            public Boolean call() {
-                                return true;
-                            }
-                        });
+        final SettableFuture<Void> result = SettableFuture.create();
         printDocumentLayout(adapter, null, attributes,
                 new LayoutResultCallback() {
                     // Called on UI thread
@@ -2624,7 +2492,7 @@
                     }
                 });
         try {
-            result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+            WebkitUtils.waitForFuture(result);
             assertTrue(file.length() > 0);
             PdfRenderer renderer = new PdfRenderer(
                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
@@ -2635,47 +2503,37 @@
         }
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testVisualStateCallbackCalled. Modifications to this test
+     * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testVisualStateCallbackCalled() throws Exception {
         // Check that the visual state callback is called correctly.
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
-        final CountDownLatch callbackLatch = new CountDownLatch(1);
         final long kRequest = 100;
 
         mOnUiThread.loadUrl("about:blank");
 
+        final SettableFuture<Long> visualStateFuture = SettableFuture.create();
         mOnUiThread.postVisualStateCallback(kRequest, new VisualStateCallback() {
             public void onComplete(long requestId) {
-                assertEquals(kRequest, requestId);
-                callbackLatch.countDown();
+                visualStateFuture.set(requestId);
             }
         });
 
-        assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertEquals(kRequest, (long) WebkitUtils.waitForFuture(visualStateFuture));
     }
 
-    public void testOnPageCommitVisibleCalled() throws Exception {
-        // Check that the onPageCommitVisible callback is called
-        // correctly.
-        if (!NullWebViewUtils.isWebViewAvailable()) {
-            return;
-        }
-
-        final CountDownLatch callbackLatch = new CountDownLatch(1);
-
-        mOnUiThread.setWebViewClient(new WebViewClient() {
-                public void onPageCommitVisible(WebView view, String url) {
-                    assertEquals(url, "about:blank");
-                    callbackLatch.countDown();
-                }
-            });
-
-        mOnUiThread.loadUrl("about:blank");
-        assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
-    }
-
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingWhitelistWithMalformedList.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
     public void testSetSafeBrowsingWhitelistWithMalformedList() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -2684,17 +2542,22 @@
         List whitelist = new ArrayList<String>();
         // Protocols are not supported in the whitelist
         whitelist.add("http://google.com");
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<Boolean> safeBrowsingWhitelistFuture = SettableFuture.create();
         WebView.setSafeBrowsingWhitelist(whitelist, new ValueCallback<Boolean>() {
             @Override
             public void onReceiveValue(Boolean success) {
-                assertFalse(success);
-                resultLatch.countDown();
+                safeBrowsingWhitelistFuture.set(success);
             }
         });
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertFalse(WebkitUtils.waitForFuture(safeBrowsingWhitelistFuture));
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingWhitelistWithValidList. Modifications
+     * to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
     public void testSetSafeBrowsingWhitelistWithValidList() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -2702,36 +2565,41 @@
 
         List whitelist = new ArrayList<String>();
         whitelist.add("safe-browsing");
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<Boolean> safeBrowsingWhitelistFuture = SettableFuture.create();
         WebView.setSafeBrowsingWhitelist(whitelist, new ValueCallback<Boolean>() {
             @Override
             public void onReceiveValue(Boolean success) {
-                assertTrue(success);
-                resultLatch.countDown();
+                safeBrowsingWhitelistFuture.set(success);
             }
         });
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertTrue(WebkitUtils.waitForFuture(safeBrowsingWhitelistFuture));
 
-        final CountDownLatch resultLatch2 = new CountDownLatch(1);
+        final SettableFuture<Void> pageFinishedFuture = SettableFuture.create();
         mOnUiThread.setWebViewClient(new WebViewClient() {
             @Override
             public void onPageFinished(WebView view, String url) {
-                resultLatch2.countDown();
+                pageFinishedFuture.set(null);
             }
 
             @Override
             public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType,
                     SafeBrowsingResponse callback) {
-                Assert.fail("Should not invoke onSafeBrowsingHit");
+                pageFinishedFuture.setException(new IllegalStateException(
+                        "Should not invoke onSafeBrowsingHit"));
             }
         });
 
         mOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware");
 
         // Wait until page load has completed
-        assertTrue(resultLatch2.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        WebkitUtils.waitForFuture(pageFinishedFuture);
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testGetWebViewClient. Modifications to this test should be
+     * reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     @UiThreadTest
     public void testGetWebViewClient() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -2751,6 +2619,11 @@
         assertSame(client2, webView.getWebViewClient());
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testGetWebChromeClient. Modifications to this test should
+     * be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     @UiThreadTest
     public void testGetWebChromeClient() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -2818,24 +2691,34 @@
         }
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingUseApplicationContext. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testStartSafeBrowsingUseApplicationContext() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
         final MockContext ctx = new MockContext(getActivity());
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
         WebView.startSafeBrowsing(ctx, new ValueCallback<Boolean>() {
             @Override
             public void onReceiveValue(Boolean value) {
-                assertTrue(ctx.wasGetApplicationContextCalled());
-                resultLatch.countDown();
+                startSafeBrowsingFuture.set(ctx.wasGetApplicationContextCalled());
                 return;
             }
         });
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingWithNullCallbackDoesntCrash.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
     public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -2844,27 +2727,31 @@
         WebView.startSafeBrowsing(getActivity().getApplicationContext(), null);
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingInvokesCallback. Modifications to
+     * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testStartSafeBrowsingInvokesCallback() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
 
-        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create();
         WebView.startSafeBrowsing(getActivity().getApplicationContext(),
                 new ValueCallback<Boolean>() {
             @Override
             public void onReceiveValue(Boolean value) {
-                assertTrue(Looper.getMainLooper().isCurrentThread());
-                resultLatch.countDown();
+                startSafeBrowsingFuture.set(Looper.getMainLooper().isCurrentThread());
                 return;
             }
         });
-        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture));
     }
 
     private void savePrintedPage(final PrintDocumentAdapter adapter,
             final ParcelFileDescriptor descriptor, final PageRange[] pageRanges,
-            final FutureTask<Boolean> result) {
+            final SettableFuture<Void> result) {
         adapter.onWrite(pageRanges, descriptor,
                 new CancellationSignal(),
                 new WriteResultCallback() {
@@ -2872,32 +2759,26 @@
                     public void onWriteFinished(PageRange[] pages) {
                         try {
                             descriptor.close();
-                            result.run();
+                            result.set(null);
                         } catch (IOException ex) {
-                            fail("Failed file operation: " + ex.toString());
+                            result.setException(ex);
                         }
                     }
                 });
     }
 
     private void printDocumentStart(final PrintDocumentAdapter adapter) {
-        mOnUiThread.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                adapter.onStart();
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            adapter.onStart();
         });
     }
 
     private void printDocumentLayout(final PrintDocumentAdapter adapter,
             final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
             final LayoutResultCallback layoutResultCallback) {
-        mOnUiThread.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
-                        layoutResultCallback, null);
-            }
+        WebkitUtils.onMainThreadSync(() -> {
+            adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
+                    layoutResultCallback, null);
         });
     }
 
@@ -3029,37 +2910,99 @@
         }.run();
     }
 
+    static class ScaleChangedState {
+        public float mOldScale;
+        public float mNewScale;
+        public boolean mCanZoomIn;
+        public boolean mCanZoomOut;
+
+        ScaleChangedState(WebView view, float oldScale, float newScale) {
+            mOldScale = oldScale;
+            mNewScale = newScale;
+            mCanZoomIn = view.canZoomIn();
+            mCanZoomOut = view.canZoomOut();
+        }
+    }
+
     final class ScaleChangedWebViewClient extends WaitForLoadedClient {
-        private boolean mOnScaleChangedCalled = false;
+        private BlockingQueue<ScaleChangedState> mCallQueue;
+
         public ScaleChangedWebViewClient() {
             super(mOnUiThread);
+            mCallQueue = new LinkedBlockingQueue<>();
         }
 
         @Override
         public void onScaleChanged(WebView view, float oldScale, float newScale) {
             super.onScaleChanged(view, oldScale, newScale);
-            synchronized (this) {
-                mOnScaleChangedCalled = true;
-            }
+            mCallQueue.add(new ScaleChangedState(view, oldScale, newScale));
         }
 
-        public void waitForScaleChanged() {
-            new PollingCheck(TEST_TIMEOUT) {
-                 @Override
-                 protected boolean check() {
-                     return onScaleChangedCalled();
-                 }
-            }.run();
-            synchronized (this) {
-                mOnScaleChangedCalled = false;
+        public float expectZoomBy(float currentScale, float scaleAmount) {
+            assertTrue(scaleAmount != 1.0f);
+
+            float nextScale = currentScale * scaleAmount;
+            ScaleChangedState state = waitForNextScaleChange();
+            assertEquals(currentScale, state.mOldScale);
+
+            // Check that we zoomed in the expected direction wrt. the current scale.
+            if (scaleAmount > 1.0f) {
+                assertTrue(
+                        "Expected new scale > current scale when zooming in",
+                        state.mNewScale > currentScale);
+            } else {
+                assertTrue(
+                        "Expected new scale < current scale when zooming out",
+                        state.mNewScale < currentScale);
             }
+
+            // If we hit the zoom limit, then the new scale should be between the old scale
+            // and the expected new scale. Otherwise it should equal the expected new scale.
+            if (Math.abs(nextScale - state.mNewScale) > PAGE_SCALE_EPSILON) {
+                if (scaleAmount > 1.0f) {
+                    assertFalse(state.mCanZoomIn);
+                    assertTrue(
+                            "Expected new scale <= requested scale when zooming in past limit",
+                            state.mNewScale <= nextScale);
+                } else {
+                    assertFalse(state.mCanZoomOut);
+                    assertTrue(
+                            "Expected new scale >= requested scale when zooming out past limit",
+                            state.mNewScale >= nextScale);
+                }
+            }
+
+            return state.mNewScale;
         }
 
-        public synchronized boolean onScaleChangedCalled() {
-            return mOnScaleChangedCalled;
+        public float expectZoomOut(float currentScale) {
+            ScaleChangedState state = waitForNextScaleChange();
+            assertEquals(currentScale, state.mOldScale);
+            assertTrue(state.mNewScale < currentScale);
+            return state.mNewScale;
+        }
+
+        public float expectZoomIn(float currentScale) {
+            ScaleChangedState state = waitForNextScaleChange();
+            assertEquals(currentScale, state.mOldScale);
+            assertTrue(state.mNewScale > currentScale);
+            return state.mNewScale;
+        }
+
+        public ScaleChangedState waitForNextScaleChange() {
+            return WebkitUtils.waitForNextQueueElement(mCallQueue);
+        }
+
+        public boolean onScaleChangedCalled() {
+            return mCallQueue.size() > 0;
         }
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.WebViewCompatTest#testGetSafeBrowsingPrivacyPolicyUrl. Modifications to this
+     * test should be reflected in that test as necessary. See http://go/modifying-webview-cts.
+     */
     public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -3069,7 +3012,7 @@
         try {
             new URL(WebView.getSafeBrowsingPrivacyPolicyUrl().toString());
         } catch (MalformedURLException e) {
-            Assert.fail("The privacy policy URL should be a well-formed URL");
+            fail("The privacy policy URL should be a well-formed URL");
         }
     }
 
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index 80e65f7..e7929e0 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -386,15 +386,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.cts.VideoView2CtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="VideoView2CtsActivity">
-            <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.widget.cts.AutoCompleteCtsActivity"
             android:label="AutoCompleteCtsActivity"
             android:screenOrientation="nosensor"
@@ -599,17 +590,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name="android.widget.cts.appwidget.MyAppWidgetProvider" >
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-            <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/remoteviews_appwidget_info" />
-        </receiver>
-
-        <service android:name="android.widget.cts.appwidget.MyAppWidgetService"
-                 android:permission="android.permission.BIND_REMOTEVIEWS"
-                 android:exported="false" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index 27c6ea3..123b9e7 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS Widget test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <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" />
         <option name="test-file-name" value="CtsWidgetTestCases.apk" />
diff --git a/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml
deleted file mode 100644
index f736e48..0000000
--- a/tests/tests/widget/res/layout/magnifier_activity_basic_layout.xml
+++ /dev/null
@@ -1,23 +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
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/magnifier_activity_basic_layout"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
-</LinearLayout>
diff --git a/tests/tests/widget/res/layout/magnifier_activity_centered_surfaceview_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_centered_surfaceview_layout.xml
new file mode 100644
index 0000000..a879666
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_activity_centered_surfaceview_layout.xml
@@ -0,0 +1,29 @@
+<?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:id="@+id/magnifier_activity_centered_view_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center" >
+    <SurfaceView
+        android:id="@+id/magnifier_centered_view"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:background="@android:color/holo_blue_bright" />
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/magnifier_activity_centered_view_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_centered_view_layout.xml
new file mode 100644
index 0000000..bd5fd82
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_activity_centered_view_layout.xml
@@ -0,0 +1,29 @@
+<?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:id="@+id/magnifier_activity_centered_view_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center" >
+    <FrameLayout
+        android:id="@+id/magnifier_centered_view"
+        android:layout_width="120dp"
+        android:layout_height="56dp"
+        android:background="@android:color/holo_blue_bright" />
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/magnifier_activity_scrollable_views_layout.xml b/tests/tests/widget/res/layout/magnifier_activity_scrollable_views_layout.xml
new file mode 100644
index 0000000..4b2bc6f
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_activity_scrollable_views_layout.xml
@@ -0,0 +1,50 @@
+<?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:id="@+id/magnifier_activity_scrollable_views_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical" >
+
+    <HorizontalScrollView
+        android:id="@+id/horizontal_scroll_container"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content">
+        <FrameLayout
+            android:id="@+id/magnifier_activity_horizontally_scrolled_view"
+            android:layout_width="wrap_content"
+            android:layout_height="100dp"
+            android:minWidth="3000dp"
+            android:background="@android:color/holo_blue_bright" />
+    </HorizontalScrollView>
+
+    <ScrollView
+        android:id="@+id/vertical_scroll_container"
+        android:layout_width="wrap_content"
+        android:layout_height="100dp"
+        android:layout_marginTop="100dp" >
+        <FrameLayout
+            android:id="@+id/magnifier_activity_vertically_scrolled_view"
+            android:layout_width="100dp"
+            android:layout_height="wrap_content"
+            android:minHeight="3000dp"
+            android:background="@android:color/holo_red_light" />
+    </ScrollView>
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/numberpicker_layout.xml b/tests/tests/widget/res/layout/numberpicker_layout.xml
index 2e370d8..7c6dfb4 100644
--- a/tests/tests/widget/res/layout/numberpicker_layout.xml
+++ b/tests/tests/widget/res/layout/numberpicker_layout.xml
@@ -27,4 +27,10 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 
+    <NumberPicker
+        android:id="@+id/number_picker_divider_height"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:selectionDividerHeight="4px"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/remoteviews_adapter.xml b/tests/tests/widget/res/layout/remoteviews_adapter.xml
deleted file mode 100644
index 59115da..0000000
--- a/tests/tests/widget/res/layout/remoteviews_adapter.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <StackView
-        android:id="@+id/remoteViews_stack"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:loopViews="true" />
-    <TextView
-        android:id="@+id/remoteViews_empty"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center" />
-    <ListView
-        android:id="@+id/remoteViews_list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone" />
-</FrameLayout>
-
-
diff --git a/tests/tests/widget/res/layout/textview_fontvariation_test_layout.xml b/tests/tests/widget/res/layout/textview_fontvariation_test_layout.xml
new file mode 100644
index 0000000..718e748
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_fontvariation_test_layout.xml
@@ -0,0 +1,116 @@
+<?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:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth25"
+        android:fontVariationSettings="'wdth' 25"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth50"
+        android:fontVariationSettings="'wdth' 50"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth25"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth25" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth50"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth50" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wght100"
+        android:fontVariationSettings="'wght' 100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wght200"
+        android:fontVariationSettings="'wght' 200" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wght100"
+        android:textAppearance="@style/TextAppearance.FontVariationWght100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wght200"
+        android:textAppearance="@style/TextAppearance.FontVariationWght200" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth25_wght100"
+        android:fontVariationSettings="'wdth' 25, 'wght' 100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth25_wght200"
+        android:fontVariationSettings="'wdth' 25, 'wght' 200" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth50_wght100"
+        android:fontVariationSettings="'wdth' 50, 'wght' 100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textView_fontVariation_wdth50_wght200"
+        android:fontVariationSettings="'wdth' 50, 'wght' 200" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth25_wght100"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth25Wght100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth25_wght200"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth25Wght200" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth50_wght100"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth50Wght100" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/textAppearance_fontVariation_wdth50_wght200"
+        android:textAppearance="@style/TextAppearance.FontVariationWdth50Wght200" />
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textview_isHorizontallyScrollable_layout.xml b/tests/tests/widget/res/layout/textview_isHorizontallyScrollable_layout.xml
new file mode 100644
index 0000000..e2d3aaa
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_isHorizontallyScrollable_layout.xml
@@ -0,0 +1,78 @@
+<?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:layout_width="match_parent"
+                android:layout_height="wrap_content">
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    <!-- In this case, scrollHorizontally is overwritten by singleLine, which is a bug. -->
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false_singleLine_true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_default_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="false" />
+
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_false_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="false"
+            android:singleLine="false" />
+
+    <!-- In this case, scrollHorizontally is overwritten by singleLine, which is a bug. -->
+    <TextView
+            android:id="@+id/textView_scrollHorizontally_true_singleLine_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollHorizontally="true"
+            android:singleLine="false" />
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 86882de..6f1c188 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -44,6 +44,12 @@
                 android:layout_height="wrap_content"/>
 
             <TextView
+                android:id="@+id/textview_textAttr_zeroTextSize"
+                android:textSize="0sp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+
+            <TextView
                 android:id="@+id/textview_password"
                 android:password="true"
                 android:minWidth="1dp"
diff --git a/tests/tests/widget/res/layout/textview_singleline.xml b/tests/tests/widget/res/layout/textview_singleline.xml
new file mode 100644
index 0000000..1b19ad6
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_singleline.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+  <!-- test singleline is true -->
+  <TextView
+      android:id="@+id/textview_singleline_true"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:singleLine="true" />
+
+  <!-- test singleline is false -->
+  <TextView
+      android:id="@+id/textview_singleline_false"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:singleLine="false" />
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textview_textdirectionheuristic.xml b/tests/tests/widget/res/layout/textview_textdirectionheuristic.xml
new file mode 100644
index 0000000..d5eadbf
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_textdirectionheuristic.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/text"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/text_password"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:password="true"/>
+
+    <TextView android:id="@+id/text_phone"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:inputType="phone"
+              android:textIsSelectable="true"/>
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/videoview2_layout.xml b/tests/tests/widget/res/layout/videoview2_layout.xml
deleted file mode 100644
index 9030e1b..0000000
--- a/tests/tests/widget/res/layout/videoview2_layout.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
- -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <VideoView2
-        android:id="@+id/videoview"
-        android:layout_width="160dp"
-        android:layout_height="120dp"/>
-</LinearLayout>
diff --git a/tests/tests/widget/res/values/strings.xml b/tests/tests/widget/res/values/strings.xml
index 78f0f7c..9e36cc0 100644
--- a/tests/tests/widget/res/values/strings.xml
+++ b/tests/tests/widget/res/values/strings.xml
@@ -197,6 +197,7 @@
     <string name="toolbar_subtitle">Toolbar subtitle</string>
     <string name="toolbar_navigation">Toolbar navigation</string>
     <string name="toolbar_logo">Toolbar logo</string>
+    <string name="toolbar_collapse">Toolbar collapse</string>
 
     <string name="search_query_hint">query hint</string>
 
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index 8e324c9..b35ba64 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -252,6 +252,38 @@
         <item name="android:textStyle">bold</item>
     </style>
 
+    <style name="TextAppearance.FontVariationWdth25">
+        <item name="android:fontVariationSettings">\'wdth\' 25</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWdth50">
+        <item name="android:fontVariationSettings">\'wdth\' 50</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWght100">
+        <item name="android:fontVariationSettings">\'wght\' 100</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWght200">
+        <item name="android:fontVariationSettings">\'wght\' 200</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWdth25Wght100">
+        <item name="android:fontVariationSettings">\'wdth\' 25, \'wght\' 100</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWdth25Wght200">
+        <item name="android:fontVariationSettings">\'wdth\' 25, \'wght\' 200</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWdth50Wght100">
+        <item name="android:fontVariationSettings">\'wdth\' 50, \'wght\' 100</item>
+    </style>
+
+    <style name="TextAppearance.FontVariationWdth50Wght200">
+        <item name="android:fontVariationSettings">\'wdth\' 50, \'wght\' 200</item>
+    </style>
+
     <style name="AllCapsPassword">
         <item name="android:textAllCaps">true</item>
         <item name="android:password">true</item>
diff --git a/tests/tests/widget/res/xml/remoteviews_appwidget_info.xml b/tests/tests/widget/res/xml/remoteviews_appwidget_info.xml
deleted file mode 100644
index e75ed72..0000000
--- a/tests/tests/widget/res/xml/remoteviews_appwidget_info.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<appwidget-provider
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:minWidth="200dp"
-    android:minHeight="200dp"
-    android:minResizeWidth="300dp"
-    android:minResizeHeight="300dp"
-    android:updatePeriodMillis="86400000"
-    android:initialLayout="@layout/remoteviews_adapter"
-    android:resizeMode="horizontal|vertical"
-    android:widgetCategory="home_screen|keyguard"
-    android:previewImage="@drawable/icon_red">
-</appwidget-provider>
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 32e54f1..3d4b996 100644
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -25,12 +25,14 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.Editable;
+import android.text.Layout;
 import android.text.TextUtils;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.MovementMethod;
@@ -338,6 +340,31 @@
         assertEquals(mEditText1.getSelectionEnd(), mEditText2.getSelectionEnd());
     }
 
+    private boolean isWatch() {
+        return (mActivity.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
+    }
+
+    @Test
+    public void testHyphenationFrequencyDefaultValue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final EditText editText = new EditText(context);
+
+        // Hypenation is enabled by default on watches to fit more text on their tiny screens.
+        if (isWatch()) {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, editText.getHyphenationFrequency());
+        } else {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, editText.getHyphenationFrequency());
+        }
+    }
+
+    @Test
+    public void testBreakStrategyDefaultValue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final EditText editText = new EditText(context);
+        assertEquals(Layout.BREAK_STRATEGY_SIMPLE, editText.getBreakStrategy());
+    }
+
     private class MockEditText extends EditText {
         public MockEditText(Context context) {
             super(context);
diff --git a/tests/tests/widget/src/android/widget/cts/FrameLayoutTest.java b/tests/tests/widget/src/android/widget/cts/FrameLayoutTest.java
index ee321e4..816e8d6 100644
--- a/tests/tests/widget/src/android/widget/cts/FrameLayoutTest.java
+++ b/tests/tests/widget/src/android/widget/cts/FrameLayoutTest.java
@@ -297,15 +297,18 @@
         final ArgumentCaptor<ColorStateList> colorStateListCaptor =
                 ArgumentCaptor.forClass(ColorStateList.class);
         verify(foreground, times(1)).setTintList(colorStateListCaptor.capture());
-        assertEquals(1, colorStateListCaptor.getValue().getColors().length);
-        assertEquals(Color.RED, colorStateListCaptor.getValue().getColors()[0]);
+        assertEquals(1, colorStateListCaptor.getValue().getStates().length);
+        int[] emptyState = new int[0];
+        assertEquals(Color.RED,
+                colorStateListCaptor.getValue().getColorForState(emptyState, Color.BLUE));
 
         reset(foreground);
         view.setForeground(null);
         view.setForeground(foreground);
         verify(foreground, times(1)).setTintList(colorStateListCaptor.capture());
-        assertEquals(1, colorStateListCaptor.getValue().getColors().length);
-        assertEquals(Color.RED, colorStateListCaptor.getValue().getColors()[0]);
+        assertEquals(1, colorStateListCaptor.getValue().getStates().length);
+        assertEquals(Color.RED,
+                colorStateListCaptor.getValue().getColorForState(emptyState, Color.BLUE));
     }
 
     private static void assertCenterAligned(View container, Drawable drawable) {
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index 5476b22..8b43bb1 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -42,13 +43,20 @@
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Matrix;
+import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Xfermode;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.PaintDrawable;
 import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
@@ -500,6 +508,46 @@
         verify(mockImageView, times(1)).onSizeChanged(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
+    @Test
+    public void testSetColorFilterPreservesDrawableProperties() {
+        ImageView imageView = new ImageView(InstrumentationRegistry.getTargetContext());
+
+        int colorAlpha = 128;
+        MockDrawable mockDrawable = new MockDrawable();
+        mockDrawable.setAlpha(colorAlpha);
+        mockDrawable.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+
+        imageView.setImageDrawable(mockDrawable);
+
+        imageView.setColorFilter(Color.RED);
+        assertEquals(colorAlpha, mockDrawable.getAlpha());
+        assertNotNull(mockDrawable.getXfermode());
+    }
+
+    @Test
+    public void testImageViewSetColorFilterPropagatedToDrawable() {
+        ImageView imageView = new ImageView(InstrumentationRegistry.getTargetContext());
+
+        MockDrawable mockDrawable = new MockDrawable();
+        imageView.setImageDrawable(mockDrawable);
+        imageView.setColorFilter(Color.RED);
+
+        ColorFilter imageViewColorFilter = imageView.getColorFilter();
+        assertTrue(imageViewColorFilter instanceof PorterDuffColorFilter);
+
+        PorterDuffColorFilter imageViewPorterDuffFilter =
+                (PorterDuffColorFilter) imageViewColorFilter;
+        assertEquals(Color.RED, imageViewPorterDuffFilter.getColor());
+        assertEquals(Mode.SRC_ATOP, imageViewPorterDuffFilter.getMode());
+
+        ColorFilter colorFilter = mockDrawable.getColorFilter();
+        assertTrue(colorFilter instanceof PorterDuffColorFilter);
+
+        PorterDuffColorFilter porterDuffColorFilter = (PorterDuffColorFilter) colorFilter;
+        assertEquals(Color.RED, porterDuffColorFilter.getColor());
+        assertEquals(PorterDuff.Mode.SRC_ATOP, porterDuffColorFilter.getMode());
+    }
+
     @UiThreadTest
     @Test
     public void testVerifyDrawable() {
@@ -623,6 +671,55 @@
                 0xFF0000FF, 1, false);
     }
 
+    @UiThreadTest
+    @Test
+    public void testAnimateTransform() {
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8);
+        mImageViewRegular.setScaleType(ScaleType.FIT_XY);
+        mImageViewRegular.setImageBitmap(bitmap);
+        Rect viewRect = new Rect(0, 0, mImageViewRegular.getWidth(), mImageViewRegular.getHeight());
+        Rect bitmapRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+        assertEquals(viewRect, mImageViewRegular.getDrawable().getBounds());
+        assertTrue(mImageViewRegular.getImageMatrix().isIdentity());
+
+        Matrix matrix = new Matrix();
+        mImageViewRegular.animateTransform(matrix);
+
+        assertEquals(bitmapRect, mImageViewRegular.getDrawable().getBounds());
+        assertEquals(matrix, mImageViewRegular.getImageMatrix());
+
+        // clear temporary transformation
+        mImageViewRegular.setImageBitmap(bitmap);
+
+        assertEquals(viewRect, mImageViewRegular.getDrawable().getBounds());
+        assertTrue(mImageViewRegular.getImageMatrix().isIdentity());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAnimateTransformWithNullPassed() {
+        Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8);
+        mImageViewRegular.setScaleType(ScaleType.CENTER);
+        mImageViewRegular.setImageBitmap(bitmap);
+        Rect viewRect = new Rect(0, 0, mImageViewRegular.getWidth(), mImageViewRegular.getHeight());
+        Rect bitmapRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+        assertEquals(bitmapRect, mImageViewRegular.getDrawable().getBounds());
+        assertFalse(mImageViewRegular.getImageMatrix().isIdentity());
+
+        mImageViewRegular.animateTransform(null);
+
+        assertEquals(viewRect, mImageViewRegular.getDrawable().getBounds());
+        assertTrue(mImageViewRegular.getImageMatrix().isIdentity());
+
+        // clear temporary transformation
+        mImageViewRegular.setImageBitmap(bitmap);
+
+        assertEquals(bitmapRect, mImageViewRegular.getDrawable().getBounds());
+        assertFalse(mImageViewRegular.getImageMatrix().isIdentity());
+    }
+
     public static class MockImageView extends ImageView {
         public MockImageView(Context context) {
             super(context);
@@ -674,4 +771,49 @@
             super.onSizeChanged(w, h, oldw, oldh);
         }
     }
+
+    public static class MockDrawable extends Drawable {
+
+        private ColorFilter mFilter;
+        private int mAlpha;
+        private Xfermode mXfermode;
+
+        @Override
+        public void draw(Canvas canvas) {
+            // NO-OP
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            mAlpha = alpha;
+        }
+
+        public int getAlpha() {
+            return mAlpha;
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+            mFilter = colorFilter;
+        }
+
+        @Override
+        public void setXfermode(Xfermode mode) {
+            mXfermode = mode;
+        }
+
+        public @Nullable Xfermode getXfermode() {
+            return mXfermode;
+        }
+
+        @Override
+        public @Nullable ColorFilter getColorFilter() {
+            return mFilter;
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.TRANSLUCENT;
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
index 55d9f81..4af14b3 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
@@ -26,6 +26,6 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.magnifier_activity_basic_layout);
+        setContentView(R.layout.magnifier_activity_centered_view_layout);
     }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index 0ab086c..1624e4f 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -18,22 +18,36 @@
 
 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.Assume.assumeTrue;
 
 import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.DisplayMetrics;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
 import android.view.View;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 import android.widget.Magnifier;
+import android.widget.ScrollView;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
@@ -62,6 +76,8 @@
 
     private Activity mActivity;
     private LinearLayout mLayout;
+    private View mView;
+    private int[] mViewLocationInSurface;
     private Magnifier mMagnifier;
 
     @Rule
@@ -69,13 +85,22 @@
             new ActivityTestRule<>(MagnifierCtsActivity.class);
 
     @Before
-    public void setup() {
+    public void setup() throws Throwable {
         mActivity = mActivityRule.getActivity();
         PollingCheck.waitFor(mActivity::hasWindowFocus);
-        mLayout = mActivity.findViewById(R.id.magnifier_activity_basic_layout);
-
-        // Do not run the tests, unless the device screen is big enough to fit the magnifier.
+        // Do not run the tests, unless the device screen is big enough to fit a magnifier
+        // having the default size.
         assumeTrue(isScreenBigEnough());
+
+        mLayout = mActivity.findViewById(R.id.magnifier_activity_centered_view_layout);
+        mView = mActivity.findViewById(R.id.magnifier_centered_view);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mLayout, null);
+
+        mViewLocationInSurface = new int[2];
+        mView.getLocationInSurface(mViewLocationInSurface);
+        mMagnifier = new Magnifier.Builder(mView)
+                .setSize(mView.getWidth() / 2, mView.getHeight() / 2)
+                .build();
     }
 
     private boolean isScreenBigEnough() {
@@ -89,6 +114,8 @@
         return dpScreenWidth >= dpMagnifier.x * 1.1 && dpScreenHeight >= dpMagnifier.y * 1.1;
     }
 
+    //***** Tests for constructor *****//
+
     @Test
     public void testConstructor() {
         new Magnifier(new View(mActivity));
@@ -99,113 +126,267 @@
         new Magnifier(null);
     }
 
+    //***** Tests for builder *****//
+
     @Test
-    @UiThreadTest
-    public void testShow() {
-        final View view = new View(mActivity);
-        mLayout.addView(view, new LayoutParams(200, 200));
-        mMagnifier = new Magnifier(view);
-        // Valid coordinates.
-        mMagnifier.show(0, 0);
-        // Invalid coordinates, should both be clamped to 0.
-        mMagnifier.show(-1, -1);
-        // Valid coordinates.
-        mMagnifier.show(10, 10);
-        // Same valid coordinate as above, should skip making another copy request.
-        mMagnifier.show(10, 10);
+    public void testBuilder_setsPropertiesCorrectly_whenTheyAreValid() {
+        final int magnifierWidth = 90;
+        final int magnifierHeight = 120;
+        final float zoom = 1.5f;
+        final int sourceToMagnifierHorizontalOffset = 10;
+        final int sourceToMagnifierVerticalOffset = -100;
+        final float cornerRadius = 20.0f;
+        final float elevation = 15.0f;
+        final boolean enableClipping = false;
+        final Drawable overlay = new ColorDrawable(Color.BLUE);
+
+        final Magnifier.Builder builder = new Magnifier.Builder(mView)
+                .setSize(magnifierWidth, magnifierHeight)
+                .setInitialZoom(zoom)
+                .setDefaultSourceToMagnifierOffset(sourceToMagnifierHorizontalOffset,
+                        sourceToMagnifierVerticalOffset)
+                .setCornerRadius(cornerRadius)
+                .setInitialZoom(zoom)
+                .setElevation(elevation)
+                .setOverlay(overlay)
+                .setClippingEnabled(enableClipping);
+        final Magnifier magnifier = builder.build();
+
+        assertEquals(magnifierWidth, magnifier.getWidth());
+        assertEquals(magnifierHeight, magnifier.getHeight());
+        assertEquals(zoom, magnifier.getZoom(), 0f);
+        assertEquals(Math.round(magnifierWidth / zoom), magnifier.getSourceWidth());
+        assertEquals(Math.round(magnifierHeight / zoom), magnifier.getSourceHeight());
+        assertEquals(sourceToMagnifierHorizontalOffset,
+                magnifier.getDefaultHorizontalSourceToMagnifierOffset());
+        assertEquals(sourceToMagnifierVerticalOffset,
+                magnifier.getDefaultVerticalSourceToMagnifierOffset());
+        assertEquals(cornerRadius, magnifier.getCornerRadius(), 0f);
+        assertEquals(elevation, magnifier.getElevation(), 0f);
+        assertEquals(enableClipping, magnifier.isClippingEnabled());
+        assertEquals(overlay, magnifier.getOverlay());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testBuilder_throwsException_whenViewIsNull() {
+        new Magnifier.Builder(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenWidthIsInvalid() {
+        new Magnifier.Builder(mView).setSize(0, 10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenHeightIsInvalid() {
+        new Magnifier.Builder(mView).setSize(10, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenZoomIsZero() {
+        new Magnifier.Builder(mView).setInitialZoom(0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenZoomIsNegative() {
+        new Magnifier.Builder(mView).setInitialZoom(-1f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenElevationIsInvalid() {
+        new Magnifier.Builder(mView).setElevation(-1f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilder_throwsException_whenCornerRadiusIsNegative() {
+        new Magnifier.Builder(mView).setCornerRadius(-1f);
+    }
+
+    //***** Tests for default parameters *****//
+
+    @Test
+    public void testMagnifierDefaultParameters_withDeprecatedConstructor() {
+        final Magnifier magnifier = new Magnifier(mView);
+
+        final Context context = mView.getContext();
+        final TypedArray a = context.obtainStyledAttributes(
+                null, com.android.internal.R.styleable.Magnifier,
+                com.android.internal.R.attr.magnifierStyle, 0);
+        final int width = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.Magnifier_magnifierWidth, 0);
+        assertEquals(width, magnifier.getWidth());
+        final int height = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.Magnifier_magnifierHeight, 0);
+        assertEquals(height, magnifier.getHeight());
+        final float elevation = a.getDimension(
+                com.android.internal.R.styleable.Magnifier_magnifierElevation, 0f);
+        assertEquals(elevation, magnifier.getElevation(), 0.01f);
+        final float zoom = a.getFloat(com.android.internal.R.styleable.Magnifier_magnifierZoom, 0f);
+        assertEquals(zoom, magnifier.getZoom(), 0.01f);
+        final int verticalOffset = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.Magnifier_magnifierVerticalOffset, 0);
+        assertEquals(verticalOffset, magnifier.getDefaultVerticalSourceToMagnifierOffset());
+        final int horizontalOffset = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.Magnifier_magnifierHorizontalOffset, 0);
+        assertEquals(horizontalOffset, magnifier.getDefaultHorizontalSourceToMagnifierOffset());
+        final Context deviceDefaultContext = new ContextThemeWrapper(mView.getContext(),
+                android.R.style.Theme_DeviceDefault);
+        final TypedArray ta = deviceDefaultContext.obtainStyledAttributes(
+                new int[]{android.R.attr.dialogCornerRadius});
+        final float dialogCornerRadius = ta.getDimension(0, 0);
+        ta.recycle();
+        assertEquals(dialogCornerRadius, magnifier.getCornerRadius(), 0.01f);
+        final boolean isClippingEnabled = true;
+        assertEquals(isClippingEnabled, magnifier.isClippingEnabled());
+        final int overlayColor = a.getColor(
+                com.android.internal.R.styleable.Magnifier_magnifierColorOverlay,
+                Color.TRANSPARENT);
+        assertEquals(overlayColor, ((ColorDrawable) magnifier.getOverlay()).getColor());
     }
 
     @Test
-    @UiThreadTest
-    public void testDismiss() {
-        final View view = new View(mActivity);
-        mLayout.addView(view, new LayoutParams(200, 200));
-        mMagnifier = new Magnifier(view);
-        // Valid coordinates.
-        mMagnifier.show(10, 10);
-        mMagnifier.dismiss();
-        // Should be no-op.
-        mMagnifier.dismiss();
-    }
+    public void testMagnifierDefaultParameters_withBuilder() {
+        final Magnifier magnifier = new Magnifier.Builder(mView).build();
 
-    @Test
-    @UiThreadTest
-    public void testUpdate() {
-        final View view = new View(mActivity);
-        mLayout.addView(view, new LayoutParams(200, 200));
-        mMagnifier = new Magnifier(view);
-        // Should be no-op.
-        mMagnifier.update();
-        // Valid coordinates.
-        mMagnifier.show(10, 10);
-        // Should not crash.
-        mMagnifier.update();
-        mMagnifier.dismiss();
-        // Should be no-op.
-        mMagnifier.update();
+        final Resources resources = mView.getContext().getResources();
+        final int width = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_magnifier_width);
+        assertEquals(width, magnifier.getWidth());
+        final int height = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_magnifier_height);
+        assertEquals(height, magnifier.getHeight());
+        final float elevation = resources.getDimension(
+                com.android.internal.R.dimen.default_magnifier_elevation);
+        assertEquals(elevation, magnifier.getElevation(), 0.01f);
+        final float zoom = resources.getFloat(com.android.internal.R.dimen.default_magnifier_zoom);
+        assertEquals(zoom, magnifier.getZoom(), 0.01f);
+        final int verticalOffset = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_magnifier_vertical_offset);
+        assertEquals(verticalOffset, magnifier.getDefaultVerticalSourceToMagnifierOffset());
+        final int horizontalOffset = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_magnifier_horizontal_offset);
+        assertEquals(horizontalOffset, magnifier.getDefaultHorizontalSourceToMagnifierOffset());
+        final float dialogCornerRadius = resources.getDimension(
+                com.android.internal.R.dimen.default_magnifier_corner_radius);
+        assertEquals(dialogCornerRadius, magnifier.getCornerRadius(), 0.01f);
+        final boolean isClippingEnabled = true;
+        assertEquals(isClippingEnabled, magnifier.isClippingEnabled());
+        final int overlayColor = resources.getColor(
+                com.android.internal.R.color.default_magnifier_color_overlay, null);
+        assertEquals(overlayColor, ((ColorDrawable) magnifier.getOverlay()).getColor());
     }
 
     @Test
     @UiThreadTest
     public void testSizeAndZoom_areValid() {
-        final View view = new View(mActivity);
-        mLayout.addView(view, new LayoutParams(200, 200));
-        mMagnifier = new Magnifier(view);
-        mMagnifier.show(10, 10);
-        // Size should be non-zero.
+        mMagnifier = new Magnifier(mView);
+        // Size should be positive.
         assertTrue(mMagnifier.getWidth() > 0);
         assertTrue(mMagnifier.getHeight() > 0);
+        // Source size should be positive.
+        assertTrue(mMagnifier.getSourceWidth() > 0);
+        assertTrue(mMagnifier.getSourceHeight() > 0);
         // The magnified view region should be zoomed in, not out.
         assertTrue(mMagnifier.getZoom() > 1.0f);
     }
 
+
+    //***** Tests for #show() *****//
+
     @Test
-    public void testWindowContent() throws Throwable {
-        prepareFourQuadrantsScenario();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mMagnifier.setOnOperationCompleteCallback(latch::countDown);
+    public void testShow() throws Throwable {
+        final float xCenter = mView.getWidth() / 2f;
+        final float yCenter = mView.getHeight() / 2f;
+        showMagnifier(xCenter, yCenter);
 
-        // Show the magnifier at the center of the activity.
-        mActivityRule.runOnUiThread(() -> {
-            mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
-        });
-        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(1, TimeUnit.SECONDS));
+        // Check the coordinates of the content being copied.
+        final Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(xCenter + mViewLocationInSurface[0],
+                sourcePosition.x + mMagnifier.getSourceWidth() / 2f, 0.01f);
+        assertEquals(yCenter + mViewLocationInSurface[1],
+                sourcePosition.y + mMagnifier.getSourceHeight() / 2f, 0.01f);
 
-        assertEquals(mMagnifier.getWidth(), mMagnifier.getContent().getWidth());
-        assertEquals(mMagnifier.getHeight(), mMagnifier.getContent().getHeight());
-        assertFourQuadrants(mMagnifier.getContent());
+        // Check the coordinates of the magnifier.
+        final Point magnifierPosition = mMagnifier.getPosition();
+        assertNotNull(magnifierPosition);
+        assertEquals(sourcePosition.x + mMagnifier.getDefaultHorizontalSourceToMagnifierOffset()
+                        - mMagnifier.getWidth() / 2f + mMagnifier.getSourceWidth() / 2f,
+                magnifierPosition.x, 0.01f);
+        assertEquals(sourcePosition.y + mMagnifier.getDefaultVerticalSourceToMagnifierOffset()
+                        - mMagnifier.getHeight() / 2f + mMagnifier.getSourceHeight() / 2f,
+                magnifierPosition.y, 0.01f);
     }
 
     @Test
-    public void testWindowPosition() throws Throwable {
-        prepareFourQuadrantsScenario();
-        final CountDownLatch latch = new CountDownLatch(1);
-        mMagnifier.setOnOperationCompleteCallback(latch::countDown);
-
-        // Show the magnifier at the center of the activity.
-        mActivityRule.runOnUiThread(() -> {
-            mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
-        });
-        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(1, TimeUnit.SECONDS));
-
-        // Assert that the magnifier position represents a valid rectangle on screen.
-        final Rect position = mMagnifier.getWindowPositionOnScreen();
-        assertFalse(position.isEmpty());
-        assertTrue(0 <= position.left && position.right <= mLayout.getWidth());
-        assertTrue(0 <= position.top && position.bottom <= mLayout.getHeight());
+    public void testShow_doesNotCrash_whenCalledWithExtremeCoordinates() throws Throwable {
+        showMagnifier(Integer.MIN_VALUE, Integer.MIN_VALUE);
+        showMagnifier(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        showMagnifier(Integer.MAX_VALUE, Integer.MIN_VALUE);
+        showMagnifier(Integer.MAX_VALUE, Integer.MAX_VALUE);
     }
 
     @Test
-    public void testWindowContent_modifiesAfterUpdate() throws Throwable {
+    public void testShow_withDecoupledMagnifierPosition() throws Throwable {
+        final float xCenter = mView.getWidth() / 2;
+        final float yCenter = mView.getHeight() / 2;
+
+        final int xMagnifier = -20;
+        final int yMagnifier = -10;
+        showMagnifier(xCenter, yCenter, xMagnifier, yMagnifier);
+
+        final Point magnifierPosition = mMagnifier.getPosition();
+        assertNotNull(magnifierPosition);
+        assertEquals(
+                mViewLocationInSurface[0] + xMagnifier - mMagnifier.getWidth() / 2,
+                magnifierPosition.x, 0.01f);
+        assertEquals(
+                mViewLocationInSurface[1] + yMagnifier - mMagnifier.getHeight() / 2,
+                magnifierPosition.y, 0.01f);
+    }
+
+    //***** Tests for #dismiss() *****//
+
+    @Test
+    public void testDismiss_doesNotCrash() throws Throwable {
+        showMagnifier(0, 0);
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            mMagnifier.dismiss();
+            mMagnifier.dismiss();
+            mMagnifier.show(0, 0);
+            mMagnifier.dismiss();
+            mMagnifier.dismiss();
+            latch.countDown();
+        });
+        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(2, TimeUnit.SECONDS));
+    }
+
+    //***** Tests for #update() *****//
+
+    @Test
+    public void testUpdate_doesNotCrash() throws Throwable {
+        showMagnifier(0, 0);
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            mMagnifier.update();
+            mMagnifier.update();
+            mMagnifier.show(10, 10);
+            mMagnifier.update();
+            mMagnifier.update();
+            mMagnifier.dismiss();
+            mMagnifier.update();
+            latch.countDown();
+        });
+        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(2, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testMagnifierContent_refreshesAfterUpdate() throws Throwable {
         prepareFourQuadrantsScenario();
 
         // Show the magnifier at the center of the activity.
-        final CountDownLatch latchForShow = new CountDownLatch(1);
-        mMagnifier.setOnOperationCompleteCallback(latchForShow::countDown);
-        mActivityRule.runOnUiThread(() -> {
-            mMagnifier.show(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
-        });
-        assertTrue(TIME_LIMIT_EXCEEDED, latchForShow.await(1, TimeUnit.SECONDS));
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
 
         final Bitmap initialBitmap = mMagnifier.getContent()
                 .copy(mMagnifier.getContent().getConfig(), true);
@@ -219,16 +400,549 @@
         });
 
         // Update the magnifier.
-        final CountDownLatch latchForUpdate = new CountDownLatch(1);
-        mMagnifier.setOnOperationCompleteCallback(latchForUpdate::countDown);
-        mActivityRule.runOnUiThread(mMagnifier::update);
-        assertTrue(TIME_LIMIT_EXCEEDED, latchForUpdate.await(1, TimeUnit.SECONDS));
+        runAndWaitForMagnifierOperationComplete(mMagnifier::update);
 
         final Bitmap newBitmap = mMagnifier.getContent();
         assertFourQuadrants(newBitmap);
         assertFalse(newBitmap.sameAs(initialBitmap));
     }
 
+    //***** Tests for the position of the magnifier *****//
+
+    @Test
+    public void testWindowPosition_isClampedInsideMainApplicationWindow_topLeft() throws Throwable {
+        prepareFourQuadrantsScenario();
+
+        // Magnify the center of the activity in a magnifier outside bounds.
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2,
+                -mMagnifier.getWidth(), -mMagnifier.getHeight());
+
+        // The window should have been positioned to the top left of the activity,
+        // such that it does not overlap system insets.
+        final Insets systemInsets = mLayout.getRootWindowInsets().getSystemWindowInsets();
+        final Rect surfaceInsets = mLayout.getViewRootImpl().mWindowAttributes.surfaceInsets;
+        final Point magnifierCoords = mMagnifier.getPosition();
+        assertNotNull(magnifierCoords);
+        assertEquals(systemInsets.left + surfaceInsets.left, magnifierCoords.x);
+        assertEquals(systemInsets.top + surfaceInsets.top, magnifierCoords.y);
+    }
+
+    @Test
+    public void testWindowPosition_isClampedInsideMainApplicationWindow_bottomRight()
+            throws Throwable {
+        prepareFourQuadrantsScenario();
+
+        // Magnify the center of the activity in a magnifier outside bounds.
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2,
+                mLayout.getViewRootImpl().getWidth() + mMagnifier.getWidth(),
+                mLayout.getViewRootImpl().getHeight() + mMagnifier.getHeight());
+
+        // The window should have been positioned to the bottom right of the activity.
+        final Insets systemInsets = mLayout.getRootWindowInsets().getSystemWindowInsets();
+        final Rect surfaceInsets = mLayout.getViewRootImpl().mWindowAttributes.surfaceInsets;
+        final Point magnifierCoords = mMagnifier.getPosition();
+        assertNotNull(magnifierCoords);
+        assertEquals(mLayout.getViewRootImpl().getWidth()
+                        - systemInsets.right - mMagnifier.getWidth() + surfaceInsets.left,
+                magnifierCoords.x);
+        assertEquals(mLayout.getViewRootImpl().getHeight()
+                        - systemInsets.bottom - mMagnifier.getHeight() + surfaceInsets.top,
+                magnifierCoords.y);
+    }
+
+    @Test
+    public void testWindowPosition_isNotClamped_whenClampingFlagIsOff_topLeft() throws Throwable {
+        prepareFourQuadrantsScenario();
+        mMagnifier = new Magnifier.Builder(mLayout)
+                .setClippingEnabled(false)
+                .build();
+
+        // Magnify the center of the activity in a magnifier outside bounds.
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2,
+                -mMagnifier.getWidth(), -mMagnifier.getHeight());
+
+        // The window should have not been clamped.
+        final Point magnifierCoords = mMagnifier.getPosition();
+        final int[] magnifiedViewPosition = new int[2];
+        mLayout.getLocationInSurface(magnifiedViewPosition);
+        assertNotNull(magnifierCoords);
+        assertEquals(magnifiedViewPosition[0] - 3 * mMagnifier.getWidth() / 2, magnifierCoords.x);
+        assertEquals(magnifiedViewPosition[1] - 3 * mMagnifier.getHeight() / 2, magnifierCoords.y);
+    }
+
+    @Test
+    public void testWindowPosition_isNotClamped_whenClampingFlagIsOff_bottomRight()
+            throws Throwable {
+        prepareFourQuadrantsScenario();
+        mMagnifier = new Magnifier.Builder(mLayout)
+                .setClippingEnabled(false)
+                .setSize(40, 40)
+                .build();
+
+        // Magnify the center of the activity in a magnifier outside bounds.
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2,
+                mLayout.getViewRootImpl().getWidth() + mMagnifier.getWidth(),
+                mLayout.getViewRootImpl().getHeight() + mMagnifier.getHeight());
+
+        // The window should have not been clamped.
+        final Point magnifierCoords = mMagnifier.getPosition();
+        final int[] magnifiedViewPosition = new int[2];
+        mLayout.getLocationInSurface(magnifiedViewPosition);
+        assertNotNull(magnifierCoords);
+        assertEquals(magnifiedViewPosition[0] + mLayout.getViewRootImpl().getWidth()
+                        + mMagnifier.getWidth() / 2,
+                magnifierCoords.x);
+        assertEquals(magnifiedViewPosition[1] + mLayout.getViewRootImpl().getHeight()
+                        + mMagnifier.getHeight() / 2,
+                magnifierCoords.y);
+    }
+
+    @Test
+    public void testWindowPosition_isCorrect_whenADefaultContentToMagnifierOffsetIsUsed()
+            throws Throwable {
+        prepareFourQuadrantsScenario();
+        final int horizontalOffset = 5;
+        final int verticalOffset = -10;
+        mMagnifier = new Magnifier.Builder(mLayout)
+                .setSize(20, 10) /* make magnifier small to avoid having it clamped */
+                .setDefaultSourceToMagnifierOffset(horizontalOffset, verticalOffset)
+                .build();
+
+        // Magnify the center of the activity in a magnifier outside bounds.
+        showMagnifier(mLayout.getWidth() / 2, mLayout.getHeight() / 2);
+
+        final Point magnifierCoords = mMagnifier.getPosition();
+        final Point sourceCoords = mMagnifier.getSourcePosition();
+        assertNotNull(magnifierCoords);
+        assertEquals(sourceCoords.x + mMagnifier.getSourceWidth() / 2f + horizontalOffset,
+                magnifierCoords.x + mMagnifier.getWidth() / 2f, 0.01f);
+        assertEquals(sourceCoords.y + mMagnifier.getSourceHeight() / 2f + verticalOffset,
+                magnifierCoords.y + mMagnifier.getHeight() / 2f, 0.01f);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testWindowPosition_isNull_whenMagnifierIsNotShowing() {
+        mMagnifier = new Magnifier.Builder(mLayout)
+                .setSize(20, 10) /* make magnifier small to avoid having it clamped */
+                .build();
+
+        // No #show has been requested, so the position should be null.
+        assertNull(mMagnifier.getPosition());
+        // #show should make the position not null.
+        mMagnifier.show(0, 0);
+        assertNotNull(mMagnifier.getPosition());
+        // #dismiss should make the position null.
+        mMagnifier.dismiss();
+        assertNull(mMagnifier.getPosition());
+    }
+
+    //***** Tests for the position of the content copied to the magnifier *****//
+
+    @Test
+    @UiThreadTest
+    public void testSourcePosition_isNull_whenMagnifierIsNotShowing() {
+        mMagnifier = new Magnifier.Builder(mLayout)
+                .setSize(20, 10) /* make magnifier small to avoid having it clamped */
+                .build();
+
+        // No #show has been requested, so the source position should be null.
+        assertNull(mMagnifier.getSourcePosition());
+        // #show should make the source position not null.
+        mMagnifier.show(0, 0);
+        assertNotNull(mMagnifier.getSourcePosition());
+        // #dismiss should make the source position null.
+        mMagnifier.dismiss();
+        assertNull(mMagnifier.getSourcePosition());
+    }
+
+    @Test
+    public void testSourcePosition_respectsMaxVisibleBounds_inHorizontalScrollableContainer()
+            throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_scrollable_views_layout);
+        }, false /*forceLayout*/);
+        final View view = mActivity
+                .findViewById(R.id.magnifier_activity_horizontally_scrolled_view);
+        final HorizontalScrollView container = (HorizontalScrollView) mActivity
+                .findViewById(R.id.horizontal_scroll_container);
+        final Magnifier.Builder builder = new Magnifier.Builder(view)
+                .setSize(100, 100)
+                .setInitialZoom(20f) /* 5x5 source size */
+                .setSourceBounds(
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE
+                );
+
+        runOnUiThreadAndWaitForCompletion(() -> {
+            mMagnifier = builder.build();
+            // Scroll halfway horizontally.
+            container.scrollTo(view.getWidth() / 2, 0);
+        });
+
+        final int[] containerPosition = new int[2];
+        container.getLocationInSurface(containerPosition);
+
+        // Try to copy from an x to the left of the currently visible region.
+        showMagnifier(view.getWidth() / 4, 0);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(containerPosition[0], sourcePosition.x);
+
+        // Try to copy from an x to the right of the currently visible region.
+        showMagnifier(3 * view.getWidth() / 4, 0);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(containerPosition[0] + container.getWidth() - mMagnifier.getSourceWidth() + 1,
+                sourcePosition.x);
+    }
+
+    @Test
+    public void testSourcePosition_respectsMaxVisibleBounds_inVerticalScrollableContainer()
+            throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_scrollable_views_layout);
+        }, false /*forceLayout*/);
+        final View view = mActivity.findViewById(R.id.magnifier_activity_vertically_scrolled_view);
+        final ScrollView container = (ScrollView) mActivity
+                .findViewById(R.id.vertical_scroll_container);
+        final Magnifier.Builder builder = new Magnifier.Builder(view)
+                .setSize(100, 100)
+                .setInitialZoom(10f) /* 10x10 source size */
+                .setSourceBounds(
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE
+                );
+
+        runOnUiThreadAndWaitForCompletion(() -> {
+            mMagnifier = builder.build();
+            // Scroll halfway vertically.
+            container.scrollTo(0, view.getHeight() / 2);
+        });
+
+        final int[] containerPosition = new int[2];
+        container.getLocationInSurface(containerPosition);
+
+        // Try to copy from an y above the currently visible region.
+        showMagnifier(0, view.getHeight() / 4);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(containerPosition[1], sourcePosition.y);
+
+        // Try to copy from an x below the currently visible region.
+        showMagnifier(0, 3 * view.getHeight() / 4);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(containerPosition[1] + container.getHeight() - mMagnifier.getSourceHeight(),
+                sourcePosition.y);
+    }
+
+    @Test
+    public void testSourcePosition_respectsMaxInSurfaceBounds() throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_centered_view_layout);
+        }, 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 */
+                .setSourceBounds(
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE
+                );
+
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        final int[] viewPosition = new int[2];
+        view.getLocationInSurface(viewPosition);
+
+        // Copy content centered on relative position (0, 0) and expect the top left
+        // corner of the source NOT to have been pulled to coincide with (0, 0) of the view.
+        showMagnifier(0, 0);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(viewPosition[0] - mMagnifier.getSourceWidth() / 2, sourcePosition.x);
+        assertEquals(viewPosition[1] - mMagnifier.getSourceHeight() / 2, sourcePosition.y);
+
+        // Copy content centered on the bottom right corner of the view and expect the top left
+        // corner of the source NOT to have been pulled inside the view.
+        showMagnifier(view.getWidth(), view.getHeight());
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(viewPosition[0] + view.getWidth() - mMagnifier.getSourceWidth() / 2,
+                sourcePosition.x);
+        assertEquals(viewPosition[1] + view.getHeight() - mMagnifier.getSourceHeight() / 2,
+                sourcePosition.y);
+
+        // Copy content centered on the top left corner of the main app surface and expect the top
+        // left corner of the source to have been pulled to the top left corner of the surface.
+        showMagnifier(-viewPosition[0], -viewPosition[1]);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(0, sourcePosition.x);
+        assertEquals(0, sourcePosition.y);
+
+        // Copy content centered on the bottom right corner of the main app surface and expect the
+        // source to have been pulled inside the surface at its bottom right.
+        final Rect surfaceInsets = view.getViewRootImpl().mWindowAttributes.surfaceInsets;
+        final int surfaceWidth = view.getViewRootImpl().getWidth() + surfaceInsets.left
+                + surfaceInsets.right;
+        final int surfaceHeight = view.getViewRootImpl().getHeight() + surfaceInsets.top
+                + surfaceInsets.bottom;
+        showMagnifier(surfaceWidth - viewPosition[0] + view.getWidth(),
+                surfaceHeight - viewPosition[1] + view.getHeight());
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(surfaceWidth - mMagnifier.getSourceWidth(), sourcePosition.x);
+        assertEquals(surfaceHeight - mMagnifier.getSourceHeight(), sourcePosition.y);
+    }
+
+    @Test
+    public void testSourcePosition_respectsMaxInSurfaceBounds_forSurfaceView() throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_centered_surfaceview_layout);
+        }, 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 */
+                .setSourceBounds(
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+                        Magnifier.SOURCE_BOUND_MAX_IN_SURFACE
+                );
+
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        // Copy content centered on relative position (0, 0) and expect the top left
+        // corner of the source to have been pulled to coincide with (0, 0) of the view
+        // (since the view coincides with the surface content is copied from).
+        showMagnifier(0, 0);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(0, sourcePosition.x);
+        assertEquals(0, sourcePosition.y);
+
+        // Copy content centered on the bottom right corner of the view and expect the top left
+        // corner of the source to have been pulled inside the surface view.
+        showMagnifier(view.getWidth(), view.getHeight());
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(view.getWidth() - mMagnifier.getSourceWidth(), sourcePosition.x);
+        assertEquals(view.getHeight() - mMagnifier.getSourceHeight(), sourcePosition.y);
+
+        // Copy content from the center of the surface view and expect no clamping to be done.
+        showMagnifier(view.getWidth() / 2, view.getHeight() / 2);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertNotNull(sourcePosition);
+        assertEquals(view.getWidth() / 2 - mMagnifier.getSourceWidth() / 2, sourcePosition.x);
+        assertEquals(view.getHeight() / 2 - mMagnifier.getSourceHeight() / 2, sourcePosition.y);
+    }
+
+    @Test
+    public void testSourceBounds_areAdjustedWhenInvalid() throws Throwable {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
+            mActivity.setContentView(R.layout.magnifier_activity_centered_view_layout);
+        }, false /*forceLayout*/);
+        final View view = mActivity.findViewById(R.id.magnifier_centered_view);
+        final Insets systemInsets = view.getRootWindowInsets().getSystemWindowInsets();
+        final Magnifier.Builder builder = new Magnifier.Builder(view)
+                .setSize(2 * view.getWidth() + systemInsets.right,
+                        2 * view.getHeight() + systemInsets.bottom)
+                .setInitialZoom(1f) /* source double the size of the view + right/bottom insets */
+                .setSourceBounds(/* invalid bounds */
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+                        Magnifier.SOURCE_BOUND_MAX_VISIBLE
+                );
+
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        final int[] viewPosition = new int[2];
+        view.getLocationInSurface(viewPosition);
+
+        // Make sure that the left and top bounds are respected, since this is possible
+        // for this source size, when the view is centered.
+        showMagnifier(0, 0);
+        Point sourcePosition = mMagnifier.getSourcePosition();
+        assertEquals(viewPosition[0], sourcePosition.x);
+        assertEquals(viewPosition[1], sourcePosition.y);
+
+        // Move the magnified view to the top left of the screen, and make sure that
+        // the top and left bounds are still respected.
+        mActivityRule.runOnUiThread(() -> {
+            final LinearLayout layout =
+                    mActivity.findViewById(R.id.magnifier_activity_centered_view_layout);
+            layout.setGravity(Gravity.TOP | Gravity.LEFT);
+        });
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, view, null);
+        view.getLocationInSurface(viewPosition);
+
+        showMagnifier(0, 0);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertEquals(viewPosition[0], sourcePosition.x);
+        assertEquals(viewPosition[1], sourcePosition.y);
+
+        // Move the magnified view to the bottom right of the layout, and expect the top and left
+        // bounds to have been shifted such that the source sits inside the surface.
+        mActivityRule.runOnUiThread(() -> {
+            final LinearLayout layout =
+                    mActivity.findViewById(R.id.magnifier_activity_centered_view_layout);
+            layout.setGravity(Gravity.BOTTOM | Gravity.RIGHT);
+        });
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, view, null);
+        view.getLocationInSurface(viewPosition);
+
+        showMagnifier(0, 0);
+        sourcePosition = mMagnifier.getSourcePosition();
+        assertEquals(viewPosition[0] - view.getWidth(), sourcePosition.x);
+        assertEquals(viewPosition[1] - view.getHeight(), sourcePosition.y);
+    }
+
+    //***** Tests for zoom change *****//
+
+    @Test
+    public void testZoomChange() throws Throwable {
+        // Setup.
+        final View view = new View(mActivity);
+        final int width = 300;
+        final int height = 270;
+        final Magnifier.Builder builder = new Magnifier.Builder(view)
+                .setSize(width, height)
+                .setInitialZoom(1.0f);
+        mMagnifier = builder.build();
+        final float newZoom = 1.5f;
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, view, () -> {
+            mLayout.addView(view, new LayoutParams(200, 200));
+            mMagnifier.setZoom(newZoom);
+        });
+        assertEquals((int) (width / newZoom), mMagnifier.getSourceWidth());
+        assertEquals((int) (height / newZoom), mMagnifier.getSourceHeight());
+
+        // Show.
+        showMagnifier(200, 200);
+
+        // Check bitmap size.
+        assertNotNull(mMagnifier.getOriginalContent());
+        assertEquals((int) (width / newZoom), mMagnifier.getOriginalContent().getWidth());
+        assertEquals((int) (height / newZoom), mMagnifier.getOriginalContent().getHeight());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZoomChange_throwsException_whenZoomIsZero() {
+        final View view = new View(mActivity);
+        new Magnifier(view).setZoom(0f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testZoomChange_throwsException_whenZoomIsNegative() {
+        final View view = new View(mActivity);
+        new Magnifier(view).setZoom(-1f);
+    }
+
+    //***** Tests for overlay *****//
+
+    @Test
+    public void testOverlay_isDrawn() throws Throwable {
+        final Magnifier.Builder builder = new Magnifier.Builder(mView)
+                .setSize(50, 50)
+                .setOverlay(new ColorDrawable(Color.BLUE));
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        showMagnifier(0, 0);
+        // Assert that the content has the correct size and is all blue.
+        final Bitmap content = mMagnifier.getContent();
+        assertNotNull(content);
+        assertEquals(mMagnifier.getWidth(), content.getWidth());
+        assertEquals(mMagnifier.getHeight(), content.getHeight());
+        for (int i = 0; i < content.getWidth(); ++i) {
+            for (int j = 0; j < content.getHeight(); ++j) {
+                assertEquals(Color.BLUE, content.getPixel(i, j));
+            }
+        }
+    }
+
+    @Test
+    public void testOverlay_redrawsOnInvalidation() throws Throwable {
+        final ColorDrawable overlay = new ColorDrawable(Color.BLUE);
+        final Magnifier.Builder builder = new Magnifier.Builder(mView)
+                .setSize(50, 50)
+                .setOverlay(overlay);
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        showMagnifier(0, 0);
+        overlay.setColor(Color.WHITE);
+        // Assert that the content has the correct size and is all blue.
+        final Bitmap content = mMagnifier.getContent();
+        assertNotNull(content);
+        assertEquals(mMagnifier.getWidth(), content.getWidth());
+        assertEquals(mMagnifier.getHeight(), content.getHeight());
+        for (int i = 0; i < content.getWidth(); ++i) {
+            for (int j = 0; j < content.getHeight(); ++j) {
+                assertEquals(Color.WHITE, content.getPixel(i, j));
+            }
+        }
+    }
+
+    @Test
+    public void testOverlay_isNotVisible_whenSetToNull() throws Throwable {
+        final Magnifier.Builder builder = new Magnifier.Builder(mView)
+                .setSize(50, 50)
+                .setInitialZoom(10f) /* 5x5 source size */
+                .setOverlay(null);
+        runOnUiThreadAndWaitForCompletion(() -> mMagnifier = builder.build());
+
+        showMagnifier(mView.getWidth() / 2, mView.getHeight() / 2);
+        // Assert that the content has the correct size and is all the view color.
+        final Bitmap content = mMagnifier.getContent();
+        assertNotNull(content);
+        assertEquals(mMagnifier.getWidth(), content.getWidth());
+        assertEquals(mMagnifier.getHeight(), content.getHeight());
+        final int viewColor = mView.getContext().getResources().getColor(
+                android.R.color.holo_blue_bright, null);
+        for (int i = 0; i < content.getWidth(); ++i) {
+            for (int j = 0; j < content.getHeight(); ++j) {
+                assertEquals(viewColor, content.getPixel(i, j));
+            }
+        }
+    }
+
+    //***** Helper methods / classes *****//
+
+    private void showMagnifier(float sourceX, float sourceY) throws Throwable {
+        runAndWaitForMagnifierOperationComplete(() -> mMagnifier.show(sourceX, sourceY));
+    }
+
+    private void showMagnifier(float sourceX, float sourceY, float magnifierX, float magnifierY)
+            throws Throwable {
+        runAndWaitForMagnifierOperationComplete(() -> mMagnifier.show(sourceX, sourceY,
+                magnifierX, magnifierY));
+    }
+
+    private void runAndWaitForMagnifierOperationComplete(final Runnable lambda) throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mMagnifier.setOnOperationCompleteCallback(latch::countDown);
+        mActivityRule.runOnUiThread(lambda);
+        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(2, TimeUnit.SECONDS));
+    }
+
+    private void runOnUiThreadAndWaitForCompletion(final Runnable lambda) throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            lambda.run();
+            latch.countDown();
+        });
+        assertTrue(TIME_LIMIT_EXCEEDED, latch.await(2, TimeUnit.SECONDS));
+    }
+
     /**
      * Sets the activity to contain four equal quadrants coloured differently and
      * instantiates a magnifier. This method should not be called on the UI thread.
@@ -238,7 +952,7 @@
             mActivity.setContentView(R.layout.magnifier_activity_four_quadrants_layout);
             mLayout = mActivity.findViewById(R.id.magnifier_activity_four_quadrants_layout);
             mMagnifier = new Magnifier(mLayout);
-        }, false);
+        }, false /*forceLayout*/);
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mLayout, null);
     }
 
diff --git a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
index 7392c99..e537bdf 100644
--- a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
@@ -20,6 +20,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -28,7 +32,6 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.Instrumentation;
-import android.app.UiAutomation;
 import android.content.res.Configuration;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
@@ -37,6 +40,7 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.TextUtils;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.NumberPicker;
 
@@ -47,6 +51,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @FlakyTest
 @SmallTest
@@ -58,9 +64,9 @@
     private static final long TIMEOUT_ACCESSIBILITY_EVENT = 5 * 1000;
 
     private Instrumentation mInstrumentation;
-    private UiAutomation mUiAutomation;
     private NumberPickerCtsActivity mActivity;
     private NumberPicker mNumberPicker;
+    @Mock private View.AccessibilityDelegate mMockA11yDelegate;
 
     @Rule
     public ActivityTestRule<NumberPickerCtsActivity> mActivityRule =
@@ -68,8 +74,10 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiAutomation = mInstrumentation.getUiAutomation();
+        // Create a UiAutomation, which will enable accessibility and allow us to test a11y events.
+        mInstrumentation.getUiAutomation();
         mActivity = mActivityRule.getActivity();
         mNumberPicker = (NumberPicker) mActivity.findViewById(R.id.number_picker);
     }
@@ -279,21 +287,17 @@
             mNumberPicker.setDisplayedValues(NUMBER_NAMES3);
 
             mNumberPicker.setOnValueChangedListener(mockValueChangeListener);
-        });
 
-        mInstrumentation.runOnMainSync(() -> {
             mNumberPicker.setValue(21);
             assertEquals(21, mNumberPicker.getValue());
-        });
 
-        mUiAutomation.executeAndWaitForEvent(() ->
-                        mInstrumentation.runOnMainSync(() -> mNumberPicker.setValue(20)),
-                (AccessibilityEvent event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
-                TIMEOUT_ACCESSIBILITY_EVENT);
-
-        mInstrumentation.runOnMainSync(() -> {
+            ((View) mNumberPicker.getParent()).setAccessibilityDelegate(mMockA11yDelegate);
+            mNumberPicker.setValue(20);
             assertEquals(20, mNumberPicker.getValue());
+            verify(mMockA11yDelegate, atLeastOnce()).onRequestSendAccessibilityEvent(
+                    any(), eq(mNumberPicker), argThat(event -> event.getEventType()
+                            == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED));
+            ((View) mNumberPicker.getParent()).setAccessibilityDelegate(null);
 
             mNumberPicker.setValue(22);
             assertEquals(22, mNumberPicker.getValue());
@@ -388,15 +392,16 @@
         final int[] numberPickerLocationOnScreen = new int[2];
         mNumberPicker.getLocationOnScreen(numberPickerLocationOnScreen);
 
-        mUiAutomation.executeAndWaitForEvent(() ->
-                        CtsTouchUtils.emulateDragGesture(mInstrumentation,
-                                numberPickerLocationOnScreen[0] + mNumberPicker.getWidth() / 2,
-                                numberPickerLocationOnScreen[1] + mNumberPicker.getHeight() - 1,
-                                0,
-                                -(mNumberPicker.getHeight() - 2)),
-                (AccessibilityEvent event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED,
-                TIMEOUT_ACCESSIBILITY_EVENT);
+        ((View) mNumberPicker.getParent()).setAccessibilityDelegate(mMockA11yDelegate);
+        CtsTouchUtils.emulateDragGesture(mInstrumentation,
+                numberPickerLocationOnScreen[0] + mNumberPicker.getWidth() / 2,
+                numberPickerLocationOnScreen[1] + mNumberPicker.getHeight() - 1,
+                0,
+                -(mNumberPicker.getHeight() - 2));
+        verify(mMockA11yDelegate, atLeastOnce()).onRequestSendAccessibilityEvent(
+                any(), eq(mNumberPicker),
+                argThat(event -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED));
+        ((View) mNumberPicker.getParent()).setAccessibilityDelegate(null);
 
         // At this point we expect that the drag-up gesture has selected the value
         // that was "below" the previously selected one, and that our value change listener
@@ -433,4 +438,16 @@
         mNumberPicker.setWrapSelectorWheel(true);
         assertTrue(mNumberPicker.getWrapSelectorWheel());
     }
+
+    @UiThreadTest
+    @Test
+    public void testSelectionDividerHeight() {
+        final NumberPicker numberPicker =
+                (NumberPicker) mActivity.findViewById(R.id.number_picker_divider_height);
+        final int initialValue = numberPicker.getSelectionDividerHeight();
+        assertEquals("Height set via XML", 4, initialValue);
+        final int newValue = 8;
+        numberPicker.setSelectionDividerHeight(newValue);
+        assertEquals(newValue, numberPicker.getSelectionDividerHeight());
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 2c483d7..f083053 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -58,6 +58,7 @@
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.ImageView;
@@ -72,6 +73,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @FlakyTest
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -1175,20 +1179,37 @@
 
     @Test
     public void testSetTouchInterceptor() throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
-        mInstrumentation.waitForIdleSync();
-        mPopupWindow = new PopupWindow(mTextView);
+        mActivityRule.runOnUiThread(() -> mTextView.setText("Testing"));
+        ViewTreeObserver observer = mTextView.getViewTreeObserver();
+        observer.addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
+            @Override
+            public void onWindowFocusChanged(boolean hasFocus) {
+                if (hasFocus) {
+                    ViewTreeObserver currentObserver = mTextView.getViewTreeObserver();
+                    currentObserver.removeOnWindowFocusChangeListener(this);
+                    latch.countDown();
+                }
+            }
+        });
+        mPopupWindow = new PopupWindow(mTextView, LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT, true /* focusable */);
 
         OnTouchListener onTouchListener = mock(OnTouchListener.class);
         when(onTouchListener.onTouch(any(View.class), any(MotionEvent.class))).thenReturn(true);
 
         mPopupWindow.setTouchInterceptor(onTouchListener);
-        mPopupWindow.setFocusable(true);
         mPopupWindow.setOutsideTouchable(true);
         Drawable drawable = new ColorDrawable();
         mPopupWindow.setBackgroundDrawable(drawable);
+        mPopupWindow.setAnimationStyle(0);
         showPopup();
+        mInstrumentation.waitForIdleSync();
 
+        latch.await(2000, TimeUnit.MILLISECONDS);
+        // Extra delay to allow input system to get fully set up (b/113686346)
+        SystemClock.sleep(500);
         int[] xy = new int[2];
         mPopupWindow.getContentView().getLocationOnScreen(xy);
         final int viewWidth = mPopupWindow.getContentView().getWidth();
@@ -1407,7 +1428,8 @@
         final View anchor = mActivity.findViewById(R.id.anchor_middle);
         final LayoutParams anchorLayoutParams = anchor.getLayoutParams();
 
-        final int[] originalLocation = mPopupWindow.getContentView().getLocationOnScreen();
+        final int[] originalLocation = new int[2];
+        mPopupWindow.getContentView().getLocationOnScreen(originalLocation);
 
         final int deltaX = 30;
         final int deltaY = 20;
@@ -1541,8 +1563,10 @@
                 mPopupWindow.getContentView().findViewById(R.id.anchor_middle)));
         mInstrumentation.waitForIdleSync();
 
-        final int[] popupLocation = mPopupWindow.getContentView().getLocationOnScreen();
-        final int[] subPopupLocation = subPopup.getContentView().getLocationOnScreen();
+        final int[] popupLocation = new int[2];
+        mPopupWindow.getContentView().getLocationOnScreen(popupLocation);
+        final int[] subPopupLocation = new int[2];
+        subPopup.getContentView().getLocationOnScreen(subPopupLocation);
 
         final int deltaX = 20;
         final int deltaY = 30;
@@ -1558,11 +1582,13 @@
         // the anchor change), we need to wait until all traversals are done.
         mInstrumentation.waitForIdleSync();
 
-        final int[] newPopupLocation = mPopupWindow.getContentView().getLocationOnScreen();
+        final int[] newPopupLocation = new int[2];
+        mPopupWindow.getContentView().getLocationOnScreen(newPopupLocation);
         assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]);
         assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]);
 
-        final int[] newSubPopupLocation = subPopup.getContentView().getLocationOnScreen();
+        final int[] newSubPopupLocation = new int[2];
+        subPopup.getContentView().getLocationOnScreen(newSubPopupLocation);
         assertEquals(subPopupLocation[0] - deltaX, newSubPopupLocation[0]);
         assertEquals(subPopupLocation[1] - deltaY, newSubPopupLocation[1]);
     }
@@ -1577,7 +1603,8 @@
     }
 
     private void assertPopupLocation(int[] originalLocation, int deltaX, int deltaY) {
-        final int[] actualLocation = mPopupWindow.getContentView().getLocationOnScreen();
+        final int[] actualLocation = new int[2];
+        mPopupWindow.getContentView().getLocationOnScreen(actualLocation);
         assertEquals(originalLocation[0] - deltaX, actualLocation[0]);
         assertEquals(originalLocation[1] - deltaY, actualLocation[1]);
     }
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsWidgetTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsWidgetTest.java
deleted file mode 100644
index 0e05a0d..0000000
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsWidgetTest.java
+++ /dev/null
@@ -1,523 +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.widget.cts;
-
-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 static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-import android.widget.RemoteViews;
-import android.widget.RemoteViewsService;
-import android.widget.StackView;
-import android.widget.cts.appwidget.MyAppWidgetProvider;
-import android.widget.cts.appwidget.MyAppWidgetService;
-
-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.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Test {@link RemoteViews} that expect to operate within a {@link AppWidgetHostView} root.
- */
-@LargeTest
-@AppModeFull
-@RunWith(AndroidJUnit4.class)
-public class RemoteViewsWidgetTest {
-    public static final String[] COUNTRY_LIST = new String[] {
-        "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus",
-        "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
-        "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
-        "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
-        "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
-        "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
-        "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
-    };
-
-    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
-        "appwidget grantbind --package android.widget.cts --user 0";
-
-    private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
-        "appwidget revokebind --package android.widget.cts --user 0";
-
-    private static final long TEST_TIMEOUT_MS = 5000;
-
-    @Rule
-    public ActivityTestRule<RemoteViewsCtsActivity> mActivityRule =
-            new ActivityTestRule<>(RemoteViewsCtsActivity.class);
-
-    private Instrumentation mInstrumentation;
-
-    private Context mContext;
-
-    private boolean mHasAppWidgets;
-
-    private AppWidgetHostView mAppWidgetHostView;
-
-    private int mAppWidgetId;
-
-    private StackView mStackView;
-
-    private ListView mListView;
-
-    private AppWidgetHost mAppWidgetHost;
-
-    @Before
-    public void setup() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mContext = mInstrumentation.getTargetContext();
-
-        mHasAppWidgets = hasAppWidgets();
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        // We want to bind widgets - run a shell command to grant bind permission to our
-        // package.
-        grantBindAppWidgetPermission();
-
-        mAppWidgetHost = new AppWidgetHost(mContext, 0);
-
-        mAppWidgetHost.deleteHost();
-        mAppWidgetHost.startListening();
-
-        // Configure the app widget provider behavior
-        final CountDownLatch providerCountDownLatch = new CountDownLatch(2);
-        MyAppWidgetProvider.configure(providerCountDownLatch, null, null);
-
-        // Grab the provider to be bound
-        final AppWidgetProviderInfo providerInfo = getAppWidgetProviderInfo();
-
-        // Allocate a widget id to bind
-        mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
-
-        // Bind the app widget
-        boolean isBinding = getAppWidgetManager().bindAppWidgetIdIfAllowed(mAppWidgetId,
-                providerInfo.getProfile(), providerInfo.provider, null);
-        assertTrue(isBinding);
-
-        // Wait for onEnabled and onUpdate calls on our provider
-        try {
-            assertTrue(providerCountDownLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ie) {
-            fail(ie.getMessage());
-        }
-
-        // Configure the app widget service behavior
-        final CountDownLatch factoryCountDownLatch = new CountDownLatch(2);
-        RemoteViewsService.RemoteViewsFactory factory =
-                mock(RemoteViewsService.RemoteViewsFactory.class);
-        when(factory.getCount()).thenReturn(COUNTRY_LIST.length);
-        doAnswer(new Answer<RemoteViews>() {
-            @Override
-            public RemoteViews answer(InvocationOnMock invocation) throws Throwable {
-                final int position = (Integer) invocation.getArguments()[0];
-                RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
-                        R.layout.remoteviews_adapter_item);
-                remoteViews.setTextViewText(R.id.item, COUNTRY_LIST[position]);
-
-                // Set a fill-intent which will be used to fill-in the pending intent template
-                // which is set on the collection view in MyAppWidgetProvider.
-                Bundle extras = new Bundle();
-                extras.putString(MockURLSpanTestActivity.KEY_PARAM, COUNTRY_LIST[position]);
-                Intent fillInIntent = new Intent();
-                fillInIntent.putExtras(extras);
-                remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
-
-                if (position == 0) {
-                    factoryCountDownLatch.countDown();
-                }
-                return remoteViews;
-            }
-        }).when(factory).getViewAt(any(int.class));
-        when(factory.getViewTypeCount()).thenReturn(1);
-        MyAppWidgetService.setFactory(factory);
-
-        mActivityRule.runOnUiThread(
-                () -> mAppWidgetHostView = mAppWidgetHost.createView(
-                        mContext, mAppWidgetId, providerInfo));
-
-        // Wait our factory to be called to create the first item
-        try {
-            assertTrue(factoryCountDownLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ie) {
-            fail(ie.getMessage());
-        }
-
-        // Add our host view to the activity behind this test. This is similar to how launchers
-        // add widgets to the on-screen UI.
-        ViewGroup root = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.remoteView_host);
-        FrameLayout.MarginLayoutParams lp = new FrameLayout.MarginLayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-        mAppWidgetHostView.setLayoutParams(lp);
-
-        mActivityRule.runOnUiThread(() -> root.addView(mAppWidgetHostView));
-    }
-
-    @After
-    public void teardown() {
-        if (!mHasAppWidgets) {
-            return;
-        }
-        mAppWidgetHost.deleteHost();
-        revokeBindAppWidgetPermission();
-    }
-
-    private void grantBindAppWidgetPermission() {
-        try {
-            SystemUtil.runShellCommand(mInstrumentation, GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
-        } catch (IOException e) {
-            fail("Error granting app widget permission. Command: "
-                    + GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND + ": ["
-                    + e.getMessage() + "]");
-        }
-    }
-
-    private void revokeBindAppWidgetPermission() {
-        try {
-            SystemUtil.runShellCommand(mInstrumentation, REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
-        } catch (IOException e) {
-            fail("Error revoking app widget permission. Command: "
-                    + REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND + ": ["
-                    + e.getMessage() + "]");
-        }
-    }
-
-    private boolean hasAppWidgets() {
-        return mInstrumentation.getTargetContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
-    }
-
-    private AppWidgetManager getAppWidgetManager() {
-        return (AppWidgetManager) mContext.getSystemService(Context.APPWIDGET_SERVICE);
-    }
-
-    private AppWidgetProviderInfo getAppWidgetProviderInfo() {
-        ComponentName firstComponentName = new ComponentName(mContext.getPackageName(),
-                MyAppWidgetProvider.class.getName());
-
-        return getProviderInfo(firstComponentName);
-    }
-
-    private AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
-        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
-
-        final int providerCount = providers.size();
-        for (int i = 0; i < providerCount; i++) {
-            AppWidgetProviderInfo provider = providers.get(i);
-            if (componentName.equals(provider.provider)
-                    && Process.myUserHandle().equals(provider.getProfile())) {
-                return provider;
-
-            }
-        }
-
-        return null;
-    }
-
-    @Test
-    public void testInitialState() {
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        assertNotNull(mAppWidgetHostView);
-        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
-        assertNotNull(mStackView);
-
-        assertEquals(COUNTRY_LIST.length, mStackView.getCount());
-        assertEquals(0, mStackView.getDisplayedChild());
-        assertEquals(R.id.remoteViews_empty, mStackView.getEmptyView().getId());
-    }
-
-    private void verifySetDisplayedChild(int displayedChildIndex) {
-        final CountDownLatch updateLatch = new CountDownLatch(1);
-        MyAppWidgetProvider.configure(updateLatch, null, null);
-
-        // Create the intent to update the widget. Note that we're passing the value
-        // for displayed child index in the intent
-        Intent intent = new Intent(mContext, MyAppWidgetProvider.class);
-        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
-        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
-        intent.putExtra(MyAppWidgetProvider.KEY_DISPLAYED_CHILD_INDEX, displayedChildIndex);
-        mContext.sendBroadcast(intent);
-
-        // Wait until the update request has been processed
-        try {
-            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ie) {
-            fail(ie.getMessage());
-        }
-        // And wait until the underlying StackView has been updated to switch to the requested
-        // child
-        PollingCheck.waitFor(TEST_TIMEOUT_MS,
-                () -> mStackView.getDisplayedChild() == displayedChildIndex);
-    }
-
-    @Test
-    public void testSetDisplayedChild() {
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
-
-        verifySetDisplayedChild(4);
-        verifySetDisplayedChild(2);
-        verifySetDisplayedChild(6);
-    }
-
-    private void verifyShowCommand(String intentShowKey, int expectedDisplayedChild) {
-        final CountDownLatch updateLatch = new CountDownLatch(1);
-        MyAppWidgetProvider.configure(updateLatch, null, null);
-
-        // Create the intent to update the widget. Note that we're passing the "indication"
-        // which one of showNext / showPrevious APIs to execute in the intent that we're
-        // creating.
-        Intent intent = new Intent(mContext, MyAppWidgetProvider.class);
-        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
-        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
-        intent.putExtra(intentShowKey, true);
-        mContext.sendBroadcast(intent);
-
-        // Wait until the update request has been processed
-        try {
-            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ie) {
-            fail(ie.getMessage());
-        }
-        // And wait until the underlying StackView has been updated to switch to the expected
-        // child
-        PollingCheck.waitFor(TEST_TIMEOUT_MS,
-                () -> mStackView.getDisplayedChild() == expectedDisplayedChild);
-    }
-
-    @Test
-    public void testShowNextPrevious() {
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
-
-        // Two forward
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, 1);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, 2);
-        // Four back (looping to the end of the adapter data)
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_PREVIOUS, 1);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_PREVIOUS, 0);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_PREVIOUS, COUNTRY_LIST.length - 1);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_PREVIOUS, COUNTRY_LIST.length - 2);
-        // And three forward (looping to the start of the adapter data)
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, COUNTRY_LIST.length - 1);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, 0);
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, 1);
-    }
-
-    private void verifyItemClickIntents(int indexToClick) throws Throwable {
-        Instrumentation.ActivityMonitor am = mInstrumentation.addMonitor(
-                MockURLSpanTestActivity.class.getName(), null, false);
-
-        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
-        PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
-        final View initialView = mStackView.getCurrentView();
-        mActivityRule.runOnUiThread(
-                () -> mStackView.performItemClick(initialView, indexToClick, 0L));
-
-        Activity newActivity = am.waitForActivityWithTimeout(TEST_TIMEOUT_MS);
-        assertNotNull(newActivity);
-        assertTrue(newActivity instanceof MockURLSpanTestActivity);
-        assertEquals(COUNTRY_LIST[indexToClick], ((MockURLSpanTestActivity) newActivity).getParam());
-        newActivity.finish();
-    }
-
-    @Test
-    public void testSetOnClickPendingIntent() throws Throwable {
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        verifyItemClickIntents(0);
-
-        // Switch to another child
-        verifySetDisplayedChild(2);
-        verifyItemClickIntents(2);
-
-        // And one more
-        verifyShowCommand(MyAppWidgetProvider.KEY_SHOW_NEXT, 3);
-        verifyItemClickIntents(3);
-    }
-
-    private class ListScrollListener implements AbsListView.OnScrollListener {
-        private CountDownLatch mLatchToNotify;
-
-        private int mTargetPosition;
-
-        public ListScrollListener(CountDownLatch latchToNotify, int targetPosition) {
-            mLatchToNotify = latchToNotify;
-            mTargetPosition = targetPosition;
-        }
-
-        @Override
-        public void onScrollStateChanged(AbsListView view, int scrollState) {
-        }
-
-        @Override
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-            if ((mTargetPosition >= firstVisibleItem) &&
-                    (mTargetPosition <= (firstVisibleItem + visibleItemCount))) {
-                mLatchToNotify.countDown();
-            }
-        }
-    }
-
-    @Test
-    public void testSetScrollPosition() {
-        if (!mHasAppWidgets) {
-            return;
-        }
-
-        mListView = (ListView) mAppWidgetHostView.findViewById(R.id.remoteViews_list);
-
-        final CountDownLatch updateLatch = new CountDownLatch(1);
-        final AtomicBoolean scrollToPositionIsComplete = new AtomicBoolean(false);
-        // We're configuring our provider with three parameters:
-        // 1. The CountDownLatch to be notified when the provider has been enabled
-        // 2. The gating condition that waits until ListView has populated its content
-        //    so that we can proceed to call setScrollPosition on it
-        // 3. The gating condition that waits until the setScrollPosition has completed
-        //    its processing / scrolling so that we can proceed to call
-        //    setRelativeScrollPosition on it
-        MyAppWidgetProvider.configure(updateLatch, () -> mListView.getChildCount() > 0,
-                scrollToPositionIsComplete::get);
-
-        final int positionToScrollTo = COUNTRY_LIST.length - 10;
-        final int scrollByAmount = COUNTRY_LIST.length / 2;
-        final int offsetScrollTarget = positionToScrollTo - scrollByAmount;
-
-        // Register the first scroll listener on our ListView. The listener will notify our latch
-        // when the "target" item comes into view. If that never happens, the latch will
-        // time out and fail the test.
-        final CountDownLatch scrollToPositionLatch = new CountDownLatch(1);
-        mListView.setOnScrollListener(
-                new ListScrollListener(scrollToPositionLatch, positionToScrollTo));
-
-        // Create the intent to update the widget. Note that we're passing the "indication"
-        // to switch to our ListView in the intent that we're creating.
-        Intent intent = new Intent(mContext, MyAppWidgetProvider.class);
-        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
-        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new  int[] { mAppWidgetId });
-        intent.putExtra(MyAppWidgetProvider.KEY_SWITCH_TO_LIST, true);
-        intent.putExtra(MyAppWidgetProvider.KEY_SCROLL_POSITION, positionToScrollTo);
-        intent.putExtra(MyAppWidgetProvider.KEY_SCROLL_OFFSET, -scrollByAmount);
-        mContext.sendBroadcast(intent);
-
-        // Wait until the update request has been processed
-        try {
-            assertTrue(updateLatch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ie) {
-            fail(ie.getMessage());
-        }
-        // And wait until the underlying ListView has been updated to be visible
-        PollingCheck.waitFor(TEST_TIMEOUT_MS, () -> mListView.getVisibility() == View.VISIBLE);
-
-        // Wait until our ListView has at least one visible child view. At that point we know
-        // that not only the host view is on screen, but also that the list view has completed
-        // its layout pass after having asked its adapter to populate the list content.
-        PollingCheck.waitFor(TEST_TIMEOUT_MS, () -> mListView.getChildCount() > 0);
-
-        // If we're on a really big display, we might be in a situation where the position
-        // we're going to scroll to is already visible. In that case the logic in the rest
-        // of this test will never fire off a listener callback and then fail the test.
-        final int lastVisiblePosition = mListView.getLastVisiblePosition();
-        if (positionToScrollTo <= lastVisiblePosition) {
-            return;
-        }
-
-        boolean result = false;
-        try {
-            result = scrollToPositionLatch.await(20, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        assertTrue("Timed out while waiting for the target view to be scrolled into view", result);
-
-        if ((offsetScrollTarget < 0) ||
-                (offsetScrollTarget >= mListView.getFirstVisiblePosition())) {
-            // We can't scroll up because the target is either already visible or negative
-            return;
-        }
-
-        // Now register another scroll listener on our ListView. The listener will notify our latch
-        // when our new "target" item comes into view. If that never happens, the latch will
-        // time out and fail the test.
-        final CountDownLatch scrollByOffsetLatch = new CountDownLatch(1);
-        mListView.setOnScrollListener(
-                new ListScrollListener(scrollByOffsetLatch, offsetScrollTarget));
-
-        // Update our atomic boolean to "kick off" the widget provider request to call
-        // setRelativeScrollPosition on our RemoteViews
-        scrollToPositionIsComplete.set(true);
-        try {
-            result = scrollByOffsetLatch.await(20, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        assertTrue("Timed out while waiting for the target view to be scrolled into view", result);
-    }
-}
diff --git a/tests/tests/widget/src/android/widget/cts/TabHost_TabSpecTest.java b/tests/tests/widget/src/android/widget/cts/TabHost_TabSpecTest.java
index a0498d5..cfa32f9 100644
--- a/tests/tests/widget/src/android/widget/cts/TabHost_TabSpecTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TabHost_TabSpecTest.java
@@ -194,6 +194,10 @@
         Uri uri = Uri.parse("ctstest://tabhost_tabspec/test");
         final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
 
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        ActivityMonitor am = instrumentation.addMonitor(MockURLSpanTestActivity.class.getName(),
+                null, false);
+
         mActivity.runOnUiThread(() -> {
             TabHost.TabSpec tabSpec = mTabHost.newTabSpec("tab spec");
             tabSpec.setIndicator("tab");
@@ -202,10 +206,6 @@
             mTabHost.setCurrentTab(1);
         });
 
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        ActivityMonitor am = instrumentation.addMonitor(MockURLSpanTestActivity.class.getName(),
-                null, false);
-
         Activity newActivity = am.waitForActivityWithTimeout(5000);
         assertNotNull(newActivity);
         newActivity.finish();
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewFontVariationTest.java b/tests/tests/widget/src/android/widget/cts/TextViewFontVariationTest.java
new file mode 100644
index 0000000..794b771
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewFontVariationTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for font variation attribute in TextView and TextAppearance
+ */
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class TextViewFontVariationTest {
+    private String getTextViewFontVariationSettings(int id) {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final ViewGroup container =
+                (ViewGroup) inflater.inflate(R.layout.textview_fontvariation_test_layout, null);
+        return ((TextView) container.findViewById(id)).getFontVariationSettings();
+    }
+
+    @Test
+    public void testFontVariation() {
+        assertEquals("'wdth' 25",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth25));
+        assertEquals("'wdth' 50",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth50));
+        assertEquals("'wght' 100",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wght100));
+        assertEquals("'wght' 200",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wght200));
+        assertEquals("'wdth' 25, 'wght' 100",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth25_wght100));
+        assertEquals("'wdth' 25, 'wght' 200",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth25_wght200));
+        assertEquals("'wdth' 50, 'wght' 100",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth50_wght100));
+        assertEquals("'wdth' 50, 'wght' 200",
+                getTextViewFontVariationSettings(R.id.textView_fontVariation_wdth50_wght200));
+    }
+
+    @Test
+    public void testTextAppearance() {
+        assertEquals("'wdth' 25",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth25));
+        assertEquals("'wdth' 50",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth50));
+        assertEquals("'wght' 100",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wght100));
+        assertEquals("'wght' 200",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wght200));
+        assertEquals("'wdth' 25, 'wght' 100",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth25_wght100));
+        assertEquals("'wdth' 25, 'wght' 200",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth25_wght200));
+        assertEquals("'wdth' 50, 'wght' 100",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth50_wght100));
+        assertEquals("'wdth' 50, 'wght' 200",
+                getTextViewFontVariationSettings(R.id.textAppearance_fontVariation_wdth50_wght200));
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollableTest.java b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollableTest.java
new file mode 100644
index 0000000..4dd1bf7
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewIsHorizontallyScrollableTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Test {@link TextView#isHorizontallyScrollable}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewIsHorizontallyScrollableTest {
+    private ViewGroup mViewGroup;
+
+    @Before
+    public void setup() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mViewGroup = (ViewGroup) inflater.inflate(R.layout.textview_isHorizontallyScrollable_layout,
+                null);
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingDefaultIsFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        assertFalse(textView.isHorizontallyScrollable());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSameAsGiven() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrollable());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingTrueToFalse() {
+        final TextView textView = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default);
+        textView.setHorizontallyScrolling(true);
+        assertTrue(textView.isHorizontallyScrollable());
+
+        textView.setHorizontallyScrolling(false);
+        assertFalse(textView.isHorizontallyScrollable());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML() {
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true);
+        // It should return true here. But because of this bug b/120448952,
+        // singleLine will overwrite scrollHorizontally.
+        assertFalse(textViewTrue.isHorizontallyScrollable());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false);
+        assertFalse(textViewFalse.isHorizontallyScrollable());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnTrueWhenSingleLineIsTrue() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_true);
+        assertTrue(textViewDefault.isHorizontallyScrollable());
+
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true_singleLine_true);
+        assertTrue(textViewTrue.isHorizontallyScrollable());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_true);
+        assertTrue(textViewFalse.isHorizontallyScrollable());
+    }
+
+    @Test
+    public void testIsHorizontallyScrollingSetInXML_returnGivenValueWhenSingleLineIsFalse() {
+        final TextView textViewDefault = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_default_singleLine_false);
+        assertFalse(textViewDefault.isHorizontallyScrollable());
+
+        final TextView textViewTrue = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_true_singleLine_false);
+        // It should return true here. But because of this bug b/120448952,
+        // singleLine will overwrite scrollHorizontally.
+        assertFalse(textViewTrue.isHorizontallyScrollable());
+
+        final TextView textViewFalse = mViewGroup.findViewById(
+                R.id.textView_scrollHorizontally_false_singleLine_false);
+        assertFalse(textViewFalse.isHorizontallyScrollable());
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewPrecomputedTextTest.java b/tests/tests/widget/src/android/widget/cts/TextViewPrecomputedTextTest.java
index ffa5a28..a4584e5 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewPrecomputedTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewPrecomputedTextTest.java
@@ -24,8 +24,6 @@
 import android.text.PrecomputedText;
 import android.text.PrecomputedText.Params;
 import android.text.PrecomputedText.Params.Builder;
-import android.text.TextDirectionHeuristic;
-import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -67,8 +65,6 @@
     @Parameterized.Parameter(8)
     public boolean differentHyphenationFrequency;
     @Parameterized.Parameter(9)
-    public boolean differentTextDir;
-    @Parameterized.Parameter(10)
     public boolean differentFontVariationSettings;
 
     // text size from the default value.
@@ -135,15 +131,8 @@
             differenceList.add("Hyphenation Frequency");
         }
 
-        TextDirectionHeuristic dir = params.getTextDirection();
-        if (differentTextDir) {
-            dir = dir == TextDirectionHeuristics.LTR
-                    ?  TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR;
-            differenceList.add("Text Direction");
-        }
-
         final Params outParams = new Builder(paint).setBreakStrategy(strategy)
-                .setHyphenationFrequency(hyFreq).setTextDirection(dir).build();
+                .setHyphenationFrequency(hyFreq).build();
         return new Pair(outParams, differenceList.toArray(new String[differenceList.size()]));
     }
 
@@ -156,7 +145,7 @@
         ArrayList<Object[]> allParams = new ArrayList<>();
 
         // Compute the powerset except for all false case.
-        final int allParameterCount = 11;
+        final int allParameterCount = 10;
         // The 11-th bit is for font variation settings. Don't add test case if the system don't
         // have variable fonts.
         final int fullBits = hasVarFont()
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 7c55f90..490a04b 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -53,6 +53,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
@@ -73,8 +74,6 @@
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -91,6 +90,7 @@
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
@@ -150,6 +150,9 @@
 import android.widget.TextView.BufferType;
 import android.widget.cts.util.TestUtils;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.CtsKeyEventUtil;
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -2271,6 +2274,34 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
+    public void testCopyAndPaste_byCtrlInsert() throws Throwable {
+        // Test copy-and-paste by Ctrl-Insert and Shift-Insert.
+        initTextViewForTypingOnUiThread();
+
+        // Type "abc"
+        CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
+        mActivityRule.runOnUiThread(() -> {
+            // Select "bc"
+            Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Copy "bc"
+        CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
+                KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_CTRL_LEFT);
+        mActivityRule.runOnUiThread(() -> {
+            // Set cursor between 'b' and 'c'
+            Selection.setSelection((Spannable) mTextView.getText(), 2, 2);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Paste "bc"
+        CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
+                KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_SHIFT_LEFT);
+        assertEquals("abbcc", mTextView.getText().toString());
+    }
+
     @UiThreadTest
     @Test
     public void testCutAndPaste() {
@@ -2341,6 +2372,35 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @Test
+    public void testCutAndPaste_byShiftDelete() throws Throwable {
+        // Test cut and paste by Shift-Delete and Shift-Insert
+        initTextViewForTypingOnUiThread();
+
+        // Type "abc".
+        CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
+        mActivityRule.runOnUiThread(() -> {
+            // Select "bc"
+            Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Cut "bc"
+        CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
+                KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.KEYCODE_SHIFT_LEFT);
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals("a", mTextView.getText().toString());
+            // Move cursor to the head
+            Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Paste "bc"
+        CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
+                KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_SHIFT_LEFT);
+        assertEquals("bca", mTextView.getText().toString());
+    }
+
     private static boolean hasSpansAtMiddleOfText(final TextView textView, final Class<?> type) {
         final Spannable spannable = (Spannable)textView.getText();
         final int at = spannable.length() / 2;
@@ -3288,6 +3348,19 @@
 
     @UiThreadTest
     @Test
+    public void testTextAttr_zeroTextSize() {
+        mTextView = findTextView(R.id.textview_textAttr_zeroTextSize);
+        // text size should be 0 as set in xml, rather than the text view default (15.0)
+        assertEquals(0f, mTextView.getTextSize(), 0.01f);
+        // text size can be set programmatically to non-negative values
+        mTextView.setTextSize(20f);
+        assertTrue(mTextView.getTextSize() > 0.0f);
+        mTextView.setTextSize(0f);
+        assertEquals(0f, mTextView.getTextSize(), 0.01f);
+    }
+
+    @UiThreadTest
+    @Test
     public void testAppend() {
         mTextView = new TextView(mActivity);
 
@@ -4008,6 +4081,216 @@
         assertNull(drawables[3]);
     }
 
+    @UiThreadTest
+    @Test
+    public void testCursorDrawable_isNotNullByDefault() {
+        assertNotNull(new TextView(mActivity).getTextCursorDrawable());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testCursorDrawable_canBeSet_toDrawable() {
+        mTextView = new TextView(mActivity);
+        final Drawable cursor = TestUtils.getDrawable(mActivity, R.drawable.blue);
+        mTextView.setTextCursorDrawable(cursor);
+        assertSame(cursor, mTextView.getTextCursorDrawable());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testCursorDrawable_canBeSet_toDrawableResource() {
+        mTextView = new TextView(mActivity);
+        mTextView.setTextCursorDrawable(R.drawable.start);
+        WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.start),
+                ((BitmapDrawable) mTextView.getTextCursorDrawable()).getBitmap());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testCursorDrawable_canBeSetToNull() {
+        new TextView(mActivity).setTextCursorDrawable(null);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testCursorDrawable_canBeSetToZeroResId() {
+        new TextView(mActivity).setTextCursorDrawable(0);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testHandleDrawables_areNotNullByDefault() {
+        mTextView = new TextView(mActivity);
+        assertNotNull(mTextView.getTextSelectHandle());
+        assertNotNull(mTextView.getTextSelectHandleLeft());
+        assertNotNull(mTextView.getTextSelectHandleRight());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testHandleDrawables_canBeSet_toDrawables() {
+        mTextView = new TextView(mActivity);
+
+        final Drawable blue = TestUtils.getDrawable(mActivity, R.drawable.blue);
+        final Drawable yellow = TestUtils.getDrawable(mActivity, R.drawable.yellow);
+        final Drawable red = TestUtils.getDrawable(mActivity, R.drawable.red);
+
+        mTextView.setTextSelectHandle(blue);
+        mTextView.setTextSelectHandleLeft(yellow);
+        mTextView.setTextSelectHandleRight(red);
+
+        assertSame(blue, mTextView.getTextSelectHandle());
+        assertSame(yellow, mTextView.getTextSelectHandleLeft());
+        assertSame(red, mTextView.getTextSelectHandleRight());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testHandleDrawables_canBeSet_toDrawableResources() {
+        mTextView = new TextView(mActivity);
+
+        mTextView.setTextSelectHandle(R.drawable.start);
+        mTextView.setTextSelectHandleLeft(R.drawable.pass);
+        mTextView.setTextSelectHandleRight(R.drawable.failed);
+
+        WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.start),
+                ((BitmapDrawable) mTextView.getTextSelectHandle()).getBitmap());
+        WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.pass),
+                ((BitmapDrawable) mTextView.getTextSelectHandleLeft()).getBitmap());
+        WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.failed),
+                ((BitmapDrawable) mTextView.getTextSelectHandleRight()).getBitmap());
+    }
+
+    @UiThreadTest
+    @Test(expected = NullPointerException.class)
+    public void testSelectHandleDrawable_cannotBeSetToNull() {
+        new TextView(mActivity).setTextSelectHandle(null);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSelectHandleDrawable_cannotBeSetToZeroResId() {
+        new TextView(mActivity).setTextSelectHandle(0);
+    }
+
+    @UiThreadTest
+    @Test(expected = NullPointerException.class)
+    public void testSelectHandleDrawableLeft_cannotBeSetToNull() {
+        new TextView(mActivity).setTextSelectHandleLeft(null);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSelectHandleDrawableLeft_cannotBeSetToZeroResId() {
+        new TextView(mActivity).setTextSelectHandleLeft(0);
+    }
+
+    @UiThreadTest
+    @Test(expected = NullPointerException.class)
+    public void testSelectHandleDrawableRight_cannotBeSetToNull() {
+        new TextView(mActivity).setTextSelectHandleRight(null);
+    }
+
+    @UiThreadTest
+    @Test(expected = IllegalArgumentException.class)
+    public void testSelectHandleDrawableRight_cannotBeSetToZeroResId() {
+        new TextView(mActivity).setTextSelectHandleRight(0);
+    }
+
+    @Test
+    public void testHandleDrawable_canBeSet_whenInsertionHandleIsShown() throws Throwable {
+        initTextViewForTypingOnUiThread();
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setTextIsSelectable(true);
+            mTextView.setText("abcd", BufferType.EDITABLE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Trigger insertion.
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mTextView);
+
+        final boolean[] mDrawn = new boolean[3];
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setTextSelectHandle(new TestHandleDrawable(mDrawn, 0));
+            mTextView.setTextSelectHandleLeft(new TestHandleDrawable(mDrawn, 1));
+            mTextView.setTextSelectHandleRight(new TestHandleDrawable(mDrawn, 2));
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(mDrawn[0]);
+        assertFalse(mDrawn[1]);
+        assertFalse(mDrawn[2]);
+    }
+
+    @Test
+    public void testHandleDrawables_canBeSet_whenSelectionHandlesAreShown()
+            throws Throwable {
+        initTextViewForTypingOnUiThread();
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setTextIsSelectable(true);
+            mTextView.setText("abcd", BufferType.EDITABLE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Trigger selection.
+        CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mTextView);
+
+        final boolean[] mDrawn = new boolean[3];
+        mActivityRule.runOnUiThread(() -> {
+            mTextView.setTextSelectHandle(new TestHandleDrawable(mDrawn, 0));
+            mTextView.setTextSelectHandleLeft(new TestHandleDrawable(mDrawn, 1));
+            mTextView.setTextSelectHandleRight(new TestHandleDrawable(mDrawn, 2));
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertFalse(mDrawn[0]);
+        assertTrue(mDrawn[1]);
+        assertTrue(mDrawn[2]);
+    }
+
+    @Test
+    public void testTextActionModeCallback_loadsHandleDrawables() throws Throwable {
+        final String text = "abcde";
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = new EditText(mActivity);
+            mActivity.setContentView(mTextView);
+            mTextView.setText(text, BufferType.SPANNABLE);
+            mTextView.setTextIsSelectable(true);
+            mTextView.requestFocus();
+            mTextView.setSelected(true);
+            mTextView.setTextClassifier(TextClassifier.NO_OP);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Set selection and try to start action mode.
+            final Bundle args = new Bundle();
+            args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
+            args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
+            mTextView.performAccessibilityAction(
+                    AccessibilityNodeInfo.ACTION_SET_SELECTION, args);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // There should be no null pointer exception caused by handle drawables not being loaded.
+    }
+
+    private class TestHandleDrawable extends ColorDrawable {
+        private final boolean[] mArray;
+        private final int mIndex;
+
+        TestHandleDrawable(final boolean[] array, final int index) {
+            mArray = array;
+            mIndex = index;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            super.draw(canvas);
+            mArray[mIndex] = true;
+        }
+    }
+
     @Test
     public void testSingleLine() throws Throwable {
         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
@@ -4064,6 +4347,48 @@
 
     @UiThreadTest
     @Test
+    public void testIsSingleLineTrue() {
+        mTextView = new TextView(mActivity);
+
+        mTextView.setSingleLine(true);
+
+        assertTrue(mTextView.isSingleLine());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testIsSingleLineFalse() {
+        mTextView = new TextView(mActivity);
+
+        mTextView.setSingleLine(false);
+
+        assertFalse(mTextView.isSingleLine());
+    }
+
+    @Test
+    public void testXmlIsSingleLineTrue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final LayoutInflater layoutInflater = LayoutInflater.from(context);
+        final View root = layoutInflater.inflate(R.layout.textview_singleline, null);
+
+        mTextView = root.findViewById(R.id.textview_singleline_true);
+
+        assertTrue(mTextView.isSingleLine());
+    }
+
+    @Test
+    public void testXmlIsSingleLineFalse() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final LayoutInflater layoutInflater = LayoutInflater.from(context);
+        final View root = layoutInflater.inflate(R.layout.textview_singleline, null);
+
+        mTextView = root.findViewById(R.id.textview_singleline_false);
+
+        assertFalse(mTextView.isSingleLine());
+    }
+
+    @UiThreadTest
+    @Test
     public void testAccessMaxLines() {
         mTextView = findTextView(R.id.textview_text);
         mTextView.setWidth((int) (mTextView.getPaint().measureText(LONG_TEXT) / 4));
@@ -6260,16 +6585,21 @@
     public void testSetGetHyphenationFrequency() {
         TextView tv = new TextView(mActivity);
 
-        assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, tv.getHyphenationFrequency());
-
-        tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
-        assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, tv.getHyphenationFrequency());
+        // Hypenation is enabled by default on watches to fit more text on their tiny screens.
+        if (isWatch()) {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, tv.getHyphenationFrequency());
+        } else {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, tv.getHyphenationFrequency());
+        }
 
         tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
         assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, tv.getHyphenationFrequency());
 
         tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
         assertEquals(Layout.HYPHENATION_FREQUENCY_FULL, tv.getHyphenationFrequency());
+
+        tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
+        assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, tv.getHyphenationFrequency());
     }
 
     @UiThreadTest
@@ -7987,6 +8317,142 @@
         });
     }
 
+    @Test
+    public void testBreakStrategyDefaultValue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final TextView textView = new TextView(context);
+        assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, textView.getBreakStrategy());
+    }
+
+    @Test
+    public void testHyphenationFrequencyDefaultValue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final TextView textView = new TextView(context);
+
+        // Hypenation is enabled by default on watches to fit more text on their tiny screens.
+        if (isWatch()) {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, textView.getHyphenationFrequency());
+        } else {
+            assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, textView.getHyphenationFrequency());
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_password_returnsLTR() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text_password);
+
+        assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_LtrLayout_TextDirectionFirstStrong() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrong() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionAnyRtl() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+        assertEquals(TextDirectionHeuristics.ANYRTL_LTR, textView.getTextDirectionHeuristic());
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+        assertEquals(TextDirectionHeuristics.ANYRTL_LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionLtr() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_LTR);
+
+        assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionRtl() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_RTL);
+
+        assertEquals(TextDirectionHeuristics.RTL, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrongLtr() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrongRtl() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
+
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+        assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_phoneInputType_returnsLTR() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text_phone);
+
+        textView.setTextLocale(Locale.forLanguageTag("ar"));
+        textView.setTextDirection(View.TEXT_DIRECTION_RTL);
+        textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionLocale() {
+        mActivity.setContentView(R.layout.textview_textdirectionheuristic);
+        final TextView textView = mActivity.findViewById(R.id.text);
+        textView.setTextDirection(View.TEXT_DIRECTION_LOCALE);
+
+        assertEquals(TextDirectionHeuristics.LOCALE, textView.getTextDirectionHeuristic());
+    }
+
     private void initializeTextForSmartSelection(CharSequence text) throws Throwable {
         assertTrue(text.length() >= SMARTSELECT_END);
         mActivityRule.runOnUiThread(() -> {
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index 4ea9b88..202ed09 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -24,10 +24,12 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.LargeTest;
@@ -37,10 +39,13 @@
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.Toast;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -51,6 +56,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ToastTest {
     private static final String TEST_TOAST_TEXT = "test toast";
+    private static final String SETTINGS_ACCESSIBILITY_UI_TIMEOUT =
+            "accessibility_non_interactive_ui_timeout_ms";
+    private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
     private static final long TIME_FOR_UI_OPERATION  = 1000L;
     private static final long TIME_OUT = 5000L;
     private Toast mToast;
@@ -203,6 +211,69 @@
     }
 
     @Test
+    public void testAccessDuration_withA11yTimeoutEnabled() throws Throwable {
+        makeToast();
+        final Runnable showToast = () -> {
+            mToast.setDuration(Toast.LENGTH_SHORT);
+            mToast.show();
+        };
+        long start = SystemClock.uptimeMillis();
+        mActivityRule.runOnUiThread(showToast);
+        mInstrumentation.waitForIdleSync();
+        assertShowAndHide(mToast.getView());
+        final long shortDuration = SystemClock.uptimeMillis() - start;
+
+        final UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
+        final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(),
+                SETTINGS_ACCESSIBILITY_UI_TIMEOUT);
+        try {
+            final int a11ySettingDuration = (int) shortDuration + 1000;
+            SystemUtil.runWithShellPermissionIdentity(uiAutomation,
+                    () -> Settings.Secure.putInt(mContext.getContentResolver(),
+                            SETTINGS_ACCESSIBILITY_UI_TIMEOUT, a11ySettingDuration));
+            waitForA11yRecommendedTimeoutChanged(mContext,
+                    ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration);
+            start = SystemClock.uptimeMillis();
+            mActivityRule.runOnUiThread(showToast);
+            mInstrumentation.waitForIdleSync();
+            assertShowAndHide(mToast.getView());
+            final long a11yDuration = SystemClock.uptimeMillis() - start;
+            assertTrue(a11yDuration >= a11ySettingDuration);
+        } finally {
+            SystemUtil.runWithShellPermissionIdentity(uiAutomation,
+                    () -> Settings.Secure.putString(mContext.getContentResolver(),
+                            SETTINGS_ACCESSIBILITY_UI_TIMEOUT, originalSetting));
+        }
+    }
+
+    /**
+     * Wait for accessibility recommended timeout changed and equals to expected timeout.
+     *
+     * @param expectedTimeoutMs expected recommended timeout
+     */
+    private void waitForA11yRecommendedTimeoutChanged(Context context,
+            long waitTimeoutMs, int expectedTimeoutMs) {
+        final AccessibilityManager manager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        final Object lock = new Object();
+        AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
+            synchronized (lock) {
+                lock.notifyAll();
+            }
+        };
+        manager.addAccessibilityServicesStateChangeListener(listener, null);
+        try {
+            TestUtils.waitOn(lock,
+                    () -> manager.getRecommendedTimeoutMillis(0,
+                            AccessibilityManager.FLAG_CONTENT_TEXT) == expectedTimeoutMs,
+                    waitTimeoutMs,
+                    "Wait for accessibility recommended timeout changed");
+        } finally {
+            manager.removeAccessibilityServicesStateChangeListener(listener);
+        }
+    }
+
+    @Test
     public void testAccessMargin() throws Throwable {
         makeToast();
         View view = mToast.getView();
diff --git a/tests/tests/widget/src/android/widget/cts/ToolbarTest.java b/tests/tests/widget/src/android/widget/cts/ToolbarTest.java
index ff4da10..8a70859 100644
--- a/tests/tests/widget/src/android/widget/cts/ToolbarTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToolbarTest.java
@@ -287,6 +287,41 @@
     }
 
     @Test
+    public void testCollapseConfiguration() throws Throwable {
+        // Inflate menu and expand action view to display collapse button
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMainToolbar,
+                () -> mMainToolbar.inflateMenu(R.menu.toolbar_menu_search));
+        final MenuItem searchMenuItem = mMainToolbar.getMenu().findItem(R.id.action_search);
+        mActivityRule.runOnUiThread(searchMenuItem::expandActionView);
+        mInstrumentation.waitForIdleSync();
+
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMainToolbar,
+                () -> mMainToolbar.setCollapseIcon(R.drawable.icon_green));
+        Drawable toolbarCollapseIcon = mMainToolbar.getCollapseIcon();
+        TestUtils.assertAllPixelsOfColor("Collapse icon is green", toolbarCollapseIcon,
+                toolbarCollapseIcon.getIntrinsicWidth(),
+                toolbarCollapseIcon.getIntrinsicHeight(),
+                true, Color.GREEN, 1, false);
+
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMainToolbar,
+                () -> mMainToolbar.setCollapseIcon(mActivity.getDrawable(R.drawable.icon_blue)));
+        toolbarCollapseIcon = mMainToolbar.getCollapseIcon();
+        TestUtils.assertAllPixelsOfColor("Collapse icon is blue", toolbarCollapseIcon,
+                toolbarCollapseIcon.getIntrinsicWidth(),
+                toolbarCollapseIcon.getIntrinsicHeight(),
+                true, Color.BLUE, 1, false);
+
+        mActivityRule.runOnUiThread(
+                () -> mMainToolbar.setCollapseContentDescription(R.string.toolbar_collapse));
+        assertEquals(mActivity.getResources().getString(R.string.toolbar_collapse),
+                mMainToolbar.getCollapseContentDescription());
+
+        mActivityRule.runOnUiThread(
+                () -> mMainToolbar.setCollapseContentDescription("Collapse legend"));
+        assertEquals("Collapse legend", mMainToolbar.getCollapseContentDescription());
+    }
+
+    @Test
     public void testLogoConfiguration() throws Throwable {
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMainToolbar,
                 () -> mMainToolbar.setLogo(R.drawable.icon_yellow));
diff --git a/tests/tests/widget/src/android/widget/cts/VideoView2CtsActivity.java b/tests/tests/widget/src/android/widget/cts/VideoView2CtsActivity.java
deleted file mode 100644
index e20f24e..0000000
--- a/tests/tests/widget/src/android/widget/cts/VideoView2CtsActivity.java
+++ /dev/null
@@ -1,35 +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.widget.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.VideoView2;
-
-/**
- * A minimal application for {@link VideoView2} test.
- */
-public class VideoView2CtsActivity extends Activity {
-    /**
-     * Called with the activity is first created.
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.videoview2_layout);
-    }
-}
diff --git a/tests/tests/widget/src/android/widget/cts/VideoView2Test.java b/tests/tests/widget/src/android/widget/cts/VideoView2Test.java
deleted file mode 100644
index 3043f45..0000000
--- a/tests/tests/widget/src/android/widget/cts/VideoView2Test.java
+++ /dev/null
@@ -1,206 +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.widget.cts;
-
-import static android.content.Context.KEYGUARD_SERVICE;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.VideoView2;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-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 org.mockito.ArgumentCaptor;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-
-/**
- * Test {@link VideoView2}.
- */
-@Ignore
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class VideoView2Test {
-    /** Debug TAG. **/
-    private static final String TAG = "VideoView2Test";
-    /** The maximum time to wait for an operation. */
-    private static final long   TIME_OUT = 15000L;
-    /** The interval time to wait for completing an operation. */
-    private static final long   OPERATION_INTERVAL  = 1500L;
-    /** The duration of R.raw.testvideo. */
-    private static final int    TEST_VIDEO_DURATION = 11047;
-    /** The full name of R.raw.testvideo. */
-    private static final String VIDEO_NAME   = "testvideo.3gp";
-    /** delta for duration in case user uses different decoders on different
-        hardware that report a duration that's different by a few milliseconds */
-    private static final int DURATION_DELTA = 100;
-    /** AudioAttributes to be used by this player */
-    private static final AudioAttributes AUDIO_ATTR = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_GAME)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
-    private Instrumentation mInstrumentation;
-    private Activity mActivity;
-    private KeyguardManager mKeyguardManager;
-    private VideoView2 mVideoView;
-    private MediaController mController;
-    private String mVideoPath;
-
-    @Rule
-    public ActivityTestRule<VideoView2CtsActivity> mActivityRule =
-            new ActivityTestRule<>(VideoView2CtsActivity.class);
-
-    @Before
-    public void setup() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mKeyguardManager = (KeyguardManager)
-                mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
-        mActivity = mActivityRule.getActivity();
-        mVideoView = (VideoView2) mActivity.findViewById(R.id.videoview);
-        mVideoPath = prepareSampleVideo();
-
-        mActivityRule.runOnUiThread(() -> {
-            // Keep screen on while testing.
-            mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            mActivity.setTurnScreenOn(true);
-            mActivity.setShowWhenLocked(true);
-            mKeyguardManager.requestDismissKeyguard(mActivity, null);
-        });
-        mInstrumentation.waitForIdleSync();
-
-        final View.OnAttachStateChangeListener mockAttachListener =
-                mock(View.OnAttachStateChangeListener.class);
-        if (!mVideoView.isAttachedToWindow()) {
-            mVideoView.addOnAttachStateChangeListener(mockAttachListener);
-            verify(mockAttachListener, timeout(TIME_OUT)).onViewAttachedToWindow(same(mVideoView));
-        }
-        mController = mVideoView.getMediaController();
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        /** call media controller's stop */
-    }
-
-    private boolean hasCodec() {
-        return MediaUtils.hasCodecsForResource(mActivity, R.raw.testvideo);
-    }
-
-    private String prepareSampleVideo() throws IOException {
-        try (InputStream source = mActivity.getResources().openRawResource(R.raw.testvideo);
-             OutputStream target = mActivity.openFileOutput(VIDEO_NAME, Context.MODE_PRIVATE)) {
-            final byte[] buffer = new byte[1024];
-            for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
-                target.write(buffer, 0, len);
-            }
-        }
-
-        return mActivity.getFileStreamPath(VIDEO_NAME).getAbsolutePath();
-    }
-
-    @UiThreadTest
-    @Test
-    public void testConstructor() {
-        new VideoView2(mActivity);
-        new VideoView2(mActivity, null);
-        new VideoView2(mActivity, null, 0);
-    }
-
-    @Test
-    public void testPlayVideo() throws Throwable {
-        // Don't run the test if the codec isn't supported.
-        if (!hasCodec()) {
-            Log.i(TAG, "SKIPPING testPlayVideo(): codec is not supported");
-            return;
-        }
-        final MediaController.Callback mockControllerCallback =
-                mock(MediaController.Callback.class);
-        mActivityRule.runOnUiThread(() -> {
-            mController.registerCallback(mockControllerCallback);
-            mVideoView.setVideoPath(mVideoPath);
-            mController.getTransportControls().play();
-        });
-        ArgumentCaptor<PlaybackState> someState = ArgumentCaptor.forClass(PlaybackState.class);
-        verify(mockControllerCallback, timeout(TIME_OUT).atLeast(3)).onPlaybackStateChanged(
-                someState.capture());
-        List<PlaybackState> states = someState.getAllValues();
-        assertEquals(PlaybackState.STATE_PAUSED, states.get(0).getState());
-        assertEquals(PlaybackState.STATE_PLAYING, states.get(1).getState());
-        assertEquals(PlaybackState.STATE_STOPPED, states.get(2).getState());
-    }
-
-    @Test
-    public void testPlayVideoOnTextureView() throws Throwable {
-        // Don't run the test if the codec isn't supported.
-        if (!hasCodec()) {
-            Log.i(TAG, "SKIPPING testPlayVideoOnTextureView(): codec is not supported");
-            return;
-        }
-        final VideoView2.OnViewTypeChangedListener mockViewTypeListener =
-                mock(VideoView2.OnViewTypeChangedListener.class);
-        final MediaController.Callback mockControllerCallback =
-                mock(MediaController.Callback.class);
-        mActivityRule.runOnUiThread(() -> {
-            mVideoView.setOnViewTypeChangedListener(mockViewTypeListener);
-            mVideoView.setViewType(mVideoView.VIEW_TYPE_TEXTUREVIEW);
-            mController.registerCallback(mockControllerCallback);
-            mVideoView.setVideoPath(mVideoPath);
-        });
-        verify(mockViewTypeListener, timeout(TIME_OUT))
-                .onViewTypeChanged(mVideoView, VideoView2.VIEW_TYPE_TEXTUREVIEW);
-
-        mActivityRule.runOnUiThread(() -> {
-            mController.getTransportControls().play();
-        });
-        ArgumentCaptor<PlaybackState> someState = ArgumentCaptor.forClass(PlaybackState.class);
-        verify(mockControllerCallback, timeout(TIME_OUT).atLeast(3)).onPlaybackStateChanged(
-                someState.capture());
-        List<PlaybackState> states = someState.getAllValues();
-        assertEquals(PlaybackState.STATE_PAUSED, states.get(0).getState());
-        assertEquals(PlaybackState.STATE_PLAYING, states.get(1).getState());
-        assertEquals(PlaybackState.STATE_STOPPED, states.get(2).getState());
-    }
-}
diff --git a/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java b/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
index 84eba18..cf296d5 100644
--- a/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ZoomButtonTest.java
@@ -43,10 +43,6 @@
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ZoomButtonTest {
@@ -127,87 +123,62 @@
         assertFalse(mZoomButton.dispatchUnhandledMove(null, View.FOCUS_DOWN));
     }
 
-    private void verifyZoomSpeed(ZoomClickListener zoomClickListener, long zoomSpeedMs) {
-        mZoomButton.setZoomSpeed(zoomSpeedMs);
-
-        final long startTime = System.nanoTime();
-        // Emulate long click that "lasts" for ten seconds
-        CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mZoomButton, 10000);
-
-        final List<Long> callbackInvocations = zoomClickListener.getClickTimes();
-        assertFalse("Expecting at least one callback", callbackInvocations.isEmpty());
-
-        // Verify that the first callback is fired after the system-level long press timeout.
-        final long minTimeUntilFirstInvocationMs = ViewConfiguration.getLongPressTimeout();
-        final long actualTimeUntilFirstInvocationNs = callbackInvocations.get(0) - startTime;
-        assertTrue("First callback not during long press timeout was " +
-                        actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI +
-                        " while long press timeout is " + minTimeUntilFirstInvocationMs,
-                (callbackInvocations.get(0) - startTime) >
-                        minTimeUntilFirstInvocationMs * NANOS_IN_MILLI);
-
-        // Verify that subsequent callbacks are at least zoom-speed milliseconds apart. Note that
-        // we do not have any hard guarantee about the max limit on the time between successive
-        // callbacks.
-        final long minTimeBetweenInvocationsNs = zoomSpeedMs * NANOS_IN_MILLI;
-        if (callbackInvocations.size() > 1) {
-            for (int i = 0; i < callbackInvocations.size() - 1; i++) {
-                final long actualTimeBetweenInvocationsNs =
-                        (callbackInvocations.get(i + 1) - callbackInvocations.get(i)) *
-                                NANOS_IN_MILLI;
-                assertTrue("Callback " + (i + 1) + " happened " +
-                                actualTimeBetweenInvocationsNs / NANOS_IN_MILLI +
-                                " after the previous one, while zoom speed is " + zoomSpeedMs,
-                        actualTimeBetweenInvocationsNs > minTimeBetweenInvocationsNs);
-            }
-        }
-    }
-
-    @LargeTest
-    @Test
-    public void testOnLongClick() {
-        // Since Mockito doesn't have utilities to track the timestamps of method invocations,
-        // we're using our own custom click listener for that. We want to verify that the
-        // first listener invocation was after long press timeout, and the rest were spaced
-        // by at least our zoom speed milliseconds
-
-        mZoomButton.setEnabled(true);
-        ZoomClickListener zoomClickListener = new ZoomClickListener();
-        mZoomButton.setOnClickListener(zoomClickListener);
-
-        verifyZoomSpeed(zoomClickListener, 2000);
-    }
-
     @LargeTest
     @Test
     public void testSetZoomSpeed() {
-        final long[] zoomSpeeds = { 100, -1, 5000, 1000, 2500 };
+        final long[] zoomSpeeds = { 0, 100 };
         mZoomButton.setEnabled(true);
         ZoomClickListener zoomClickListener = new ZoomClickListener();
         mZoomButton.setOnClickListener(zoomClickListener);
 
         for (long zoomSpeed : zoomSpeeds) {
-            // Reset the tracker list of our listener, but continue using it for testing
+            // Reset the tracking state of our listener, but continue using it for testing
             // various zoom speeds on the same ZoomButton
             zoomClickListener.reset();
-            verifyZoomSpeed(zoomClickListener, zoomSpeed);
+
+            mZoomButton.setZoomSpeed(zoomSpeed);
+
+            final long startTime = System.nanoTime();
+            // Emulate long click
+            long longPressWait = ViewConfiguration.getLongPressTimeout()
+                    + zoomSpeed + 100;
+            CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mZoomButton,
+                    longPressWait);
+
+            final Long callbackFirstInvocationTime = zoomClickListener.getTimeOfFirstClick();
+            assertNotNull("Expecting at least one callback", callbackFirstInvocationTime);
+
+            // Verify that the first callback is fired after the system-level long press timeout.
+            final long minTimeUntilFirstInvocationMs = ViewConfiguration.getLongPressTimeout();
+            final long actualTimeUntilFirstInvocationNs = callbackFirstInvocationTime - startTime;
+            assertTrue("First callback not during long press timeout was "
+                            + actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI
+                            + " while long press timeout is " + minTimeUntilFirstInvocationMs,
+                    (callbackFirstInvocationTime - startTime)
+                            > minTimeUntilFirstInvocationMs * NANOS_IN_MILLI);
+            assertTrue("First callback should have happened sooner than "
+                            + actualTimeUntilFirstInvocationNs / NANOS_IN_MILLI,
+                    (callbackFirstInvocationTime - startTime)
+                            <= (minTimeUntilFirstInvocationMs + 100) * NANOS_IN_MILLI);
         }
     }
 
     private static class ZoomClickListener implements View.OnClickListener {
-        private List<Long> mClickTimes = new ArrayList<>();
+        private Long mTimeOfFirstClick = null;
 
         public void reset() {
-            mClickTimes.clear();
+            mTimeOfFirstClick = null;
         }
 
-        public List<Long> getClickTimes() {
-            return Collections.unmodifiableList(mClickTimes);
+        public Long getTimeOfFirstClick() {
+            return mTimeOfFirstClick;
         }
 
         public void onClick(View v) {
-            // Add the current system time to the tracker list
-            mClickTimes.add(System.nanoTime());
+            if (mTimeOfFirstClick == null) {
+                // Mark the current system time as the time of first click
+                mTimeOfFirstClick = System.nanoTime();
+            }
         }
     }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetProvider.java b/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetProvider.java
deleted file mode 100644
index a0711e2..0000000
--- a/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetProvider.java
+++ /dev/null
@@ -1,178 +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.widget.cts.appwidget;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.cts.R;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.util.concurrent.CountDownLatch;
-
-public final class MyAppWidgetProvider extends AppWidgetProvider {
-    private static final long TIME_SLICE = 100;
-
-    public static final String KEY_DISPLAYED_CHILD_INDEX =
-            "MyAppWidgetProvider.displayedChildIndex";
-    public static final String KEY_SHOW_NEXT = "MyAppWidgetProvider.showNext";
-    public static final String KEY_SHOW_PREVIOUS = "MyAppWidgetProvider.showPrevious";
-    public static final String KEY_SWITCH_TO_LIST = "MyAppWidgetProvider.switchToList";
-    public static final String KEY_SCROLL_POSITION = "MyAppWidgetProvider.scrollPosition";
-    public static final String KEY_SCROLL_OFFSET = "MyAppWidgetProvider.scrollOffset";
-
-    // This latch will be notified when onEnabled is called on our provider.
-    private static CountDownLatch sCountDownLatch;
-    // Gating condition to be polled to proceed with setScrollPosition call.
-    private static PollingCheck.PollingCheckCondition sSetScrollCondition;
-    // Gating condition to be polled to proceed with setRelativeScrollPosition call.
-    private static PollingCheck.PollingCheckCondition sSetRelativeScrollCondition;
-
-    private int mDisplayedChildIndex;
-    private boolean mShowNext;
-    private boolean mShowPrevious;
-    private boolean mSwitchToList;
-    private int mScrollPosition;
-    private int mScrollOffset;
-
-    public static void configure(CountDownLatch countDownLatch,
-            PollingCheck.PollingCheckCondition setScrollCondition,
-            PollingCheck.PollingCheckCondition setRelativeScrollCondition) {
-        sCountDownLatch = countDownLatch;
-        sSetScrollCondition = setScrollCondition;
-        sSetRelativeScrollCondition = setRelativeScrollCondition;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        mDisplayedChildIndex = intent.getIntExtra(KEY_DISPLAYED_CHILD_INDEX, -1);
-        mShowNext = intent.getBooleanExtra(KEY_SHOW_NEXT, false);
-        mShowPrevious = intent.getBooleanExtra(KEY_SHOW_PREVIOUS, false);
-        mSwitchToList = intent.getBooleanExtra(KEY_SWITCH_TO_LIST, false);
-        mScrollPosition = intent.getIntExtra(KEY_SCROLL_POSITION, -1);
-        mScrollOffset = intent.getIntExtra(KEY_SCROLL_OFFSET, 0);
-
-        super.onReceive(context, intent);
-    }
-
-    @Override
-    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
-        final int appWidgetId = appWidgetIds[0];
-        final RemoteViews widgetAdapterView = new RemoteViews(context.getPackageName(),
-                R.layout.remoteviews_adapter);
-
-        final Intent stackIntent = new Intent(context, MyAppWidgetService.class);
-        stackIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
-        stackIntent.setData(Uri.parse(stackIntent.toUri(Intent.URI_INTENT_SCHEME)));
-
-        widgetAdapterView.setRemoteAdapter(R.id.remoteViews_stack, stackIntent);
-        widgetAdapterView.setEmptyView(R.id.remoteViews_stack, R.id.remoteViews_empty);
-
-        if (mDisplayedChildIndex >= 0) {
-            widgetAdapterView.setDisplayedChild(R.id.remoteViews_stack, mDisplayedChildIndex);
-        }
-        if (mShowNext) {
-            widgetAdapterView.showNext(R.id.remoteViews_stack);
-        }
-        if (mShowPrevious) {
-            widgetAdapterView.showPrevious(R.id.remoteViews_stack);
-        }
-
-        // Here we setup the a pending intent template. Individuals items of a collection
-        // cannot setup their own pending intents, instead, the collection as a whole can
-        // setup a pending intent template, and the individual items can set a fillInIntent
-        // to create unique before on an item to item basis.
-        Intent viewIntent = new Intent(Intent.ACTION_VIEW,
-                Uri.parse("ctstest://RemoteView/testWidget"));
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-
-        widgetAdapterView.setPendingIntentTemplate(R.id.remoteViews_stack, pendingIntent);
-
-        if (mSwitchToList) {
-            final Intent listIntent = new Intent(context, MyAppWidgetService.class);
-            listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
-            listIntent.setData(Uri.parse(stackIntent.toUri(Intent.URI_INTENT_SCHEME)));
-
-            widgetAdapterView.setRemoteAdapter(R.id.remoteViews_list, listIntent);
-
-            widgetAdapterView.setViewVisibility(R.id.remoteViews_stack, View.GONE);
-            widgetAdapterView.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
-        }
-
-        final Handler handler = new Handler(Looper.myLooper());
-        if (mScrollPosition >= 0) {
-            // We need to schedule the call to setScrollPosition as a separate event that runs
-            // after the underlying ListView has been laid out on the screen. Otherwise calling
-            // that API on a ListView with 0x0 dimension has no effect - the list content is only
-            // populated via the adapter when ListView has "real" bounds.
-            final Runnable setScrollRunnable = new Runnable() {
-                public void run() {
-                    if (sSetScrollCondition.canProceed()) {
-                        // Gating condition has been satisfied. Call setScrollPosition and
-                        // ask the widget manager to update our widget
-                        widgetAdapterView.setScrollPosition(R.id.remoteViews_list, mScrollPosition);
-                        appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widgetAdapterView);
-                    } else {
-                        // Keep on "waiting" until the gating condition is satisfied
-                        handler.postDelayed(this, TIME_SLICE);
-                    }
-                }
-            };
-            handler.postDelayed(setScrollRunnable, TIME_SLICE);
-        }
-
-        if (mScrollOffset != 0) {
-            // We need to schedule the call to setRelativeScrollPosition as a separate event that
-            // runs after the underlying ListView has been laid out on the screen. Otherwise calling
-            // that API on a ListView with 0x0 dimension has no effect - the list content is only
-            // populated via the adapter when ListView has "real" bounds.
-            final Runnable setRelativeScrollRunnable = new Runnable() {
-                public void run() {
-                    if (sSetRelativeScrollCondition.canProceed()) {
-                        // Gating condition has been satisfied. Call setRelativeScrollPosition and
-                        // ask the widget manager to update our widget
-                        widgetAdapterView.setRelativeScrollPosition(
-                                R.id.remoteViews_list, mScrollOffset);
-                        appWidgetManager.partiallyUpdateAppWidget(appWidgetId, widgetAdapterView);
-                    } else {
-                        // Keep on "waiting" until the gating condition is satisfied
-                        handler.postDelayed(this, TIME_SLICE);
-                    }
-                }
-            };
-            handler.postDelayed(setRelativeScrollRunnable, TIME_SLICE);
-        }
-
-        appWidgetManager.updateAppWidget(appWidgetId, widgetAdapterView);
-
-        sCountDownLatch.countDown();
-    }
-
-    @Override
-    public void onEnabled(Context context) {
-        sCountDownLatch.countDown();
-    }
-}
diff --git a/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetService.java b/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetService.java
deleted file mode 100644
index 7fc6b49..0000000
--- a/tests/tests/widget/src/android/widget/cts/appwidget/MyAppWidgetService.java
+++ /dev/null
@@ -1,39 +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.widget.cts.appwidget;
-
-import android.content.Intent;
-import android.widget.RemoteViewsService;
-
-public class MyAppWidgetService extends RemoteViewsService {
-    private static final Object sLock = new Object();
-
-    private static RemoteViewsFactory sFactory;
-
-    public static void setFactory(RemoteViewsFactory factory) {
-        synchronized (sLock) {
-            sFactory = factory;
-        }
-    }
-
-    @Override
-    public RemoteViewsFactory onGetViewFactory(Intent intent) {
-        synchronized (sLock) {
-            return sFactory;
-        }
-    }
-}
diff --git a/tests/ui/AndroidTest.xml b/tests/ui/AndroidTest.xml
index 3f52b4a..9e51fba 100644
--- a/tests/ui/AndroidTest.xml
+++ b/tests/ui/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS UI test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <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/vr/Android.mk b/tests/vr/Android.mk
index dfd5655..0c842cf 100644
--- a/tests/vr/Android.mk
+++ b/tests/vr/Android.mk
@@ -36,6 +36,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src) ../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
 
 LOCAL_SDK_VERSION := test_current
+LOCAL_MIN_SDK_VERSION := 14
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/vr/AndroidTest.xml b/tests/vr/AndroidTest.xml
index 02b2918..0212be6 100644
--- a/tests/vr/AndroidTest.xml
+++ b/tests/vr/AndroidTest.xml
@@ -23,5 +23,6 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.vr.cts" />
         <option name="runtime-hint" value="13m" />
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/vr/jni/VrExtensionsJni.cpp b/tests/vr/jni/VrExtensionsJni.cpp
index 4495621..fadc5e9 100644
--- a/tests/vr/jni/VrExtensionsJni.cpp
+++ b/tests/vr/jni/VrExtensionsJni.cpp
@@ -106,9 +106,13 @@
     vasprintf(&msg, format, args);
     va_end(args);
     jclass exClass;
-    const char* className = "java/lang/AssertionError";
-    exClass = env->FindClass(className);
-    env->ThrowNew(exClass, msg);
+    exClass = env->FindClass("java/lang/AssertionError");
+    jmethodID constructor =
+        env->GetMethodID(exClass, "<init>",
+                         "(Ljava/lang/String;Ljava/lang/Throwable;)V");
+    jstring msgStr = env->NewStringUTF(msg);
+    jobject exception = env->NewObject(exClass, constructor, msgStr, nullptr);
+    env->Throw(static_cast<jthrowable>(exception));
     free(msg);
 }
 
diff --git a/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
index 9fd01b1..b73a5d3 100644
--- a/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
+++ b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
@@ -39,6 +39,10 @@
     private static final int SCHED_RESET_ON_FORK = 0x40000000;
     public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
     private static final String TAG = "VrSetFIFOThreadTest";
+    // After setVrModeEnabled call, wait some time for change to take effect.
+    // Arbitrary timeout is set as there is no way to query the result from app
+    // see b/119819897
+    private static final int SLEEP_TIME_MS = 3000;
 
     public VrSetFIFOThreadTest() {
         super(OpenGLESActivity.class);
@@ -81,6 +85,7 @@
                 PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
             int vr_thread = 0, policy = 0;
             mActivity.setVrModeEnabled(true, requestedComponent);
+            Thread.sleep(SLEEP_TIME_MS);
             vr_thread = Process.myTid();
             mActivityManager =
                   (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -103,6 +108,7 @@
                 PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
             int vr_thread = 0, policy = 0;
             mActivity.setVrModeEnabled(false, requestedComponent);
+            Thread.sleep(SLEEP_TIME_MS);
             vr_thread = Process.myTid();
             mActivityManager =
                   (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
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 20c5c27..160c9b5 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
@@ -487,6 +487,7 @@
         charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1.getName());
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
index 2066d14..ca9ab63 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
@@ -79,6 +79,7 @@
     private static final String KEY_BUFFER_FEATURES = "bufferFeatures";
     private static final String KEY_BUFFER_IMAGE_GRANULARITY = "bufferImageGranularity";
     private static final String KEY_COMPATIBLE_HANDLE_TYPES = "compatibleHandleTypes";
+    private static final String KEY_CONFORMANCE_VERSION = "conformanceVersion";
     private static final String KEY_DEPTH = "depth";
     private static final String KEY_DEPTH_BIAS_CLAMP = "depthBiasClamp";
     private static final String KEY_DEPTH_BOUNDS = "depthBounds";
@@ -95,8 +96,12 @@
     private static final String KEY_DEVICES = "devices";
     private static final String KEY_DISCRETE_QUEUE_PRIORITIES = "discreteQueuePriorities";
     private static final String KEY_DRAW_INDIRECT_FIRST_INSTANCE = "drawIndirectFirstInstance";
-    private static final String KEY_DRIVER_VERSION = "driverVersion";
+    private static final String KEY_DRIVER_ID = "driverID";
+    private static final String KEY_DRIVER_INFO = "driverInfo";
+    private static final String KEY_DRIVER_NAME = "driverName";
+    private static final String KEY_DRIVER_PROPERTIES_KHR = "driverPropertiesKHR";
     private static final String KEY_DRIVER_UUID = "driverUUID";
+    private static final String KEY_DRIVER_VERSION = "driverVersion";
     private static final String KEY_DUAL_SRC_BLEND = "dualSrcBlend";
     private static final String KEY_EXPORT_FROM_IMPORTED_HANDLE_TYPES = "exportFromImportedHandleTypes";
     private static final String KEY_EXTENSION_NAME = "extensionName";
@@ -132,6 +137,7 @@
     private static final String KEY_LINEAR_TILING_FEATURES = "linearTilingFeatures";
     private static final String KEY_LOGIC_OP = "logicOp";
     private static final String KEY_MAINTENANCE_3_PROPERTIES = "maintenance3Properties";
+    private static final String KEY_MAJOR = "major";
     private static final String KEY_MAX_BOUND_DESCRIPTOR_SETS = "maxBoundDescriptorSets";
     private static final String KEY_MAX_CLIP_DISTANCES = "maxClipDistances";
     private static final String KEY_MAX_COLOR_ATTACHMENTS = "maxColorAttachments";
@@ -219,6 +225,7 @@
     private static final String KEY_MIN_TEXEL_GATHER_OFFSET = "minTexelGatherOffset";
     private static final String KEY_MIN_TEXEL_OFFSET = "minTexelOffset";
     private static final String KEY_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT = "minUniformBufferOffsetAlignment";
+    private static final String KEY_MINOR = "minor";
     private static final String KEY_MIPMAP_PRECISION_BITS = "mipmapPrecisionBits";
     private static final String KEY_MULTI_DRAW_INDIRECT = "multiDrawIndirect";
     private static final String KEY_MULTI_VIEWPORT = "multiViewport";
@@ -232,6 +239,7 @@
     private static final String KEY_OPTIMAL_BUFFER_COPY_OFFSET_ALIGNMENT = "optimalBufferCopyOffsetAlignment";
     private static final String KEY_OPTIMAL_BUFFER_COPY_ROW_PITCH_ALIGNMENT = "optimalBufferCopyRowPitchAlignment";
     private static final String KEY_OPTIMAL_TILING_FEATURES = "optimalTilingFeatures";
+    private static final String KEY_PATCH = "patch";
     private static final String KEY_PIPELINE_CACHE_UUID = "pipelineCacheUUID";
     private static final String KEY_PIPELINE_STATISTICS_QUERY = "pipelineStatisticsQuery";
     private static final String KEY_POINT_CLIPPING_BEHAVIOR = "pointClippingBehavior";
@@ -303,6 +311,7 @@
     private static final String KEY_SUB_TEXEL_PRECISION_BITS = "subTexelPrecisionBits";
     private static final String KEY_SUBGROUP_PROPERTIES = "subgroupProperties";
     private static final String KEY_SUBGROUP_SIZE = "subgroupSize";
+    private static final String KEY_SUBMINOR = "subminor";
     private static final String KEY_SUBSET_ALLOCATION = "subsetAllocation";
     private static final String KEY_SUPPORTED_OPERATIONS = "supportedOperations";
     private static final String KEY_SUPPORTED_STAGES = "supportedStages";
@@ -323,18 +332,21 @@
     private static final String KEY_VERTEX_PIPELINE_STORES_AND_ATOMICS = "vertexPipelineStoresAndAtomics";
     private static final String KEY_VIEWPORT_BOUNDS_RANGE = "viewportBoundsRange";
     private static final String KEY_VIEWPORT_SUB_PIXEL_BITS = "viewportSubPixelBits";
+    private static final String KEY_VK_KHR_DRIVER_PROPERTIES = "VK_KHR_driver_properties";
     private static final String KEY_VK_KHR_VARIABLE_POINTERS = "VK_KHR_variable_pointers";
     private static final String KEY_WIDE_LINES = "wideLines";
     private static final String KEY_WIDTH = "width";
 
     private static final int VK_API_VERSION_1_1 = 4198400;
     private static final int ENUM_VK_KHR_VARIABLE_POINTERS = 0;
+    private static final int ENUM_VK_KHR_DRIVER_PROPERTIES = 1;
 
     private static HashMap<String, Integer> extensionNameToEnum;
 
     static {
         System.loadLibrary("ctsdeviceinfo");
         extensionNameToEnum = new HashMap<>();
+        extensionNameToEnum.put(KEY_VK_KHR_DRIVER_PROPERTIES, ENUM_VK_KHR_DRIVER_PROPERTIES);
         extensionNameToEnum.put(KEY_VK_KHR_VARIABLE_POINTERS, ENUM_VK_KHR_VARIABLE_POINTERS);
     }
 
@@ -811,6 +823,41 @@
         store.endArray();
     }
 
+    private static void emitDriverPropertiesKHR(DeviceInfoStore store, JSONObject parent)
+            throws Exception {
+        try {
+            JSONObject extDriverProperties = parent.getJSONObject(KEY_VK_KHR_DRIVER_PROPERTIES);
+            try {
+                store.startGroup(convertName(KEY_VK_KHR_DRIVER_PROPERTIES));
+                {
+                    JSONObject driverPropertiesKHR = extDriverProperties.getJSONObject(KEY_DRIVER_PROPERTIES_KHR);
+                    store.startGroup(convertName(KEY_DRIVER_PROPERTIES_KHR));
+                    {
+                        emitLong(store, driverPropertiesKHR, KEY_DRIVER_ID);
+                        emitString(store, driverPropertiesKHR, KEY_DRIVER_NAME);
+                        emitString(store, driverPropertiesKHR, KEY_DRIVER_INFO);
+
+                        JSONObject conformanceVersion = driverPropertiesKHR.getJSONObject(KEY_CONFORMANCE_VERSION);
+                        store.startGroup(convertName(KEY_CONFORMANCE_VERSION));
+                        {
+                            emitLong(store, conformanceVersion, KEY_MAJOR);
+                            emitLong(store, conformanceVersion, KEY_MINOR);
+                            emitLong(store, conformanceVersion, KEY_SUBMINOR);
+                            emitLong(store, conformanceVersion, KEY_PATCH);
+                        }
+                        store.endGroup();
+                    }
+                    store.endGroup();
+                }
+                store.endGroup();
+            } catch (JSONException e) {
+                e.printStackTrace();
+                throw new RuntimeException(e);
+            }
+        } catch (JSONException ignored) {
+        }
+    }
+
     private static void emitVariablePointerFeaturesKHR(DeviceInfoStore store, JSONObject parent)
             throws Exception {
         try {
@@ -842,6 +889,9 @@
             case ENUM_VK_KHR_VARIABLE_POINTERS:
               emitVariablePointerFeaturesKHR(store, parent);
               break;
+            case ENUM_VK_KHR_DRIVER_PROPERTIES:
+              emitDriverPropertiesKHR(store, parent);
+              break;
         }
     }
 
@@ -917,6 +967,7 @@
             case KEY_BUFFER_FEATURES: return "buffer_features";
             case KEY_BUFFER_IMAGE_GRANULARITY: return "buffer_image_granularity";
             case KEY_COMPATIBLE_HANDLE_TYPES: return "compatible_handle_types";
+            case KEY_CONFORMANCE_VERSION: return "conformance_version";
             case KEY_DEPTH: return "depth";
             case KEY_DEPTH_BIAS_CLAMP: return "depth_bias_clamp";
             case KEY_DEPTH_BOUNDS: return "depth_bounds";
@@ -933,8 +984,12 @@
             case KEY_DEVICES: return "devices";
             case KEY_DISCRETE_QUEUE_PRIORITIES: return "discrete_queue_priorities";
             case KEY_DRAW_INDIRECT_FIRST_INSTANCE: return "draw_indirect_first_instance";
-            case KEY_DRIVER_VERSION: return "driver_version";
+            case KEY_DRIVER_ID: return "driver_id";
+            case KEY_DRIVER_INFO: return "driver_info";
+            case KEY_DRIVER_NAME: return "driver_name";
+            case KEY_DRIVER_PROPERTIES_KHR: return "driver_properties_khr";
             case KEY_DRIVER_UUID: return "driver_uuid";
+            case KEY_DRIVER_VERSION: return "driver_version";
             case KEY_DUAL_SRC_BLEND: return "dual_src_blend";
             case KEY_EXPORT_FROM_IMPORTED_HANDLE_TYPES: return "export_from_imported_handle_types";
             case KEY_EXTENSION_NAME: return "extension_name";
@@ -970,6 +1025,7 @@
             case KEY_LINEAR_TILING_FEATURES: return "linear_tiling_features";
             case KEY_LOGIC_OP: return "logic_op";
             case KEY_MAINTENANCE_3_PROPERTIES: return "maintenance_3_properties";
+            case KEY_MAJOR: return "major";
             case KEY_MAX_BOUND_DESCRIPTOR_SETS: return "max_bound_descriptor_sets";
             case KEY_MAX_CLIP_DISTANCES: return "max_clip_distances";
             case KEY_MAX_COLOR_ATTACHMENTS: return "max_color_attachments";
@@ -1057,6 +1113,7 @@
             case KEY_MIN_TEXEL_GATHER_OFFSET: return "min_texel_gather_offset";
             case KEY_MIN_TEXEL_OFFSET: return "min_texel_offset";
             case KEY_MIN_UNIFORM_BUFFER_OFFSET_ALIGNMENT: return "min_uniform_buffer_offset_alignment";
+            case KEY_MINOR: return "minor";
             case KEY_MIPMAP_PRECISION_BITS: return "mipmap_precision_bits";
             case KEY_MULTI_DRAW_INDIRECT: return "multi_draw_indirect";
             case KEY_MULTI_VIEWPORT: return "multi_viewport";
@@ -1070,6 +1127,7 @@
             case KEY_OPTIMAL_BUFFER_COPY_OFFSET_ALIGNMENT: return "optimal_buffer_copy_offset_alignment";
             case KEY_OPTIMAL_BUFFER_COPY_ROW_PITCH_ALIGNMENT: return "optimal_buffer_copy_row_pitch_alignment";
             case KEY_OPTIMAL_TILING_FEATURES: return "optimal_tiling_features";
+            case KEY_PATCH: return "patch";
             case KEY_PIPELINE_CACHE_UUID: return "pipeline_cache_uuid";
             case KEY_PIPELINE_STATISTICS_QUERY: return "pipeline_statistics_query";
             case KEY_POINT_CLIPPING_BEHAVIOR: return "point_clipping_behavior";
@@ -1141,6 +1199,7 @@
             case KEY_SUB_TEXEL_PRECISION_BITS: return "sub_texel_precision_bits";
             case KEY_SUBGROUP_PROPERTIES: return "subgroup_properties";
             case KEY_SUBGROUP_SIZE: return "subgroup_size";
+            case KEY_SUBMINOR: return "subminor";
             case KEY_SUBSET_ALLOCATION: return "subset_allocation";
             case KEY_SUPPORTED_OPERATIONS: return "supported_operations";
             case KEY_SUPPORTED_STAGES: return "supported_stages";
@@ -1161,6 +1220,7 @@
             case KEY_VERTEX_PIPELINE_STORES_AND_ATOMICS: return "vertex_pipeline_stores_and_atomics";
             case KEY_VIEWPORT_BOUNDS_RANGE: return "viewport_bounds_range";
             case KEY_VIEWPORT_SUB_PIXEL_BITS: return "viewport_sub_pixel_bits";
+            case KEY_VK_KHR_DRIVER_PROPERTIES: return "vk_khr_driver_properties";
             case KEY_VK_KHR_VARIABLE_POINTERS: return "vk_khr_variable_pointers";
             case KEY_WIDE_LINES: return "wide_lines";
             case KEY_WIDTH: return "width";
diff --git a/tools/device-setup/TestDeviceSetup/Android.mk b/tools/device-setup/TestDeviceSetup/Android.mk
index 1820347..d79293f 100644
--- a/tools/device-setup/TestDeviceSetup/Android.mk
+++ b/tools/device-setup/TestDeviceSetup/Android.mk
@@ -25,6 +25,8 @@
 LOCAL_DEX_PREOPT := false
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
+
 # uncomment when b/13282254 is fixed
 #LOCAL_SDK_VERSION := current
 LOCAL_SDK_VERSION := system_current
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index b752c91..8de116a 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -37,6 +37,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -121,13 +123,14 @@
         // network
         String network = tm.getNetworkOperatorName();
         addResult(NETWORK, network.trim());
-
         // imei
-        String imei = tm.getDeviceId();
+        String imei = ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+                (telephonyManager) -> telephonyManager.getDeviceId());
         addResult(IMEI, imei);
 
         // imsi
-        String imsi = tm.getSubscriberId();
+        String imsi = ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+                (telephonyManager) -> telephonyManager.getSubscriberId());
         addResult(IMSI, imsi);
 
         // phone number
diff --git a/tools/release-parser/Android.bp b/tools/release-parser/Android.bp
new file mode 100644
index 0000000..99b1e29
--- /dev/null
+++ b/tools/release-parser/Android.bp
@@ -0,0 +1,44 @@
+// 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.
+
+// cts release-parser java library
+// ============================================================
+java_library_host {
+    name: "release-parser",
+
+    srcs: [
+        "**/*.java",
+        "proto/**/*.proto",
+    ],
+
+    proto: {
+        type: "full",
+    },
+    // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_PROTOC_FLAGS
+    // LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
+
+    manifest: "MANIFEST.mf",
+
+    // This tool is not checking any dependencies or metadata, so all of the
+    // dependencies of all of the tests must be on its classpath. This is
+    // super fragile.
+    static_libs: [
+        "compatibility-host-util",
+        "hosttestlib",
+        "dexlib2",
+        "jsonlib",
+        "tradefed",
+    ],
+
+}
diff --git a/tools/release-parser/MANIFEST.mf b/tools/release-parser/MANIFEST.mf
new file mode 100644
index 0000000..8acac32
--- /dev/null
+++ b/tools/release-parser/MANIFEST.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.releaseparser.Main
+Implementation-Version: %BUILD_NUMBER%
\ No newline at end of file
diff --git a/tools/release-parser/OWNERS b/tools/release-parser/OWNERS
new file mode 100644
index 0000000..3a7f94a
--- /dev/null
+++ b/tools/release-parser/OWNERS
@@ -0,0 +1 @@
+samlin@google.com
diff --git a/tools/release-parser/proto/release.proto b/tools/release-parser/proto/release.proto
new file mode 100644
index 0000000..145ccd9
--- /dev/null
+++ b/tools/release-parser/proto/release.proto
@@ -0,0 +1,408 @@
+// 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.
+
+// [START declaration]
+syntax = "proto3";
+package com_android_cts_releaseparser;
+// [END declaration]
+
+// [START java_declaration]
+option java_package = "com.android.cts.releaseparser";
+option java_outer_classname = "ReleaseProto";
+// [END java_declaration]
+
+// [START messages]
+message Option {
+    string name = 1;
+    string key = 2;
+    string value =3;
+}
+
+message TestModuleConfig {
+    string module_name = 1;
+    string component = 2;
+    string description = 3;
+    repeated Option options = 4;
+
+    message TargetPreparer {
+        string test_class = 1;
+        repeated Option options = 2;
+    }
+    repeated TargetPreparer target_preparers = 5;
+    repeated string test_file_names = 6;
+
+    message TestClass {
+        string test_class = 1;
+        string package = 2;
+        repeated Option options = 3;
+    }
+    repeated TestClass test_classes = 7;
+    repeated string test_jars = 8;
+}
+
+message Element {
+    string name = 1;
+    string value = 2;
+}
+
+message ApiAnnotation {
+    int32 visibility = 1;
+    string type = 2;
+
+    repeated Element elements = 3;
+}
+
+message ApiField {
+    string defining_class = 1;
+    string name = 2;
+    string type = 3;
+    int32 access_flags = 4;
+    string initial_value = 5;
+    repeated ApiAnnotation annotations = 6;
+    string value = 7;
+}
+
+message ApiMethod {
+    string defining_class = 1;
+    string name = 2;
+    repeated string parameters = 3;
+    string return_type = 4;
+    int32 access_flags = 5;
+    string known_failure_filter = 6;
+    repeated ApiAnnotation annotations = 7;
+}
+
+message ApiClass {
+    string name = 1;
+    string type = 2;
+    string super_class = 3;
+    repeated string interfaces = 4;
+    TestClassType test_class_type = 5;
+    int32 access_flags = 6;
+    repeated ApiAnnotation annotations = 7;
+    repeated ApiMethod methods = 8;
+    repeated ApiField fields = 9;
+}
+
+message ApiPackage {
+    string name = 1;
+    string package_file = 2;
+    string content_id = 3;
+    string op_codes = 4;
+
+    repeated ApiClass classes = 5;
+    string error = 6;
+}
+
+enum TestClassType {
+    UNKNOWN = 0;
+    JUNIT3 = 1;
+    JUNIT4 = 2;
+    PARAMETERIZED = 3;
+    JAVAHOST = 4;
+}
+
+message TestSuite {
+    string name = 1;
+    // Version
+    string version = 2;
+    // Build Number
+    string build_number = 3;
+    // Content ID
+    string content_id = 4;
+
+    enum TestType {
+        UNKNOWN = 0;
+        ANDROIDJUNIT = 1;
+        JAVAHOST = 2;
+        GTEST = 3;
+        LIBCORE = 4;
+        DALVIK = 5;
+        DEQP = 6;
+    }
+
+    message Module {
+        string name = 1;
+        string config_file = 2;
+        TestType test_type = 3;
+        string test_class = 4;
+        repeated ApiPackage packages = 5;
+    }
+    repeated Module modules = 5;
+}
+
+message Service {
+    string name = 1;
+    string file = 2;
+    repeated string arguments = 3;
+    string clazz = 4;
+    string user = 5;
+    string group = 6;
+    string writepid = 7;
+    repeated string options = 8;
+}
+
+message Api {
+    string name = 1;
+    string version = 2;
+    // map of Package Name & Package Message
+    map<string, ApiPackage> packages = 3;
+}
+
+message UsesFeature {
+    string name = 1;
+    string required = 2;
+}
+
+message UsesLibrary {
+    string name = 1;
+    string required = 2;
+}
+
+message AppInfo {
+    string package_name = 1;
+    string version_code = 2;
+    string version_name = 3;
+    string sdk_version = 4;
+    string target_sdk_version = 5;
+    repeated UsesFeature uses_features = 6;
+    repeated UsesLibrary uses_libraries = 7;
+    repeated string native_code = 8;
+    repeated string uses_permissions = 9;
+    repeated string activities = 10;
+    repeated string services = 11;
+    repeated string providers = 12;
+    map<string, string> properties = 13;
+    repeated ApiPackage external_api_packages = 14;
+    repeated ApiPackage internal_api_packages = 15;
+    PackageFileContent package_file_content = 16;
+    string package_signature = 17;
+}
+
+message ImageSection {
+    uint32 offset = 1;
+    uint32 size = 2;
+}
+
+// art/runtime/image.h
+message ArtInfo {
+    bool valid = 1;
+    // skip magic_[4]
+    string version = 2;
+    uint32 image_begin = 3;
+    uint32 image_size = 4;
+    uint32 oat_checksum = 5;
+    uint32 oat_file_begin = 6;
+    uint32 oat_data_begin = 7;
+    uint32 oat_data_end = 8;
+    uint32 oat_file_end = 9;
+
+    // app image headers only
+    uint32 boot_image_begin = 10;
+    uint32 boot_image_size = 11;
+    uint32 boot_oat_begin = 12;
+    uint32 boot_oat_size = 13;
+
+    int32 patch_delta = 14;
+    uint32 image_roots = 15;
+    uint32 pointer_size = 16;
+    uint32 compile_pic = 17;
+    uint32 is_pic = 18;
+
+    repeated ImageSection image_sections = 19;
+    repeated uint64 image_methods = 20;
+
+    uint32 storage_mode = 21;
+    uint32 data_size = 22;
+}
+
+// art/dex2oat/linker/oat_writer.cc OatDexFile
+message OatDexInfo {
+    string dex_file_location_data = 1;
+    // The checksum of the dex file.
+    uint32 dex_file_location_checksum = 2;
+    // Offset of the dex file in the vdex file.
+    uint32 dex_file_offset = 3;
+    // The lookup table offset in the oat file. Set in WriteTypeLookupTables.
+    uint32 lookup_table_offset = 4;
+    // Class and BSS offsets set in PrepareLayout.
+    uint32 class_offsets_offset = 5;
+    uint32 method_bss_mapping_offset = 6;
+    uint32 type_bss_mapping_offset = 7;
+    uint32 string_bss_mapping_offset = 8;
+    // Offset of dex sections that will have different runtime madvise states.
+    uint32 dex_sections_layout_offset = 9;
+}
+
+// art/runtime/oat.h
+message OatInfo {
+    // skip magic_[4]
+    string version = 1;
+    uint32 adler32_checksum = 2;
+    // art/libartbase/arch/instruction_set.h
+
+    uint32 instruction_set = 3;
+    uint32 instruction_set_features_bitmap = 4;
+    uint32 dex_file_count = 5;
+    uint32 oat_dex_files_offset = 6;
+    uint32 executable_offset = 7;
+    uint32 interpreter_to_interpreter_bridge_offset = 8;
+    uint32 interpreter_to_compiled_code_bridge_offset = 9;
+    uint32 jni_dlsym_lookup_offset = 10;
+    uint32 quick_generic_jni_trampoline_offset = 11;
+    uint32 quick_imt_conflict_trampoline_offset = 12;
+    uint32 quick_resolution_trampoline_offset = 13;
+    uint32 quick_to_interpreter_bridge_offset = 14;
+
+    // for backward compatibility, removed from version 162, see aosp/e0669326c0282b5b645aba75160425eef9d57617
+    uint32 image_patch_delta = 15;
+
+    uint32 image_file_location_oat_checksum = 16;
+    // for backward compatibility, removed from version 162, see aosp/e0669326c0282b5b645aba75160425eef9d57617
+    uint32 image_file_location_oat_data_begin = 17;
+
+    uint32 key_value_store_size = 18;
+
+    // int8_t key_value_store_[0];
+    map<string, string> key_value_store = 19;
+    repeated OatDexInfo oat_dex_info = 20;
+    bool valid = 21;
+    int32 bits = 22;
+    string architecture = 23;
+}
+
+message DexSectionHeader {
+    uint32 dex_size = 1;
+    uint32 dex_shared_data_size = 2;
+    uint32 quickening_info_size = 3;
+}
+
+// art/runtime/vdex_file.h
+message VdexInfo {
+    bool valid = 1;
+
+    // skip magic_[4]
+    string verifier_deps_version = 2;
+    string dex_section_version = 3;
+    uint32 number_of_dex_files = 4;
+    uint32 verifier_deps_size = 5;
+    repeated uint32 checksums = 6;
+    repeated DexSectionHeader dex_section_headers = 7;
+}
+
+message Permission {
+    string name = 1;
+    repeated Element elements = 2;
+}
+
+message PermissionList {
+    string name = 1;
+    repeated Permission permissions = 2;
+}
+
+message PackageFileContent {
+    map<string, Entry> entries = 1;
+}
+
+// An entry in a release
+message Entry {
+    // Name
+    string name = 1;
+
+    enum EntryType {
+        FOLDER = 0;
+        FILE = 1;
+        TEST_MODULE_CONFIG = 2;
+        JAR = 3;
+        APK = 4;
+        EXE = 5;
+        SO = 6;
+        OAT = 7;
+        ODEX = 8;
+        VDEX = 9;
+        TEST_SUITE_TRADEFED = 10;
+        BUILD_PROP = 11;
+        SYMBOLIC_LINK = 12;
+        RC = 13;
+        ART = 14;
+        XML = 15;
+        IMG = 16;
+    }
+    // Type
+    EntryType type = 2;
+
+    // Size
+    int64 size = 3;
+    // Content ID
+    string content_id = 4;
+    // code_id
+    string code_id = 5;
+    // Parent folder
+    string abi_architecture = 6;
+    int32 abi_bits = 7;
+    string parent_folder = 8;
+    // Relative path
+    string relative_path = 9;
+
+    repeated string dependencies = 10;
+    repeated string dynamic_loading_dependencies = 11;
+
+    // file type specified info
+    AppInfo app_info = 12;
+    ArtInfo art_info = 13;
+    OatInfo oat_info = 14;
+    VdexInfo vdex_info = 15;
+    // TestModule.config message
+    TestModuleConfig test_module_config = 16;
+    // Native services
+    repeated Service services = 17;
+    // Device permissions
+    map<string, PermissionList> device_permissions = 18;
+    // property map(key,value)
+    map<string, string> properties = 19;
+}
+
+enum ReleaseType {
+    DEVICE_BUILD = 0;
+    TEST_SUITE = 1;
+    APP_DISTRIBUTION_PACKAGE = 2;
+}
+
+message ReleaseContent {
+    // Name
+    string name = 1;
+    // Version
+    string version = 2;
+    // Build Number
+    string build_number = 3;
+    // Full name
+    string fullname = 4;
+    // release uid e.g. Build Fingerprint
+    string release_id = 5;
+    // Content id
+    string content_id = 6;
+    // Size in byte
+    int64 size = 7;
+    ReleaseType release_type = 8;
+    // property map(key,value)
+    map<string, string> properties = 9;
+    // File Entry map(relative_path, entry)
+    map<string, Entry> entries = 10;
+
+    // Test Suite specific
+    string test_suite_tradefed = 11;
+    string target_arch = 12;
+    repeated string known_failures = 13;
+}
+// [END messages]
\ No newline at end of file
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/AndroidManifestParser.java b/tools/release-parser/src/com/android/cts/releaseparser/AndroidManifestParser.java
new file mode 100644
index 0000000..6e3ff0d
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/AndroidManifestParser.java
@@ -0,0 +1,295 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class AndroidManifestParser extends FileParser {
+    private static Pattern pattern_element = Pattern.compile("E: (\\S*) ");
+    private static Pattern pattern_item =
+            Pattern.compile("A: (?:android:)?(\\w*)(?:\\S*)?=(?:\\(\\S*\\s*\\S*\\))?(\\S*)");
+    private AppInfo.Builder mAppInfoBuilder;
+    private String mElementName;
+    private BufferedReader mReader;
+    private Map<String, String> mProperties;
+    private UsesFeature.Builder mUsesFeatureBuilder;
+    private UsesLibrary.Builder mUsesLibraryBuilder;
+
+    public AndroidManifestParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.APK;
+    }
+
+    public AppInfo getAppInfo() {
+        return getAppInfoBuilder().build();
+    }
+
+    public AppInfo.Builder getAppInfoBuilder() {
+        if (mAppInfoBuilder == null) {
+            prase();
+        }
+        return mAppInfoBuilder;
+    }
+
+    private void prase() {
+        mAppInfoBuilder = AppInfo.newBuilder();
+        processManifest();
+    }
+
+    // build cmd list as: "aapt", "d", "xmltree", fileName, "AndroidManifest.xml"
+    private List<String> getAaptCmds(String file) {
+        List<String> cmds = new ArrayList<>();
+        cmds.add("aapt");
+        cmds.add("d");
+        cmds.add("xmltree");
+        cmds.add(file);
+        cmds.add("AndroidManifest.xml");
+        return cmds;
+    }
+
+    private void processManifest() {
+        try {
+            // ToDo prasing as android/frameworks/base/tools/aapt/Resource.cpp parsePackage()
+            Process process = new ProcessBuilder(getAaptCmds(mFile.getAbsolutePath())).start();
+            mReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            parseElement();
+        } catch (Exception ex) {
+            System.err.println("Failed to aapt d badging " + mFile.getAbsolutePath());
+            ex.printStackTrace();
+        }
+    }
+
+    private void parseElement() {
+        String eleName;
+        Matcher matcher;
+        String line;
+        mProperties = new HashMap<>();
+        int indent;
+
+        try {
+            while ((line = mReader.readLine()) != null) {
+                eleName = getElementName(line);
+                if (eleName != null) {
+                    mElementName = eleName;
+                    parseElementItem();
+                }
+            }
+            mAppInfoBuilder.putAllProperties(mProperties);
+        } catch (Exception ex) {
+            System.err.println(
+                    "Failed to aapt & parse AndroidManifest.xml from: " + mFile.getAbsolutePath());
+            ex.printStackTrace();
+        }
+    }
+
+    private void parseElementItem() throws IOException {
+        String eleName;
+        Matcher matcher;
+        String line;
+        int indent;
+        while ((line = mReader.readLine()) != null) {
+            indent = parseItem(line);
+            if (indent == -1) {
+                eleName = getElementName(line);
+                if (eleName == null) {
+                    break;
+                }
+
+                switch (mElementName) {
+                    case "uses-feature":
+                        mAppInfoBuilder.addUsesFeatures(mUsesFeatureBuilder);
+                        mUsesFeatureBuilder = null;
+                        break;
+                    case "uses-library":
+                        mAppInfoBuilder.addUsesLibraries(mUsesLibraryBuilder);
+                        mUsesLibraryBuilder = null;
+                        break;
+                }
+                mElementName = eleName;
+            }
+        }
+    }
+
+    private int parseItem(String line) {
+        String key;
+        String value;
+        Matcher matcher;
+        int indent = line.indexOf("A: ");
+        if (indent != -1) {
+            matcher = pattern_item.matcher(line);
+            if (matcher.find()) {
+                int processed = 0;
+                key = matcher.group(1);
+                value = matcher.group(2).replace("\"", "");
+
+                switch (mElementName) {
+                    case "manifest":
+                        if (key.equals("package")) {
+                            mAppInfoBuilder.setPackageName(value);
+                            processed = 1;
+                            break;
+                        }
+                    case "uses-sdk":
+                    case "application":
+                    case "supports-screens":
+                        mProperties.put(key, value);
+                        processed = 1;
+                        break;
+                    case "original-package":
+                        if (key.equals("name")) {
+                            mProperties.put(mElementName, value);
+                            processed = 1;
+                        }
+                        break;
+                    case "uses-permission":
+                        if (key.equals("name")) {
+                            mAppInfoBuilder.addUsesPermissions(value);
+                            processed = 1;
+                        }
+                        break;
+                    case "activity":
+                        if (key.equals("name")) {
+                            mAppInfoBuilder.addActivities(value);
+                            processed = 1;
+                        }
+                        break;
+                    case "service":
+                        if (key.equals("name")) {
+                            mAppInfoBuilder.addServices(value);
+                            processed = 1;
+                        }
+                        break;
+                    case "provider":
+                        if (key.equals("name")) {
+                            mAppInfoBuilder.addProviders(value);
+                            processed = 1;
+                        }
+                        break;
+                    case "uses-feature":
+                        putFeature(key, value);
+                        processed = 1;
+                        break;
+                    case "uses-library":
+                        putLibrary(key, value);
+                        processed = 1;
+                        break;
+                    default:
+                }
+                System.out.println(
+                        String.format("%s,%s,%s,%d", mElementName, key, value, processed));
+            }
+        }
+        return indent;
+    }
+
+    private void putFeature(String key, String value) {
+        if (mUsesFeatureBuilder == null) {
+            mUsesFeatureBuilder = UsesFeature.newBuilder();
+        }
+        switch (key) {
+            case "name":
+                mUsesFeatureBuilder.setName(value);
+            case "required":
+                mUsesFeatureBuilder.setRequired(value);
+        }
+    }
+
+    private void putLibrary(String key, String value) {
+        if (mUsesLibraryBuilder == null) {
+            mUsesLibraryBuilder = UsesLibrary.newBuilder();
+        }
+        switch (key) {
+            case "name":
+                mUsesLibraryBuilder.setName(value);
+            case "required":
+                mUsesLibraryBuilder.setRequired(value);
+        }
+    }
+
+    private String getElementName(String line) {
+        String name;
+        Matcher matcher;
+        int indent = line.indexOf("E: ");
+        if (indent != -1) {
+            matcher = pattern_element.matcher(line);
+            if (matcher.find()) {
+                name = matcher.group(1);
+                return name;
+            }
+        }
+        return null;
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar com.android.cts.releaseparser.ApkParser [-options] <path> [args...]\n"
+                    + "           to prase an APK for API\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t APK path \n";
+
+    /** Get the argument or print out the usage and exit. */
+    private static void printUsage() {
+        System.out.printf(USAGE_MESSAGE);
+        System.exit(1);
+    }
+
+    /** Get the argument or print out the usage and exit. */
+    private static String getExpectedArg(String[] args, int index) {
+        if (index < args.length) {
+            return args[index];
+        } else {
+            printUsage();
+            return null; // Never will happen because printUsage will call exit(1)
+        }
+    }
+
+    public static void main(String[] args) throws IOException {
+        String apkFileName = null;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-i".equals(args[i])) {
+                    apkFileName = getExpectedArg(args, ++i);
+                }
+            }
+        }
+
+        if (apkFileName == null) {
+            printUsage();
+        }
+
+        File apkFile = new File(apkFileName);
+        AndroidManifestParser manifestParser = new AndroidManifestParser(apkFile);
+        System.out.println();
+        System.out.println(TextFormat.printToString(manifestParser.getAppInfo()));
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ApkParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ApkParser.java
new file mode 100644
index 0000000..a08c12a
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ApkParser.java
@@ -0,0 +1,126 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ApkParser extends ZipParser {
+    private ApiPackage mExternalApiPackage;
+    private ApiPackage mInternalApiPackage;
+    private AppInfo.Builder mAppInfoBuilder;
+
+    public ApkParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.APK;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        getFileEntryBuilder().setAppInfo(getAppInfo());
+    }
+
+    public ApiPackage getExternalApiPackage() {
+        if (mExternalApiPackage == null) {
+            prase();
+        }
+        return mExternalApiPackage;
+    }
+
+    // Todo
+    public ApiPackage getTestCaseApiPackage() {
+        if (mInternalApiPackage == null) {
+            prase();
+        }
+        return mInternalApiPackage;
+    }
+
+    public AppInfo getAppInfo() {
+        if (mAppInfoBuilder == null) {
+            prase();
+        }
+        return mAppInfoBuilder.build();
+    }
+
+    private void prase() {
+        // todo parse dependencies
+        processManifest();
+        processDex();
+        processZip();
+        getLogger().log(Level.WARNING, "ToDo,parse dependencies," + getFileName());
+    }
+
+    private void processManifest() {
+        AndroidManifestParser manifestParser = new AndroidManifestParser(getFile());
+        mAppInfoBuilder = manifestParser.getAppInfoBuilder();
+    }
+
+    private void processDex() {
+        DexParser dexParser = new DexParser(getFile());
+        dexParser.setPackageName(mAppInfoBuilder.getPackageName());
+        mExternalApiPackage = dexParser.getExternalApiPackage();
+        mAppInfoBuilder.addExternalApiPackages(mExternalApiPackage);
+        mInternalApiPackage = dexParser.getInternalApiPackage();
+        mAppInfoBuilder.addInternalApiPackages(mInternalApiPackage);
+    }
+
+    private void processZip() {
+        mAppInfoBuilder.setPackageFileContent(getPackageFileContent());
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + ApkParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase APK file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n"
+                    + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
+                    + "\t-s \t Skips parsing embedded SO files if it takes too long time.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+            boolean parseSo = !argParser.containsOption("s");
+            boolean parseInternalApi = argParser.containsOption("pi");
+
+            File aFile = new File(fileName);
+            ApkParser aParser = new ApkParser(aFile);
+            aParser.setParseSo(parseSo);
+            aParser.setParseInternalApi(parseInternalApi);
+            Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+            writeTextFormatMessage(outputFileName, fileEntryBuilder.build());
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(ApkParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ArgumentParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ArgumentParser.java
new file mode 100644
index 0000000..a6a8dc1
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ArgumentParser.java
@@ -0,0 +1,94 @@
+/*
+ * 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.releaseparser;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ArgumentParser {
+    private HashMap<String, List<String>> mArgumentMap;
+    private String[] mArguments;
+
+    public ArgumentParser(String args[]) {
+        mArguments = args;
+        mArgumentMap = null;
+    }
+
+    public Map<String, List<String>> getArgumentMap() {
+        if (mArgumentMap == null) {
+            parse();
+        }
+        return mArgumentMap;
+    }
+
+    /**
+     * Gets parameter list of an option
+     *
+     * @return List<String> of parameters or null if no such option
+     */
+    public List<String> getParameterList(String option) {
+        return getArgumentMap().get(option);
+    }
+
+    /**
+     * Gets parameter element by index of an option
+     *
+     * @return String of the parameter or null
+     */
+    public String getParameterElement(String option, int index) {
+        String ele = null;
+        try {
+            ele = getArgumentMap().get(option).get(index);
+        } catch (Exception ex) {
+        }
+        return ele;
+    }
+
+    /**
+     * Checks if arguments contin an option
+     *
+     * @return true or false
+     */
+    public boolean containsOption(String option) {
+        return getArgumentMap().containsKey(option);
+    }
+
+    /**
+     * Parses arguments for a map of <options, parameterList> format: -[option1] <parameter1.1>
+     * <parameter1.2>...
+     */
+    private void parse() {
+        mArgumentMap = new HashMap<String, List<String>>();
+
+        for (int i = 0; i < mArguments.length; i++) {
+            // an option starts with "-"
+            if (mArguments[i].startsWith("-")) {
+                String option = mArguments[i].substring(1);
+
+                // gets parameters till the next option, starts with -
+                ArrayList parameterList = new ArrayList<String>();
+                while ((i + 1 < mArguments.length) && (!mArguments[i + 1].startsWith("-"))) {
+                    ++i;
+                    parameterList.add(mArguments[i]);
+                }
+                mArgumentMap.put(option, parameterList);
+            }
+        }
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ArtParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ArtParser.java
new file mode 100644
index 0000000..79de0d1
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ArtParser.java
@@ -0,0 +1,168 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+// ART file format at art/runtime/image.h
+public class ArtParser extends FileParser {
+    // The magic values for the ART identification.
+    private static final byte[] ART_MAGIC = {(byte) 'a', (byte) 'r', (byte) 't', (byte) 0x0A};
+    private static final int HEADER_SIZE = 512;
+    private ArtInfo.Builder mArtInfoBuilder;
+
+    public ArtParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.ART;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        getFileEntryBuilder().setArtInfo(getArtInfo());
+    }
+
+    @Override
+    public String getCodeId() {
+        if (mArtInfoBuilder == null) {
+            parse();
+        }
+        return mCodeId;
+    }
+
+    public ArtInfo getArtInfo() {
+        if (mArtInfoBuilder == null) {
+            parse();
+        }
+        return mArtInfoBuilder.build();
+    }
+
+    private void parse() {
+        byte[] buffer = new byte[HEADER_SIZE];
+        mArtInfoBuilder = ArtInfo.newBuilder();
+        // ToDo check this
+        int kSectionCount = 11;
+        int kImageMethodsCount = 20;
+        try {
+            RandomAccessFile raFile = new RandomAccessFile(getFile(), "r");
+            raFile.seek(0);
+            raFile.readFully(buffer, 0, HEADER_SIZE);
+            raFile.close();
+
+            if (buffer[0] != ART_MAGIC[0]
+                    || buffer[1] != ART_MAGIC[1]
+                    || buffer[2] != ART_MAGIC[2]
+                    || buffer[3] != ART_MAGIC[3]) {
+                String content = new String(buffer);
+                System.err.println("Invalid ART file:" + getFileName() + " " + content);
+                mArtInfoBuilder.setValid(false);
+                return;
+            }
+
+            int offset = 4;
+            mArtInfoBuilder.setVersion(new String(Arrays.copyOfRange(buffer, offset, offset + 4)));
+            offset += 4;
+            mArtInfoBuilder.setImageBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setImageSize(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            int oatChecksum = getIntLittleEndian(buffer, offset);
+            offset += 4;
+            mArtInfoBuilder.setOatChecksum(oatChecksum);
+            mCodeId = String.format(CODE_ID_FORMAT, oatChecksum);
+
+            mArtInfoBuilder.setOatFileBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setOatDataBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setOatDataEnd(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setOatFileEnd(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            mArtInfoBuilder.setBootImageBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setBootImageSize(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setBootOatBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setBootOatSize(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            mArtInfoBuilder.setPatchDelta(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setImageRoots(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            mArtInfoBuilder.setPointerSize(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setCompilePic(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            // Todo check further
+            mArtInfoBuilder.setIsPic(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setStorageMode(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mArtInfoBuilder.setDataSize(getIntLittleEndian(buffer, offset));
+
+            mArtInfoBuilder.setValid(true);
+        } catch (Exception ex) {
+            System.err.println("Invalid ART file:" + getFileName());
+            mArtInfoBuilder.setValid(false);
+        }
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + ArtParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to parse APK file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+
+            File aFile = new File(fileName);
+            ArtParser aParser = new ArtParser(aFile);
+            Entry fileEntry = aParser.getFileEntryBuilder().build();
+
+            writeTextFormatMessage(outputFileName, fileEntry);
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(ArtParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/BuildPropParser.java b/tools/release-parser/src/com/android/cts/releaseparser/BuildPropParser.java
new file mode 100644
index 0000000..c9ae15e
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/BuildPropParser.java
@@ -0,0 +1,139 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+public class BuildPropParser extends FileParser {
+    private Entry.EntryType mType;
+    private HashMap<String, String> mProp;
+
+    public BuildPropParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mType;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        Map<String, String> properties = getProperties();
+        if (properties != null) {
+            getFileEntryBuilder().putAllProperties(properties);
+        }
+    }
+
+    public String getBuildNumber() {
+        return getProperty("ro.build.version.incremental");
+    }
+
+    public String getVersion() {
+        return getProperty("ro.build.id");
+    }
+
+    public String getName() {
+        return getProperty("ro.product.device");
+    }
+
+    public String getFullName() {
+        return getProperty("ro.build.flavor");
+    }
+
+    public Map<String, String> getProperties() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mProp;
+    }
+
+    public String getProperty(String propertyName) {
+        if (mType == null) {
+            parseFile();
+        }
+        return mProp.get(propertyName);
+    }
+
+    private void parseFile() {
+        try {
+            FileReader fileReader = new FileReader(getFile());
+            BufferedReader buffReader = new BufferedReader(fileReader);
+            String line;
+            mProp = new HashMap<>();
+            while ((line = buffReader.readLine()) != null) {
+                String trimLine = line.trim();
+                // skips blank lines or comments e.g. # begin build properties
+                if (trimLine.length() > 0 && !trimLine.startsWith("#")) {
+                    // gets name=value pair, e.g. ro.build.id=PPR1.180610.011
+                    String[] phases = trimLine.split("=");
+                    if (phases.length > 1) {
+                        mProp.put(phases[0], phases[1]);
+                    } else {
+                        mProp.put(phases[0], "");
+                    }
+                }
+            }
+            fileReader.close();
+            mType = Entry.EntryType.BUILD_PROP;
+        } catch (IOException e) {
+            // file is not a Test Module Config
+            System.err.println("BuildProp err:" + getFileName() + "\n" + e.getMessage());
+            mType = super.getType();
+        }
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + BuildPropParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase build.prop file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+
+            File aFile = new File(fileName);
+            BuildPropParser aParser = new BuildPropParser(aFile);
+            Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+            writeTextFormatMessage(outputFileName, fileEntryBuilder.build());
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(BuildPropParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ClassUtils.java b/tools/release-parser/src/com/android/cts/releaseparser/ClassUtils.java
new file mode 100644
index 0000000..a57de6f
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ClassUtils.java
@@ -0,0 +1,205 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ClassUtils {
+    public static final char PACKAGE_SEPARATOR = '/';
+    public static final char INNER_CLASS_SEPARATOR = '$';
+    public static final char SPECIAL_CLASS_CHARACTER = '-';
+    public static final char SPECIAL_MEMBER_SEPARATOR = '$';
+    public static final char CLASS_MEMBER_SELECTOR = '.';
+
+    public static final char TYPE_VOID = 'V';
+    public static final char TYPE_BOOLEAN = 'Z';
+    public static final char TYPE_BYTE = 'B';
+    public static final char TYPE_CHAR = 'C';
+    public static final char TYPE_SHORT = 'S';
+    public static final char TYPE_INT = 'I';
+    public static final char TYPE_LONG = 'J';
+    public static final char TYPE_FLOAT = 'F';
+    public static final char TYPE_DOUBLE = 'D';
+    public static final char TYPE_CLASS_START = 'L';
+    public static final char TYPE_CLASS_END = ';';
+    public static final char TYPE_ARRAY = '[';
+    public static final String TYPE_ARRAY_ACCESS = "[]";
+
+    private static Map<Character, String> primitiveTypes = new HashMap<>();
+
+    static {
+        primitiveTypes.put(TYPE_VOID, "void");
+        primitiveTypes.put(TYPE_BOOLEAN, "boolean");
+        primitiveTypes.put(TYPE_BYTE, "byte");
+        primitiveTypes.put(TYPE_CHAR, "char");
+        primitiveTypes.put(TYPE_SHORT, "short");
+        primitiveTypes.put(TYPE_INT, "int");
+        primitiveTypes.put(TYPE_LONG, "long");
+        primitiveTypes.put(TYPE_FLOAT, "float");
+        primitiveTypes.put(TYPE_DOUBLE, "double");
+    }
+
+    /**
+     * Gets canonical name of a class
+     *
+     * @param name such as: [[Lcom/foo/bar/MyClass$Inner;
+     * @return canonical name as: com.foo.bar.MyClass.Inner[][]
+     */
+    public static String getCanonicalName(String name) {
+        int arrDimension = 0;
+        for (int i = 0; i < name.length(); i++) {
+            if (name.charAt(i) == TYPE_ARRAY) {
+                arrDimension++;
+            } else {
+                break;
+            }
+        }
+
+        // test the first character.
+        final char firstChar = name.charAt(arrDimension);
+        if (primitiveTypes.containsKey(firstChar)) {
+            name = primitiveTypes.get(firstChar);
+        } else if (firstChar == TYPE_CLASS_START) {
+            // omit the leading 'L' and the trailing ';'
+            name = name.substring(arrDimension + 1, name.length() - 1);
+
+            // replace '/' and '$' to '.'
+            name =
+                    name.replace(PACKAGE_SEPARATOR, CLASS_MEMBER_SELECTOR)
+                            .replace(INNER_CLASS_SEPARATOR, CLASS_MEMBER_SELECTOR);
+        }
+
+        // add []'s, if any
+        for (int i = 0; i < arrDimension; i++) {
+            name += TYPE_ARRAY_ACCESS;
+        }
+
+        return name;
+    }
+
+    /**
+     * Reads a file from Resource and write to a tmp file
+     *
+     * @param clazz the Class contins resrouce files
+     * @param fileName of a resource file
+     * @return the File object of a tmp file
+     */
+    public static File getResrouceFile(Class clazz, String fileName) throws IOException {
+        File tempFile = File.createTempFile(fileName, "");
+        tempFile.deleteOnExit();
+        try (InputStream input = openResourceAsStream(clazz, fileName);
+                OutputStream output = new FileOutputStream(tempFile)) {
+            byte[] buffer = new byte[4096];
+            int length;
+            while ((length = input.read(buffer)) > 0) {
+                output.write(buffer, 0, length);
+            }
+        }
+        return tempFile;
+    }
+
+    /**
+     * Gets Resource file content as a String
+     *
+     * @param clazz the Class contins resrouce files
+     * @param fileName of a resource file
+     * @return a String of the resrouce file content
+     */
+    public static String getResrouceContentString(Class clazz, String fileName) throws IOException {
+        InputStream inStream = openResourceAsStream(clazz, fileName);
+        StringBuilder stringBuilder = new StringBuilder();
+        String line = null;
+
+        try (BufferedReader bufferedReader =
+                new BufferedReader(new InputStreamReader(inStream, Charset.forName("UTF-8")))) {
+            while ((line = bufferedReader.readLine()) != null) {
+                stringBuilder.append(line);
+            }
+        }
+
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Gets an InputStrem of a file from Resource
+     *
+     * @param clazz the Class contins resrouce files
+     * @param fileName of a resource file
+     * @return the (@link InputStream} object of the file
+     */
+    public static InputStream openResourceAsStream(Class clazz, String fileName) {
+        InputStream input = clazz.getResourceAsStream("/" + fileName);
+        return input;
+    }
+
+    /**
+     * Gets an InputStremReader of a file from Resource
+     *
+     * @param clazz the Class contins resrouce files
+     * @param fileName of a resource file
+     * @return the (@link InputStream} object of the file
+     */
+    public static InputStreamReader openResourceAsStreamReader(Class clazz, String fileName) {
+        return new InputStreamReader(
+                openResourceAsStream(clazz, fileName), Charset.forName("UTF-8"));
+    }
+
+    /**
+     * Gets/new ApiClass.Builder from a map by the class name
+     *
+     * @param HashMap<class name, ApiClass.Builder>
+     * @param Class name
+     * @return ApiClass.Builder
+     */
+    public static ApiClass.Builder getApiClassBuilder(
+            HashMap<String, ApiClass.Builder> apiClassBuilderMap, String name) {
+        ApiClass.Builder builder = apiClassBuilderMap.get(name);
+        if (builder == null) {
+            builder = ApiClass.newBuilder().setName(ClassUtils.getCanonicalName(name));
+            apiClassBuilderMap.put(name, builder);
+        }
+        return builder;
+    }
+
+    /**
+     * Adds all ApiClass in a map to ApiPackage.Builder
+     *
+     * @param HashMap<class name, ApiClass.Builder>
+     * @param ApiPackage.Builder
+     */
+    public static void addAllApiClasses(
+            HashMap<String, ApiClass.Builder> apiClassBuilderMap,
+            ApiPackage.Builder apiPackageBuilder) {
+        apiClassBuilderMap
+                .values()
+                .forEach(
+                        value -> {
+                            apiPackageBuilder.addClasses(value.build());
+                        });
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/DepCsvPrinter.java b/tools/release-parser/src/com/android/cts/releaseparser/DepCsvPrinter.java
new file mode 100644
index 0000000..7656886
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/DepCsvPrinter.java
@@ -0,0 +1,409 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class DepCsvPrinter {
+    private ReleaseContent mRelContent;
+    private PrintWriter mPWriter;
+    private HashMap<String, String> mLibMap;
+    private HashMap<String, String> mDepPathMap;
+    private TreeMap<String, Entry> mTreeEntryMap;
+    private ReleaseContent mBRelContent;
+    private int mBits;
+    private String mTitle;
+    private int mCurLevel;
+    private ArrayList<Entry> mEntryList;
+
+    private static String getTitle(ReleaseContent relContent) {
+        return relContent.getName() + relContent.getVersion() + relContent.getBuildNumber();
+    }
+
+    public DepCsvPrinter(ReleaseContent relContent) {
+        mRelContent = relContent;
+        mTitle = getTitle(relContent);
+        mBRelContent = null;
+        mTreeEntryMap = new TreeMap<String, Entry>(mRelContent.getEntries());
+    }
+
+    public void writeDeltaDigraphs(ReleaseContent bRelContent, String dirName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareEntries(dirName);
+    }
+
+    public void compareEntries(String dirName) {
+        for (Entry entry : mRelContent.getEntries().values()) {
+            if (entry.getType() == Entry.EntryType.EXE) {
+                String exeName = entry.getName();
+                String fileName = String.format("%s/%s.csv", dirName, exeName);
+                writeCsv(entry, fileName);
+            }
+        }
+    }
+
+    public void writeDeltaDigraph(String exeName, ReleaseContent bRelContent, String fileName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareEntry(exeName, fileName);
+    }
+
+    public void compareEntry(String exeName, String fileName) {
+        for (Entry entry : mRelContent.getEntries().values()) {
+            if (entry.getName().equals(exeName)) {
+                writeCsv(entry, fileName);
+                break;
+            }
+        }
+    }
+
+    public void writeCsv(Entry entry, String fileName) {
+        try {
+            String exeName = entry.getName();
+            FileWriter fWriter = new FileWriter(fileName);
+            mPWriter = new PrintWriter(fWriter);
+            mLibMap = new HashMap<String, String>();
+            mEntryList = new ArrayList<Entry>();
+            mDepPathMap = new HashMap<String, String>();
+            mBits = entry.getAbiBits();
+
+            String sourceNode = getNodeName(exeName);
+            int delta = checkDelta(entry);
+
+            mCurLevel = 0;
+            mEntryList.add(entry);
+            printDep(entry, sourceNode);
+
+            mPWriter.println("id,label,value,delta");
+            for (Map.Entry<String, String> node : mLibMap.entrySet()) {
+                mPWriter.println(String.format("%s,%s", node.getKey(), node.getValue()));
+            }
+
+            mPWriter.println("source,target,value,label");
+            for (Map.Entry<String, String> link : mDepPathMap.entrySet()) {
+                mPWriter.println(String.format("%s,%s", link.getKey(), link.getValue()));
+            }
+
+            // Adjacency list
+            mPWriter.println("vertex,edge1,edge2,edge3,edge4,edge5,edge6,edge7,...");
+            for (Entry e : mEntryList) {
+                mPWriter.println(
+                        String.format(
+                                "%s,%s", e.getName(), String.join(",", e.getDependenciesList())));
+                // String.join(",", e.getDynamicLoadingDependenciesList())));
+            }
+
+            mPWriter.flush();
+            mPWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public void writeRcDeltaDigraphs(ReleaseContent bRelContent, String dirName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareRcEntries(dirName);
+    }
+
+    public void compareRcEntries(String dirName) {
+        String fileName = String.format("%s/%s.csv", dirName, "RC-files");
+        try {
+            FileWriter fWriter = new FileWriter(fileName);
+            mPWriter = new PrintWriter(fWriter);
+            String rootNode = "root";
+            mPWriter.println("digraph {");
+            mPWriter.println("rankdir=LR;");
+            mPWriter.println("node [shape = box]");
+            mPWriter.println(String.format("%s [label=\"%s\"]", rootNode, getNodeName(mTitle)));
+
+            for (Entry entry : mRelContent.getEntries().values()) {
+                if (entry.getType() == Entry.EntryType.RC) {
+                    // only care if a RC starts Services
+                    if (entry.getDependenciesList().size() > 0) {
+                        writeRcDigraph(entry, rootNode);
+                    }
+                }
+            }
+
+            mPWriter.println("}");
+            mPWriter.flush();
+            mPWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public void writeRcDigraph(Entry entry, String rootNode) {
+        String rcName = entry.getRelativePath();
+        String sourceNode = getNodeName(rcName);
+        mPWriter.printf(String.format("%s [label=\"%s\"", sourceNode, rcName));
+        checkDelta(entry);
+        mPWriter.println("]");
+
+        mPWriter.println(String.format("%s -> %s", rootNode, sourceNode));
+
+        for (Service target : entry.getServicesList()) {
+            String targetFile =
+                    target.getFile().replace("/system/", "SYSTEM/").replace("/vendor/", "VENDOR/");
+            String targetNode = getNodeName(targetFile);
+            mPWriter.printf(String.format("%s [label=\"%s\"", targetNode, targetFile));
+            Entry targetEntry = mRelContent.getEntries().get(targetFile);
+            if (targetEntry != null) {
+                checkDelta(targetEntry);
+            }
+            mPWriter.println("]");
+
+            String depPath = String.format("%s -> %s", sourceNode, targetNode);
+            mPWriter.println(depPath);
+        }
+    }
+
+    private int checkDelta(Entry srcEntry) {
+        // compare
+        if (mBRelContent != null) {
+            Entry bEntry = mBRelContent.getEntries().get(srcEntry.getRelativePath());
+            if (bEntry == null) {
+                // New Entry
+                return -1;
+            } else {
+                if (srcEntry.getContentId().equals(bEntry.getContentId())) {
+                    // Same
+                    return 0;
+                } else {
+                    // Different
+                    return 1;
+                }
+            }
+        }
+        return -2;
+    }
+
+    private String getNodeName(String note) {
+        return note;
+    }
+
+    private void printDep(Entry srcEntry, String sourceNode) {
+        mCurLevel += 1;
+        ArrayList<String> allDepList = new ArrayList<>();
+        allDepList.addAll(srcEntry.getDependenciesList());
+        int depCnt = allDepList.size();
+        allDepList.addAll(srcEntry.getDynamicLoadingDependenciesList());
+        int no = 0;
+
+        for (String dep : allDepList) {
+            boolean goFurther = false;
+            String targetNode;
+            Entry depEntry = null;
+            String filePath = dep;
+
+            // libEGL*.so to VENDOR/lib64/egl/libEGL_adreno.so
+            int idx = dep.indexOf("*.so");
+            if (idx > -1) {
+                if (mBits == 32) {
+                    filePath = "VENDOR/lib/egl/" + dep.substring(0, idx);
+                } else {
+                    filePath = "VENDOR/lib64/egl/" + dep.substring(0, idx);
+                }
+                depEntry = mTreeEntryMap.tailMap(filePath, false).firstEntry().getValue();
+                dep = depEntry.getName();
+            }
+            targetNode = getNodeName(dep);
+
+            String depPath;
+            if (no < depCnt) {
+                depPath = String.format("%s,%s,black", sourceNode, targetNode);
+            } else {
+                // This is Dyanmic Loading
+                depPath = String.format("%s,%s,steelblue", sourceNode, targetNode);
+            }
+
+            if (mDepPathMap.get(depPath) == null) {
+                // Print path once only
+                mDepPathMap.put(depPath, String.format("%d", 1));
+            }
+
+            if (depEntry == null) {
+                if (dep.startsWith("/system")) {
+                    depEntry = mRelContent.getEntries().get(dep.replace("/system/", "SYSTEM/"));
+                } else if (dep.startsWith("/vendor")) {
+                    depEntry = mRelContent.getEntries().get(dep.replace("/vendor/", "VENDOR/"));
+                } else {
+                    if (mBits == 32) {
+                        filePath = String.format("SYSTEM/lib/%s", dep);
+                    } else {
+                        filePath = String.format("SYSTEM/lib64/%s", dep);
+                    }
+
+                    depEntry = mRelContent.getEntries().get(filePath);
+
+                    if (depEntry == null) {
+                        // try Vendor
+                        if (mBits == 32) {
+                            filePath = String.format("VENDOR/lib/%s", dep);
+                        } else {
+                            filePath = String.format("VENDOR/lib64/%s", dep);
+                        }
+                        depEntry = mRelContent.getEntries().get(filePath);
+                    }
+
+                    if (depEntry == null) {
+                        // try Vendor
+                        if (mBits == 32) {
+                            filePath = String.format("VENDOR/lib/%s", dep);
+                        } else {
+                            filePath = String.format("VENDOR/lib64/%s", dep);
+                        }
+                        depEntry = mRelContent.getEntries().get(filePath);
+                    }
+                }
+            }
+
+            if (depEntry == null && dep.endsWith("libGLES_android.so")) {
+                // try Vendor
+                if (mBits == 32) {
+                    filePath = "SYSTEM/lib/egl/libGLES_android.so";
+                } else {
+                    filePath = "SYSTEM/lib64/egl/libGLES_android.so";
+                }
+                depEntry = mRelContent.getEntries().get(filePath);
+            }
+
+            if (depEntry != null) {
+                if (mLibMap.get(targetNode) == null) {
+                    // Print Entry node once only
+                    int delta = checkDelta(depEntry);
+                    mLibMap.put(
+                            targetNode,
+                            String.format(
+                                    "%s,%d,%d", depEntry.getName(), depEntry.getSize(), delta));
+                    mEntryList.add(depEntry);
+
+                    // Try to patch symbolic link to the target file
+                    if (depEntry.getType() == Entry.EntryType.SYMBOLIC_LINK) {
+                        filePath = depEntry.getParentFolder() + "/egl/" + depEntry.getName();
+                        Entry sDepEntry = mRelContent.getEntries().get(filePath);
+                        if (sDepEntry == null) {
+                            System.err.println(
+                                    "cannot find a target file for symbolic link: "
+                                            + depEntry.getRelativePath());
+                        } else {
+                            depEntry = sDepEntry;
+                        }
+                    }
+                    // Only visit once
+                    goFurther = true;
+                }
+
+                if (goFurther) {
+                    printDep(depEntry, targetNode);
+                }
+            } else {
+                System.err.println("cannot find: " + filePath);
+            }
+            no++;
+        }
+        mCurLevel -= 1;
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -cp releaseparser.jar com.android.cts.releaseparser.DepPrinter [-options]\n"
+                    + "           to compare A B builds dependency for X \n"
+                    + "Options:\n"
+                    + "\t-a A-Release.pb\t A release Content Protobuf file \n"
+                    + "\t-b B-Release.pb\t B release Content Protobuf file \n"
+                    + "\t-e Exe Name\t generates the Delta Dependency Digraph for the Execuable \n"
+                    + "\t-r \t generates RC file Delta Dependency Digraphs \n"
+                    + "\t \t without -e & -t, it will generate all Delta Dependency Digraphs \n";
+
+    /** Get the argument or print out the usage and exit. */
+    private static void printUsage() {
+        System.out.printf(USAGE_MESSAGE);
+        System.exit(1);
+    }
+
+    /** Get the argument or print out the usage and exit. */
+    private static String getExpectedArg(String[] args, int index) {
+        if (index < args.length) {
+            return args[index];
+        } else {
+            printUsage();
+            return null; // Never will happen because printUsage will call exit(1)
+        }
+    }
+
+    public static void main(final String[] args) {
+        String aPB = null;
+        String bPB = null;
+        String exeName = null;
+        boolean processRcOnly = false;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-a".equals(args[i])) {
+                    aPB = getExpectedArg(args, ++i);
+                } else if ("-b".equals(args[i])) {
+                    bPB = getExpectedArg(args, ++i);
+                } else if ("-e".equals(args[i])) {
+                    exeName = getExpectedArg(args, ++i);
+                } else if ("-r".equals(args[i])) {
+                    processRcOnly = true;
+                } else {
+                    printUsage();
+                }
+            }
+        }
+        if (aPB == null || bPB == null) {
+            printUsage();
+        }
+
+        try {
+            ReleaseContent aRelContent = ReleaseContent.parseFrom(new FileInputStream(aPB));
+            DepCsvPrinter depPrinter = new DepCsvPrinter(aRelContent);
+            ReleaseContent bRelContent = ReleaseContent.parseFrom(new FileInputStream(bPB));
+            String dirName =
+                    String.format("%s-vs-%s", getTitle(aRelContent), getTitle(bRelContent));
+            File dir = new File(dirName);
+            dir.mkdir();
+            if (processRcOnly) {
+                // General RC delta digraphs
+                depPrinter.writeRcDeltaDigraphs(bRelContent, dirName);
+            } else if (exeName == null) {
+                // General all execuable delta digraphs
+                depPrinter.writeDeltaDigraphs(bRelContent, dirName);
+            } else {
+                depPrinter.writeDeltaDigraph(
+                        exeName, bRelContent, String.format("%s/%s.csv", dirName, exeName));
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println(e);
+        }
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/DepPrinter.java b/tools/release-parser/src/com/android/cts/releaseparser/DepPrinter.java
new file mode 100644
index 0000000..5852f1b
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/DepPrinter.java
@@ -0,0 +1,401 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+public class DepPrinter {
+    private ReleaseContent mRelContent;
+    private PrintWriter mPWriter;
+    private HashMap<String, Integer> mLibMap;
+    private HashMap<String, Integer> mDepPathMap;
+    private TreeMap<String, Entry> mTreeEntryMap;
+    private ReleaseContent mBRelContent;
+    private int mBits;
+    private String mTitle;
+    private int mCurLevel;
+
+    private static String getTitle(ReleaseContent relContent) {
+        return relContent.getName() + relContent.getVersion() + relContent.getBuildNumber();
+    }
+
+    public DepPrinter(ReleaseContent relContent) {
+        mRelContent = relContent;
+        mTitle = getTitle(relContent);
+        mBRelContent = null;
+        mTreeEntryMap = new TreeMap<String, Entry>(mRelContent.getEntries());
+    }
+
+    public void writeDeltaDigraphs(ReleaseContent bRelContent, String dirName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareEntries(dirName);
+    }
+
+    public void compareEntries(String dirName) {
+        for (Entry entry : mRelContent.getEntries().values()) {
+            if (entry.getType() == Entry.EntryType.EXE) {
+                String exeName = entry.getName();
+                String fileName = String.format("%s/%s.gv", dirName, exeName);
+                writeDigraph(entry, fileName);
+            }
+        }
+    }
+
+    public void writeDeltaDigraph(String exeName, ReleaseContent bRelContent, String fileName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareEntry(exeName, fileName);
+    }
+
+    public void compareEntry(String exeName, String fileName) {
+        for (Entry entry : mRelContent.getEntries().values()) {
+            if (entry.getName().equals(exeName)) {
+                writeDigraph(entry, fileName);
+                break;
+            }
+        }
+    }
+
+    public void writeDigraph(Entry entry, String fileName) {
+        try {
+            String exeName = entry.getName();
+            FileWriter fWriter = new FileWriter(fileName);
+            mPWriter = new PrintWriter(fWriter);
+            mLibMap = new HashMap<String, Integer>();
+            mDepPathMap = new HashMap<String, Integer>();
+            mBits = entry.getAbiBits();
+            // Header
+            mPWriter.println("digraph {");
+            mPWriter.println(
+                    String.format(
+                            "a [label=\"%s %dbits\" shape=plaintext]", getNodeName(mTitle), mBits));
+
+            String sourceNode = getNodeName(exeName);
+            mPWriter.printf(String.format("%s [label=\"%s\"", sourceNode, exeName));
+            checkDelta(entry);
+            mPWriter.println("]");
+
+            mCurLevel = 0;
+            printDep(entry, sourceNode);
+
+            mPWriter.println("}");
+            mPWriter.flush();
+            mPWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public void writeRcDeltaDigraphs(ReleaseContent bRelContent, String dirName) {
+        mBRelContent = bRelContent;
+        mTitle = mTitle + "_vs_" + getTitle(bRelContent);
+        compareRcEntries(dirName);
+    }
+
+    public void compareRcEntries(String dirName) {
+        String fileName = String.format("%s/%s.gv", dirName, "RC-files");
+        try {
+            FileWriter fWriter = new FileWriter(fileName);
+            mPWriter = new PrintWriter(fWriter);
+            String rootNode = "root";
+            mPWriter.println("digraph {");
+            mPWriter.println("rankdir=LR;");
+            mPWriter.println("node [shape = box]");
+            mPWriter.println(String.format("%s [label=\"%s\"]", rootNode, getNodeName(mTitle)));
+
+            for (Entry entry : mRelContent.getEntries().values()) {
+                if (entry.getType() == Entry.EntryType.RC) {
+                    // only care if a RC starts Services
+                    if (entry.getDependenciesList().size() > 0) {
+                        writeRcDigraph(entry, rootNode);
+                    }
+                }
+            }
+
+            mPWriter.println("}");
+            mPWriter.flush();
+            mPWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public void writeRcDigraph(Entry entry, String rootNode) {
+        String rcName = entry.getRelativePath();
+        String sourceNode = getNodeName(rcName);
+        mPWriter.printf(String.format("%s [label=\"%s\"", sourceNode, rcName));
+        checkDelta(entry);
+        mPWriter.println("]");
+
+        mPWriter.println(String.format("%s -> %s", rootNode, sourceNode));
+
+        for (Service target : entry.getServicesList()) {
+            String targetFile =
+                    target.getFile().replace("/system/", "SYSTEM/").replace("/vendor/", "VENDOR/");
+            String targetNode = getNodeName(targetFile);
+            mPWriter.printf(String.format("%s [label=\"%s\"", targetNode, targetFile));
+            Entry targetEntry = mRelContent.getEntries().get(targetFile);
+            if (targetEntry != null) {
+                checkDelta(targetEntry);
+            }
+            mPWriter.println("]");
+
+            String depPath = String.format("%s -> %s", sourceNode, targetNode);
+            mPWriter.println(depPath);
+        }
+    }
+
+    private void checkDelta(Entry srcEntry) {
+        // compare
+        if (mBRelContent != null) {
+            // System.err.println(srcEntry.getRelativePath());
+            Entry bEntry = mBRelContent.getEntries().get(srcEntry.getRelativePath());
+            if (bEntry == null) {
+                // New Entry
+                mPWriter.printf(" fillcolor=\"gold1\" style=\"filled\"");
+            } else {
+                if (srcEntry.getContentId().equals(bEntry.getContentId())) {
+                    // Same
+                    mPWriter.printf(" fillcolor=\"green\" style=\"filled\"");
+                } else {
+                    // Different
+                    mPWriter.printf(" fillcolor=\"red\" style=\"filled\"");
+                }
+            }
+        }
+    }
+
+    private String getNodeName(String note) {
+        return note.replace(".", "_")
+                .replace("@", "")
+                .replace("+", "p")
+                .replace("-", "_")
+                .replace("/", "_");
+    }
+
+    private void printDep(Entry srcEntry, String sourceNode) {
+        mCurLevel += 1;
+        ArrayList<String> allDepList = new ArrayList<>();
+        allDepList.addAll(srcEntry.getDependenciesList());
+        int depCnt = allDepList.size();
+        allDepList.addAll(srcEntry.getDynamicLoadingDependenciesList());
+        int no = 0;
+
+        for (String dep : allDepList) {
+            boolean goFurther = false;
+            String targetNode;
+            Entry depEntry = null;
+            String filePath = dep;
+
+            // libEGL*.so to VENDOR/lib64/egl/libEGL_adreno.so
+            int idx = dep.indexOf("*.so");
+            if (idx > -1) {
+                if (mBits == 32) {
+                    filePath = "VENDOR/lib/egl/" + dep.substring(0, idx);
+                } else {
+                    filePath = "VENDOR/lib64/egl/" + dep.substring(0, idx);
+                }
+                depEntry = mTreeEntryMap.tailMap(filePath, false).firstEntry().getValue();
+                dep = depEntry.getName();
+            }
+            targetNode = getNodeName(dep);
+
+            String depPath = String.format("%s -> %s", sourceNode, targetNode);
+            if (no < depCnt) {
+                depPath = String.format("%s -> %s", sourceNode, targetNode);
+            } else {
+                // This is Dyanmic Loading
+                depPath = String.format("%s -> %s [color=\"blue\"]", sourceNode, targetNode);
+            }
+
+            if (mDepPathMap.get(depPath) == null) {
+                // Print path once only
+                mDepPathMap.put(depPath, 1);
+                mPWriter.println(depPath);
+            }
+
+            if (depEntry == null) {
+                if (dep.startsWith("/system")) {
+                    depEntry = mRelContent.getEntries().get(dep.replace("/system/", "SYSTEM/"));
+                } else if (dep.startsWith("/vendor")) {
+                    depEntry = mRelContent.getEntries().get(dep.replace("/vendor/", "VENDOR/"));
+                } else {
+                    if (mBits == 32) {
+                        filePath = String.format("SYSTEM/lib/%s", dep);
+                    } else {
+                        filePath = String.format("SYSTEM/lib64/%s", dep);
+                    }
+
+                    depEntry = mRelContent.getEntries().get(filePath);
+
+                    if (depEntry == null) {
+                        // try Vendor
+                        if (mBits == 32) {
+                            filePath = String.format("VENDOR/lib/%s", dep);
+                        } else {
+                            filePath = String.format("VENDOR/lib64/%s", dep);
+                        }
+                        depEntry = mRelContent.getEntries().get(filePath);
+                    }
+
+                    if (depEntry == null) {
+                        // try Vendor
+                        if (mBits == 32) {
+                            filePath = String.format("VENDOR/lib/%s", dep);
+                        } else {
+                            filePath = String.format("VENDOR/lib64/%s", dep);
+                        }
+                        depEntry = mRelContent.getEntries().get(filePath);
+                    }
+                }
+            }
+
+            if (depEntry == null && dep.endsWith("libGLES_android.so")) {
+                // try Vendor
+                if (mBits == 32) {
+                    filePath = "SYSTEM/lib/egl/libGLES_android.so";
+                } else {
+                    filePath = "SYSTEM/lib64/egl/libGLES_android.so";
+                }
+                depEntry = mRelContent.getEntries().get(filePath);
+            }
+
+            if (depEntry != null) {
+                if (mLibMap.get(targetNode) == null) {
+                    // Print Entry node once only
+                    mLibMap.put(targetNode, 1);
+                    mPWriter.printf(String.format("%s [label=\"%s\"", targetNode, dep));
+                    checkDelta(depEntry);
+
+                    // Try to patch symbolic link to the target file
+                    if (depEntry.getType() == Entry.EntryType.SYMBOLIC_LINK) {
+                        mPWriter.printf(", shape=diamond");
+                        filePath = depEntry.getParentFolder() + "/egl/" + depEntry.getName();
+                        Entry sDepEntry = mRelContent.getEntries().get(filePath);
+                        if (sDepEntry == null) {
+                            System.err.println(
+                                    "cannot find a target file for symbolic link: "
+                                            + depEntry.getRelativePath());
+                        } else {
+                            depEntry = sDepEntry;
+                        }
+                    }
+                    mPWriter.println("]");
+                    // Only visit once
+                    goFurther = true;
+                } else {
+                    // Record max Level for a target
+                    int i = mLibMap.get(targetNode);
+                    mLibMap.put(targetNode, Math.max(i, mCurLevel));
+                }
+
+                if (goFurther) {
+                    printDep(depEntry, targetNode);
+                }
+            } else {
+                System.err.println("cannot find: " + filePath);
+            }
+            no++;
+        }
+        mCurLevel -= 1;
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -cp releaseparser.jar com.android.cts.releaseparser.DepPrinter [-options]\n"
+                    + "           to compare A B builds dependency for X \n"
+                    + "Options:\n"
+                    + "\t-a A-Release.pb\t A release Content Protobuf file \n"
+                    + "\t-b B-Release.pb\t B release Content Protobuf file \n"
+                    + "\t-e Exe Name\t generates the Delta Dependency Digraph for the Execuable \n"
+                    + "\t-r \t generates RC file Delta Dependency Digraphs \n"
+                    + "\t \t without -e & -t, it will generate all Delta Dependency Digraphs \n";
+
+    /** Get the argument or print out the usage and exit. */
+    private static void printUsage() {
+        System.out.printf(USAGE_MESSAGE);
+        System.exit(1);
+    }
+
+    /** Get the argument or print out the usage and exit. */
+    private static String getExpectedArg(String[] args, int index) {
+        if (index < args.length) {
+            return args[index];
+        } else {
+            printUsage();
+            return null; // Never will happen because printUsage will call exit(1)
+        }
+    }
+
+    public static void main(final String[] args) {
+        String aPB = null;
+        String bPB = null;
+        String exeName = null;
+        boolean processRcOnly = false;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-a".equals(args[i])) {
+                    aPB = getExpectedArg(args, ++i);
+                } else if ("-b".equals(args[i])) {
+                    bPB = getExpectedArg(args, ++i);
+                } else if ("-e".equals(args[i])) {
+                    exeName = getExpectedArg(args, ++i);
+                } else if ("-r".equals(args[i])) {
+                    processRcOnly = true;
+                } else {
+                    printUsage();
+                }
+            }
+        }
+        if (aPB == null || bPB == null) {
+            printUsage();
+        }
+
+        try {
+            ReleaseContent aRelContent = ReleaseContent.parseFrom(new FileInputStream(aPB));
+            DepPrinter depPrinter = new DepPrinter(aRelContent);
+            ReleaseContent bRelContent = ReleaseContent.parseFrom(new FileInputStream(bPB));
+            String dirName =
+                    String.format("%s-vs-%s", getTitle(aRelContent), getTitle(bRelContent));
+            File dir = new File(dirName);
+            dir.mkdir();
+            if (processRcOnly) {
+                // General RC delta digraphs
+                depPrinter.writeRcDeltaDigraphs(bRelContent, dirName);
+            } else if (exeName == null) {
+                // General all execuable delta digraphs
+                depPrinter.writeDeltaDigraphs(bRelContent, dirName);
+            } else {
+                depPrinter.writeDeltaDigraph(exeName, bRelContent, String.format("%s/%s.gv", dirName, exeName));
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/DexParser.java b/tools/release-parser/src/com/android/cts/releaseparser/DexParser.java
new file mode 100644
index 0000000..eecc43a
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/DexParser.java
@@ -0,0 +1,378 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.dexlib2.ValueType;
+import org.jf.dexlib2.dexbacked.DexBackedClassDef;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedField;
+import org.jf.dexlib2.dexbacked.DexBackedMethod;
+import org.jf.dexlib2.dexbacked.reference.DexBackedFieldReference;
+import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference;
+import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.reference.*;
+import org.jf.dexlib2.iface.value.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+public class DexParser extends FileParser {
+    private ApiPackage.Builder mExternalApiPackageBuilder;
+    private HashMap<String, ApiClass.Builder> mExternalApiClassBuilderMap;
+    private ApiPackage.Builder mInternalApiPackageBuilder;
+    private HashMap<String, ApiClass.Builder> mInternalApiClassBuilderMap;
+    private String mPackageName;
+    private boolean mParseInternalApi;
+
+    public DexParser(File file) {
+        super(file);
+        // default is the file name with out extenion
+        mPackageName = getFileName().split("\\.")[0];
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.APK;
+    }
+
+    public void setPackageName(String name) {
+        mPackageName = name;
+    }
+
+    public void setParseInternalApi(boolean parseInternalApi) {
+        mParseInternalApi = parseInternalApi;
+    }
+
+    public ApiPackage getExternalApiPackage() {
+        if (mExternalApiPackageBuilder == null) {
+            parse();
+        }
+        return mExternalApiPackageBuilder.build();
+    }
+
+    public ApiPackage getInternalApiPackage() {
+        if (mInternalApiPackageBuilder == null) {
+            parse();
+        }
+        return mInternalApiPackageBuilder.build();
+    }
+
+    private void parse() {
+        mExternalApiPackageBuilder = ApiPackage.newBuilder();
+        mExternalApiPackageBuilder.setName(mPackageName);
+        mExternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>();
+        mInternalApiPackageBuilder = ApiPackage.newBuilder();
+        mInternalApiPackageBuilder.setName(mPackageName);
+        mInternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>();
+        DexBackedDexFile dexFile = null;
+
+        // Loads a Dex file
+        System.out.println("dexFile: " + getFile().getAbsoluteFile());
+        try {
+            dexFile = DexFileFactory.loadDexFile(getFile().getAbsoluteFile(), Opcodes.getDefault());
+
+            // Iterates through all clesses in the Dex file
+            for (DexBackedClassDef classDef : dexFile.getClasses()) {
+                // Still need to build the map to filter out internal classes later
+                ApiClass.Builder classBuilder =
+                        ClassUtils.getApiClassBuilder(
+                                mInternalApiClassBuilderMap, classDef.getType());
+                if (mParseInternalApi) {
+                    classBuilder.setAccessFlags(classDef.getAccessFlags());
+                    classBuilder.setSuperClass(
+                            ClassUtils.getCanonicalName(classDef.getSuperclass()));
+                    classBuilder.addAllInterfaces(
+                            classDef.getInterfaces()
+                                    .stream()
+                                    .map(iType -> ClassUtils.getCanonicalName(iType))
+                                    .collect(Collectors.toList()));
+
+                    List<ApiAnnotation> annLst = getAnnotationList(classDef.getAnnotations());
+                    if (!annLst.isEmpty()) {
+                        classBuilder.addAllAnnotations(annLst);
+                    }
+
+                    for (DexBackedField dxField : classDef.getFields()) {
+                        ApiField.Builder fieldBuilder = ApiField.newBuilder();
+                        fieldBuilder.setName(dxField.getName());
+                        fieldBuilder.setType(ClassUtils.getCanonicalName(dxField.getType()));
+                        fieldBuilder.setAccessFlags(dxField.getAccessFlags());
+                        annLst = getAnnotationList(dxField.getAnnotations());
+                        if (!annLst.isEmpty()) {
+                            fieldBuilder.addAllAnnotations(annLst);
+                        }
+                        classBuilder.addFields(fieldBuilder.build());
+                    }
+
+                    for (DexBackedMethod dxMethod : classDef.getMethods()) {
+                        ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
+                        methodBuilder.setName(dxMethod.getName());
+                        methodBuilder.setAccessFlags(dxMethod.getAccessFlags());
+                        for (String parameter : dxMethod.getParameterTypes()) {
+                            methodBuilder.addParameters(ClassUtils.getCanonicalName(parameter));
+                        }
+                        methodBuilder.setReturnType(
+                                ClassUtils.getCanonicalName(dxMethod.getReturnType()));
+                        annLst = getAnnotationList(dxMethod.getAnnotations());
+                        if (!annLst.isEmpty()) {
+                            methodBuilder.addAllAnnotations(annLst);
+                        }
+                        classBuilder.addMethods(methodBuilder.build());
+                    }
+                }
+            }
+
+            dexFile.getReferences(ReferenceType.FIELD)
+                    .stream()
+                    .map(f -> (DexBackedFieldReference) f)
+                    .filter(f -> (!mInternalApiClassBuilderMap.containsKey(f.getDefiningClass())))
+                    .forEach(f -> processField(f));
+
+            dexFile.getReferences(ReferenceType.METHOD)
+                    .stream()
+                    .map(m -> (DexBackedMethodReference) m)
+                    .filter(m -> (!mInternalApiClassBuilderMap.containsKey(m.getDefiningClass())))
+                    .filter(
+                            m ->
+                                    !(m.getDefiningClass().startsWith("[")
+                                            && m.getName().equals("clone")))
+                    .forEach(m -> processMethod(m));
+
+            ClassUtils.addAllApiClasses(mExternalApiClassBuilderMap, mExternalApiPackageBuilder);
+            if (mParseInternalApi) {
+                ClassUtils.addAllApiClasses(
+                        mInternalApiClassBuilderMap, mInternalApiPackageBuilder);
+            }
+        } catch (IOException | DexFileFactory.DexFileNotFoundException ex) {
+            String error = "Unable to load dex file: " + getFile().getAbsoluteFile();
+            mExternalApiPackageBuilder.setError(error);
+            System.err.println(error);
+            ex.printStackTrace();
+        }
+    }
+
+    private List<ApiAnnotation> getAnnotationList(Set<? extends Annotation> annotations) {
+        List<ApiAnnotation> apiAnnotationList = new ArrayList<ApiAnnotation>();
+        for (Annotation annotation : annotations) {
+            ApiAnnotation.Builder apiAnnotationBuilder = ApiAnnotation.newBuilder();
+            apiAnnotationBuilder.setType(ClassUtils.getCanonicalName(annotation.getType()));
+            Set<? extends AnnotationElement> elements = annotation.getElements();
+            for (AnnotationElement ele : elements) {
+                Element.Builder elementBuilder = Element.newBuilder();
+                elementBuilder.setName(ele.getName());
+                elementBuilder.setValue(getEncodedValueString(ele.getValue()));
+                apiAnnotationBuilder.addElements(elementBuilder.build());
+            }
+            apiAnnotationList.add(apiAnnotationBuilder.build());
+        }
+        return apiAnnotationList;
+    }
+
+    private String getEncodedValueString(EncodedValue encodedValue) {
+        switch (encodedValue.getValueType()) {
+            case ValueType.BYTE:
+                return String.format("0x%X", ((ByteEncodedValue) encodedValue).getValue());
+            case ValueType.SHORT:
+                return String.format("%d", ((ShortEncodedValue) encodedValue).getValue());
+            case ValueType.CHAR:
+                return String.format("%c", ((CharEncodedValue) encodedValue).getValue());
+            case ValueType.INT:
+                return String.format("%d", ((IntEncodedValue) encodedValue).getValue());
+            case ValueType.LONG:
+                return String.format("%d", ((LongEncodedValue) encodedValue).getValue());
+            case ValueType.FLOAT:
+                return String.format("%f", ((FloatEncodedValue) encodedValue).getValue());
+            case ValueType.DOUBLE:
+                return String.format("%f", ((DoubleEncodedValue) encodedValue).getValue());
+            case ValueType.STRING:
+                return ((StringEncodedValue) encodedValue).getValue();
+            case ValueType.NULL:
+                return "null";
+            case ValueType.BOOLEAN:
+                return Boolean.toString(((BooleanEncodedValue) encodedValue).getValue());
+            case ValueType.TYPE:
+                return ClassUtils.getCanonicalName(((TypeEncodedValue) encodedValue).getValue());
+            case ValueType.ARRAY:
+                ArrayList<String> lst = new ArrayList<String>();
+                for (EncodedValue eValue : ((ArrayEncodedValue) encodedValue).getValue()) {
+                    lst.add(getEncodedValueString(eValue));
+                }
+                return String.join(",", lst);
+            case ValueType.FIELD:
+                return ((FieldReference) ((FieldEncodedValue) encodedValue).getValue()).getName();
+            case ValueType.METHOD:
+                return ((MethodReference) ((MethodEncodedValue) encodedValue).getValue()).getName();
+            case ValueType.ENUM:
+                return ((FieldReference) ((EnumEncodedValue) encodedValue).getValue()).getName();
+            default:
+                getLogger()
+                        .log(
+                                Level.WARNING,
+                                String.format(
+                                        "ToDo,Encoded Type,0x%X", encodedValue.getValueType()));
+                return String.format("Encoded Type:%x", encodedValue.getValueType());
+        }
+    }
+
+    private void processField(DexBackedFieldReference f) {
+        ApiField.Builder fieldBuilder = ApiField.newBuilder();
+        fieldBuilder.setName(f.getName());
+        fieldBuilder.setType(ClassUtils.getCanonicalName(f.getType()));
+        ApiClass.Builder classBuilder =
+                ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, f.getDefiningClass());
+        classBuilder.addFields(fieldBuilder.build());
+    }
+
+    private void processMethod(DexBackedMethodReference m) {
+        ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
+        methodBuilder.setName(m.getName());
+        for (String parameter : m.getParameterTypes()) {
+            methodBuilder.addParameters(ClassUtils.getCanonicalName(parameter));
+        }
+        methodBuilder.setReturnType(ClassUtils.getCanonicalName(m.getReturnType()));
+        ApiClass.Builder classBuilder =
+                ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, m.getDefiningClass());
+        classBuilder.addMethods(methodBuilder.build());
+    }
+
+    private boolean isInternal(DexBackedTypeReference t) {
+        if (t.getType().length() == 1) {
+            // primitive class
+            return true;
+        } else if (t.getType().charAt(0) == ClassUtils.TYPE_ARRAY) {
+            return true;
+        }
+        return false;
+    }
+
+    private String getSignature(DexBackedTypeReference f) {
+        return f.getType();
+    }
+
+    private String getSignature(DexBackedMethod m) {
+        return m.getDefiningClass()
+                + "."
+                + m.getName()
+                + ","
+                + String.join(",", m.getParameterTypes())
+                + ","
+                + m.getReturnType();
+    }
+
+    private String getSignature(DexBackedField f) {
+        return ClassUtils.getCanonicalName(f.getDefiningClass())
+                + "."
+                + f.getName()
+                + "."
+                + f.getType();
+    }
+
+    private String getCanonicalName(DexBackedTypeReference f) {
+        return ClassUtils.getCanonicalName(f.getType());
+    }
+
+    private String getCanonicalName(DexBackedFieldReference f) {
+        return ClassUtils.getCanonicalName(f.getDefiningClass())
+                + "."
+                + f.getName()
+                + " : "
+                + ClassUtils.getCanonicalName(f.getType());
+    }
+
+    private String getCanonicalName(DexBackedMethodReference m) {
+        return ClassUtils.getCanonicalName(m.getDefiningClass())
+                + "."
+                + m.getName()
+                + " ("
+                + toParametersString(m.getParameterTypes())
+                + ")"
+                + ClassUtils.getCanonicalName(m.getReturnType());
+    }
+
+    private String toParametersString(List<String> pList) {
+        return pList.stream()
+                .map(p -> ClassUtils.getCanonicalName(p))
+                .collect(Collectors.joining(", "));
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + DexParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase APK file Dex data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+            boolean parseInternalApi = argParser.containsOption("pi");
+
+            String apkFileName = null;
+            File apkFile = new File(fileName);
+            DexParser aParser = new DexParser(apkFile);
+            aParser.setParseInternalApi(parseInternalApi);
+
+            if (outputFileName != null) {
+                FileOutputStream txtOutput = new FileOutputStream(outputFileName);
+                txtOutput.write(
+                        TextFormat.printToString(aParser.getExternalApiPackage())
+                                .getBytes(Charset.forName("UTF-8")));
+                if (parseInternalApi) {
+                    txtOutput.write(
+                            TextFormat.printToString(aParser.getInternalApiPackage())
+                                    .getBytes(Charset.forName("UTF-8")));
+                }
+                txtOutput.flush();
+                txtOutput.close();
+            } else {
+                System.out.println(TextFormat.printToString(aParser.getExternalApiPackage()));
+                if (parseInternalApi) {
+                    System.out.println(TextFormat.printToString(aParser.getInternalApiPackage()));
+                }
+            }
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(DexParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ExeParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ExeParser.java
new file mode 100644
index 0000000..0f0af10
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ExeParser.java
@@ -0,0 +1,32 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+
+public class ExeParser extends SoParser {
+    public ExeParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.EXE;
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/FileParser.java b/tools/release-parser/src/com/android/cts/releaseparser/FileParser.java
new file mode 100644
index 0000000..aa52e11
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/FileParser.java
@@ -0,0 +1,208 @@
+/*
+ * 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.releaseparser;
+
+import com.android.compatibility.common.util.ReadElf;
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.List;
+
+public class FileParser {
+    private static final String NO_ID = "";
+    protected static final int READ_BLOCK_SIZE = 1024;
+
+    // Target File Extensions
+    public static final String APK_EXT_TAG = ".apk";
+    public static final String JAR_EXT_TAG = ".jar";
+    public static final String DEX_EXT_TAG = ".dex";
+    public static final String ODEX_EXT_TAG = ".odex";
+    public static final String VDEX_EXT_TAG = ".vdex";
+    public static final String ART_EXT_TAG = ".art";
+    public static final String OAT_EXT_TAG = ".oat";
+    public static final String SO_EXT_TAG = ".so";
+    public static final String BUILD_PROP_EXT_TAG = "build.prop";
+    public static final String RC_EXT_TAG = ".rc";
+    public static final String XML_EXT_TAG = ".xml";
+    public static final String IMG_EXT_TAG = ".img";
+    public static final String TEST_SUITE_TRADEFED_TAG = "-tradefed.jar";
+    public static final String CONFIG_EXT_TAG = ".config";
+    public static final String ANDROID_MANIFEST_TAG = "AndroidManifest.xml";
+    // Code Id format: [0xchecksum1] [...
+    public static final String CODE_ID_FORMAT = "%x ";
+
+    protected File mFile;
+    protected String mContentId;
+    protected String mCodeId;
+    protected Entry.Builder mFileEntryBuilder;
+
+    public static FileParser getParser(File file) {
+        String fName = file.getName();
+        // Starts with SymbolicLink
+        if (isSymbolicLink(file)) {
+            return new SymbolicLinkParser(file);
+        } else if (fName.endsWith(APK_EXT_TAG)) {
+            return new ApkParser(file);
+        } else if (fName.endsWith(CONFIG_EXT_TAG)) {
+            return new TestModuleConfigParser(file);
+        } else if (fName.endsWith(TEST_SUITE_TRADEFED_TAG)) {
+            return new TestSuiteTradefedParser(file);
+        } else if (fName.endsWith(JAR_EXT_TAG)) {
+            // keeps this after TEST_SUITE_TRADEFED_TAG to avoid missing it
+            return new JarParser(file);
+        } else if (fName.endsWith(SO_EXT_TAG)) {
+            return new SoParser(file);
+        } else if (fName.endsWith(ART_EXT_TAG)) {
+            return new ArtParser(file);
+        } else if (fName.endsWith(OAT_EXT_TAG)) {
+            return new OatParser(file);
+        } else if (fName.endsWith(ODEX_EXT_TAG)) {
+            return new OdexParser(file);
+        } else if (fName.endsWith(VDEX_EXT_TAG)) {
+            return new VdexParser(file);
+        } else if (fName.endsWith(BUILD_PROP_EXT_TAG)) {
+            // ToDo prop.default & etc in system/core/init/property_service.cpp
+            return new BuildPropParser(file);
+        } else if (fName.endsWith(RC_EXT_TAG)) {
+            return new RcParser(file);
+        } else if (fName.endsWith(XML_EXT_TAG)) {
+            return new XmlParser(file);
+        } else if (fName.endsWith(IMG_EXT_TAG)) {
+            return new ImgParser(file);
+        } else if (ReadElf.isElf(file)) {
+            // keeps this in the end as no Exe Ext name
+            return new ExeParser(file);
+        } else {
+            // Common File Parser
+            return new FileParser(file);
+        }
+    }
+
+    FileParser(File file) {
+        mFile = file;
+        mCodeId = NO_ID;
+        mContentId = NO_ID;
+        mFileEntryBuilder = null;
+    }
+
+    public File getFile() {
+        return mFile;
+    }
+
+    public String getFileName() {
+        return mFile.getName();
+    }
+
+    public Entry.Builder getFileEntryBuilder() {
+        if (mFileEntryBuilder == null) {
+            parse();
+        }
+        return mFileEntryBuilder;
+    }
+
+    public Entry.EntryType getType() {
+        return Entry.EntryType.FILE;
+    }
+
+    public String getFileContentId() {
+        if (NO_ID.equals(mContentId)) {
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA-256");
+                FileInputStream fis = new FileInputStream(mFile);
+                byte[] dataBytes = new byte[READ_BLOCK_SIZE];
+                int nread = 0;
+                while ((nread = fis.read(dataBytes)) != -1) {
+                    md.update(dataBytes, 0, nread);
+                }
+                // Converts to Base64 String
+                mContentId = Base64.getEncoder().encodeToString(md.digest());
+            } catch (IOException e) {
+                System.err.println("IOException:" + e.getMessage());
+            } catch (NoSuchAlgorithmException e) {
+                System.err.println("NoSuchAlgorithmException:" + e.getMessage());
+            }
+        }
+        return mContentId;
+    }
+
+    public int getAbiBits() {
+        return 0;
+    }
+
+    public String getAbiArchitecture() {
+        return NO_ID;
+    }
+
+    public String getCodeId() {
+        return mCodeId;
+    }
+
+    public List<String> getDependencies() {
+        return null;
+    }
+
+    public List<String> getDynamicLoadingDependencies() {
+        return null;
+    }
+
+    /** For subclass parser to add file type specific info into the entry. */
+    public void setAdditionalInfo() {}
+
+    private static boolean isSymbolicLink(File f) {
+        // Assumes 0b files are Symbolic Link
+        return (f.length() == 0);
+    }
+
+    public static int getIntLittleEndian(byte[] byteArray, int start) {
+        int answer = 0;
+        // Assume Little-Endian. ToDo: if to care about big-endian
+        for (int i = start + 3; i >= start; --i) {
+            answer = (answer << 8) | (byteArray[i] & 0xff);
+        }
+        return answer;
+    }
+
+    public static void writeTextFormatMessage(String outputFileName, Entry fileEntry)
+            throws IOException {
+        if (outputFileName != null) {
+            FileOutputStream txtOutput = new FileOutputStream(outputFileName);
+            txtOutput.write(TextFormat.printToString(fileEntry).getBytes(Charset.forName("UTF-8")));
+            txtOutput.flush();
+            txtOutput.close();
+        } else {
+            System.out.println(TextFormat.printToString(fileEntry));
+        }
+    }
+
+    private void parse() {
+        mFileEntryBuilder = Entry.newBuilder();
+        mFileEntryBuilder.setName(getFileName());
+        mFileEntryBuilder.setSize(getFile().length());
+        mFileEntryBuilder.setContentId(getFileContentId());
+        mFileEntryBuilder.setCodeId(getCodeId());
+        mFileEntryBuilder.setType(getType());
+        setAdditionalInfo();
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ImgParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ImgParser.java
new file mode 100644
index 0000000..c508932
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ImgParser.java
@@ -0,0 +1,40 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+public class ImgParser extends FileParser {
+
+    public ImgParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.IMG;
+    }
+
+    // ToDo: to parse image file whever there is a valid use csae
+
+    private static Logger getLogger() {
+        return Logger.getLogger(ImgParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/JarParser.java b/tools/release-parser/src/com/android/cts/releaseparser/JarParser.java
new file mode 100644
index 0000000..8948bba
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/JarParser.java
@@ -0,0 +1,32 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+
+public class JarParser extends FileParser {
+    public JarParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.JAR;
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/JarTestFinder.java b/tools/release-parser/src/com/android/cts/releaseparser/JarTestFinder.java
new file mode 100644
index 0000000..b2b693d
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/JarTestFinder.java
@@ -0,0 +1,85 @@
+/*
+ * 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.releaseparser;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/** Representation of the entire CDD. */
+class JarTestFinder {
+
+    public static Collection<Class<?>> getClasses(File jarTestFile)
+            throws IllegalArgumentException {
+        List<Class<?>> classes = new ArrayList<>();
+
+        try (JarFile jarFile = new JarFile(jarTestFile)) {
+            Enumeration<JarEntry> e = jarFile.entries();
+
+            URL[] urls = {new URL(String.format("jar:file:%s!/", jarTestFile.getAbsolutePath()))};
+            URLClassLoader cl =
+                    URLClassLoader.newInstance(urls, JarTestFinder.class.getClassLoader());
+
+            while (e.hasMoreElements()) {
+                JarEntry je = e.nextElement();
+                if (je.isDirectory()
+                        || !je.getName().endsWith(".class")
+                        || je.getName().contains("$")) {
+                    continue;
+                }
+                String className = getClassName(je.getName());
+                if (!className.endsWith("Test")) {
+                    continue;
+                }
+                try {
+                    Class<?> cls = cl.loadClass(className);
+                    int modifiers = cls.getModifiers();
+                    if (!Modifier.isStatic(modifiers)
+                            && !Modifier.isPrivate(modifiers)
+                            && !Modifier.isProtected(modifiers)
+                            && !Modifier.isInterface(modifiers)
+                            && !Modifier.isAbstract(modifiers)) {
+
+                        classes.add(cls);
+                    }
+                } catch (ClassNotFoundException | Error x) {
+                    System.err.println(
+                            String.format(
+                                    "Cannot find test class %s from %s",
+                                    className, jarTestFile.getName()));
+                    x.printStackTrace();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return classes;
+    }
+
+    private static String getClassName(String name) {
+        // -6 because of .class
+        return name.substring(0, name.length() - 6).replace('/', '.');
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/JsonPrinter.java b/tools/release-parser/src/com/android/cts/releaseparser/JsonPrinter.java
new file mode 100644
index 0000000..f4c1e40
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/JsonPrinter.java
@@ -0,0 +1,255 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.android.json.stream.NewlineDelimitedJsonWriter;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map;
+
+public class JsonPrinter {
+    private String mOutputFilePathPrefix;
+    private ReleaseContent mRelContent;
+    private NewlineDelimitedJsonWriter mJsonWriter;
+
+    public JsonPrinter(ReleaseContent relContent, String outputFilePathPrefix) {
+        mRelContent = relContent;
+        mOutputFilePathPrefix = outputFilePathPrefix;
+    }
+
+    public void write() {
+        try {
+            open(mOutputFilePathPrefix + "-ReleaseContent.json");
+            writeReleaseContent();
+            close();
+
+            open(mOutputFilePathPrefix + "-AppInfo.json");
+            writeAppInfo();
+            close();
+            open(mOutputFilePathPrefix + "-ApiDep.json");
+            writeApiDep(mRelContent.getEntries().values());
+            close();
+        } catch (IOException ex) {
+            System.err.println("Unable to write json files: " + mOutputFilePathPrefix);
+            ex.printStackTrace();
+        }
+    }
+
+    private void writeReleaseContent() throws IOException {
+        mJsonWriter.beginObject();
+        writeReleaseIDs();
+        mJsonWriter.name("name").value(mRelContent.getName());
+        mJsonWriter.name("version").value(mRelContent.getVersion());
+        mJsonWriter.name("build_number").value(mRelContent.getBuildNumber());
+        mJsonWriter.name("fullname").value(mRelContent.getFullname());
+        mJsonWriter.name("size").value(mRelContent.getSize());
+        mJsonWriter.name("release_type").value(mRelContent.getReleaseType().toString());
+        mJsonWriter.name("test_suite_tradefed").value(mRelContent.getTestSuiteTradefed());
+        mJsonWriter.name("target_arch").value(mRelContent.getTargetArch());
+        writeProperties(mRelContent.getProperties());
+        writeTargetFileInfo(mRelContent.getEntries().values());
+        mJsonWriter.endObject();
+        mJsonWriter.newlineDelimited();
+    }
+
+    private void writeReleaseIDs() throws IOException {
+        mJsonWriter.name("release_id").value(mRelContent.getReleaseId());
+        mJsonWriter.name("content_id").value(mRelContent.getContentId());
+    }
+
+    private void writeProperties(Map<String, String> pMap) throws IOException {
+        mJsonWriter.name("properties");
+        mJsonWriter.beginArray();
+        for (Map.Entry<String, String> pSet : pMap.entrySet()) {
+            mJsonWriter.beginObject();
+            mJsonWriter.name("key").value(pSet.getKey());
+            mJsonWriter.name("value").value(pSet.getValue());
+            mJsonWriter.endObject();
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writeTargetFileInfo(Collection<Entry> entries) throws IOException {
+        mJsonWriter.name("files");
+        mJsonWriter.beginArray();
+        for (Entry entry : entries) {
+            mJsonWriter.beginObject();
+            mJsonWriter.name("name").value(entry.getName());
+            mJsonWriter.name("type").value(entry.getType().toString());
+            mJsonWriter.name("size").value(entry.getSize());
+            mJsonWriter.name("content_id").value(entry.getContentId());
+            mJsonWriter.name("code_id").value(entry.getCodeId());
+            mJsonWriter.name("abi_architecture").value(entry.getAbiArchitecture());
+            mJsonWriter.name("abi_bits").value(entry.getAbiBits());
+            mJsonWriter.name("parent_folder").value(entry.getParentFolder());
+            mJsonWriter.name("relative_path").value(entry.getRelativePath());
+            writeStringCollection("dependencies", entry.getDependenciesList());
+            writeStringCollection(
+                    "dynamic_loading_dependencies", entry.getDynamicLoadingDependenciesList());
+            mJsonWriter.endObject();
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writeAppInfo() throws IOException {
+        mJsonWriter.beginObject();
+        writeReleaseIDs();
+
+        mJsonWriter.name("apps");
+        mJsonWriter.beginArray();
+        Collection<Entry> entries = mRelContent.getEntries().values();
+        for (Entry entry : entries) {
+            if (entry.getType() == Entry.EntryType.APK) {
+                AppInfo appInfo = entry.getAppInfo();
+                mJsonWriter.beginObject();
+                mJsonWriter.name("name").value(entry.getName());
+                mJsonWriter.name("content_id").value(entry.getContentId());
+                // from AppInfo Message
+                mJsonWriter.name("package_name").value(appInfo.getPackageName());
+                mJsonWriter.name("version_code").value(appInfo.getVersionCode());
+                mJsonWriter.name("version_name").value(appInfo.getVersionName());
+                mJsonWriter.name("sdk_version").value(appInfo.getSdkVersion());
+                mJsonWriter.name("target_sdk_version").value(appInfo.getTargetSdkVersion());
+                writeFeatureCollection("uses_features", appInfo.getUsesFeaturesList());
+                writeLibraryCollection("uses_libraries", appInfo.getUsesLibrariesList());
+                writeStringCollection("native_code", appInfo.getNativeCodeList());
+                writeStringCollection("uses_permissions", appInfo.getUsesPermissionsList());
+                writeStringCollection("activities", appInfo.getActivitiesList());
+                writeStringCollection("services", appInfo.getServicesList());
+                writeStringCollection("provideries", appInfo.getProvidersList());
+                writeProperties(appInfo.getProperties());
+                writePackageFileContent("package_file_content", appInfo.getPackageFileContent());
+                mJsonWriter.name("package_signature").value(appInfo.getPackageSignature());
+                mJsonWriter.endObject();
+            }
+        }
+        mJsonWriter.endArray();
+        mJsonWriter.endObject();
+        mJsonWriter.newlineDelimited();
+    }
+
+    private void writeFeatureCollection(String name, Collection<UsesFeature> features)
+            throws IOException {
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (UsesFeature feature : features) {
+            mJsonWriter.beginObject();
+            mJsonWriter.name("name").value(feature.getName());
+            mJsonWriter.name("required").value(feature.getRequired());
+            mJsonWriter.endObject();
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writeLibraryCollection(String name, Collection<UsesLibrary> libraries)
+            throws IOException {
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (UsesLibrary library : libraries) {
+            mJsonWriter.beginObject();
+            mJsonWriter.name("name").value(library.getName());
+            mJsonWriter.name("required").value(library.getRequired());
+            mJsonWriter.endObject();
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writeStringCollection(String name, Collection<String> strings) throws IOException {
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (String str : strings) {
+            mJsonWriter.value(str);
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writePackageFileContent(String name, PackageFileContent packageFileContent)
+            throws IOException {
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+
+        Collection<Entry> entries = packageFileContent.getEntries().values();
+        for (Entry entry : entries) {
+            mJsonWriter.beginObject();
+            mJsonWriter.name("name").value(entry.getName());
+            mJsonWriter.name("size").value(entry.getSize());
+            mJsonWriter.name("content_id").value(entry.getContentId());
+            mJsonWriter.endObject();
+        }
+        mJsonWriter.endArray();
+    }
+
+    private void writeApiDep(Collection<Entry> entries) throws IOException {
+        for (Entry entry : entries) {
+            if (entry.getType() == Entry.EntryType.APK) {
+                writeApiPkg(entry);
+            }
+        }
+    }
+
+    private void writeApiPkg(Entry entry) throws IOException {
+        for (ApiPackage pkg : entry.getAppInfo().getExternalApiPackagesList()) {
+            for (ApiClass clazz : pkg.getClassesList()) {
+                for (ApiMethod method : clazz.getMethodsList()) {
+                    mJsonWriter.beginObject();
+                    mJsonWriter.name("content_id").value(entry.getContentId());
+                    mJsonWriter.name("api_signature").value(getApiSignature(pkg, clazz, method));
+                    mJsonWriter.name("class").value(clazz.getName());
+                    mJsonWriter.name("method").value(method.getName());
+                    mJsonWriter.name("file_name").value(entry.getName());
+                    mJsonWriter.name("release_id").value(ReleaseParser.getReleaseId(mRelContent));
+                    mJsonWriter.endObject();
+                    mJsonWriter.newlineDelimited();
+                }
+            }
+        }
+    }
+
+    private String getApiSignature(ApiPackage pkg, ApiClass clazz, ApiMethod method) {
+        StringBuilder signatureBuilder = new StringBuilder();
+        signatureBuilder.append(method.getReturnType());
+        signatureBuilder.append(" ");
+        signatureBuilder.append(clazz.getName());
+        signatureBuilder.append(".");
+        signatureBuilder.append(method.getName());
+        signatureBuilder.append("(");
+        signatureBuilder.append(String.join(",", method.getParametersList()));
+        signatureBuilder.append(")");
+        return signatureBuilder.toString();
+    }
+
+    private void open(String fileName) throws IOException {
+        File jsonFile;
+
+        jsonFile = new File(fileName);
+        FileOutputStream fOutStrem = new FileOutputStream(jsonFile);
+        mJsonWriter =
+                new NewlineDelimitedJsonWriter(
+                        new OutputStreamWriter(fOutStrem, StandardCharsets.UTF_8));
+    }
+
+    private void close() throws IOException {
+        mJsonWriter.flush();
+        mJsonWriter.close();
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/Main.java b/tools/release-parser/src/com/android/cts/releaseparser/Main.java
new file mode 100644
index 0000000..ecc0a2a
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/Main.java
@@ -0,0 +1,109 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import java.io.FileOutputStream;
+import java.nio.charset.Charset;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+/** Main of release parser */
+public class Main {
+    private Main() {}
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar [-options <parameter>]...\n"
+                    + "\tto prase a release, such as device build, test suite or app distribution package\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t path to a release folder\n"
+                    + "\t-o PATH\t path to output files\n";
+
+    public static void main(final String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String relFolder = argParser.getParameterElement("i", 0);
+            String outputPath = argParser.getParameterElement("o", 0);
+
+            // parse a release folder
+            ReleaseParser relParser = new ReleaseParser(relFolder);
+            String relNameVer = relParser.getReleaseId();
+            relParser.writeRelesaeContentCsvFile(
+                    relNameVer, getPathString(outputPath, "%s-ReleaseContent.csv", relNameVer));
+
+            // write release content JSON file
+            JsonPrinter jPrinter =
+                    new JsonPrinter(
+                            relParser.getReleaseContent(),
+                            getPathString(outputPath, "%s", relNameVer));
+            jPrinter.write();
+
+            // Write release content message to disk.
+            ReleaseContent relContent = relParser.getReleaseContent();
+            FileOutputStream output =
+                    new FileOutputStream(
+                            getPathString(outputPath, "%s-ReleaseContent.pb", relNameVer));
+            relContent.writeTo(output);
+            output.flush();
+            output.close();
+
+            FileOutputStream txtOutput =
+                    new FileOutputStream(
+                            getPathString(outputPath, "%s-ReleaseContent.txt", relNameVer));
+            txtOutput.write(
+                    TextFormat.printToString(relContent).getBytes(Charset.forName("UTF-8")));
+            txtOutput.flush();
+            txtOutput.close();
+
+            // parse Test Suite
+            TestSuiteParser tsParser = new TestSuiteParser(relContent, relFolder);
+            if (tsParser.getTestSuite().getModulesList().size() == 0) {
+                // skip if no test module
+                return;
+            }
+
+            // write Known Failus & etc. CSV files
+            relParser.writeKnownFailureCsvFile(
+                    relNameVer, getPathString(outputPath, "%s-KnownFailure.csv", relNameVer));
+            tsParser.writeCsvFile(
+                    relNameVer, getPathString(outputPath, "%s-TestCase.csv", relNameVer));
+            tsParser.writeModuleCsvFile(
+                    relNameVer, getPathString(outputPath, "%s-TestModule.csv", relNameVer));
+
+            // Write test suite content message to disk.
+            TestSuite testSuite = tsParser.getTestSuite();
+            FileOutputStream tsOutput =
+                    new FileOutputStream(getPathString(outputPath, "%s-TestSuite.pb", relNameVer));
+            testSuite.writeTo(tsOutput);
+            tsOutput.flush();
+            tsOutput.close();
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    public static String getPathString(String outputPath, String format, String id) {
+        return Paths.get(outputPath, String.format(format, id)).toString();
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(Main.class.getSimpleName());
+    }
+}
\ No newline at end of file
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/OatParser.java b/tools/release-parser/src/com/android/cts/releaseparser/OatParser.java
new file mode 100644
index 0000000..b4c5e02
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/OatParser.java
@@ -0,0 +1,290 @@
+/*
+ * 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.releaseparser;
+
+import com.android.compatibility.common.util.ReadElf;
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+// Oat is embedded in an ELF file .rodata secion
+// art/runtime/oat.h & oat.cc
+// art/dex2oat/linker/oat_writer.h
+public class OatParser extends FileParser {
+    // The magic values for the OAT identification.
+    private static final byte[] OAT_MAGIC = {(byte) 'o', (byte) 'a', (byte) 't', (byte) 0x0A};
+    private static final int HEADER_SIZE = 64;
+    private static String FIRST_NO_PIC_VERSION = "162\0";
+    private OatInfo.Builder mOatInfoBuilder;
+    private int mBits;
+    private String mArch;
+    private byte[] mRoData;
+    private List<String> mDependencies;
+
+    public OatParser(File file) {
+        super(file);
+        mDependencies = null;
+    }
+
+    @Override
+    public List<String> getDependencies() {
+        if (mDependencies == null) {
+            parse();
+        }
+        return mDependencies;
+    }
+
+    @Override
+    public String getCodeId() {
+        if (mOatInfoBuilder == null) {
+            parse();
+        }
+        return mCodeId;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        getFileEntryBuilder().setOatInfo(getOatInfo());
+    }
+
+    public OatInfo getOatInfo() {
+        if (mOatInfoBuilder == null) {
+            parse();
+        }
+        return mOatInfoBuilder.build();
+    }
+
+    private void parse() {
+        mOatInfoBuilder = OatInfo.newBuilder();
+        try {
+            mDependencies = new ArrayList<String>();
+            ReadElf elf = ReadElf.read(getFile());
+            mOatInfoBuilder.setBits(elf.getBits());
+            mOatInfoBuilder.setArchitecture(elf.getArchitecture());
+            mRoData = elf.getRoData();
+            praseOat(mRoData);
+            mOatInfoBuilder.setValid(true);
+        } catch (Exception ex) {
+            System.err.println("Invalid OAT file:" + getFileName());
+            ex.printStackTrace(System.out);
+            mOatInfoBuilder.setValid(false);
+        }
+    }
+
+    private void praseOat(byte[] buffer) throws IllegalArgumentException {
+        if (buffer[0] != OAT_MAGIC[0]
+                || buffer[1] != OAT_MAGIC[1]
+                || buffer[2] != OAT_MAGIC[2]
+                || buffer[3] != OAT_MAGIC[3]) {
+            String content = new String(buffer);
+            System.err.println("Invalid OAT file:" + getFileName() + " " + content);
+            throw new IllegalArgumentException("Invalid OAT MAGIC");
+        }
+
+        int offset = 4;
+        String version = new String(Arrays.copyOfRange(buffer, offset, offset + 4));
+        mOatInfoBuilder.setVersion(version);
+        offset += 4;
+        mOatInfoBuilder.setAdler32Checksum(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setInstructionSet(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setInstructionSetFeaturesBitmap(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        int dexFileCount = getIntLittleEndian(buffer, offset);
+        mOatInfoBuilder.setDexFileCount(dexFileCount);
+        offset += 4;
+        int dexFileOffset = getIntLittleEndian(buffer, offset);
+        mOatInfoBuilder.setOatDexFilesOffset(dexFileOffset);
+        offset += 4;
+        mOatInfoBuilder.setExecutableOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setInterpreterToInterpreterBridgeOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setInterpreterToCompiledCodeBridgeOffset(
+                getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setJniDlsymLookupOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setQuickGenericJniTrampolineOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setQuickImtConflictTrampolineOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setQuickResolutionTrampolineOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+        mOatInfoBuilder.setQuickToInterpreterBridgeOffset(getIntLittleEndian(buffer, offset));
+        offset += 4;
+
+        // for backward compatibility, removed from version 162, see
+        // aosp/e0669326c0282b5b645aba75160425eef9d57617
+        if (version.compareTo(FIRST_NO_PIC_VERSION) < 0) {
+            mOatInfoBuilder.setImagePatchDelta(getIntLittleEndian(buffer, offset));
+            offset += 4;
+        }
+
+        mOatInfoBuilder.setImageFileLocationOatChecksum(getIntLittleEndian(buffer, offset));
+        offset += 4;
+
+        // for backward compatibility, removed from version 162, see
+        // aosp/e0669326c0282b5b645aba75160425eef9d57617
+        if (version.compareTo(FIRST_NO_PIC_VERSION) < 0) {
+            mOatInfoBuilder.setImageFileLocationOatDataBegin(getIntLittleEndian(buffer, offset));
+            offset += 4;
+        }
+        int storeSize = getIntLittleEndian(buffer, offset);
+        mOatInfoBuilder.setKeyValueStoreSize(storeSize);
+        offset += 4;
+
+        // adds dependencies at the build time
+        // trims device & dex_bootjars from the path
+        // e.g. out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art
+        final String dexBootJars = "/dex_bootjars/";
+        Map<String, String> kvMap = getKeyValuePairMap(buffer, offset, storeSize);
+        mOatInfoBuilder.putAllKeyValueStore(kvMap);
+        String imageLocation = kvMap.get("image-location");
+        if (imageLocation != null) {
+            for (String path : imageLocation.split(":")) {
+                mDependencies.add(path.substring(path.indexOf(dexBootJars) + dexBootJars.length()));
+            }
+        }
+        String bootClasspath = kvMap.get("bootclasspath");
+        if (bootClasspath != null) {
+            for (String path : bootClasspath.split(":")) {
+                mDependencies.add(path.substring(path.indexOf(dexBootJars) + dexBootJars.length()));
+            }
+        }
+        if (!mDependencies.isEmpty()) {
+            getFileEntryBuilder().addAllDependencies(mDependencies);
+        }
+
+        StringBuilder codeIdSB = new StringBuilder();
+        // art/dex2oat/linker/oat_writer.cc OatDexFile
+        offset = dexFileOffset;
+        for (int i = 0; i < dexFileCount; i++) {
+            OatDexInfo.Builder oatDexInfoBuilder = OatDexInfo.newBuilder();
+
+            // dex_file_location_size_
+            int length = getIntLittleEndian(buffer, offset);
+            offset += 4;
+            // dex_file_location_data_
+            oatDexInfoBuilder.setDexFileLocationData(
+                    new String(Arrays.copyOfRange(buffer, offset, offset + length)));
+            offset += length;
+
+            // dex_file_location_checksum_
+            int dexFileLocationChecksum = getIntLittleEndian(buffer, offset);
+            offset += 4;
+            oatDexInfoBuilder.setDexFileLocationChecksum(dexFileLocationChecksum);
+            codeIdSB.append(String.format(CODE_ID_FORMAT, dexFileLocationChecksum));
+
+            // dex_file_offset_
+            oatDexInfoBuilder.setDexFileOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // lookup_table_offset_
+            oatDexInfoBuilder.setLookupTableOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // uint32_t class_offsets_offset_;
+            oatDexInfoBuilder.setClassOffsetsOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // uint32_t method_bss_mapping_offset_;
+            oatDexInfoBuilder.setMethodBssMappingOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // uint32_t type_bss_mapping_offset_;
+            oatDexInfoBuilder.setTypeBssMappingOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // uint32_t string_bss_mapping_offset_;
+            oatDexInfoBuilder.setStringBssMappingOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            // uint32_t dex_sections_layout_offset_;
+            oatDexInfoBuilder.setDexSectionsLayoutOffset(getIntLittleEndian(buffer, offset));
+            offset += 4;
+            mOatInfoBuilder.addOatDexInfo(oatDexInfoBuilder.build());
+        }
+        mCodeId = codeIdSB.toString();
+    }
+
+    // as art/runtime/oat.cc GetStoreValueByKey
+    private Map<String, String> getKeyValuePairMap(byte[] buffer, int start, int size) {
+        HashMap<String, String> keyValuePairMap = new HashMap<String, String>();
+        int currentPosition = start;
+        int end = start + size;
+        String key, value;
+        while (currentPosition < end) {
+            key = getString(buffer, currentPosition, end);
+            currentPosition += key.length() + 1;
+            value = getString(buffer, currentPosition, end);
+            currentPosition += value.length() + 1;
+            keyValuePairMap.put(key, value);
+        }
+        return keyValuePairMap;
+    }
+
+    private String getString(byte[] buffer, int start, int end) {
+        String str = null;
+        int currentPosition = start;
+        while (currentPosition < end) {
+            if (buffer[currentPosition] == 0x0) {
+                str = new String(Arrays.copyOfRange(buffer, start, currentPosition));
+                break;
+            } else {
+                currentPosition++;
+            }
+        }
+        return str;
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.OAT;
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + OatParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase OAT file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+
+            File aFile = new File(fileName);
+            OatParser aParser = (OatParser) FileParser.getParser(aFile);
+            Entry fileEntry = aParser.getFileEntryBuilder().build();
+
+            writeTextFormatMessage(outputFileName, fileEntry);
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(OatParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/OdexParser.java b/tools/release-parser/src/com/android/cts/releaseparser/OdexParser.java
new file mode 100644
index 0000000..64b7d1f
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/OdexParser.java
@@ -0,0 +1,32 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+
+public class OdexParser extends OatParser {
+    public OdexParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.ODEX;
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
new file mode 100644
index 0000000..a7ef925
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
@@ -0,0 +1,179 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RcParser extends FileParser {
+    private static String OPTION_CLASS = "class ";
+    private static String OPTION_USER = "user ";
+    private static String OPTION_GROUP = "group ";
+    private static String OPTION_WRITEPID = "writepid ";
+    private static String SECTION_SERVICE = "service";
+    private static String SECTION_IMPORT = "import";
+
+    private Entry.EntryType mType;
+    private List<Service> mServices;
+    private List<String> mImportRc;
+
+    public RcParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mType;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        getFileEntryBuilder().addAllServices(getServiceList());
+    }
+
+    @Override
+    public List<String> getDependencies() {
+        if (mServices == null) {
+            parseFile();
+        }
+
+        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);
+        }
+
+        for (String importRc : mImportRc) {
+            // skip /, e.g. /init.usb.rc
+            String file = importRc.substring(1);
+            dependencies.put(file, 1);
+        }
+
+        return new ArrayList<String>(dependencies.keySet());
+    }
+
+    @Override
+    public String getCodeId() {
+        return getFileContentId();
+    }
+
+    public List<Service> getServiceList() {
+        if (mServices == null) {
+            parseFile();
+        }
+        return mServices;
+    }
+
+    public String toString() {
+        return toString(mServices);
+    }
+
+    public static String toString(List<Service> services) {
+        StringBuilder result = new StringBuilder();
+        for (Service service : services) {
+            result.append(
+                    String.format(
+                            "%s %s %s %s;",
+                            service.getName(),
+                            service.getClazz(),
+                            service.getUser(),
+                            service.getGroup()));
+            // System.err.println(String.format("RcParser-toString %s %s %s ", service.getName(),
+            // service.getFile(), String.join(" ", service.getArgumentsList())));
+        }
+        return result.toString();
+    }
+
+    private void parseFile() {
+        // rc file spec. at android/system/core/init/README.md
+        // android/system/core/init/init.cpp?q=CreateParser
+        mServices = new ArrayList<Service>();
+        mImportRc = new ArrayList<String>();
+        try {
+            FileReader fileReader = new FileReader(getFile());
+            BufferedReader buffReader = new BufferedReader(fileReader);
+
+            String line;
+            while ((line = buffReader.readLine()) != null) {
+                if (line.startsWith(SECTION_SERVICE)) {
+                    parseService(line, buffReader);
+                } else if (line.startsWith(SECTION_IMPORT)) {
+                    parseImport(line);
+                }
+            }
+            fileReader.close();
+            mType = Entry.EntryType.RC;
+        } catch (IOException e) {
+            // file is not a RC Config
+            System.err.println("RcParser err:" + getFileName() + "\n" + e.getMessage());
+            mType = super.getType();
+        }
+        // System.err.println(this.toString());
+    }
+
+    private void parseService(String line, BufferedReader buffReader) throws IOException {
+        Service.Builder serviceBld = Service.newBuilder();
+        String[] phases = line.split(" ");
+        serviceBld.setName(phases[1]);
+        serviceBld.setFile(phases[2]);
+        if (phases.length > 3) {
+            serviceBld.addAllArguments(Arrays.asList(phases).subList(3, phases.length));
+        }
+        String sLine;
+        while ((sLine = buffReader.readLine()) != null) {
+            String sTrimLine = sLine.trim();
+            if (sTrimLine.isEmpty()) {
+                // End of a service block
+                break;
+            }
+            if (sTrimLine.startsWith("#")) {
+                // Skips comment
+                continue;
+            } else if (sTrimLine.startsWith(OPTION_CLASS)) {
+                serviceBld.setClazz(sTrimLine.substring(OPTION_CLASS.length()));
+            } else if (sTrimLine.startsWith(OPTION_USER)) {
+                serviceBld.setUser(sTrimLine.substring(OPTION_USER.length()));
+            } else if (sTrimLine.startsWith(OPTION_GROUP)) {
+                serviceBld.setGroup(sTrimLine.substring(OPTION_GROUP.length()));
+            } else if (sTrimLine.startsWith(OPTION_WRITEPID)) {
+                serviceBld.setGroup(sTrimLine.substring(OPTION_WRITEPID.length()));
+            } else {
+                serviceBld.addOptions(sTrimLine);
+            }
+        }
+        mServices.add(serviceBld.build());
+    }
+
+    private void parseImport(String line) {
+        // e.g.: import /init.environ.rc
+        String[] phases = line.split(" ");
+        mImportRc.add(phases[1]);
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
new file mode 100644
index 0000000..5d4b9b4
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
@@ -0,0 +1,282 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class ReleaseParser {
+    private static final String ROOT_FOLDER_TAG = "/";
+    // configuration option
+    private static final String NOT_SHARDABLE_TAG = "not-shardable";
+    // test class option
+    private static final String RUNTIME_HIT_TAG = "runtime-hint";
+    // com.android.tradefed.testtype.AndroidJUnitTest option
+    private static final String PACKAGE_TAG = "package";
+    // com.android.compatibility.common.tradefed.testtype.JarHostTest option
+    private static final String JAR_NAME_TAG = "jar";
+    // com.android.tradefed.testtype.GTest option
+    private static final String NATIVE_TEST_DEVICE_PATH_TAG = "native-test-device-path";
+    private static final String MODULE_TAG = "module-name";
+
+    private static final String SUITE_API_INSTALLER_TAG =
+            "com.android.tradefed.targetprep.suite.SuiteApkInstaller";
+    private static final String JAR_HOST_TEST_TAG =
+            "com.android.compatibility.common.tradefed.testtype.JarHostTest";
+    // com.android.tradefed.targetprep.suite.SuiteApkInstaller option
+    private static final String TEST_FILE_NAME_TAG = "test-file-name";
+    // com.android.compatibility.common.tradefed.targetprep.FilePusher option
+    private static final String PUSH_TAG = "push";
+
+    // test class
+    private static final String ANDROID_JUNIT_TEST_TAG =
+            "com.android.tradefed.testtype.AndroidJUnitTest";
+
+    private static final String TESTCASES_FOLDER_FORMAT = "testcases/%s";
+
+    private final String mFolderPath;
+    private Path mRootPath;
+    private ReleaseContent.Builder mRelContentBuilder;
+    private Map<String, Entry> mEntries;
+
+    ReleaseParser(String folder) {
+        mFolderPath = folder;
+        File fFile = new File(mFolderPath);
+        mRootPath = Paths.get(fFile.getAbsolutePath());
+        mEntries = new HashMap<String, Entry>();
+    }
+
+    public String getReleaseId() {
+        ReleaseContent relContent = getReleaseContent();
+        return getReleaseId(relContent);
+    }
+
+    public static String getReleaseId(ReleaseContent relContent) {
+        return String.format(
+                "%s-%s-%s",
+                relContent.getFullname(), relContent.getVersion(), relContent.getBuildNumber());
+    }
+
+    public ReleaseContent getReleaseContent() {
+        if (mRelContentBuilder == null) {
+            mRelContentBuilder = ReleaseContent.newBuilder();
+            // default APP_DISTRIBUTION_PACKAGE if no BUILD_PROP nor TEST_SUITE_TRADEFED is found
+            mRelContentBuilder.setReleaseType(ReleaseType.APP_DISTRIBUTION_PACKAGE);
+            // also add the root folder entry
+            Entry.Builder fBuilder = parseFolder(mFolderPath);
+            if (mRelContentBuilder.getName().equals("")) {
+                System.err.println("Release Name unknown!");
+                mRelContentBuilder.setName(mFolderPath);
+                mRelContentBuilder.setFullname(mFolderPath);
+            }
+            fBuilder.setRelativePath(ROOT_FOLDER_TAG);
+            String relId = getReleaseId(mRelContentBuilder.build());
+            fBuilder.setName(relId);
+            mRelContentBuilder.setReleaseId(relId);
+            mRelContentBuilder.setContentId(fBuilder.getContentId());
+            mRelContentBuilder.setSize(fBuilder.getSize());
+            Entry fEntry = fBuilder.build();
+            mEntries.put(fEntry.getRelativePath(), fEntry);
+            mRelContentBuilder.putAllEntries(mEntries);
+        }
+        return mRelContentBuilder.build();
+    }
+
+    // Parse all files in a folder and return the foler entry builder
+    private Entry.Builder parseFolder(String fPath) {
+        Entry.Builder folderEntry = Entry.newBuilder();
+        File folder = new File(fPath);
+        Path folderPath = Paths.get(folder.getAbsolutePath());
+        String folderRelativePath = mRootPath.relativize(folderPath).toString();
+        File[] fileList = folder.listFiles();
+        Long folderSize = 0L;
+        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);
+
+                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 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();
+            }
+        }
+        folderEntry.setName(folderRelativePath);
+        folderEntry.setSize(folderSize);
+        folderEntry.setType(Entry.EntryType.FOLDER);
+        folderEntry.setContentId(getFolderContentId(folderEntry, entryList));
+        folderEntry.setRelativePath(folderRelativePath);
+        return folderEntry;
+    }
+
+    private static String getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList) {
+        String id = null;
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            for (Entry entry : entryList) {
+                md.update(entry.getContentId().getBytes(StandardCharsets.UTF_8));
+            }
+            // Converts to Base64 String
+            id = Base64.getEncoder().encodeToString(md.digest());
+        } catch (NoSuchAlgorithmException e) {
+            System.err.println("NoSuchAlgorithmException:" + e.getMessage());
+        }
+        return id;
+    }
+
+    // writes releaes content to a CSV file
+    public void writeRelesaeContentCsvFile(String relNameVer, String csvFile) {
+        try {
+            FileWriter fWriter = new FileWriter(csvFile);
+            PrintWriter pWriter = new PrintWriter(fWriter);
+            // Header
+            pWriter.printf(
+                    "release,type,name,size,relative_path,content_id,parent_folder,code_id,architecture,bits,dependencies,dynamic_loading_dependencies,services\n");
+            for (Entry entry : getFileEntries()) {
+                pWriter.printf(
+                        "%s,%s,%s,%d,%s,%s,%s,%s,%s,%d,%s,%s,%s\n",
+                        relNameVer,
+                        entry.getType(),
+                        entry.getName(),
+                        entry.getSize(),
+                        entry.getRelativePath(),
+                        entry.getContentId(),
+                        entry.getParentFolder(),
+                        entry.getCodeId(),
+                        entry.getAbiArchitecture(),
+                        entry.getAbiBits(),
+                        String.join(" ", entry.getDependenciesList()),
+                        String.join(" ", entry.getDynamicLoadingDependenciesList()),
+                        RcParser.toString(entry.getServicesList()));
+            }
+            pWriter.flush();
+            pWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    // writes known failures to a CSV file
+    public void writeKnownFailureCsvFile(String relNameVer, String csvFile) {
+        ReleaseContent relContent = getReleaseContent();
+        if (relContent.getKnownFailuresList().size() == 0) {
+            // Skip if no Known Failures
+            return;
+        }
+
+        try {
+            FileWriter fWriter = new FileWriter(csvFile);
+            PrintWriter pWriter = new PrintWriter(fWriter);
+            //Header
+            pWriter.printf("release,compatibility:exclude-filter\n");
+            for (String kf : relContent.getKnownFailuresList()) {
+                pWriter.printf("%s,%s\n", relNameVer, kf);
+            }
+            pWriter.flush();
+            pWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public Collection<Entry> getFileEntries() {
+        return getReleaseContent().getEntries().values();
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/SoParser.java b/tools/release-parser/src/com/android/cts/releaseparser/SoParser.java
new file mode 100644
index 0000000..8dec3b3
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/SoParser.java
@@ -0,0 +1,320 @@
+/*
+ * 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.releaseparser;
+
+import com.android.compatibility.common.util.ReadElf;
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class SoParser extends FileParser {
+    private int mBits;
+    private String mArch;
+    private List<String> mDependencies;
+    private List<String> mDynamicLoadingDependencies;
+    private ReadElf mElf;
+    private String mPackageName;
+    private ApiPackage.Builder mExternalApiPackageBuilder;
+    private HashMap<String, ApiClass.Builder> mExternalApiClassBuilderMap;
+    private ApiPackage.Builder mInternalApiPackageBuilder;
+    private AppInfo.Builder mAppInfoBuilder;
+    private boolean mParseInternalApi;
+
+    public SoParser(File file) {
+        super(file);
+        mBits = 0;
+        mArch = null;
+        mDependencies = null;
+        mDynamicLoadingDependencies = null;
+        mAppInfoBuilder = null;
+        // default is the file name with out extenion
+        mPackageName = getFileName().split("\\.")[0];
+        // default off to avoid a large output
+        mParseInternalApi = false;
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.SO;
+    }
+
+    @Override
+    public String getCodeId() {
+        return getFileContentId();
+    }
+
+    @Override
+    public List<String> getDependencies() {
+        if (mDependencies == null) {
+            parse();
+        }
+        return mDependencies;
+    }
+
+    @Override
+    public List<String> getDynamicLoadingDependencies() {
+        if (mDynamicLoadingDependencies == null) {
+            parse();
+        }
+        return mDynamicLoadingDependencies;
+    }
+
+    @Override
+    public int getAbiBits() {
+        if (mBits == 0) {
+            parse();
+        }
+        return mBits;
+    }
+
+    @Override
+    public String getAbiArchitecture() {
+        if (mArch == null) {
+            parse();
+        }
+        return mArch;
+    }
+
+    public void setPackageName(String name) {
+        String[] subStr = name.split(File.separator);
+        mPackageName = subStr[subStr.length - 1];
+    }
+
+    public void setParseInternalApi(boolean parseInternalApi) {
+        mParseInternalApi = parseInternalApi;
+    }
+
+    public AppInfo getAppInfo() {
+        if (mAppInfoBuilder == null) {
+            mAppInfoBuilder = AppInfo.newBuilder();
+            mAppInfoBuilder.setPackageName(mPackageName);
+            mAppInfoBuilder.addInternalApiPackages(getInternalApiPackage());
+            mAppInfoBuilder.addExternalApiPackages(getExternalApiPackage());
+        }
+        return mAppInfoBuilder.build();
+    }
+
+    public ApiPackage getExternalApiPackage() {
+        if (mExternalApiPackageBuilder == null) {
+            parse();
+        }
+        return mExternalApiPackageBuilder.build();
+    }
+
+    public ApiPackage getInternalApiPackage() {
+        if (mInternalApiPackageBuilder == null) {
+            parse();
+        }
+        return mInternalApiPackageBuilder.build();
+    }
+
+    private void parse() {
+        mExternalApiPackageBuilder = ApiPackage.newBuilder();
+        mExternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>();
+        mInternalApiPackageBuilder = ApiPackage.newBuilder();
+        try {
+            ReadElf mElf = ReadElf.read(getFile());
+            mBits = mElf.getBits();
+            mArch = mElf.getArchitecture();
+            mDependencies = mElf.getDynamicDependencies();
+            // Check Dynamic Loading dependencies
+            mDynamicLoadingDependencies = getDynamicLoadingDependencies(mElf);
+            parseApi(mElf.getDynSymArr());
+        } catch (Exception ex) {
+            mDependencies = super.getDependencies();
+            mDynamicLoadingDependencies = super.getDynamicLoadingDependencies();
+            mBits = -1;
+            mArch = "unknown";
+            getLogger()
+                    .log(
+                            Level.SEVERE,
+                            String.format(
+                                    "SoParser fails to parse %s. \n%s",
+                                    getFileName(), ex.getMessage()));
+            ex.printStackTrace();
+        }
+    }
+
+    private void parseApi(ReadElf.Symbol[] symArr) {
+        ApiClass.Builder mInternalApiClassBuilder = ApiClass.newBuilder();
+        mInternalApiClassBuilder.setName(mPackageName);
+
+        for (ReadElf.Symbol symbol : symArr) {
+            if (symbol.isExtern()) {
+                // Internal methods & fields
+                if (mParseInternalApi) {
+                    // Skips reference symbols
+                    if (isInternalReferenceSymbol(symbol)) {
+                        continue;
+                    }
+                    if (symbol.type == ReadElf.Symbol.STT_OBJECT) {
+                        ApiField.Builder fieldBuilder = ApiField.newBuilder();
+                        fieldBuilder.setName(symbol.name);
+                        mInternalApiClassBuilder.addFields(fieldBuilder.build());
+                    } else {
+                        ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
+                        methodBuilder.setName(symbol.name);
+                        mInternalApiClassBuilder.addMethods(methodBuilder.build());
+                    }
+                }
+            } else if (symbol.isGlobalUnd()) {
+                // External dependency
+                String className = symbol.getExternalLibFileName();
+                ApiClass.Builder apiClassBuilder =
+                        ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, className);
+                if (symbol.type == ReadElf.Symbol.STT_OBJECT) {
+                    ApiField.Builder fieldBuilder = ApiField.newBuilder();
+                    fieldBuilder.setName(symbol.name);
+                    apiClassBuilder.addFields(fieldBuilder.build());
+                } else {
+                    ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
+                    methodBuilder.setName(symbol.name);
+                    apiClassBuilder.addMethods(methodBuilder.build());
+                }
+            }
+        }
+        if (mParseInternalApi) {
+            mInternalApiPackageBuilder.addClasses(mInternalApiClassBuilder.build());
+        }
+        ClassUtils.addAllApiClasses(mExternalApiClassBuilderMap, mExternalApiPackageBuilder);
+    }
+
+    private ApiClass.Builder getApiClassBuilder(
+            HashMap<String, ApiClass.Builder> apiClassBuilderMap, String name) {
+        ApiClass.Builder builder = apiClassBuilderMap.get(name);
+        if (builder == null) {
+            builder = ApiClass.newBuilder().setName(ClassUtils.getCanonicalName(name));
+            apiClassBuilderMap.put(name, builder);
+        }
+        return builder;
+    }
+
+    // internal reference symboles
+    private static final HashMap<String, String> sInternalReferenceSymboleMap;
+
+    static {
+        sInternalReferenceSymboleMap = new HashMap<String, String>();
+        sInternalReferenceSymboleMap.put("__bss_start", "bss");
+        sInternalReferenceSymboleMap.put("_end", "initialized data");
+        sInternalReferenceSymboleMap.put("_edata", "uninitialized data");
+    }
+
+    private static boolean isInternalReferenceSymbol(ReadElf.Symbol sym) {
+        String value = sInternalReferenceSymboleMap.get(sym.name);
+        if (value == null) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private List<String> getDynamicLoadingDependencies(ReadElf elf) throws IOException {
+        List<String> depList = new ArrayList<>();
+        // check if it does refer to dlopen
+        if (elf.getDynamicSymbol("dlopen") != null) {
+            List<String> roStrings = elf.getRoStrings();
+            for (String str : roStrings) {
+                // skip ".so" or less
+                if (str.length() < 4) {
+                    continue;
+                }
+
+                if (str.endsWith(".so")) {
+                    // skip itself
+                    if (str.contains(getFileName())) {
+                        continue;
+                    }
+                    if (str.contains(" ")) {
+                        continue;
+                    }
+                    if (str.contains("?")) {
+                        continue;
+                    }
+                    if (str.contains("%")) {
+                        System.err.println("ToDo getDynamicLoadingDependencies: " + str);
+                        continue;
+                    }
+                    if (str.startsWith("_")) {
+                        System.err.println("ToDo getDynamicLoadingDependencies: " + str);
+                        continue;
+                    }
+                    depList.add(str);
+                }
+            }
+        }
+
+        // specific for frameworks/native/opengl/libs/EGL/Loader.cpp load_system_driver()
+        if ("libEGL.so".equals(getFileName())) {
+            depList.add("libEGL*.so");
+            depList.add("libGLESv1_CM*.so");
+            depList.add("GLESv2*.so");
+        }
+
+        return depList;
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + ApkParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase SO file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+            boolean parseInternalApi = argParser.containsOption("pi");
+
+            File aFile = new File(fileName);
+            SoParser aParser = new SoParser(aFile);
+            aParser.setPackageName(fileName);
+            aParser.setParseInternalApi(parseInternalApi);
+
+            if (outputFileName != null) {
+                FileOutputStream txtOutput = new FileOutputStream(outputFileName);
+                txtOutput.write(
+                        TextFormat.printToString(aParser.getAppInfo())
+                                .getBytes(Charset.forName("UTF-8")));
+                txtOutput.flush();
+                txtOutput.close();
+            } else {
+                System.out.println(TextFormat.printToString(aParser.getAppInfo()));
+            }
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(SoParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/SymbolicLinkParser.java b/tools/release-parser/src/com/android/cts/releaseparser/SymbolicLinkParser.java
new file mode 100644
index 0000000..3f235d4
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/SymbolicLinkParser.java
@@ -0,0 +1,47 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SymbolicLinkParser extends FileParser {
+    public SymbolicLinkParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.SYMBOLIC_LINK;
+    }
+
+    @Override
+    public List<String> getDependencies() {
+        ArrayList results = new ArrayList<String>();
+        results.add(getFileName());
+        return results;
+    }
+
+    @Override
+    public String getFileContentId() {
+        // NO_ID for Symbolic Link
+        return mContentId;
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigHandler.java b/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigHandler.java
new file mode 100644
index 0000000..5251f07
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigHandler.java
@@ -0,0 +1,138 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * {@link DefaultHandler} that builds an empty {@link ApiCoverage} object from scanning
+ * TestModule.xml.
+ */
+class TestModuleConfigHandler extends DefaultHandler {
+    private static final String CONFIGURATION_TAG = "configuration";
+    private static final String DESCRIPTION_TAG = "description";
+    private static final String OPTION_TAG = "option";
+    private static final String TARGET_PREPARER_TAG = "target_preparer";
+    private static final String TEST_TAG = "test";
+    private static final String CLASS_TAG = "class";
+    private static final String NAME_TAG = "name";
+    private static final String KEY_TAG = "key";
+    private static final String VALUE_TAG = "value";
+    private static final String MODULE_NAME_TAG = "module-name";
+    private static final String GTEST_CLASS_TAG = "com.android.tradefed.testtype.GTest";
+    // com.android.compatibility.common.tradefed.testtype.JarHostTest option
+    private static final String JAR_TAG = "jar";
+    // com.android.tradefed.targetprep.suite.SuiteApkInstaller option
+    private static final String TEST_FILE_NAME_TAG = "test-file-name";
+
+    private TestModuleConfig.Builder mTestModuleConfig;
+    private TestModuleConfig.TargetPreparer.Builder mTargetPreparer;
+    private TestModuleConfig.TestClass.Builder mTestCase;
+    private String mModuleName = null;
+
+    TestModuleConfigHandler(String configFileName) {
+        mTestModuleConfig = TestModuleConfig.newBuilder();
+        mTestCase = null;
+        mTargetPreparer = null;
+        // Default Module Name is the Config File Name
+        mModuleName = configFileName.replaceAll(".config$", "");
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String name, Attributes attributes)
+            throws SAXException {
+        super.startElement(uri, localName, name, attributes);
+
+        switch (localName) {
+            case CONFIGURATION_TAG:
+                if (null != attributes.getValue(DESCRIPTION_TAG)) {
+                    mTestModuleConfig.setDescription(attributes.getValue(DESCRIPTION_TAG));
+                } else {
+                    mTestModuleConfig.setDescription("WARNING: no description.");
+                }
+                break;
+            case TEST_TAG:
+                mTestCase = TestModuleConfig.TestClass.newBuilder();
+                mTestCase.setTestClass(attributes.getValue(CLASS_TAG));
+                break;
+            case TARGET_PREPARER_TAG:
+                mTargetPreparer = TestModuleConfig.TargetPreparer.newBuilder();
+                mTargetPreparer.setTestClass(attributes.getValue(CLASS_TAG));
+                break;
+            case OPTION_TAG:
+                Option.Builder option = Option.newBuilder();
+                option.setName(attributes.getValue(NAME_TAG));
+                option.setValue(attributes.getValue(VALUE_TAG));
+                String keyStr = attributes.getValue(KEY_TAG);
+                if (null != keyStr) {
+                    option.setKey(keyStr);
+                }
+                if (null != mTestCase) {
+                    mTestCase.addOptions(option);
+                    switch (option.getName()) {
+                        case JAR_TAG:
+                            mTestModuleConfig.addTestJars(option.getValue());
+                            break;
+                        case GTEST_CLASS_TAG:
+                            mModuleName = option.getValue();
+                            break;
+                    }
+                } else if (null != mTargetPreparer) {
+                    mTargetPreparer.addOptions(option);
+                    if (TEST_FILE_NAME_TAG.equalsIgnoreCase(option.getName())) {
+                        mTestModuleConfig.addTestFileNames(option.getValue());
+                    }
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String name) throws SAXException {
+        super.endElement(uri, localName, name);
+        switch (localName) {
+            case CONFIGURATION_TAG:
+                mTestModuleConfig.setModuleName(mModuleName);
+                break;
+            case TARGET_PREPARER_TAG:
+                mTestModuleConfig.addTargetPreparers(mTargetPreparer);
+                mTargetPreparer = null;
+                break;
+            case TEST_TAG:
+                mTestModuleConfig.addTestClasses(mTestCase);
+                mTestCase = null;
+                break;
+        }
+    }
+
+    public String getModuleName() {
+        return mModuleName;
+    }
+
+    public String getTestClassName() {
+        //return the 1st Test Class
+        return mTestModuleConfig.getTestClassesList().get(0).getTestClass();
+    }
+
+    public TestModuleConfig getTestModuleConfig() {
+        return mTestModuleConfig.build();
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigParser.java b/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigParser.java
new file mode 100644
index 0000000..45def09
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/TestModuleConfigParser.java
@@ -0,0 +1,80 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class TestModuleConfigParser extends FileParser {
+    private Entry.EntryType mType;
+    private TestModuleConfig mTestModuleConfig;
+
+    public TestModuleConfigParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mType;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        if (mType == Entry.EntryType.TEST_MODULE_CONFIG) {
+            // only if it's actaully a test module config file
+            getFileEntryBuilder().setTestModuleConfig(getTestModuleConfig());
+        }
+
+    }
+
+    public TestModuleConfig getTestModuleConfig() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mTestModuleConfig;
+    }
+
+    private void parseFile() {
+        try {
+            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+            TestModuleConfigHandler testModuleXmlHandler =
+                    new TestModuleConfigHandler(getFileName());
+            xmlReader.setContentHandler(testModuleXmlHandler);
+            FileReader fileReader = new FileReader(getFile());
+            xmlReader.parse(new InputSource(fileReader));
+            mTestModuleConfig = testModuleXmlHandler.getTestModuleConfig();
+            fileReader.close();
+            mType = Entry.EntryType.TEST_MODULE_CONFIG;
+        } catch (IOException | SAXException e) {
+            // file is not a Test Module Config
+            System.err.println(
+                    "TestModuleConfigParser err:" + getFileName() + "\n" + e.getMessage());
+            mType = Entry.EntryType.FILE;
+        }
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteParser.java b/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteParser.java
new file mode 100644
index 0000000..d8a65ba
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteParser.java
@@ -0,0 +1,623 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import junit.framework.Test;
+
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.value.TypeEncodedValue;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+class TestSuiteParser {
+    // JUNIT3 Test suffix
+    private static final String TEST_TAG = "Test;";
+    // Some may ends with Tests e.g. cts/tests/tests/accounts/src/android/accounts/cts/AbstractAuthenticatorTests.java
+    private static final String TESTS_TAG = "Tests;";
+    private static final String TEST_PREFIX_TAG = "test";
+    private static final String DEPRECATED_ANNOTATION_TAG = "Ljava/lang/Deprecated;";
+    private static final String RUN_WITH_ANNOTATION_TAG = "Lorg/junit/runner/RunWith;";
+    private static final String TEST_ANNOTATION_TAG = "Lorg/junit/Test;";
+    private static final String SUPPRESS_ANNOTATION_TAG = "/Suppress;";
+    private static final String ANDROID_JUNIT4_TEST_TAG =
+            "Landroid/support/test/runner/AndroidJUnit4;";
+    private static final String PARAMETERIZED_TEST_TAG = "Lorg/junit/runners/Parameterized;";
+
+    // configuration option
+    private static final String NOT_SHARDABLE_TAG = "not-shardable";
+    // test class option
+    private static final String RUNTIME_HIT_TAG = "runtime-hint";
+    // com.android.tradefed.testtype.AndroidJUnitTest option
+    private static final String PACKAGE_TAG = "package";
+    // com.android.compatibility.common.tradefed.testtype.JarHostTest option
+    private static final String JAR_TAG = "jar";
+    // com.android.tradefed.testtype.GTest option
+    private static final String NATIVE_TEST_DEVICE_PATH_TAG = "native-test-device-path";
+    private static final String MODULE_TAG = "module-name";
+    private static final String TESTCASES_FOLDER_FORMAT = "testcases/%s";
+
+    private static final String SUITE_API_INSTALLER_TAG =
+            "com.android.tradefed.targetprep.suite.SuiteApkInstaller";
+    private static final String HOST_TEST_CLASS_TAG =
+            "com.android.compatibility.common.tradefed.testtype.JarHostTest";
+    // com.android.tradefed.targetprep.suite.SuiteApkInstaller option
+    private static final String TEST_FILE_NAME_TAG = "test-file-name";
+    // com.android.compatibility.common.tradefed.targetprep.FilePusher option
+    private static final String PUSH_TAG = "push";
+
+    // test class
+    private static final String ANDROID_JUNIT_TEST_TAG =
+            "com.android.tradefed.testtype.AndroidJUnitTest";
+    private static final String DEQP_TEST_TAG = "com.drawelements.deqp.runner.DeqpTestRunner";
+    private static final String GTEST_TAG = "com.android.tradefed.testtype.GTest";
+    private static final String LIBCORE_TEST_TAG = "com.android.compatibility.testtype.LibcoreTest";
+    private static final String DALVIK_TEST_TAG = "com.android.compatibility.testtype.DalvikTest";
+
+    // Target File Extensions
+    private static final String CONFIG_EXT_TAG = ".config";
+    private static final String CONFIG_REGEX = ".config$";
+    private static final String JAR_EXT_TAG = ".jar";
+    private static final String APK_EXT_TAG = ".apk";
+    private static final String SO_EXT_TAG = ".so";
+
+    // [module].[class]#[method]
+    public static final String TESTCASE_NAME_FORMAT = "%s.%s#%s";
+
+    private final String mFolderPath;
+    private ReleaseContent mRelContent;
+    private TestSuite.Builder mTSBuilder;
+
+    TestSuiteParser(ReleaseContent relContent, String folder) {
+        mFolderPath = folder;
+        mRelContent = relContent;
+    }
+
+    public TestSuite getTestSuite() {
+        if (mTSBuilder == null) {
+            mTSBuilder = praseTestSuite();
+        }
+        return mTSBuilder.build();
+    }
+
+    private TestSuite.Builder praseTestSuite() {
+        TestSuite.Builder tsBuilder = TestSuite.newBuilder();
+
+        tsBuilder.setName(mRelContent.getName());
+        tsBuilder.setVersion(mRelContent.getVersion());
+        tsBuilder.setBuildNumber(mRelContent.getBuildNumber());
+
+        // Iterates all file
+        for (Entry entry : getFileEntriesList(mRelContent)) {
+            // Only parses test module config files
+            if (Entry.EntryType.TEST_MODULE_CONFIG == entry.getType()) {
+                TestModuleConfig config = entry.getTestModuleConfig();
+                TestSuite.Module.Builder moduleBuilder = praseModule(config);
+                moduleBuilder.setConfigFile(entry.getRelativePath());
+                tsBuilder.addModules(moduleBuilder);
+            }
+        }
+        return tsBuilder;
+    }
+
+    private TestSuite.Module.Builder praseModule(TestModuleConfig config) {
+        TestSuite.Module.Builder moduleBuilder = TestSuite.Module.newBuilder();
+        // parse test package and class
+        List<TestModuleConfig.TestClass> testClassesList = config.getTestClassesList();
+        moduleBuilder.setName(config.getModuleName());
+        for (TestModuleConfig.TestClass tClass : testClassesList) {
+            String testClass = tClass.getTestClass();
+            moduleBuilder.setTestClass(testClass);
+            switch (testClass) {
+                case ANDROID_JUNIT_TEST_TAG:
+                    moduleBuilder.setTestType(TestSuite.TestType.ANDROIDJUNIT);
+                    parseAndroidJUnitTest(moduleBuilder, config, tClass.getTestClass());
+                    break;
+                case HOST_TEST_CLASS_TAG:
+                    moduleBuilder.setTestType(TestSuite.TestType.JAVAHOST);
+                    parseJavaHostTest(moduleBuilder, config, tClass.getTestClass());
+                    break;
+                default:
+                    //ToDo
+                    moduleBuilder.setTestType(TestSuite.TestType.UNKNOWN);
+                    ApiPackage.Builder pkgBuilder = ApiPackage.newBuilder();
+                    moduleBuilder.addPackages(pkgBuilder);
+                    System.err.printf(
+                            "ToDo Test Type: %s %s\n", tClass.getTestClass(), tClass.getPackage());
+            }
+        }
+        return moduleBuilder;
+    }
+
+    private void parseAndroidJUnitTest(
+            TestSuite.Module.Builder moduleBuilder, TestModuleConfig config, String tClass) {
+        // getting apk list from Test Module Configuration
+        List<TestModuleConfig.TargetPreparer> tPrepList = config.getTargetPreparersList();
+        for (TestModuleConfig.TargetPreparer tPrep : tPrepList) {
+            for (Option opt : tPrep.getOptionsList()) {
+                if (TEST_FILE_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+                    ApiPackage.Builder pkgBuilder = ApiPackage.newBuilder();
+                    String testFileName = opt.getValue();
+                    Entry tEntry = getFileEntry(testFileName);
+                    pkgBuilder.setName(testFileName);
+                    pkgBuilder.setPackageFile(tEntry.getRelativePath());
+                    pkgBuilder.setContentId(tEntry.getContentId());
+                    parseApkTestCase(pkgBuilder, config);
+                    moduleBuilder.addPackages(pkgBuilder);
+                }
+            }
+        }
+    }
+
+    private Entry getFileEntry(String name) {
+        Entry fEntry = null;
+        for (Entry et : getFileEntriesList(mRelContent)) {
+            if (name.equals(et.getName())) {
+                fEntry = et;
+                break;
+            }
+        }
+        return fEntry;
+    }
+    // Parses test case list from an APK
+    private void parseApkTestCase(ApiPackage.Builder pkgBuilder, TestModuleConfig config) {
+        DexFile dexFile = null;
+        String apkPath = Paths.get(mFolderPath, pkgBuilder.getPackageFile()).toString();
+        String moduleName = config.getModuleName();
+
+        // Loads a Dex file
+        try {
+            dexFile = DexFileFactory.loadDexFile(apkPath, Opcodes.getDefault());
+
+            // Iterates through all clesses in the Dex file
+            for (ClassDef classDef : dexFile.getClasses()) {
+                // adjust the format Lclass/y;
+                String className = classDef.getType().replace('/', '.');
+                // remove L...;
+                if (className.length() > 2) {
+                    className = className.substring(1, className.length() - 1);
+                }
+
+                // Parses test classes
+                TestClassType cType = chkTestClassType(classDef);
+                ApiClass.Builder tClassBuilder = ApiClass.newBuilder();
+                switch (cType) {
+                    case JUNIT3:
+                        tClassBuilder.setTestClassType(cType);
+                        tClassBuilder.setName(className);
+                        // Checks all test method
+                        for (Method method : classDef.getMethods()) {
+                            // Only care about Public
+                            if ((method.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0) {
+                                String mName = method.getName();
+                                // Warn current test result accounting does not work well with Supress
+                                if (hasAnnotationSuffix(
+                                        method.getAnnotations(), SUPPRESS_ANNOTATION_TAG)) {
+                                    System.err.printf("%s#%s with Suppress:\n", className, mName);
+                                } else if (mName.startsWith(TEST_PREFIX_TAG)) {
+                                    // Junit3 style test case name starts with test
+                                    tClassBuilder.addMethods(
+                                            newTestBuilder(
+                                                    moduleName, className, method.getName()));
+                                } else if (hasAnnotationSuffix(
+                                        method.getAnnotations(), TEST_ANNOTATION_TAG)) {
+                                    tClassBuilder.addMethods(
+                                            newTestBuilder(
+                                                    moduleName, className, method.getName()));
+                                    System.err.printf(
+                                            "%s#%s JUNIT3 mixes with %s annotation:\n",
+                                            className, mName, TEST_ANNOTATION_TAG);
+                                }
+                            }
+                        }
+                        pkgBuilder.addClasses(tClassBuilder);
+                        break;
+                    case PARAMETERIZED:
+                        // ToDo need to find a way to count Parameterized tests
+                        System.err.printf("To count Parameterized tests: %s\n", className);
+                        tClassBuilder.setTestClassType(cType);
+                        tClassBuilder.setName(className);
+                        for (Method method : classDef.getMethods()) {
+                            // Junit4 style test case annotated with @Test
+                            if (hasAnnotation(method.getAnnotations(), TEST_ANNOTATION_TAG)) {
+                                tClassBuilder.addMethods(
+                                        newTestBuilder(moduleName, className, method.getName()));
+                            }
+                        }
+                        pkgBuilder.addClasses(tClassBuilder);
+                        break;
+                    case JUNIT4:
+                        tClassBuilder.setTestClassType(cType);
+                        tClassBuilder.setName(className);
+                        for (Method method : classDef.getMethods()) {
+                            // Junit4 style test case annotated with @Test
+                            if (hasAnnotation(method.getAnnotations(), TEST_ANNOTATION_TAG)) {
+                                tClassBuilder.addMethods(
+                                        newTestBuilder(moduleName, className, method.getName()));
+                            }
+                        }
+                        pkgBuilder.addClasses(tClassBuilder);
+                        break;
+                    default:
+                        // Not a known test class
+                }
+            }
+        } catch (IOException | DexFileFactory.DexFileNotFoundException ex) {
+            System.err.println("Unable to load dex file: " + apkPath);
+            // ex.printStackTrace();
+        }
+    }
+
+    private ApiMethod.Builder newTestBuilder(String moduleName, String className, String testName) {
+        ApiMethod.Builder testBuilder = ApiMethod.newBuilder();
+        testBuilder.setName(testName);
+        // Check if it's an known failure
+        String nfFilter = getKnownFailureFilter(moduleName, className, testName);
+        if (null != nfFilter) {
+            testBuilder.setKnownFailureFilter(nfFilter);
+        }
+        return testBuilder;
+    }
+
+    private void parseJavaHostTest(
+            TestSuite.Module.Builder moduleBuilder, TestModuleConfig config, String tClass) {
+        ApiPackage.Builder pkgBuilder = ApiPackage.newBuilder();
+        //Assuming there is only one test Jar
+        String testFileName = config.getTestJars(0);
+        Entry tEntry = getFileEntry(testFileName);
+        String jarPath = tEntry.getRelativePath();
+
+        pkgBuilder.setName(testFileName);
+        pkgBuilder.setPackageFile(jarPath);
+        pkgBuilder.setContentId(tEntry.getContentId());
+        Collection<Class<?>> classes =
+                getJarTestClasses(
+                        Paths.get(mFolderPath, jarPath).toFile(),
+                        // Includes [x]-tradefed.jar for classes such as CompatibilityHostTestBase
+                        Paths.get(mFolderPath, mRelContent.getTestSuiteTradefed()).toFile());
+
+        for (Class<?> c : classes) {
+            ApiClass.Builder tClassBuilder = ApiClass.newBuilder();
+            tClassBuilder.setTestClassType(TestClassType.JAVAHOST);
+            tClassBuilder.setName(c.getName());
+
+            for (java.lang.reflect.Method m : c.getMethods()) {
+                int mdf = m.getModifiers();
+                if (Modifier.isPublic(mdf) || Modifier.isProtected(mdf)) {
+                    if (m.getName().startsWith(TEST_PREFIX_TAG)) {
+                        ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
+                        methodBuilder.setName(m.getName());
+                        // Check if it's an known failure
+                        String nfFilter =
+                                getKnownFailureFilter(
+                                        config.getModuleName(), c.getName(), m.getName());
+                        if (null != nfFilter) {
+                            methodBuilder.setKnownFailureFilter(nfFilter);
+                        }
+                        tClassBuilder.addMethods(methodBuilder);
+                    }
+                }
+            }
+            pkgBuilder.addClasses(tClassBuilder);
+        }
+        moduleBuilder.addPackages(pkgBuilder);
+    }
+
+    private static boolean hasAnnotation(Set<? extends Annotation> annotations, String tag) {
+        for (Annotation annotation : annotations) {
+            if (annotation.getType().equals(tag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasAnnotationSuffix(Set<? extends Annotation> annotations, String tag) {
+        for (Annotation annotation : annotations) {
+            if (annotation.getType().endsWith(tag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static TestClassType chkTestClassType(ClassDef classDef) {
+        // Only care about Public Class
+        if ((classDef.getAccessFlags() & AccessFlags.PUBLIC.getValue()) == 0) {
+            return TestClassType.UNKNOWN;
+        }
+
+        for (Annotation annotation : classDef.getAnnotations()) {
+            if (annotation.getType().equals(DEPRECATED_ANNOTATION_TAG)) {
+                return TestClassType.UNKNOWN;
+            }
+            if (annotation.getType().equals(RUN_WITH_ANNOTATION_TAG)) {
+                for (AnnotationElement annotationEle : annotation.getElements()) {
+                    if ("value".equals(annotationEle.getName())) {
+                        String aValue = ((TypeEncodedValue) annotationEle.getValue()).getValue();
+                        if (ANDROID_JUNIT4_TEST_TAG.equals(aValue)) {
+                            return TestClassType.JUNIT4;
+                        } else if (PARAMETERIZED_TEST_TAG.equals(aValue)) {
+                            return TestClassType.PARAMETERIZED;
+                        }
+                    }
+                }
+                System.err.printf("Unknown test class type: %s\n", classDef.getType());
+                return TestClassType.JUNIT4;
+            }
+        }
+
+        if (classDef.getType().endsWith(TEST_TAG) || classDef.getType().endsWith(TESTS_TAG)) {
+            return TestClassType.JUNIT3;
+        } else {
+            return TestClassType.UNKNOWN;
+        }
+    }
+
+    private static boolean isTargetClass(List<String> pkgList, String className) {
+        boolean found = false;
+        for (String pkg : pkgList) {
+            if (className.startsWith(pkg)) {
+                found = true;
+                break;
+            }
+        }
+        return found;
+    }
+
+    private static Collection<Class<?>> getJarTestClasses(File jarTestFile, File tfFile)
+            throws IllegalArgumentException {
+        List<Class<?>> classes = new ArrayList<>();
+
+        try (JarFile jarFile = new JarFile(jarTestFile)) {
+            Enumeration<JarEntry> e = jarFile.entries();
+
+            URL[] urls = {
+                new URL(String.format("jar:file:%s!/", jarTestFile.getAbsolutePath())),
+                new URL(String.format("jar:file:%s!/", tfFile.getAbsolutePath()))
+            };
+            URLClassLoader cl =
+                    URLClassLoader.newInstance(urls, JarTestFinder.class.getClassLoader());
+
+            while (e.hasMoreElements()) {
+                JarEntry je = e.nextElement();
+                if (je.isDirectory()
+                        || !je.getName().endsWith(".class")
+                        || je.getName().contains("$")
+                        || je.getName().contains("junit/")) {
+                    continue;
+                }
+                String className = getClassName(je.getName());
+
+                /*if (!className.endsWith("Test")) {
+                    continue;
+                }*/
+                try {
+                    Class<?> cls = cl.loadClass(className);
+
+                    if (IRemoteTest.class.isAssignableFrom(cls)
+                            || Test.class.isAssignableFrom(cls)) {
+                        classes.add(cls);
+                    } else if (!Modifier.isAbstract(cls.getModifiers())
+                            && hasJUnit4Annotation(cls)) {
+                        classes.add(cls);
+                    }
+                } catch (ClassNotFoundException | Error x) {
+                    System.err.println(
+                            String.format(
+                                    "Cannot find test class %s from %s",
+                                    className, jarTestFile.getName()));
+                    x.printStackTrace();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return classes;
+    }
+
+    /** Helper to determine if we are dealing with a Test class with Junit4 annotations. */
+    protected static boolean hasJUnit4Annotation(Class<?> classObj) {
+        if (classObj.isAnnotationPresent(SuiteClasses.class)) {
+            return true;
+        }
+        if (classObj.isAnnotationPresent(RunWith.class)) {
+            return true;
+        }
+        /*for (Method m : classObj.getMethods()) {
+            if (m.isAnnotationPresent(org.junit.Test.class)) {
+                return true;
+            }
+        }*/
+        return false;
+    }
+
+    private static String getClassName(String name) {
+        // -6 because of .class
+        return name.substring(0, name.length() - 6).replace('/', '.');
+    }
+
+    private String getKnownFailureFilter(String tModule, String tClass, String tMethod) {
+        List<String> knownFailures = mRelContent.getKnownFailuresList();
+        String tsName = String.format(TESTCASE_NAME_FORMAT, tModule, tClass, tMethod);
+        for (String kf : knownFailures) {
+            if (tsName.startsWith(kf)) {
+                return kf;
+            }
+        }
+        return null;
+    }
+
+    // Iterates though all test suite content and prints them.
+    public void writeCsvFile(String relNameVer, String csvFile) {
+        TestSuite ts = getTestSuite();
+        try {
+            FileWriter fWriter = new FileWriter(csvFile);
+            PrintWriter pWriter = new PrintWriter(fWriter);
+            //Header
+            pWriter.println(
+                    "release,module,test_class,test,test_package,test_type,known_failure_filter,package_content_id");
+            for (TestSuite.Module module : ts.getModulesList()) {
+                for (ApiPackage pkg : module.getPackagesList()) {
+                    for (ApiClass cls : pkg.getClassesList()) {
+                        for (ApiMethod mtd : cls.getMethodsList()) {
+                            pWriter.printf(
+                                    "%s,%s,%s,%s,%s,%s,%s,%s\n",
+                                    relNameVer,
+                                    module.getName(),
+                                    cls.getName(),
+                                    mtd.getName(),
+                                    pkg.getPackageFile(),
+                                    cls.getTestClassType(),
+                                    mtd.getKnownFailureFilter(),
+                                    getTestTargetContentId(pkg.getPackageFile()));
+                        }
+                    }
+                }
+            }
+            pWriter.flush();
+            pWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    // Iterates though all test module and prints them.
+    public void writeModuleCsvFile(String relNameVer, String csvFile) {
+        TestSuite ts = getTestSuite();
+        try {
+            FileWriter fWriter = new FileWriter(csvFile);
+            PrintWriter pWriter = new PrintWriter(fWriter);
+
+            //Header
+            pWriter.print(
+                    "release,module,test_no,known_failure_no,test_type,test_class,component,description,test_config_file,test_file_names,test_jars,module_content_id\n");
+
+            for (TestSuite.Module module : ts.getModulesList()) {
+                int classCnt = 0;
+                int methodCnt = 0;
+                int kfCnt = 0;
+                for (ApiPackage pkg : module.getPackagesList()) {
+                    for (ApiClass cls : pkg.getClassesList()) {
+                        for (ApiMethod mtd : cls.getMethodsList()) {
+                            // Filter out known failures
+                            if (mtd.getKnownFailureFilter().isEmpty()) {
+                                methodCnt++;
+                            } else {
+                                kfCnt++;
+                            }
+                        }
+                        classCnt++;
+                    }
+                }
+                String config = module.getConfigFile();
+                Entry entry = mRelContent.getEntries().get(config);
+                TestModuleConfig tmConfig = entry.getTestModuleConfig();
+                pWriter.printf(
+                        "%s,%s,%d,%d,%s,%s,%s,%s,%s,%s,%s,%s\n",
+                        relNameVer,
+                        module.getName(),
+                        methodCnt,
+                        kfCnt,
+                        module.getTestType(),
+                        module.getTestClass(),
+                        tmConfig.getComponent(),
+                        tmConfig.getDescription(),
+                        config,
+                        String.join(" ", tmConfig.getTestFileNamesList()),
+                        String.join(" ", tmConfig.getTestJarsList()),
+                        getTestModuleContentId(
+                                entry,
+                                tmConfig.getTestFileNamesList(),
+                                tmConfig.getTestJarsList()));
+            }
+            pWriter.flush();
+            pWriter.close();
+        } catch (IOException e) {
+            System.err.println("IOException:" + e.getMessage());
+        }
+    }
+
+    public static Collection<Entry> getFileEntriesList(ReleaseContent relContent) {
+        return relContent.getEntries().values();
+    }
+
+    // get Test Module Content Id = config cid + apk cids + jar cids
+    private String getTestModuleContentId(Entry config, List<String> apks, List<String> jars) {
+        String id = null;
+        //Starts with config file content_id
+        String idStr = config.getContentId();
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            //Add all apk content_id
+            for (String apk : apks) {
+                idStr += getTestTargetContentId(String.format(TESTCASES_FOLDER_FORMAT, apk));
+            }
+            //Add all jar content_id
+            for (String jar : jars) {
+                idStr += getTestTargetContentId(String.format(TESTCASES_FOLDER_FORMAT, jar));
+            }
+            md.update(idStr.getBytes(StandardCharsets.UTF_8));
+            // Converts to Base64 String
+            id = Base64.getEncoder().encodeToString(md.digest());
+            // System.out.println("getTestModuleContentId: " + idStr);
+        } catch (NoSuchAlgorithmException e) {
+            System.err.println("NoSuchAlgorithmException:" + e.getMessage());
+        }
+        return id;
+    }
+
+    private String getTestTargetContentId(String targetFile) {
+        Entry entry = mRelContent.getEntries().get(targetFile);
+        if (entry != null) {
+            return entry.getContentId();
+        } else {
+            System.err.println("No getTestTargetContentId: " + targetFile);
+            return "";
+        }
+    }
+
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteTradefedParser.java b/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteTradefedParser.java
new file mode 100644
index 0000000..fb04748
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/TestSuiteTradefedParser.java
@@ -0,0 +1,195 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+public class TestSuiteTradefedParser extends JarParser {
+    private static final String TEST_SUITE_INFO_PROPERTIES_FILE = "test-suite-info.properties";
+    private static final String KNOWN_FAILURES_XML_FILE = "-known-failures.xml";
+    private static final String EXCLUDE_FILTER_TAG = "compatibility:exclude-filter";
+    private static final String NAME_TAG = "name";
+    private static final String VALUE_TAG = "value";
+
+    private Entry.EntryType mType;
+    private List<String> mKnownFailureList;
+    private String mName;
+    private String mFullname;
+    private String mBuildNumber;
+    private String mTargetArch;
+    private String mVersion;
+
+    public TestSuiteTradefedParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mType;
+    }
+
+    public String getName() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mName;
+    }
+
+    public String getFullName() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mFullname;
+    }
+
+    public String getBuildNumber() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mBuildNumber;
+    }
+
+    public String getTargetArch() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mTargetArch;
+    }
+
+    public String getVersion() {
+        if (mType == null) {
+            parseFile();
+        }
+        return mVersion;
+    }
+
+    public List<String> getKnownFailureList() {
+        if (mKnownFailureList == null) {
+            mKnownFailureList = new ArrayList<String>();
+            praseKnownFailure();
+        }
+        return mKnownFailureList;
+    }
+
+    private void praseKnownFailure() {
+        try {
+            ZipFile zip = new ZipFile(getFile());
+            try {
+                Enumeration<? extends ZipEntry> entries = zip.entries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = entries.nextElement();
+
+                    if (entry.getName().endsWith(KNOWN_FAILURES_XML_FILE)) {
+                        SAXParserFactory spf = SAXParserFactory.newInstance();
+                        spf.setNamespaceAware(false);
+                        SAXParser saxParser = spf.newSAXParser();
+                        InputStream xmlStream = zip.getInputStream(entry);
+                        KnownFailuresXmlHandler kfXmlHandler =
+                                new KnownFailuresXmlHandler();
+                        saxParser.parse(xmlStream, kfXmlHandler);
+                        xmlStream.close();
+                    }
+                }
+            } finally {
+                zip.close();
+            }
+        } catch (Exception e) {
+            System.err.println(String.format("Cannot praseKnownFailure %s", e.getMessage()));
+        }
+    }
+
+    private class KnownFailuresXmlHandler extends DefaultHandler {
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            super.startElement(uri, localName, name, attributes);
+            if (EXCLUDE_FILTER_TAG.equals(attributes.getValue(NAME_TAG))) {
+                String kfFilter = attributes.getValue(VALUE_TAG).replace(' ', '.');
+                mKnownFailureList.add(kfFilter);
+            }
+        }
+    }
+
+    private void parseFile() {
+        try {
+            ZipFile zip = new ZipFile(getFile());
+            try {
+                Enumeration<? extends ZipEntry> entries = zip.entries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = entries.nextElement();
+
+                    if (entry.getName().equals(TEST_SUITE_INFO_PROPERTIES_FILE)) {
+                        InputStream inStream = zip.getInputStream(entry);
+                        InputStreamReader isReader = new InputStreamReader(inStream, "UTF-8");
+                        BufferedReader bfReader = new BufferedReader(isReader);
+                        String ln;
+                        while((ln = bfReader.readLine()) != null) {
+                            String[] tokens = ln.split(" = ");
+                            switch (tokens[0]) {
+                                case "build_number":
+                                    mBuildNumber = tokens[1];
+                                    break;
+                                case "target_arch":
+                                    mTargetArch = tokens[1];
+                                    break;
+                                case "name":
+                                    mName = tokens[1];
+                                    break;
+                                case "fullname":
+                                    mFullname = tokens[1];
+                                    break;
+                                case "version":
+                                    mVersion = tokens[1];
+                                    break;
+                            }
+                        }
+                        inStream.close();
+                        isReader.close();
+                        bfReader.close();
+                    }
+                }
+                mType = Entry.EntryType.TEST_SUITE_TRADEFED;
+            } finally {
+                zip.close();
+            }
+        } catch (Exception e) {
+            System.err.println(
+                    String.format("Cannot %s %s", TEST_SUITE_INFO_PROPERTIES_FILE, e.getMessage()));
+            mType = super.getType();
+        }
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/VdexParser.java b/tools/release-parser/src/com/android/cts/releaseparser/VdexParser.java
new file mode 100644
index 0000000..a843cdb
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/VdexParser.java
@@ -0,0 +1,157 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+// art/runtime/vdex_file.h & vdex_file.cc
+public class VdexParser extends FileParser {
+    // The magic values for the VDEX identification.
+    private static final byte[] VDEX_MAGIC = {(byte) 'v', (byte) 'd', (byte) 'e', (byte) 'x'};
+    private static final int HEADER_SIZE = 64;
+    private VdexInfo.Builder mVdexInfoBuilder;
+
+    public VdexParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.VDEX;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        getFileEntryBuilder().setVdexInfo(getVdexInfo());
+    }
+
+    @Override
+    public String getCodeId() {
+        if (mVdexInfoBuilder == null) {
+            parse();
+        }
+        return mCodeId;
+    }
+
+    public VdexInfo getVdexInfo() {
+        if (mVdexInfoBuilder == null) {
+            parse();
+        }
+        return mVdexInfoBuilder.build();
+    }
+
+    private void parse() {
+        byte[] buffer = new byte[HEADER_SIZE];
+        mVdexInfoBuilder = VdexInfo.newBuilder();
+
+        try {
+            RandomAccessFile raFile = new RandomAccessFile(getFile(), "r");
+            raFile.seek(0);
+            raFile.readFully(buffer, 0, HEADER_SIZE);
+            raFile.close();
+
+            // ToDo: this is specific for 019 VerifierDepsVersion. Need to handle changes for older
+            // versions
+            if (buffer[0] != VDEX_MAGIC[0]
+                    || buffer[1] != VDEX_MAGIC[1]
+                    || buffer[2] != VDEX_MAGIC[2]
+                    || buffer[3] != VDEX_MAGIC[3]) {
+                String content = new String(buffer);
+                System.err.println("Invalid VDEX file:" + getFileName() + " " + content);
+                throw new IllegalArgumentException("Invalid VDEX MAGIC");
+            }
+            int offset = 4;
+            String version = new String(Arrays.copyOfRange(buffer, offset, offset + 4));
+            mVdexInfoBuilder.setVerifierDepsVersion(version);
+            offset += 4;
+            String dex_section_version = new String(Arrays.copyOfRange(buffer, offset, offset + 4));
+            mVdexInfoBuilder.setDexSectionVersion(dex_section_version);
+            offset += 4;
+            int numberOfDexFiles = getIntLittleEndian(buffer, offset);
+            mVdexInfoBuilder.setNumberOfDexFiles(numberOfDexFiles);
+            offset += 4;
+            mVdexInfoBuilder.setVerifierDepsSize(getIntLittleEndian(buffer, offset));
+            offset += 4;
+
+            // Code Id format: [0xchecksum1],...
+            StringBuilder codeIdSB = new StringBuilder();
+            for (int i = 0; i < numberOfDexFiles; i++) {
+                int checksums = getIntLittleEndian(buffer, offset);
+                offset += 4;
+                mVdexInfoBuilder.addChecksums(checksums);
+                codeIdSB.append(String.format(CODE_ID_FORMAT, checksums));
+            }
+            mCodeId = codeIdSB.toString();
+
+            for (int i = 0; i < numberOfDexFiles; i++) {
+                DexSectionHeader.Builder dshBuilder = DexSectionHeader.newBuilder();
+                dshBuilder.setDexSize(getIntLittleEndian(buffer, offset));
+                offset += 4;
+                dshBuilder.setDexSharedDataSize(getIntLittleEndian(buffer, offset));
+                offset += 4;
+                dshBuilder.setQuickeningInfoSize(getIntLittleEndian(buffer, offset));
+                offset += 4;
+                mVdexInfoBuilder.addDexSectionHeaders(dshBuilder.build());
+                offset += 4;
+            }
+
+            for (int i = 0; i < numberOfDexFiles; i++) {
+                int quicken_table_off = getIntLittleEndian(buffer, offset);
+                offset += 4;
+                // Todo processing Dex
+            }
+
+        } catch (Exception ex) {
+            System.err.println("Invalid VDEX file:" + getFileName());
+            mVdexInfoBuilder.setValid(false);
+        }
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + VdexParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to parse VDEX file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+
+            File aFile = new File(fileName);
+            VdexParser aParser = new VdexParser(aFile);
+            Entry fileEntry = aParser.getFileEntryBuilder().build();
+            writeTextFormatMessage(outputFileName, fileEntry);
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(VdexParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
new file mode 100644
index 0000000..c30e524
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
@@ -0,0 +1,209 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+import java.util.HashMap;
+
+/** {@link DefaultHandler} */
+class XmlHandler extends DefaultHandler {
+    // Root Element Tag
+    public static final String PERMISSIONS_TAG = "permissions";
+
+    // Element Tag
+    public static final String PERMISSION_TAG = "permission";
+    public static final String ASSIGN_PERMISSION_TAG = "assign-permission";
+    public static final String LIBRARY_TAG = "library";
+    public static final String ALLOW_IN_POWER_SAVE_TAG = "allow-in-power-save";
+    public static final String SYSTEM_USER_WHITELISTED_TAG = "system-user-whitelisted-app";
+    public static final String PRIVAPP_PERMISSIONS_TAG = "privapp-permissions";
+    public static final String FEATURE_TAG = "feature";
+
+    // Attribue Tag
+    private static final String NAME_TAG = "name";
+    private static final String GID_TAG = "gid";
+    private static final String UID_TAG = "uid";
+    private static final String GROUP_TAG = "group";
+    private static final String FILE_TAG = "file";
+    private static final String PACKAGE_TAG = "package";
+    private static final String VERSION_TAG = "version";
+
+    private HashMap<String, PermissionList> mPermissions;
+    private Permission.Builder mPermissionsBuilder;
+    private PermissionList.Builder mPermissionListBuilder;
+    private PermissionList.Builder mAssignPermissionsListBuilder;
+    private PermissionList.Builder mLibraryListBuilder;
+    private PermissionList.Builder mAllowInPowerSaveListBuilder;
+    private PermissionList.Builder mSystemUserWhitelistedListBuilder;
+    private PermissionList.Builder mPrivappPermissionsListBuilder;
+    private PermissionList.Builder mFeatureListBuilder;
+
+    XmlHandler(String fileName) {
+        mPermissions = new HashMap<String, PermissionList>();
+        mPermissionListBuilder = PermissionList.newBuilder();
+        mPermissionListBuilder.setName(PERMISSION_TAG);
+        mAssignPermissionsListBuilder = PermissionList.newBuilder();
+        mAssignPermissionsListBuilder.setName(ASSIGN_PERMISSION_TAG);
+        mLibraryListBuilder = PermissionList.newBuilder();
+        mLibraryListBuilder.setName(LIBRARY_TAG);
+        mAllowInPowerSaveListBuilder = PermissionList.newBuilder();
+        mAllowInPowerSaveListBuilder.setName(ALLOW_IN_POWER_SAVE_TAG);
+        mSystemUserWhitelistedListBuilder = PermissionList.newBuilder();
+        mSystemUserWhitelistedListBuilder.setName(SYSTEM_USER_WHITELISTED_TAG);
+        mPrivappPermissionsListBuilder = PermissionList.newBuilder();
+        mPrivappPermissionsListBuilder.setName(PRIVAPP_PERMISSIONS_TAG);
+        mFeatureListBuilder = PermissionList.newBuilder();
+        mFeatureListBuilder.setName(FEATURE_TAG);
+    }
+
+    public HashMap<String, PermissionList> getPermissions() {
+        return mPermissions;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String name, Attributes attributes)
+            throws SAXException {
+        super.startElement(uri, localName, name, attributes);
+
+        switch (localName) {
+            case PERMISSIONS_TAG:
+                // start permissions tree
+                break;
+            case PERMISSION_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(NAME_TAG));
+                break;
+            case GROUP_TAG:
+                if (mPermissionsBuilder != null) {
+                    Element.Builder eleBuilder = Element.newBuilder();
+                    eleBuilder.setName(GID_TAG);
+                    eleBuilder.setValue(attributes.getValue(GID_TAG));
+                    mPermissionsBuilder.addElements(eleBuilder.build());
+                }
+                break;
+            case ASSIGN_PERMISSION_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(NAME_TAG));
+                String uid = attributes.getValue(UID_TAG);
+                if (uid != null) {
+                    Element.Builder eleBuilder = Element.newBuilder();
+                    eleBuilder.setName(UID_TAG);
+                    eleBuilder.setValue(uid);
+                    mPermissionsBuilder.addElements(eleBuilder.build());
+                }
+                break;
+            case LIBRARY_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(NAME_TAG));
+                String file = attributes.getValue(FILE_TAG);
+                if (file != null) {
+                    Element.Builder eleBuilder = Element.newBuilder();
+                    eleBuilder.setName(FILE_TAG);
+                    eleBuilder.setValue(file);
+                    mPermissionsBuilder.addElements(eleBuilder.build());
+                }
+                break;
+            case ALLOW_IN_POWER_SAVE_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
+                break;
+            case SYSTEM_USER_WHITELISTED_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
+                break;
+            case PRIVAPP_PERMISSIONS_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
+                break;
+            case FEATURE_TAG:
+                mPermissionsBuilder = Permission.newBuilder();
+                mPermissionsBuilder.setName(attributes.getValue(NAME_TAG));
+                String version = attributes.getValue(VERSION_TAG);
+                if (version != null) {
+                    Element.Builder eleBuilder = Element.newBuilder();
+                    eleBuilder.setName(VERSION_TAG);
+                    eleBuilder.setValue(version);
+                    mPermissionsBuilder.addElements(eleBuilder.build());
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String name) throws SAXException {
+        super.endElement(uri, localName, name);
+        switch (localName) {
+            case PERMISSIONS_TAG:
+                if (mPermissionListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(PERMISSION_TAG, mPermissionListBuilder.build());
+                }
+                if (mAssignPermissionsListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(ASSIGN_PERMISSION_TAG, mAssignPermissionsListBuilder.build());
+                }
+                if (mLibraryListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(LIBRARY_TAG, mLibraryListBuilder.build());
+                }
+                if (mAllowInPowerSaveListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(ALLOW_IN_POWER_SAVE_TAG, mAllowInPowerSaveListBuilder.build());
+                }
+                if (mSystemUserWhitelistedListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(
+                            SYSTEM_USER_WHITELISTED_TAG, mSystemUserWhitelistedListBuilder.build());
+                }
+                if (mPrivappPermissionsListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(
+                            PRIVAPP_PERMISSIONS_TAG, mPrivappPermissionsListBuilder.build());
+                }
+                if (mFeatureListBuilder.getPermissionsList().size() > 0) {
+                    mPermissions.put(FEATURE_TAG, mFeatureListBuilder.build());
+                }
+                break;
+            case PERMISSION_TAG:
+                mPermissionListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case ASSIGN_PERMISSION_TAG:
+                mAssignPermissionsListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case LIBRARY_TAG:
+                mLibraryListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case ALLOW_IN_POWER_SAVE_TAG:
+                mAllowInPowerSaveListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case SYSTEM_USER_WHITELISTED_TAG:
+                mSystemUserWhitelistedListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case PRIVAPP_PERMISSIONS_TAG:
+                mPrivappPermissionsListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+            case FEATURE_TAG:
+                mFeatureListBuilder.addPermissions(mPermissionsBuilder.build());
+                mPermissionsBuilder = null;
+                break;
+        }
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/XmlParser.java b/tools/release-parser/src/com/android/cts/releaseparser/XmlParser.java
new file mode 100644
index 0000000..1146106
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/XmlParser.java
@@ -0,0 +1,103 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+public class XmlParser extends FileParser {
+    private XmlHandler mHandler;
+    private HashMap<String, PermissionList> mPermissions;
+
+    public XmlParser(File file) {
+        super(file);
+    }
+
+    @Override
+    public Entry.EntryType getType() {
+        return Entry.EntryType.XML;
+    }
+
+    @Override
+    public void setAdditionalInfo() {
+        HashMap<String, PermissionList> permissions = getPermissions();
+        if (permissions != null) {
+            getFileEntryBuilder().putAllDevicePermissions(permissions);
+        }
+    }
+
+    public HashMap<String, PermissionList> getPermissions() {
+        if (mPermissions == null) {
+            parse();
+        }
+        return mPermissions;
+    }
+
+    // Todo readPermissions() from frameworks/base/core/java/com/android/server/SystemConfig.java
+    // for Feature set
+    private void parse() {
+        try {
+            mHandler = new XmlHandler(getFileName());
+            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+            xmlReader.setContentHandler(mHandler);
+            FileReader fileReader = new FileReader(getFile());
+            xmlReader.parse(new InputSource(fileReader));
+            mPermissions = mHandler.getPermissions();
+            fileReader.close();
+        } catch (Exception e) {
+            // file is not a Test Module Config
+            System.err.println("Fail to parse:" + getFileName() + "\n" + e.getMessage());
+        }
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + XmlParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase platform permissions xml file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The file path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterElement("i", 0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+
+            File aFile = new File(fileName);
+            XmlParser aParser = new XmlParser(aFile);
+            Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+            writeTextFormatMessage(outputFileName, fileEntryBuilder.build());
+        } catch (Exception ex) {
+            System.out.println(USAGE_MESSAGE);
+            ex.printStackTrace();
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(XmlParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ZipParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ZipParser.java
new file mode 100644
index 0000000..eb2642e
--- /dev/null
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ZipParser.java
@@ -0,0 +1,228 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ZipParser extends FileParser {
+    private PackageFileContent.Builder mPackageFileContentBuilder;
+    private HashMap<String, Entry> mFileMap;
+    private List<String> mDependencies;
+    private List<String> mDynamicLoadingDependencies;
+    private boolean mParseSo;
+    private boolean mParseInternalApi;
+    private StringBuilder mCodeIdStringBuilder;
+
+    // Todo: provide utilities to parse files in zip, e.g. SOs in an APK
+    ZipParser(File file) {
+        super(file);
+        mParseSo = true;
+        // default off to avoid a large output
+        mParseInternalApi = false;
+        mCodeIdStringBuilder = null;
+    }
+
+    @Override
+    public List<String> getDependencies() {
+        if (mDependencies == null) {
+            parseFileContent();
+        }
+        return mDependencies;
+    }
+
+    @Override
+    public List<String> getDynamicLoadingDependencies() {
+        if (mDynamicLoadingDependencies == null) {
+            // This also parses DynamicLoadingDependencies
+            getDependencies();
+        }
+        return mDynamicLoadingDependencies;
+    }
+
+    @Override
+    public String getCodeId() {
+        if (mCodeIdStringBuilder == null) {
+            parseFileContent();
+        }
+        return mCodeIdStringBuilder.toString();
+    }
+
+    @Override
+    public Entry.Builder getFileEntryBuilder() {
+        if (mFileEntryBuilder == null) {
+            super.getFileEntryBuilder();
+            parseFileContent();
+            mFileEntryBuilder.addAllDependencies(getDependencies());
+            mFileEntryBuilder.addAllDynamicLoadingDependencies(getDynamicLoadingDependencies());
+        }
+        return mFileEntryBuilder;
+    }
+
+    public void setParseSo(boolean parseSo) {
+        mParseSo = parseSo;
+    }
+
+    public void setParseInternalApi(boolean parseInternalApi) {
+        mParseInternalApi = parseInternalApi;
+    }
+
+    public static File getFilefromZip(ZipFile zipFile, ZipEntry zipEntry) throws IOException {
+        String fileName = zipEntry.getName();
+        File tmpFile = File.createTempFile("RPZ", "");
+        tmpFile.deleteOnExit();
+        InputStream iStream = zipFile.getInputStream(zipEntry);
+        FileOutputStream fOutputStream = new FileOutputStream(tmpFile);
+        byte[] buffer = new byte[READ_BLOCK_SIZE];
+        int length;
+        while ((length = iStream.read(buffer)) >= 0) {
+            fOutputStream.write(buffer, 0, length);
+        }
+        iStream.close();
+        fOutputStream.close();
+        return tmpFile;
+    }
+
+    public PackageFileContent getPackageFileContent() {
+        if (mPackageFileContentBuilder == null) {
+            parseFileContent();
+        }
+        return mPackageFileContentBuilder.build();
+    }
+
+    private void parseFileContent() {
+        ZipFile zFile = null;
+        mPackageFileContentBuilder = PackageFileContent.newBuilder();
+        mFileMap = new HashMap<String, Entry>();
+        mDependencies = new ArrayList<String>();
+        mDynamicLoadingDependencies = new ArrayList<String>();
+        mCodeIdStringBuilder = new StringBuilder();
+
+        try {
+            zFile = new ZipFile(getFile());
+
+            final Enumeration<? extends ZipEntry> entries = zFile.entries();
+            while (entries.hasMoreElements()) {
+                final ZipEntry entry = entries.nextElement();
+                final String name = entry.getName();
+
+                Entry.Builder entryBuilder = Entry.newBuilder();
+                entryBuilder.setName(name);
+                entryBuilder.setContentId(String.format(CODE_ID_FORMAT, entry.hashCode()));
+                entryBuilder.setSize(entry.getSize());
+                if (entry.isDirectory()) {
+                    entryBuilder.setType(Entry.EntryType.FOLDER);
+                } else {
+                    entryBuilder.setType(Entry.EntryType.FILE);
+                    appendToCodeID(entry);
+                    if (mParseSo) {
+                        // ToDo: to be optimized if taking too long
+                        if (name.endsWith(SO_EXT_TAG)) {
+                            try {
+                                final File soFile = getFilefromZip(zFile, entry);
+                                SoParser fParser = new SoParser(soFile);
+                                fParser.setPackageName(name);
+                                fParser.setParseInternalApi(mParseInternalApi);
+
+                                if (fParser.getDependencies() != null) {
+                                    mDependencies.addAll(fParser.getDependencies());
+                                }
+                                if (fParser.getDynamicLoadingDependencies() != null) {
+                                    mDynamicLoadingDependencies.addAll(
+                                            fParser.getDynamicLoadingDependencies());
+                                }
+                                entryBuilder.setAppInfo(fParser.getAppInfo());
+                            } catch (IOException ex) {
+                                System.err.println(
+                                        "Failed to parse: " + name + "\n" + ex.getMessage());
+                            }
+                        }
+                    }
+                }
+                mFileMap.put(name, entryBuilder.build());
+            }
+            mPackageFileContentBuilder.putAllEntries(mFileMap);
+        } catch (IOException e) {
+            System.err.println("Failed to parse: " + getFileName() + "\n" + e.getMessage());
+            // error while opening a ZIP file
+        } finally {
+            if (zFile != null) {
+                try {
+                    zFile.close();
+                } catch (IOException e) {
+                    System.err.println("Failed to close: " + getFileName() + "\n" + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private void appendToCodeID(ZipEntry zEntry) {
+        String name = zEntry.getName();
+        if (name.endsWith(SO_EXT_TAG)
+                || name.endsWith(DEX_EXT_TAG)
+                || name.endsWith(ANDROID_MANIFEST_TAG)) {
+            mCodeIdStringBuilder.append(String.format(CODE_ID_FORMAT, zEntry.hashCode()));
+        }
+    }
+
+    private static final String USAGE_MESSAGE =
+            "Usage: java -jar releaseparser.jar "
+                    + ZipParser.class.getCanonicalName()
+                    + " [-options <parameter>]...\n"
+                    + "           to prase ZIP file meta data\n"
+                    + "Options:\n"
+                    + "\t-i PATH\t The path of the file to be parsed.\n"
+                    + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n"
+                    + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
+                    + "\t-s \t Skips parsing embedded SO files if it takes too long time.\n";
+
+    public static void main(String[] args) {
+        try {
+            ArgumentParser argParser = new ArgumentParser(args);
+            String fileName = argParser.getParameterList("i").get(0);
+            String outputFileName = argParser.getParameterElement("of", 0);
+            boolean parseSo = !argParser.containsOption("s");
+            boolean parseInternalApi = argParser.containsOption("pi");
+
+            File aFile = new File(fileName);
+            ZipParser aParser = new ZipParser(aFile);
+            aParser.setParseSo(parseSo);
+            aParser.setParseInternalApi(parseInternalApi);
+
+            Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+            writeTextFormatMessage(outputFileName, fileEntryBuilder.build());
+        } catch (Exception ex) {
+            System.out.printf(USAGE_MESSAGE);
+            System.err.printf(ex.getMessage());
+        }
+    }
+
+    private static Logger getLogger() {
+        return Logger.getLogger(ZipParser.class.getSimpleName());
+    }
+}
diff --git a/tools/release-parser/tests/Android.bp b/tools/release-parser/tests/Android.bp
new file mode 100644
index 0000000..4f3c7e2
--- /dev/null
+++ b/tools/release-parser/tests/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+// cts release-parser java unit test library
+// ============================================================
+java_test_host {
+    name: "release-parser-tests",
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    libs: [
+        "release-parser",
+        "junit",
+        "tradefed",
+    ],
+
+    // Holds golden sample files in assets for validation
+    java_resource_dirs: ["resources/"],
+}
\ No newline at end of file
diff --git a/tools/release-parser/tests/generate_golden_sample_files.sh b/tools/release-parser/tests/generate_golden_sample_files.sh
new file mode 100755
index 0000000..2bb8eeb
--- /dev/null
+++ b/tools/release-parser/tests/generate_golden_sample_files.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# This script generats expected parse results as protobuf files in text format.
+# They will be used in unit tests to validate Parsers.
+# A typical flow is as follows:
+# 1. add the file name in targetFile below after adding a new object file in ./resources
+# 2. run: ./generate_golden_sample_files.sh
+# 3. manully validate the content of output protobuf files
+# 4. update UnitTests & respective test code to use the new files
+# 5. build: make release-parser release-parser-tests -j10
+# 6. test: ./cts/tools/release-parser/tests/run_test.sh
+
+echo Generating golden sample files for parser validation
+TargetFiles="HelloActivity.apk CtsJniTestCases.apk Shell.apk"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.ApkParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="libEGL.so"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.SoParser -i resources/$file -pi -of resources/$file.pb.txt
+done
+
+TargetFiles="CtsAslrMallocTestCases32"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.SoParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="android.test.runner.vdex"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.VdexParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="android.test.runner.odex"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.OdexParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="boot-framework.oat"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.OatParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="boot-framework.art"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.ArtParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="platform.xml android.hardware.vulkan.version.xml"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.XmlParser -i resources/$file -of resources/$file.pb.txt
+done
+
+TargetFiles="build.prop"
+for file in $TargetFiles; do
+    echo Processing $file
+    java -cp $ANDROID_HOST_OUT/framework/release-parser.jar com.android.cts.releaseparser.BuildPropParser -i resources/$file -of resources/$file.pb.txt
+done
diff --git a/tools/release-parser/tests/resources/CtsAslrMallocTestCases32 b/tools/release-parser/tests/resources/CtsAslrMallocTestCases32
new file mode 100755
index 0000000..bb709f5
--- /dev/null
+++ b/tools/release-parser/tests/resources/CtsAslrMallocTestCases32
Binary files differ
diff --git a/tools/release-parser/tests/resources/CtsAslrMallocTestCases32.pb.txt b/tools/release-parser/tests/resources/CtsAslrMallocTestCases32.pb.txt
new file mode 100644
index 0000000..18498f0
--- /dev/null
+++ b/tools/release-parser/tests/resources/CtsAslrMallocTestCases32.pb.txt
@@ -0,0 +1,575 @@
+package_name: "CtsAslrMallocTestCases32"
+external_api_packages {
+  classes {
+    name: "*global*"
+    methods {
+      name: "_ZN7android4base12StringPrintfEPKcz"
+    }
+    methods {
+      name: "_ZN7android4base14ReadFdToStringEiPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE"
+    }
+    methods {
+      name: "_ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7compareEjjPKcj"
+    }
+    methods {
+      name: "_ZNSt3__112__next_primeEj"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6assignEPKc"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev"
+    }
+    methods {
+      name: "_ZdlPv"
+    }
+    methods {
+      name: "_Znwj"
+    }
+    methods {
+      name: "__android_log_print"
+    }
+    methods {
+      name: "__cxa_pure_virtual"
+    }
+    methods {
+      name: "_ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4findEcj"
+    }
+    methods {
+      name: "_ZNKSt3__120__vector_base_commonILb1EE20__throw_length_errorEv"
+    }
+    methods {
+      name: "_ZNKSt3__120__vector_base_commonILb1EE20__throw_out_of_rangeEv"
+    }
+    methods {
+      name: "_ZNKSt3__121__basic_string_commonILb1EE20__throw_length_errorEv"
+    }
+    methods {
+      name: "_ZNKSt3__16locale9has_facetERNS0_2idE"
+    }
+    methods {
+      name: "_ZNKSt3__16locale9use_facetERNS0_2idE"
+    }
+    methods {
+      name: "_ZNKSt3__18ios_base6getlocEv"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKc"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendEPKcj"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6insertEjPKc"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6resizeEjc"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEj"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC1ERKS5_jjRKS4_"
+    }
+    methods {
+      name: "_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSERKS5_"
+    }
+    methods {
+      name: "_ZNSt3__113basic_istreamIcNS_11char_traitsIcEEE6sentryC1ERS3_b"
+    }
+    methods {
+      name: "_ZNSt3__113basic_istreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_istreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_istreamIcNS_11char_traitsIcEEED2Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_istreamIcNS_11char_traitsIcEEErsERi"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE3putEc"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE5flushEv"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE5writeEPKci"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEd"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEj"
+    }
+    methods {
+      name: "_ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEx"
+    }
+    methods {
+      name: "_ZNSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZNSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZNSt3__114basic_iostreamIcNS_11char_traitsIcEEED2Ev"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE4syncEv"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE5imbueERKNS_6localeE"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE5uflowEv"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE6setbufEPci"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE6xsgetnEPci"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE6xsputnEPKci"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEE9showmanycEv"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEEC2Ev"
+    }
+    methods {
+      name: "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEED2Ev"
+    }
+    methods {
+      name: "_ZNSt3__16localeC1ERKS0_"
+    }
+    methods {
+      name: "_ZNSt3__16localeD1Ev"
+    }
+    methods {
+      name: "_ZNSt3__18ios_base4initEPv"
+    }
+    methods {
+      name: "_ZNSt3__18ios_base5clearEj"
+    }
+    methods {
+      name: "_ZNSt3__19basic_iosIcNS_11char_traitsIcEEED2Ev"
+    }
+    methods {
+      name: "_ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_"
+    }
+    methods {
+      name: "_ZThn8_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZThn8_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__113basic_istreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__113basic_istreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__113basic_ostreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__113basic_ostreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev"
+    }
+    methods {
+      name: "_ZTv0_n12_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev"
+    }
+    methods {
+      name: "_ZdaPv"
+    }
+    methods {
+      name: "_Znaj"
+    }
+    methods {
+      name: "__cxa_guard_acquire"
+    }
+    methods {
+      name: "__cxa_guard_release"
+    }
+    methods {
+      name: "__dynamic_cast"
+    }
+    fields {
+      name: "_ZNSt3__14cerrE"
+    }
+    fields {
+      name: "_ZNSt3__15ctypeIcE2idE"
+    }
+    fields {
+      name: "_ZNSt3__17codecvtIcc9mbstate_tE2idE"
+    }
+    fields {
+      name: "_ZTINSt3__113basic_istreamIcNS_11char_traitsIcEEEE"
+    }
+    fields {
+      name: "_ZTINSt3__113basic_ostreamIcNS_11char_traitsIcEEEE"
+    }
+    fields {
+      name: "_ZTINSt3__114basic_iostreamIcNS_11char_traitsIcEEEE"
+    }
+    fields {
+      name: "_ZTINSt3__115basic_streambufIcNS_11char_traitsIcEEEE"
+    }
+    fields {
+      name: "_ZTVN10__cxxabiv117__class_type_infoE"
+    }
+    fields {
+      name: "_ZTVN10__cxxabiv120__si_class_type_infoE"
+    }
+  }
+  classes {
+    name: "libm.so"
+    methods {
+      name: "ceilf"
+    }
+    methods {
+      name: "log2"
+    }
+  }
+  classes {
+    name: "libc.so"
+    methods {
+      name: "__cxa_atexit"
+    }
+    methods {
+      name: "__libc_init"
+    }
+    methods {
+      name: "__aeabi_memcpy"
+    }
+    methods {
+      name: "__errno"
+    }
+    methods {
+      name: "__readlink_chk"
+    }
+    methods {
+      name: "__stack_chk_fail"
+    }
+    methods {
+      name: "__strchr_chk"
+    }
+    methods {
+      name: "abort"
+    }
+    methods {
+      name: "close"
+    }
+    methods {
+      name: "dup2"
+    }
+    methods {
+      name: "execl"
+    }
+    methods {
+      name: "fork"
+    }
+    methods {
+      name: "free"
+    }
+    methods {
+      name: "isspace"
+    }
+    methods {
+      name: "malloc"
+    }
+    methods {
+      name: "pipe"
+    }
+    methods {
+      name: "printf"
+    }
+    methods {
+      name: "strlen"
+    }
+    methods {
+      name: "strtoull"
+    }
+    methods {
+      name: "tolower"
+    }
+    methods {
+      name: "waitpid"
+    }
+    methods {
+      name: "__aeabi_memclr"
+    }
+    methods {
+      name: "__aeabi_memclr4"
+    }
+    methods {
+      name: "__aeabi_memclr8"
+    }
+    methods {
+      name: "__aeabi_memmove"
+    }
+    methods {
+      name: "__aeabi_memset"
+    }
+    methods {
+      name: "__fwrite_chk"
+    }
+    methods {
+      name: "__getcwd_chk"
+    }
+    methods {
+      name: "__memcpy_chk"
+    }
+    methods {
+      name: "__read_chk"
+    }
+    methods {
+      name: "__strlen_chk"
+    }
+    methods {
+      name: "__write_chk"
+    }
+    methods {
+      name: "_exit"
+    }
+    methods {
+      name: "access"
+    }
+    methods {
+      name: "chdir"
+    }
+    methods {
+      name: "clone"
+    }
+    methods {
+      name: "connect"
+    }
+    methods {
+      name: "dup"
+    }
+    methods {
+      name: "execve"
+    }
+    methods {
+      name: "exit"
+    }
+    methods {
+      name: "fclose"
+    }
+    methods {
+      name: "fcntl"
+    }
+    methods {
+      name: "fdopen"
+    }
+    methods {
+      name: "fflush"
+    }
+    methods {
+      name: "fileno"
+    }
+    methods {
+      name: "fopen"
+    }
+    methods {
+      name: "fputc"
+    }
+    methods {
+      name: "fputs"
+    }
+    methods {
+      name: "fread"
+    }
+    methods {
+      name: "freeaddrinfo"
+    }
+    methods {
+      name: "fseek"
+    }
+    methods {
+      name: "fseeko"
+    }
+    methods {
+      name: "ftell"
+    }
+    methods {
+      name: "ftello"
+    }
+    methods {
+      name: "fwrite"
+    }
+    methods {
+      name: "gai_strerror"
+    }
+    methods {
+      name: "getaddrinfo"
+    }
+    methods {
+      name: "getcwd"
+    }
+    methods {
+      name: "getenv"
+    }
+    methods {
+      name: "getpagesize"
+    }
+    methods {
+      name: "getpid"
+    }
+    methods {
+      name: "gettimeofday"
+    }
+    methods {
+      name: "isatty"
+    }
+    methods {
+      name: "localtime_r"
+    }
+    methods {
+      name: "memcmp"
+    }
+    methods {
+      name: "mkdir"
+    }
+    methods {
+      name: "mkstemp"
+    }
+    methods {
+      name: "mmap"
+    }
+    methods {
+      name: "munmap"
+    }
+    methods {
+      name: "pthread_getspecific"
+    }
+    methods {
+      name: "pthread_key_create"
+    }
+    methods {
+      name: "pthread_key_delete"
+    }
+    methods {
+      name: "pthread_mutex_destroy"
+    }
+    methods {
+      name: "pthread_mutex_init"
+    }
+    methods {
+      name: "pthread_mutex_lock"
+    }
+    methods {
+      name: "pthread_mutex_unlock"
+    }
+    methods {
+      name: "pthread_self"
+    }
+    methods {
+      name: "pthread_setspecific"
+    }
+    methods {
+      name: "putchar"
+    }
+    methods {
+      name: "puts"
+    }
+    methods {
+      name: "regexec"
+    }
+    methods {
+      name: "remove"
+    }
+    methods {
+      name: "sigaction"
+    }
+    methods {
+      name: "sigemptyset"
+    }
+    methods {
+      name: "socket"
+    }
+    methods {
+      name: "stat"
+    }
+    methods {
+      name: "strcasecmp"
+    }
+    methods {
+      name: "strchr"
+    }
+    methods {
+      name: "strcmp"
+    }
+    methods {
+      name: "strdup"
+    }
+    methods {
+      name: "strerror"
+    }
+    methods {
+      name: "strncmp"
+    }
+    methods {
+      name: "strrchr"
+    }
+    methods {
+      name: "strstr"
+    }
+    methods {
+      name: "strtol"
+    }
+    methods {
+      name: "toupper"
+    }
+    methods {
+      name: "vprintf"
+    }
+    methods {
+      name: "write"
+    }
+    methods {
+      name: "raise"
+    }
+    fields {
+      name: "__stack_chk_guard"
+    }
+    fields {
+      name: "environ"
+    }
+    fields {
+      name: "stderr"
+    }
+    fields {
+      name: "stdout"
+    }
+  }
+}
+internal_api_packages {
+}
diff --git a/tools/release-parser/tests/resources/CtsJniTestCases.apk b/tools/release-parser/tests/resources/CtsJniTestCases.apk
new file mode 100644
index 0000000..7d88310
--- /dev/null
+++ b/tools/release-parser/tests/resources/CtsJniTestCases.apk
Binary files differ
diff --git a/tools/release-parser/tests/resources/CtsJniTestCases.apk.pb.txt b/tools/release-parser/tests/resources/CtsJniTestCases.apk.pb.txt
new file mode 100644
index 0000000..ae87223
--- /dev/null
+++ b/tools/release-parser/tests/resources/CtsJniTestCases.apk.pb.txt
@@ -0,0 +1,7166 @@
+name: "CtsJniTestCases.apk"
+type: APK
+size: 4436499
+content_id: "GeBU4owZyKZzm6cboZhHiyobLKvd9/gtyK7KLrR+VnM="
+code_id: "5937cb71 74c870f5 ace0e712 10abeac 10b330b b5b67796 541bcfcb 88e93f11 69cfa42b 2965562f bdee0f18 dc33aa66 dc341ec5 ac442c9c e03f4151 7a3c63cb f9855bc7 f1b3224f "
+dependencies: "libc.so"
+dependencies: "libdl.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "liblog.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libdl.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "libjnicommon.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libc++_shared.so"
+dependencies: "libnativehelper_compat_libc++.so"
+dependencies: "liblog.so"
+dependencies: "libdl.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "liblog.so"
+dependencies: "libc.so"
+dependencies: "libm.so"
+dependencies: "libdl.so"
+dynamic_loading_dependencies: "libcrypto.so"
+dynamic_loading_dependencies: "libmedia.so"
+dynamic_loading_dependencies: "libstagefright.so"
+dynamic_loading_dependencies: "libcutils.so"
+dynamic_loading_dependencies: "libandroid_runtime.so"
+dynamic_loading_dependencies: "libbinder.so"
+dynamic_loading_dependencies: "libutils.so"
+dynamic_loading_dependencies: "libvorbisidec.so"
+dynamic_loading_dependencies: "libart.so"
+dynamic_loading_dependencies: "libnativehelper.so"
+dynamic_loading_dependencies: "libjni_test_dlclose.so"
+dynamic_loading_dependencies: "libgui.so"
+dynamic_loading_dependencies: "libsqlite.so"
+dynamic_loading_dependencies: "libwebviewchromium_plat_support.so"
+dynamic_loading_dependencies: "libexpat.so"
+dynamic_loading_dependencies: "libjni_test_dlclose.so"
+dynamic_loading_dependencies: "libcrypto.so"
+dynamic_loading_dependencies: "libmedia.so"
+dynamic_loading_dependencies: "libstagefright.so"
+dynamic_loading_dependencies: "libcutils.so"
+dynamic_loading_dependencies: "libandroid_runtime.so"
+dynamic_loading_dependencies: "libbinder.so"
+dynamic_loading_dependencies: "libutils.so"
+dynamic_loading_dependencies: "libvorbisidec.so"
+dynamic_loading_dependencies: "libart.so"
+dynamic_loading_dependencies: "libnativehelper.so"
+dynamic_loading_dependencies: "libgui.so"
+dynamic_loading_dependencies: "libsqlite.so"
+dynamic_loading_dependencies: "libwebviewchromium_plat_support.so"
+dynamic_loading_dependencies: "libexpat.so"
+app_info {
+  package_name: "android.jni.cts"
+  uses_libraries {
+    name: "android.test.runner"
+    required: "android.test.runner"
+  }
+  uses_permissions: "android.permission.DISABLE_KEYGUARD"
+  properties {
+    key: "compileSdkVersionCodename"
+    value: "Q"
+  }
+  properties {
+    key: "minSdkVersion"
+    value: "Q"
+  }
+  properties {
+    key: "platformBuildVersionName"
+    value: "Q"
+  }
+  properties {
+    key: "compileSdkVersion"
+    value: "0x1c"
+  }
+  properties {
+    key: "targetSdkVersion"
+    value: "Q"
+  }
+  properties {
+    key: "versionName"
+    value: "Q"
+  }
+  properties {
+    key: "platformBuildVersionCode"
+    value: "0x1c"
+  }
+  properties {
+    key: "versionCode"
+    value: "0x1c"
+  }
+  external_api_packages {
+    name: "android.jni.cts"
+    classes {
+      name: "java.util.Comparator"
+      methods {
+        name: "compare"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.io.FileInputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "getChannel"
+        return_type: "java.nio.channels.FileChannel"
+      }
+    }
+    classes {
+      name: "java.net.URL"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "openConnection"
+        return_type: "java.net.URLConnection"
+      }
+    }
+    classes {
+      name: "java.lang.annotation.Annotation"
+      methods {
+        name: "annotationType"
+        return_type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.util.Collection"
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Method"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getAnnotation"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.annotation.Annotation"
+      }
+      methods {
+        name: "getAnnotations"
+        return_type: "java.lang.annotation.Annotation[]"
+      }
+      methods {
+        name: "getDeclaredAnnotations"
+        return_type: "java.lang.annotation.Annotation[]"
+      }
+      methods {
+        name: "getDeclaringClass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getGenericParameterTypes"
+        return_type: "java.lang.reflect.Type[]"
+      }
+      methods {
+        name: "getModifiers"
+        return_type: "int"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getParameterAnnotations"
+        return_type: "java.lang.annotation.Annotation[][]"
+      }
+      methods {
+        name: "getParameterTypes"
+        return_type: "java.lang.Class[]"
+      }
+      methods {
+        name: "getReturnType"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "invoke"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "isAnnotationPresent"
+        parameters: "java.lang.Class"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isSynthetic"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.util.Log"
+      methods {
+        name: "d"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "d"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "e"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "e"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "getStackTraceString"
+        parameters: "java.lang.Throwable"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "i"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "i"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "isLoggable"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "w"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "w"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "wtf"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Constructor"
+      methods {
+        name: "getParameterAnnotations"
+        return_type: "java.lang.annotation.Annotation[][]"
+      }
+      methods {
+        name: "getParameterTypes"
+        return_type: "java.lang.Class[]"
+      }
+      methods {
+        name: "newInstance"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.graphics.Bitmap"
+      methods {
+        name: "compress"
+        parameters: "android.graphics.Bitmap.CompressFormat"
+        parameters: "int"
+        parameters: "java.io.OutputStream"
+        return_type: "boolean"
+      }
+      methods {
+        name: "createBitmap"
+        parameters: "android.graphics.Bitmap"
+        return_type: "android.graphics.Bitmap"
+      }
+      methods {
+        name: "sameAs"
+        parameters: "android.graphics.Bitmap"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.os.Message"
+      methods {
+        name: "getData"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "setData"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      fields {
+        name: "replyTo"
+        type: "android.os.Messenger"
+      }
+      fields {
+        name: "what"
+        type: "int"
+      }
+    }
+    classes {
+      name: "java.io.ObjectInputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.InputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "readFields"
+        return_type: "java.io.ObjectInputStream.GetField"
+      }
+      methods {
+        name: "readObject"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.math.BigDecimal"
+      methods {
+        name: "abs"
+        return_type: "java.math.BigDecimal"
+      }
+      methods {
+        name: "compareTo"
+        parameters: "java.math.BigDecimal"
+        return_type: "int"
+      }
+      methods {
+        name: "stripTrailingZeros"
+        return_type: "java.math.BigDecimal"
+      }
+      methods {
+        name: "subtract"
+        parameters: "java.math.BigDecimal"
+        parameters: "java.math.MathContext"
+        return_type: "java.math.BigDecimal"
+      }
+      fields {
+        name: "ZERO"
+        type: "java.math.BigDecimal"
+      }
+    }
+    classes {
+      name: "javax.xml.xpath.XPathExpressionException"
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.atomic.AtomicBoolean"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "compareAndSet"
+        parameters: "boolean"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getAndSet"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "set"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.StringReader"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.CountDownLatch"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "await"
+        return_type: "void"
+      }
+      methods {
+        name: "await"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        return_type: "boolean"
+      }
+      methods {
+        name: "countDown"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.UUID"
+      methods {
+        name: "randomUUID"
+        return_type: "java.util.UUID"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ConcurrentLinkedQueue"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.AssertionError"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.Error"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.WindowManager"
+      methods {
+        name: "getDefaultDisplay"
+        return_type: "android.view.Display"
+      }
+    }
+    classes {
+      name: "java.util.Objects"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hash"
+        parameters: "java.lang.Object[]"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.Math"
+      methods {
+        name: "abs"
+        parameters: "double"
+        return_type: "double"
+      }
+      methods {
+        name: "abs"
+        parameters: "float"
+        return_type: "float"
+      }
+      methods {
+        name: "abs"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "max"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "min"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.InterruptedException"
+      methods {
+        name: "getCause"
+        return_type: "java.lang.Throwable"
+      }
+      methods {
+        name: "printStackTrace"
+        parameters: "java.io.PrintStream"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ThreadPoolExecutor"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "int"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        parameters: "java.util.concurrent.BlockingQueue"
+        parameters: "java.util.concurrent.ThreadFactory"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.KeyguardManager.KeyguardLock"
+      methods {
+        name: "disableKeyguard"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.ClassLoader"
+      methods {
+        name: "getSystemClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+    }
+    classes {
+      name: "java.io.FileOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "getChannel"
+        return_type: "java.nio.channels.FileChannel"
+      }
+    }
+    classes {
+      name: "android.os.HandlerThread"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "getLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "start"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.net.Uri.Builder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "authority"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+      methods {
+        name: "build"
+        return_type: "android.net.Uri"
+      }
+      methods {
+        name: "path"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+      methods {
+        name: "scheme"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+    }
+    classes {
+      name: "java.nio.charset.Charset"
+      methods {
+        name: "forName"
+        parameters: "java.lang.String"
+        return_type: "java.nio.charset.Charset"
+      }
+    }
+    classes {
+      name: "java.util.Collections"
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        parameters: "java.lang.Object[]"
+        return_type: "boolean"
+      }
+      methods {
+        name: "emptyList"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "enumeration"
+        parameters: "java.util.Collection"
+        return_type: "java.util.Enumeration"
+      }
+      methods {
+        name: "reverse"
+        parameters: "java.util.List"
+        return_type: "void"
+      }
+      methods {
+        name: "singletonList"
+        parameters: "java.lang.Object"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "sort"
+        parameters: "java.util.List"
+        parameters: "java.util.Comparator"
+        return_type: "void"
+      }
+      methods {
+        name: "synchronizedList"
+        parameters: "java.util.List"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "unmodifiableCollection"
+        parameters: "java.util.Collection"
+        return_type: "java.util.Collection"
+      }
+      methods {
+        name: "unmodifiableList"
+        parameters: "java.util.List"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "unmodifiableMap"
+        parameters: "java.util.Map"
+        return_type: "java.util.Map"
+      }
+      methods {
+        name: "unmodifiableSet"
+        parameters: "java.util.Set"
+        return_type: "java.util.Set"
+      }
+    }
+    classes {
+      name: "java.io.InputStreamReader"
+      methods {
+        name: "<init>"
+        parameters: "java.io.InputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.io.InputStream"
+        parameters: "java.nio.charset.Charset"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.Callable"
+      methods {
+        name: "call"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.os.Parcel"
+      methods {
+        name: "enforceInterface"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "obtain"
+        return_type: "android.os.Parcel"
+      }
+      methods {
+        name: "readArray"
+        parameters: "java.lang.ClassLoader"
+        return_type: "java.lang.Object[]"
+      }
+      methods {
+        name: "readArrayList"
+        parameters: "java.lang.ClassLoader"
+        return_type: "java.util.ArrayList"
+      }
+      methods {
+        name: "readException"
+        return_type: "void"
+      }
+      methods {
+        name: "readHashMap"
+        parameters: "java.lang.ClassLoader"
+        return_type: "java.util.HashMap"
+      }
+      methods {
+        name: "readInt"
+        return_type: "int"
+      }
+      methods {
+        name: "readList"
+        parameters: "java.util.List"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      methods {
+        name: "readMap"
+        parameters: "java.util.Map"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      methods {
+        name: "readParcelable"
+        parameters: "java.lang.ClassLoader"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "readString"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "readStrongBinder"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+      methods {
+        name: "writeArray"
+        parameters: "java.lang.Object[]"
+        return_type: "void"
+      }
+      methods {
+        name: "writeInt"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeInterfaceToken"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "writeNoException"
+        return_type: "void"
+      }
+      methods {
+        name: "writeParcelable"
+        parameters: "android.os.Parcelable"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeString"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "writeStrongBinder"
+        parameters: "android.os.IBinder"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ThreadFactory"
+      methods {
+        name: "newThread"
+        parameters: "java.lang.Runnable"
+        return_type: "java.lang.Thread"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.ParameterizedType"
+      methods {
+        name: "getActualTypeArguments"
+        return_type: "java.lang.reflect.Type[]"
+      }
+    }
+    classes {
+      name: "dalvik.system.PathClassLoader"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      methods {
+        name: "loadClass"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.StringBuilder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "append"
+        parameters: "char"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "int"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "long"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.String"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.os.Bundle"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "containsKey"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getParcelable"
+        parameters: "java.lang.String"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "getParcelableArray"
+        parameters: "java.lang.String"
+        return_type: "android.os.Parcelable[]"
+      }
+      methods {
+        name: "getSerializable"
+        parameters: "java.lang.String"
+        return_type: "java.io.Serializable"
+      }
+      methods {
+        name: "getString"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getStringArrayList"
+        parameters: "java.lang.String"
+        return_type: "java.util.ArrayList"
+      }
+      methods {
+        name: "putInt"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "putParcelable"
+        parameters: "java.lang.String"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "putParcelableArray"
+        parameters: "java.lang.String"
+        parameters: "android.os.Parcelable[]"
+        return_type: "void"
+      }
+      methods {
+        name: "putSerializable"
+        parameters: "java.lang.String"
+        parameters: "java.io.Serializable"
+        return_type: "void"
+      }
+      methods {
+        name: "putString"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "putStringArrayList"
+        parameters: "java.lang.String"
+        parameters: "java.util.ArrayList"
+        return_type: "void"
+      }
+      methods {
+        name: "setClassLoader"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.os.Build"
+      fields {
+        name: "CPU_ABI"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "DEVICE"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "HARDWARE"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "MODEL"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.nio.file.Path"
+      methods {
+        name: "getFileName"
+        return_type: "java.nio.file.Path"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.Throwable"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "addSuppressed"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "getCause"
+        return_type: "java.lang.Throwable"
+      }
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "printStackTrace"
+        parameters: "java.io.PrintWriter"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.UnsatisfiedLinkError"
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.io.IOException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.io.ObjectStreamClass"
+      methods {
+        name: "getFields"
+        return_type: "java.io.ObjectStreamField[]"
+      }
+      methods {
+        name: "lookup"
+        parameters: "java.lang.Class"
+        return_type: "java.io.ObjectStreamClass"
+      }
+    }
+    classes {
+      name: "java.lang.StringBuffer"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.String"
+        return_type: "java.lang.StringBuffer"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.Enum"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "java.lang.Class"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Enum"
+      }
+    }
+    classes {
+      name: "android.content.ComponentName"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "getClassName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPackageName"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.System"
+      methods {
+        name: "arraycopy"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "currentTimeMillis"
+        return_type: "long"
+      }
+      methods {
+        name: "exit"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "getProperties"
+        return_type: "java.util.Properties"
+      }
+      methods {
+        name: "getProperty"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getProperty"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "load"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "loadLibrary"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "nanoTime"
+        return_type: "long"
+      }
+      methods {
+        name: "setProperties"
+        parameters: "java.util.Properties"
+        return_type: "void"
+      }
+      methods {
+        name: "setProperty"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      fields {
+        name: "err"
+        type: "java.io.PrintStream"
+      }
+      fields {
+        name: "in"
+        type: "java.io.InputStream"
+      }
+      fields {
+        name: "out"
+        type: "java.io.PrintStream"
+      }
+    }
+    classes {
+      name: "java.text.NumberFormat"
+      methods {
+        name: "format"
+        parameters: "double"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getInstance"
+        return_type: "java.text.NumberFormat"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ConcurrentHashMap"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "putIfAbsent"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.util.ArrayList"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Collection"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "toArray"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object[]"
+      }
+    }
+    classes {
+      name: "java.util.Vector"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "elements"
+        return_type: "java.util.Enumeration"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.Thread"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Runnable"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "currentThread"
+        return_type: "java.lang.Thread"
+      }
+      methods {
+        name: "getAllStackTraces"
+        return_type: "java.util.Map"
+      }
+      methods {
+        name: "getContextClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getStackTrace"
+        return_type: "java.lang.StackTraceElement[]"
+      }
+      methods {
+        name: "getUncaughtExceptionHandler"
+        return_type: "java.lang.Thread.UncaughtExceptionHandler"
+      }
+      methods {
+        name: "interrupt"
+        return_type: "void"
+      }
+      methods {
+        name: "isInterrupted"
+        return_type: "boolean"
+      }
+      methods {
+        name: "join"
+        return_type: "void"
+      }
+      methods {
+        name: "setContextClassLoader"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      methods {
+        name: "setDaemon"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setName"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "setUncaughtExceptionHandler"
+        parameters: "java.lang.Thread.UncaughtExceptionHandler"
+        return_type: "void"
+      }
+      methods {
+        name: "sleep"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "start"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.os.IBinder"
+      methods {
+        name: "queryLocalInterface"
+        parameters: "java.lang.String"
+        return_type: "android.os.IInterface"
+      }
+      methods {
+        name: "transact"
+        parameters: "int"
+        parameters: "android.os.Parcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.app.ActivityManager"
+      methods {
+        name: "getRunningAppProcesses"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "isLowRamDevice"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.test.mock.MockContentResolver"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "addProvider"
+        parameters: "java.lang.String"
+        parameters: "android.content.ContentProvider"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.net.HttpURLConnection"
+      methods {
+        name: "disconnect"
+        return_type: "void"
+      }
+      methods {
+        name: "getOutputStream"
+        return_type: "java.io.OutputStream"
+      }
+      methods {
+        name: "getResponseCode"
+        return_type: "int"
+      }
+      methods {
+        name: "getResponseMessage"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "setConnectTimeout"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setDoOutput"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setFixedLengthStreamingMode"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setReadTimeout"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.IllegalStateException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.KeyguardManager"
+      methods {
+        name: "newKeyguardLock"
+        parameters: "java.lang.String"
+        return_type: "android.app.KeyguardManager.KeyguardLock"
+      }
+    }
+    classes {
+      name: "android.database.sqlite.SQLiteDatabase"
+      methods {
+        name: "execSQL"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.View"
+      methods {
+        name: "getDrawingCache"
+        return_type: "android.graphics.Bitmap"
+      }
+      methods {
+        name: "getRootView"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "setDrawingCacheEnabled"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.math.MathContext"
+      fields {
+        name: "DECIMAL128"
+        type: "java.math.MathContext"
+      }
+    }
+    classes {
+      name: "java.net.CookieHandler"
+      methods {
+        name: "setDefault"
+        parameters: "java.net.CookieHandler"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Properties"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Properties"
+        return_type: "void"
+      }
+      methods {
+        name: "getProperty"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "load"
+        parameters: "java.io.InputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "setProperty"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "store"
+        parameters: "java.io.OutputStream"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Runtime"
+      methods {
+        name: "exec"
+        parameters: "java.lang.String[]"
+        return_type: "java.lang.Process"
+      }
+      methods {
+        name: "freeMemory"
+        return_type: "long"
+      }
+      methods {
+        name: "gc"
+        return_type: "void"
+      }
+      methods {
+        name: "getRuntime"
+        return_type: "java.lang.Runtime"
+      }
+      methods {
+        name: "totalMemory"
+        return_type: "long"
+      }
+    }
+    classes {
+      name: "android.content.BroadcastReceiver"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.StackTraceElement"
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.util.Set"
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "containsAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "toArray"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object[]"
+      }
+    }
+    classes {
+      name: "javax.xml.xpath.XPathExpression"
+      methods {
+        name: "evaluate"
+        parameters: "java.lang.Object"
+        parameters: "javax.xml.namespace.QName"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.lang.Void"
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.IllegalArgumentException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Process"
+      methods {
+        name: "getInputStream"
+        return_type: "java.io.InputStream"
+      }
+    }
+    classes {
+      name: "android.os.Debug"
+      methods {
+        name: "isDebuggerConnected"
+        return_type: "boolean"
+      }
+      methods {
+        name: "waitForDebugger"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.NumberFormatException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.List"
+      methods {
+        name: "add"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "subList"
+        parameters: "int"
+        parameters: "int"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "toArray"
+        return_type: "java.lang.Object[]"
+      }
+      methods {
+        name: "toArray"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object[]"
+      }
+    }
+    classes {
+      name: "java.lang.ThreadLocal"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Appendable"
+      methods {
+        name: "append"
+        parameters: "char"
+        return_type: "java.lang.Appendable"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.lang.Appendable"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.FutureTask"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Runnable"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.concurrent.Callable"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "get"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "run"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "javax.xml.xpath.XPathFactory"
+      methods {
+        name: "newInstance"
+        return_type: "javax.xml.xpath.XPathFactory"
+      }
+      methods {
+        name: "newXPath"
+        return_type: "javax.xml.xpath.XPath"
+      }
+    }
+    classes {
+      name: "java.lang.NoSuchMethodException"
+      methods {
+        name: "initCause"
+        parameters: "java.lang.Throwable"
+        return_type: "java.lang.Throwable"
+      }
+    }
+    classes {
+      name: "javax.net.ssl.HttpsURLConnection"
+      methods {
+        name: "getDefaultHostnameVerifier"
+        return_type: "javax.net.ssl.HostnameVerifier"
+      }
+      methods {
+        name: "getDefaultSSLSocketFactory"
+        return_type: "javax.net.ssl.SSLSocketFactory"
+      }
+      methods {
+        name: "setDefaultHostnameVerifier"
+        parameters: "javax.net.ssl.HostnameVerifier"
+        return_type: "void"
+      }
+      methods {
+        name: "setDefaultSSLSocketFactory"
+        parameters: "javax.net.ssl.SSLSocketFactory"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Environment"
+      methods {
+        name: "getExternalStoragePublicDirectory"
+        parameters: "java.lang.String"
+        return_type: "java.io.File"
+      }
+      fields {
+        name: "DIRECTORY_PICTURES"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.nio.channels.FileChannel"
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "size"
+        return_type: "long"
+      }
+      methods {
+        name: "transferTo"
+        parameters: "long"
+        parameters: "long"
+        parameters: "java.nio.channels.WritableByteChannel"
+        return_type: "long"
+      }
+    }
+    classes {
+      name: "android.os.SystemClock"
+      methods {
+        name: "uptimeMillis"
+        return_type: "long"
+      }
+    }
+    classes {
+      name: "java.io.PrintWriter"
+      methods {
+        name: "<init>"
+        parameters: "java.io.Writer"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ExecutionException"
+      methods {
+        name: "getCause"
+        return_type: "java.lang.Throwable"
+      }
+    }
+    classes {
+      name: "android.app.UiAutomation"
+      methods {
+        name: "executeShellCommand"
+        parameters: "java.lang.String"
+        return_type: "android.os.ParcelFileDescriptor"
+      }
+      methods {
+        name: "takeScreenshot"
+        return_type: "android.graphics.Bitmap"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.atomic.AtomicLong"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "addAndGet"
+        parameters: "long"
+        return_type: "long"
+      }
+      methods {
+        name: "get"
+        return_type: "long"
+      }
+      methods {
+        name: "longValue"
+        return_type: "long"
+      }
+      methods {
+        name: "set"
+        parameters: "long"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Byte"
+      methods {
+        name: "valueOf"
+        parameters: "byte"
+        return_type: "java.lang.Byte"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Modifier"
+      methods {
+        name: "isAbstract"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isPublic"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isStatic"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.io.StringWriter"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.app.Instrumentation.ActivityResult"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "android.content.Intent"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.EventObject"
+      methods {
+        name: "getSource"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.os.MessageQueue"
+      methods {
+        name: "addIdleHandler"
+        parameters: "android.os.MessageQueue.IdleHandler"
+        return_type: "void"
+      }
+      methods {
+        name: "removeIdleHandler"
+        parameters: "android.os.MessageQueue.IdleHandler"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.TimeUnit"
+      methods {
+        name: "convert"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        return_type: "long"
+      }
+      methods {
+        name: "name"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toMillis"
+        parameters: "long"
+        return_type: "long"
+      }
+      fields {
+        name: "MILLISECONDS"
+        type: "java.util.concurrent.TimeUnit"
+      }
+      fields {
+        name: "MINUTES"
+        type: "java.util.concurrent.TimeUnit"
+      }
+      fields {
+        name: "NANOSECONDS"
+        type: "java.util.concurrent.TimeUnit"
+      }
+      fields {
+        name: "SECONDS"
+        type: "java.util.concurrent.TimeUnit"
+      }
+    }
+    classes {
+      name: "android.os.ParcelFileDescriptor.AutoCloseInputStream"
+      methods {
+        name: "<init>"
+        parameters: "android.os.ParcelFileDescriptor"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Iterator"
+      methods {
+        name: "hasNext"
+        return_type: "boolean"
+      }
+      methods {
+        name: "next"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "remove"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.net.Uri"
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.InvocationTargetException"
+      methods {
+        name: "fillInStackTrace"
+        return_type: "java.lang.Throwable"
+      }
+      methods {
+        name: "getCause"
+        return_type: "java.lang.Throwable"
+      }
+      methods {
+        name: "getTargetException"
+        return_type: "java.lang.Throwable"
+      }
+    }
+    classes {
+      name: "java.util.HashMap"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Map"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Parcelable.Creator"
+      methods {
+        name: "createFromParcel"
+        parameters: "android.os.Parcel"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.Future"
+      methods {
+        name: "cancel"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.lang.Object"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "finalize"
+        return_type: "void"
+      }
+      methods {
+        name: "getClass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "notifyAll"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "wait"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.annotation.ElementType"
+      fields {
+        name: "ANNOTATION_TYPE"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "CONSTRUCTOR"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "FIELD"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "LOCAL_VARIABLE"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "METHOD"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "PACKAGE"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "PARAMETER"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.annotation.ElementType"
+      }
+    }
+    classes {
+      name: "java.lang.Package"
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.net.Authenticator"
+      methods {
+        name: "setDefault"
+        parameters: "java.net.Authenticator"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Thread.UncaughtExceptionHandler"
+      methods {
+        name: "uncaughtException"
+        parameters: "java.lang.Thread"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Binder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "onTransact"
+        parameters: "int"
+        parameters: "android.os.Parcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.WildcardType"
+      methods {
+        name: "getLowerBounds"
+        return_type: "java.lang.reflect.Type[]"
+      }
+      methods {
+        name: "getUpperBounds"
+        return_type: "java.lang.reflect.Type[]"
+      }
+    }
+    classes {
+      name: "javax.xml.xpath.XPathConstants"
+      fields {
+        name: "NODE"
+        type: "javax.xml.namespace.QName"
+      }
+      fields {
+        name: "STRING"
+        type: "javax.xml.namespace.QName"
+      }
+    }
+    classes {
+      name: "java.util.EnumSet"
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "range"
+        parameters: "java.lang.Enum"
+        parameters: "java.lang.Enum"
+        return_type: "java.util.EnumSet"
+      }
+    }
+    classes {
+      name: "java.util.LinkedHashMap"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.os.Process"
+      methods {
+        name: "myPid"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.content.pm.ProviderInfo"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      fields {
+        name: "authority"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.content.Context"
+      methods {
+        name: "bindService"
+        parameters: "android.content.Intent"
+        parameters: "android.content.ServiceConnection"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "checkCallingOrSelfPermission"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "deleteDatabase"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "deleteFile"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getApplicationInfo"
+        return_type: "android.content.pm.ApplicationInfo"
+      }
+      methods {
+        name: "getCacheDir"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+      methods {
+        name: "getDatabasePath"
+        parameters: "java.lang.String"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getDir"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getFileStreamPath"
+        parameters: "java.lang.String"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getFilesDir"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getPackageCodePath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPackageManager"
+        return_type: "android.content.pm.PackageManager"
+      }
+      methods {
+        name: "getPackageName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getSystemService"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "openFileInput"
+        parameters: "java.lang.String"
+        return_type: "java.io.FileInputStream"
+      }
+      methods {
+        name: "openFileOutput"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "java.io.FileOutputStream"
+      }
+      methods {
+        name: "openOrCreateDatabase"
+        parameters: "java.lang.String"
+        parameters: "int"
+        parameters: "android.database.sqlite.SQLiteDatabase.CursorFactory"
+        return_type: "android.database.sqlite.SQLiteDatabase"
+      }
+      methods {
+        name: "openOrCreateDatabase"
+        parameters: "java.lang.String"
+        parameters: "int"
+        parameters: "android.database.sqlite.SQLiteDatabase.CursorFactory"
+        parameters: "android.database.DatabaseErrorHandler"
+        return_type: "android.database.sqlite.SQLiteDatabase"
+      }
+      methods {
+        name: "registerReceiver"
+        parameters: "android.content.BroadcastReceiver"
+        parameters: "android.content.IntentFilter"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "sendBroadcast"
+        parameters: "android.content.Intent"
+        return_type: "void"
+      }
+      methods {
+        name: "startService"
+        parameters: "android.content.Intent"
+        return_type: "android.content.ComponentName"
+      }
+      methods {
+        name: "stopService"
+        parameters: "android.content.Intent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "unbindService"
+        parameters: "android.content.ServiceConnection"
+        return_type: "void"
+      }
+      methods {
+        name: "unregisterReceiver"
+        parameters: "android.content.BroadcastReceiver"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.ref.WeakReference"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.test.InstrumentationTestCase"
+      methods {
+        name: "injectInstrumentation"
+        parameters: "android.app.Instrumentation"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Double"
+      methods {
+        name: "<init>"
+        parameters: "double"
+        return_type: "void"
+      }
+      methods {
+        name: "compare"
+        parameters: "double"
+        parameters: "double"
+        return_type: "int"
+      }
+      methods {
+        name: "doubleValue"
+        return_type: "double"
+      }
+      methods {
+        name: "isNaN"
+        parameters: "double"
+        return_type: "boolean"
+      }
+      methods {
+        name: "parseDouble"
+        parameters: "java.lang.String"
+        return_type: "double"
+      }
+      methods {
+        name: "toString"
+        parameters: "double"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "double"
+        return_type: "java.lang.Double"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "android.app.ActivityManager.RunningAppProcessInfo"
+      fields {
+        name: "pid"
+        type: "int"
+      }
+      fields {
+        name: "processName"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.net.ResponseCache"
+      methods {
+        name: "setDefault"
+        parameters: "java.net.ResponseCache"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Enumeration"
+      methods {
+        name: "hasMoreElements"
+        return_type: "boolean"
+      }
+      methods {
+        name: "nextElement"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.content.IntentFilter"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.HashSet"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Collection"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.lang.Comparable"
+      methods {
+        name: "compareTo"
+        parameters: "java.lang.Object"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.String"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "charAt"
+        parameters: "int"
+        return_type: "char"
+      }
+      methods {
+        name: "compareTo"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "concat"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.CharSequence"
+        return_type: "boolean"
+      }
+      methods {
+        name: "endsWith"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "equalsIgnoreCase"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "format"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getBytes"
+        return_type: "byte[]"
+      }
+      methods {
+        name: "getBytes"
+        parameters: "java.lang.String"
+        return_type: "byte[]"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "lastIndexOf"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "lastIndexOf"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "length"
+        return_type: "int"
+      }
+      methods {
+        name: "matches"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "replace"
+        parameters: "java.lang.CharSequence"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "replaceAll"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "split"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String[]"
+      }
+      methods {
+        name: "split"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "java.lang.String[]"
+      }
+      methods {
+        name: "startsWith"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "substring"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "substring"
+        parameters: "int"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toLowerCase"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "trim"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.io.BufferedOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.OutputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "flush"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.GenericArrayType"
+      methods {
+        name: "getGenericComponentType"
+        return_type: "java.lang.reflect.Type"
+      }
+    }
+    classes {
+      name: "java.lang.Long"
+      methods {
+        name: "compareTo"
+        parameters: "java.lang.Long"
+        return_type: "int"
+      }
+      methods {
+        name: "longValue"
+        return_type: "long"
+      }
+      methods {
+        name: "parseLong"
+        parameters: "java.lang.String"
+        return_type: "long"
+      }
+      methods {
+        name: "toString"
+        parameters: "long"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "long"
+        return_type: "java.lang.Long"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.net.URLEncoder"
+      methods {
+        name: "encode"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.os.Messenger"
+      methods {
+        name: "<init>"
+        parameters: "android.os.Handler"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.os.IBinder"
+        return_type: "void"
+      }
+      methods {
+        name: "getBinder"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "send"
+        parameters: "android.os.Message"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Exception"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "setStackTrace"
+        parameters: "java.lang.StackTraceElement[]"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.Float"
+      methods {
+        name: "<init>"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "compare"
+        parameters: "float"
+        parameters: "float"
+        return_type: "int"
+      }
+      methods {
+        name: "floatValue"
+        return_type: "float"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "float"
+        return_type: "java.lang.Float"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.security.MessageDigest"
+      methods {
+        name: "digest"
+        return_type: "byte[]"
+      }
+      methods {
+        name: "getInstance"
+        parameters: "java.lang.String"
+        return_type: "java.security.MessageDigest"
+      }
+      methods {
+        name: "reset"
+        return_type: "void"
+      }
+      methods {
+        name: "update"
+        parameters: "byte[]"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.Activity"
+      methods {
+        name: "finish"
+        return_type: "void"
+      }
+      methods {
+        name: "getWindow"
+        return_type: "android.view.Window"
+      }
+      methods {
+        name: "isFinishing"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.view.Display"
+      methods {
+        name: "getHeight"
+        return_type: "int"
+      }
+      methods {
+        name: "getWidth"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.Executors"
+      methods {
+        name: "defaultThreadFactory"
+        return_type: "java.util.concurrent.ThreadFactory"
+      }
+      methods {
+        name: "newCachedThreadPool"
+        return_type: "java.util.concurrent.ExecutorService"
+      }
+      methods {
+        name: "newSingleThreadExecutor"
+        return_type: "java.util.concurrent.ExecutorService"
+      }
+      methods {
+        name: "newSingleThreadExecutor"
+        parameters: "java.util.concurrent.ThreadFactory"
+        return_type: "java.util.concurrent.ExecutorService"
+      }
+    }
+    classes {
+      name: "java.util.Locale"
+      methods {
+        name: "getDefault"
+        return_type: "java.util.Locale"
+      }
+      methods {
+        name: "setDefault"
+        parameters: "java.util.Locale"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Field"
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getAnnotation"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.annotation.Annotation"
+      }
+      methods {
+        name: "getAnnotations"
+        return_type: "java.lang.annotation.Annotation[]"
+      }
+      methods {
+        name: "getDeclaringClass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getModifiers"
+        return_type: "int"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getType"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.os.IInterface"
+      methods {
+        name: "asBinder"
+        return_type: "android.os.IBinder"
+      }
+    }
+    classes {
+      name: "java.io.BufferedReader"
+      methods {
+        name: "<init>"
+        parameters: "java.io.Reader"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "readLine"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.annotation.RetentionPolicy"
+      fields {
+        name: "CLASS"
+        type: "java.lang.annotation.RetentionPolicy"
+      }
+      fields {
+        name: "RUNTIME"
+        type: "java.lang.annotation.RetentionPolicy"
+      }
+      fields {
+        name: "SOURCE"
+        type: "java.lang.annotation.RetentionPolicy"
+      }
+    }
+    classes {
+      name: "android.os.Parcelable"
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "dalvik.system.DexFile"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "entries"
+        return_type: "java.util.Enumeration"
+      }
+    }
+    classes {
+      name: "android.view.Window"
+      methods {
+        name: "getDecorView"
+        return_type: "android.view.View"
+      }
+    }
+    classes {
+      name: "javax.xml.xpath.XPath"
+      methods {
+        name: "compile"
+        parameters: "java.lang.String"
+        return_type: "javax.xml.xpath.XPathExpression"
+      }
+      methods {
+        name: "setNamespaceContext"
+        parameters: "javax.xml.namespace.NamespaceContext"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.atomic.AtomicInteger"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "decrementAndGet"
+        return_type: "int"
+      }
+      methods {
+        name: "get"
+        return_type: "int"
+      }
+      methods {
+        name: "getAndIncrement"
+        return_type: "int"
+      }
+      methods {
+        name: "incrementAndGet"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.io.FileNotFoundException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.LinkedHashSet"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.SecurityException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Map"
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "containsKey"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "entrySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "keySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "putAll"
+        parameters: "java.util.Map"
+        return_type: "void"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "values"
+        return_type: "java.util.Collection"
+      }
+    }
+    classes {
+      name: "java.lang.UnsupportedOperationException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Looper"
+      methods {
+        name: "getMainLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "getThread"
+        return_type: "java.lang.Thread"
+      }
+      methods {
+        name: "myLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "myQueue"
+        return_type: "android.os.MessageQueue"
+      }
+      methods {
+        name: "quit"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.OutputStream"
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "write"
+        parameters: "byte[]"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.pm.PackageManager"
+      methods {
+        name: "getApplicationInfo"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "android.content.pm.ApplicationInfo"
+      }
+      methods {
+        name: "getInstrumentationInfo"
+        parameters: "android.content.ComponentName"
+        parameters: "int"
+        return_type: "android.content.pm.InstrumentationInfo"
+      }
+      methods {
+        name: "hasSystemFeature"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isInstantApp"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.lang.Class"
+      methods {
+        name: "asSubclass"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "cast"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "forName"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "forName"
+        parameters: "java.lang.String"
+        parameters: "boolean"
+        parameters: "java.lang.ClassLoader"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getAnnotation"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.annotation.Annotation"
+      }
+      methods {
+        name: "getAnnotations"
+        return_type: "java.lang.annotation.Annotation[]"
+      }
+      methods {
+        name: "getCanonicalName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+      methods {
+        name: "getClasses"
+        return_type: "java.lang.Class[]"
+      }
+      methods {
+        name: "getComponentType"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getConstructor"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Constructor"
+      }
+      methods {
+        name: "getConstructors"
+        return_type: "java.lang.reflect.Constructor[]"
+      }
+      methods {
+        name: "getDeclaredConstructor"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Constructor"
+      }
+      methods {
+        name: "getDeclaredField"
+        parameters: "java.lang.String"
+        return_type: "java.lang.reflect.Field"
+      }
+      methods {
+        name: "getDeclaredFields"
+        return_type: "java.lang.reflect.Field[]"
+      }
+      methods {
+        name: "getDeclaredMethod"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Method"
+      }
+      methods {
+        name: "getDeclaredMethods"
+        return_type: "java.lang.reflect.Method[]"
+      }
+      methods {
+        name: "getEnclosingClass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getEnumConstants"
+        return_type: "java.lang.Object[]"
+      }
+      methods {
+        name: "getMethod"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Method"
+      }
+      methods {
+        name: "getMethods"
+        return_type: "java.lang.reflect.Method[]"
+      }
+      methods {
+        name: "getModifiers"
+        return_type: "int"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPackage"
+        return_type: "java.lang.Package"
+      }
+      methods {
+        name: "getSimpleName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getSuperclass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "isAnnotationPresent"
+        parameters: "java.lang.Class"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isArray"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isAssignableFrom"
+        parameters: "java.lang.Class"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isEnum"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isInstance"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isMemberClass"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isPrimitive"
+        return_type: "boolean"
+      }
+      methods {
+        name: "newInstance"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.content.ContentProvider"
+      methods {
+        name: "attachInfo"
+        parameters: "android.content.Context"
+        parameters: "android.content.pm.ProviderInfo"
+        return_type: "void"
+      }
+      methods {
+        name: "shutdown"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.NullPointerException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.pm.InstrumentationInfo"
+      fields {
+        name: "metaData"
+        type: "android.os.Bundle"
+      }
+    }
+    classes {
+      name: "android.os.Handler"
+      methods {
+        name: "<init>"
+        parameters: "android.os.Looper"
+        return_type: "void"
+      }
+      methods {
+        name: "handleMessage"
+        parameters: "android.os.Message"
+        return_type: "void"
+      }
+      methods {
+        name: "post"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.io.ObjectOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.OutputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "putFields"
+        return_type: "java.io.ObjectOutputStream.PutField"
+      }
+      methods {
+        name: "writeFields"
+        return_type: "void"
+      }
+      methods {
+        name: "writeObject"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Boolean"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "parseBoolean"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "toString"
+        parameters: "boolean"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "boolean"
+        return_type: "java.lang.Boolean"
+      }
+      fields {
+        name: "TRUE"
+        type: "java.lang.Boolean"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.CopyOnWriteArrayList"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Collection"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.io.ByteArrayOutputStream"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "toByteArray"
+        return_type: "byte[]"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "write"
+        parameters: "byte[]"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.Instrumentation"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "finish"
+        parameters: "int"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "getComponentName"
+        return_type: "android.content.ComponentName"
+      }
+      methods {
+        name: "getContext"
+        return_type: "android.content.Context"
+      }
+      methods {
+        name: "getTargetContext"
+        return_type: "android.content.Context"
+      }
+      methods {
+        name: "getUiAutomation"
+        return_type: "android.app.UiAutomation"
+      }
+      methods {
+        name: "runOnMainSync"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+      methods {
+        name: "sendStatus"
+        parameters: "int"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "setInTouchMode"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "startActivitySync"
+        parameters: "android.content.Intent"
+        return_type: "android.app.Activity"
+      }
+      methods {
+        name: "waitForIdleSync"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ExecutorService"
+      methods {
+        name: "awaitTermination"
+        parameters: "long"
+        parameters: "java.util.concurrent.TimeUnit"
+        return_type: "boolean"
+      }
+      methods {
+        name: "shutdown"
+        return_type: "void"
+      }
+      methods {
+        name: "shutdownNow"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "submit"
+        parameters: "java.lang.Runnable"
+        return_type: "java.util.concurrent.Future"
+      }
+      methods {
+        name: "submit"
+        parameters: "java.util.concurrent.Callable"
+        return_type: "java.util.concurrent.Future"
+      }
+    }
+    classes {
+      name: "java.io.PrintStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.OutputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "append"
+        parameters: "char"
+        return_type: "java.io.PrintStream"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "format"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object[]"
+        return_type: "java.io.PrintStream"
+      }
+      methods {
+        name: "print"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.IllegalAccessException"
+      methods {
+        name: "fillInStackTrace"
+        return_type: "java.lang.Throwable"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.RuntimeException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.ClassNotFoundException"
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.util.Map.Entry"
+      methods {
+        name: "getKey"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getValue"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.TimeoutException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.InputStream"
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "read"
+        return_type: "int"
+      }
+      methods {
+        name: "read"
+        parameters: "byte[]"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.util.Arrays"
+      methods {
+        name: "asList"
+        parameters: "java.lang.Object[]"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "deepEquals"
+        parameters: "java.lang.Object[]"
+        parameters: "java.lang.Object[]"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hashCode"
+        parameters: "java.lang.Object[]"
+        return_type: "int"
+      }
+      methods {
+        name: "sort"
+        parameters: "java.lang.Object[]"
+        parameters: "java.util.Comparator"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.TimeZone"
+      methods {
+        name: "getDefault"
+        return_type: "java.util.TimeZone"
+      }
+      methods {
+        name: "setDefault"
+        parameters: "java.util.TimeZone"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Character"
+      methods {
+        name: "charValue"
+        return_type: "char"
+      }
+      methods {
+        name: "isLetterOrDigit"
+        parameters: "char"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isWhitespace"
+        parameters: "char"
+        return_type: "boolean"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "char"
+        return_type: "java.lang.Character"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.Runnable"
+      methods {
+        name: "run"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.Intent"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Intent"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "addFlags"
+        parameters: "int"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "cloneFilter"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "getBundleExtra"
+        parameters: "java.lang.String"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "getComponent"
+        return_type: "android.content.ComponentName"
+      }
+      methods {
+        name: "getFlags"
+        return_type: "int"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "android.os.Bundle"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setClassName"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setComponent"
+        parameters: "android.content.ComponentName"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setPackage"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+    }
+    classes {
+      name: "android.text.TextUtils"
+      methods {
+        name: "isEmpty"
+        parameters: "java.lang.CharSequence"
+        return_type: "boolean"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "java.lang.CharSequence"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CHAR_SEQUENCE_CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.graphics.Bitmap.CompressFormat"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+      fields {
+        name: "PNG"
+        type: "android.graphics.Bitmap.CompressFormat"
+      }
+    }
+    classes {
+      name: "java.io.ObjectOutputStream.PutField"
+      methods {
+        name: "put"
+        parameters: "java.lang.String"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Integer"
+      methods {
+        name: "intValue"
+        return_type: "int"
+      }
+      methods {
+        name: "parseInt"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "signum"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "toHexString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "int"
+        return_type: "java.lang.Integer"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Array"
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getLength"
+        parameters: "java.lang.Object"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.util.regex.Pattern"
+      methods {
+        name: "compile"
+        parameters: "java.lang.String"
+        return_type: "java.util.regex.Pattern"
+      }
+      methods {
+        name: "matcher"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.util.regex.Matcher"
+      }
+    }
+    classes {
+      name: "android.os.Build.VERSION"
+      fields {
+        name: "SDK_INT"
+        type: "int"
+      }
+    }
+    classes {
+      name: "java.io.File"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "canRead"
+        return_type: "boolean"
+      }
+      methods {
+        name: "canWrite"
+        return_type: "boolean"
+      }
+      methods {
+        name: "createNewFile"
+        return_type: "boolean"
+      }
+      methods {
+        name: "createTempFile"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.io.File"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "delete"
+        return_type: "boolean"
+      }
+      methods {
+        name: "exists"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getAbsolutePath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getCanonicalPath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getParent"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getParentFile"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "isDirectory"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isFile"
+        return_type: "boolean"
+      }
+      methods {
+        name: "length"
+        return_type: "long"
+      }
+      methods {
+        name: "listFiles"
+        return_type: "java.io.File[]"
+      }
+      methods {
+        name: "listFiles"
+        parameters: "java.io.FilenameFilter"
+        return_type: "java.io.File[]"
+      }
+      methods {
+        name: "mkdir"
+        return_type: "boolean"
+      }
+      methods {
+        name: "mkdirs"
+        return_type: "boolean"
+      }
+      methods {
+        name: "toPath"
+        return_type: "java.nio.file.Path"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+      fields {
+        name: "separator"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.math.BigInteger"
+      methods {
+        name: "<init>"
+        parameters: "byte[]"
+        return_type: "void"
+      }
+      methods {
+        name: "toString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.test.AndroidTestCase"
+      methods {
+        name: "setContext"
+        parameters: "android.content.Context"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.ObjectInputStream.GetField"
+      methods {
+        name: "get"
+        parameters: "java.lang.String"
+        parameters: "long"
+        return_type: "long"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.content.ServiceConnection"
+      methods {
+        name: "onServiceConnected"
+        parameters: "android.content.ComponentName"
+        parameters: "android.os.IBinder"
+        return_type: "void"
+      }
+      methods {
+        name: "onServiceDisconnected"
+        parameters: "android.content.ComponentName"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.atomic.AtomicReference"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.SynchronousQueue"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.pm.ApplicationInfo"
+      fields {
+        name: "nativeLibraryDir"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "processName"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "sourceDir"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.Short"
+      methods {
+        name: "valueOf"
+        parameters: "short"
+        return_type: "java.lang.Short"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "android.content.ContextWrapper"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.text.MessageFormat"
+      methods {
+        name: "format"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.io.FileReader"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Iterable"
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+    }
+    classes {
+      name: "java.util.regex.Matcher"
+      methods {
+        name: "end"
+        return_type: "int"
+      }
+      methods {
+        name: "find"
+        return_type: "boolean"
+      }
+      methods {
+        name: "group"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "matches"
+        return_type: "boolean"
+      }
+      methods {
+        name: "start"
+        return_type: "int"
+      }
+    }
+  }
+  internal_api_packages {
+    name: "android.jni.cts"
+  }
+  package_file_content {
+    entries {
+      key: "lib/arm64-v8a/libjninamespacea2.so"
+      value {
+        name: "lib/arm64-v8a/libjninamespacea2.so"
+        type: FILE
+        size: 11272
+        content_id: "10b330b "
+        app_info {
+          package_name: "libjninamespacea2.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libjninamespaceb.so"
+      value {
+        name: "lib/arm64-v8a/libjninamespaceb.so"
+        type: FILE
+        size: 11272
+        content_id: "b5b67796 "
+        app_info {
+          package_name: "libjninamespaceb.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "AndroidManifest.xml"
+      value {
+        name: "AndroidManifest.xml"
+        type: FILE
+        size: 2128
+        content_id: "f9855bc7 "
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libc++_shared.so"
+      value {
+        name: "lib/armeabi-v7a/libc++_shared.so"
+        type: FILE
+        size: 657000
+        content_id: "69cfa42b "
+        app_info {
+          package_name: "libc++_shared.so"
+          external_api_packages {
+            classes {
+              name: "*local*"
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__aeabi_memclr4"
+              }
+              methods {
+                name: "__aeabi_memcpy4"
+              }
+              methods {
+                name: "__aeabi_memmove4"
+              }
+              methods {
+                name: "__aeabi_memmove"
+              }
+              methods {
+                name: "__aeabi_memset"
+              }
+              methods {
+                name: "__aeabi_memclr"
+              }
+              methods {
+                name: "__cxa_thread_atexit_impl"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "strtoimax"
+              }
+              methods {
+                name: "strtoumax"
+              }
+              methods {
+                name: "isblank"
+              }
+              methods {
+                name: "memalign"
+              }
+              methods {
+                name: "strncpy"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "strstr"
+              }
+              methods {
+                name: "clock_gettime"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "raise"
+              }
+              methods {
+                name: "pthread_create"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "ungetc"
+              }
+              methods {
+                name: "getc"
+              }
+              methods {
+                name: "fwrite"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "memset"
+              }
+              methods {
+                name: "memcpy"
+              }
+              methods {
+                name: "vsscanf"
+              }
+              methods {
+                name: "vsnprintf"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "open"
+              }
+              methods {
+                name: "close"
+              }
+              methods {
+                name: "read"
+              }
+              methods {
+                name: "memchr"
+              }
+              methods {
+                name: "strftime"
+              }
+              methods {
+                name: "sscanf"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "strcoll"
+              }
+              methods {
+                name: "strxfrm"
+              }
+              methods {
+                name: "wcscoll"
+              }
+              methods {
+                name: "wcsxfrm"
+              }
+              methods {
+                name: "iswlower"
+              }
+              methods {
+                name: "islower"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "toupper"
+              }
+              methods {
+                name: "tolower"
+              }
+              methods {
+                name: "iswspace"
+              }
+              methods {
+                name: "iswprint"
+              }
+              methods {
+                name: "iswcntrl"
+              }
+              methods {
+                name: "iswupper"
+              }
+              methods {
+                name: "iswalpha"
+              }
+              methods {
+                name: "iswdigit"
+              }
+              methods {
+                name: "iswpunct"
+              }
+              methods {
+                name: "iswxdigit"
+              }
+              methods {
+                name: "towupper"
+              }
+              methods {
+                name: "towlower"
+              }
+              methods {
+                name: "btowc"
+              }
+              methods {
+                name: "wctob"
+              }
+              methods {
+                name: "mbrlen"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "__assert2"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "strtoll"
+              }
+              methods {
+                name: "strtoull"
+              }
+              methods {
+                name: "strtod"
+              }
+              methods {
+                name: "strtol"
+              }
+              methods {
+                name: "strtoul"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+              fields {
+                name: "__sF"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libc++_shared.so"
+      value {
+        name: "lib/arm64-v8a/libc++_shared.so"
+        type: FILE
+        size: 1058904
+        content_id: "5937cb71 "
+        app_info {
+          package_name: "libc++_shared.so"
+          external_api_packages {
+            classes {
+              name: "*local*"
+              methods {
+                name: "__cxa_thread_atexit_impl"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dl_iterate_phdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "memcpy"
+              }
+              methods {
+                name: "memchr"
+              }
+              methods {
+                name: "toupper_l"
+              }
+              methods {
+                name: "freelocale"
+              }
+              methods {
+                name: "__ctype_get_mb_cur_max"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "strtold_l"
+              }
+              methods {
+                name: "mbrtowc"
+              }
+              methods {
+                name: "btowc"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "close"
+              }
+              methods {
+                name: "strxfrm_l"
+              }
+              methods {
+                name: "wcstoull"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "syslog"
+              }
+              methods {
+                name: "wmemmove"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "wcsxfrm_l"
+              }
+              methods {
+                name: "strtoll_l"
+              }
+              methods {
+                name: "islower_l"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "wcslen"
+              }
+              methods {
+                name: "isxdigit_l"
+              }
+              methods {
+                name: "clock_gettime"
+              }
+              methods {
+                name: "sscanf"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "uselocale"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "strtold"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "wcstoll"
+              }
+              methods {
+                name: "wmemset"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "wcstold"
+              }
+              methods {
+                name: "wcstoul"
+              }
+              methods {
+                name: "iswcntrl_l"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "mbrlen"
+              }
+              methods {
+                name: "posix_memalign"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "read"
+              }
+              methods {
+                name: "memmove"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "towlower_l"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "mbsnrtowcs"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "wcscoll_l"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "strtod"
+              }
+              methods {
+                name: "isupper_l"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "mbtowc"
+              }
+              methods {
+                name: "iswlower_l"
+              }
+              methods {
+                name: "iswprint_l"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "wmemcpy"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "getc"
+              }
+              methods {
+                name: "iswupper_l"
+              }
+              methods {
+                name: "strtol"
+              }
+              methods {
+                name: "iswblank_l"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "iswdigit_l"
+              }
+              methods {
+                name: "wctob"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_create"
+              }
+              methods {
+                name: "isdigit_l"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "fwrite"
+              }
+              methods {
+                name: "vsnprintf"
+              }
+              methods {
+                name: "android_set_abort_message"
+              }
+              methods {
+                name: "strftime_l"
+              }
+              methods {
+                name: "newlocale"
+              }
+              methods {
+                name: "wmemcmp"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "memset"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "wcstof"
+              }
+              methods {
+                name: "iswpunct_l"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "wmemchr"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "iswxdigit_l"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "iswspace_l"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "closelog"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "iswalpha_l"
+              }
+              methods {
+                name: "strtoull_l"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "wcrtomb"
+              }
+              methods {
+                name: "ungetc"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "strtoll"
+              }
+              methods {
+                name: "localeconv"
+              }
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "wcsnrtombs"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "strtoul"
+              }
+              methods {
+                name: "towupper_l"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "strcoll_l"
+              }
+              methods {
+                name: "wcstod"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "mbsrtowcs"
+              }
+              methods {
+                name: "strtof"
+              }
+              methods {
+                name: "vsscanf"
+              }
+              methods {
+                name: "setlocale"
+              }
+              methods {
+                name: "open"
+              }
+              methods {
+                name: "swprintf"
+              }
+              methods {
+                name: "tolower_l"
+              }
+              methods {
+                name: "openlog"
+              }
+              methods {
+                name: "strtoull"
+              }
+              methods {
+                name: "wcstol"
+              }
+              fields {
+                name: "__sF"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libjninamespacea1.so"
+      value {
+        name: "lib/arm64-v8a/libjninamespacea1.so"
+        type: FILE
+        size: 11272
+        content_id: "10abeac "
+        app_info {
+          package_name: "libjninamespacea1.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjni_test_dlclose.so"
+      value {
+        name: "lib/armeabi-v7a/libjni_test_dlclose.so"
+        type: FILE
+        size: 20192
+        content_id: "2965562f "
+        app_info {
+          package_name: "libjni_test_dlclose.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjnitest.so"
+      value {
+        name: "lib/armeabi-v7a/libjnitest.so"
+        type: FILE
+        size: 677128
+        content_id: "e03f4151 "
+        app_info {
+          package_name: "libjnitest.so"
+          external_api_packages {
+            classes {
+              name: "*local*"
+              methods {
+                name: "android_get_LD_LIBRARY_PATH"
+              }
+              methods {
+                name: "android_fdsan_close_with_tag"
+              }
+              methods {
+                name: "android_fdsan_create_owner_tag"
+              }
+              methods {
+                name: "android_fdsan_exchange_owner_tag"
+              }
+            }
+            classes {
+              name: "*global*"
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memmove"
+              }
+              methods {
+                name: "__aeabi_memcpy8"
+              }
+              methods {
+                name: "jniRegisterNativeMethods"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__aeabi_memset"
+              }
+              methods {
+                name: "jniThrowNullPointerException"
+              }
+              methods {
+                name: "jniThrowException"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__aeabi_memclr4"
+              }
+              methods {
+                name: "__aeabi_memcpy4"
+              }
+              methods {
+                name: "__aeabi_memmove4"
+              }
+              methods {
+                name: "__android_log_buf_print"
+              }
+              methods {
+                name: "__aeabi_memclr"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dlclose"
+              }
+              methods {
+                name: "dlerror"
+              }
+              methods {
+                name: "dlopen"
+              }
+              methods {
+                name: "dlsym"
+              }
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libm.so"
+              methods {
+                name: "ceilf"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "mprotect"
+              }
+              methods {
+                name: "strerror"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "basename"
+              }
+              methods {
+                name: "closedir"
+              }
+              methods {
+                name: "memchr"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "opendir"
+              }
+              methods {
+                name: "readdir"
+              }
+              methods {
+                name: "stat"
+              }
+              methods {
+                name: "strncmp"
+              }
+              methods {
+                name: "asprintf"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "isspace"
+              }
+              methods {
+                name: "strcasecmp"
+              }
+              methods {
+                name: "strncasecmp"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "vsscanf"
+              }
+              methods {
+                name: "vsnprintf"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "strftime"
+              }
+              methods {
+                name: "gettid"
+              }
+              methods {
+                name: "__open_2"
+              }
+              methods {
+                name: "__vsnprintf_chk"
+              }
+              methods {
+                name: "android_set_abort_message"
+              }
+              methods {
+                name: "fcntl"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "getenv"
+              }
+              methods {
+                name: "getpid"
+              }
+              methods {
+                name: "getprogname"
+              }
+              methods {
+                name: "localtime_r"
+              }
+              methods {
+                name: "strrchr"
+              }
+              methods {
+                name: "strtoll"
+              }
+              methods {
+                name: "time"
+              }
+              methods {
+                name: "writev"
+              }
+              methods {
+                name: "__pread_chk"
+              }
+              methods {
+                name: "__read_chk"
+              }
+              methods {
+                name: "close"
+              }
+              methods {
+                name: "dirname"
+              }
+              methods {
+                name: "fchmod"
+              }
+              methods {
+                name: "fchown"
+              }
+              methods {
+                name: "fstat"
+              }
+              methods {
+                name: "lstat"
+              }
+              methods {
+                name: "open"
+              }
+              methods {
+                name: "pread"
+              }
+              methods {
+                name: "read"
+              }
+              methods {
+                name: "readlink"
+              }
+              methods {
+                name: "realpath"
+              }
+              methods {
+                name: "unlink"
+              }
+              methods {
+                name: "write"
+              }
+              methods {
+                name: "sscanf"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "strcoll"
+              }
+              methods {
+                name: "strxfrm"
+              }
+              methods {
+                name: "wcscoll"
+              }
+              methods {
+                name: "wcsxfrm"
+              }
+              methods {
+                name: "iswlower"
+              }
+              methods {
+                name: "islower"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "toupper"
+              }
+              methods {
+                name: "tolower"
+              }
+              methods {
+                name: "iswspace"
+              }
+              methods {
+                name: "iswprint"
+              }
+              methods {
+                name: "iswcntrl"
+              }
+              methods {
+                name: "iswupper"
+              }
+              methods {
+                name: "iswalpha"
+              }
+              methods {
+                name: "iswdigit"
+              }
+              methods {
+                name: "iswpunct"
+              }
+              methods {
+                name: "iswxdigit"
+              }
+              methods {
+                name: "towupper"
+              }
+              methods {
+                name: "towlower"
+              }
+              methods {
+                name: "btowc"
+              }
+              methods {
+                name: "wctob"
+              }
+              methods {
+                name: "mbrlen"
+              }
+              methods {
+                name: "strtoull"
+              }
+              methods {
+                name: "strtod"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "strtol"
+              }
+              methods {
+                name: "strtoul"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "__assert2"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "strstr"
+              }
+              methods {
+                name: "isblank"
+              }
+              methods {
+                name: "memalign"
+              }
+              methods {
+                name: "strncpy"
+              }
+              methods {
+                name: "raise"
+              }
+              methods {
+                name: "memcpy"
+              }
+              methods {
+                name: "memset"
+              }
+              methods {
+                name: "pthread_key_delete"
+              }
+              methods {
+                name: "pthread_create"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "stderr"
+              }
+              fields {
+                name: "stdout"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libjnitest.so"
+      value {
+        name: "lib/arm64-v8a/libjnitest.so"
+        type: FILE
+        size: 1036840
+        content_id: "541bcfcb "
+        app_info {
+          package_name: "libjnitest.so"
+          external_api_packages {
+            classes {
+              name: "*local*"
+              methods {
+                name: "android_get_LD_LIBRARY_PATH"
+              }
+            }
+            classes {
+              name: "*global*"
+              methods {
+                name: "jniRegisterNativeMethods"
+              }
+              methods {
+                name: "jniThrowNullPointerException"
+              }
+              methods {
+                name: "jniThrowException"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dlclose"
+              }
+              methods {
+                name: "dlerror"
+              }
+              methods {
+                name: "dlopen"
+              }
+              methods {
+                name: "dlsym"
+              }
+              methods {
+                name: "dl_iterate_phdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "memcpy"
+              }
+              methods {
+                name: "memmove"
+              }
+              methods {
+                name: "mprotect"
+              }
+              methods {
+                name: "strerror"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "basename"
+              }
+              methods {
+                name: "closedir"
+              }
+              methods {
+                name: "memchr"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "memset"
+              }
+              methods {
+                name: "opendir"
+              }
+              methods {
+                name: "readdir"
+              }
+              methods {
+                name: "stat"
+              }
+              methods {
+                name: "strncmp"
+              }
+              methods {
+                name: "asprintf"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "isspace"
+              }
+              methods {
+                name: "strcasecmp"
+              }
+              methods {
+                name: "strncasecmp"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "wmemcpy"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "__ctype_get_mb_cur_max"
+              }
+              methods {
+                name: "btowc"
+              }
+              methods {
+                name: "freelocale"
+              }
+              methods {
+                name: "isdigit_l"
+              }
+              methods {
+                name: "islower_l"
+              }
+              methods {
+                name: "isupper_l"
+              }
+              methods {
+                name: "iswalpha_l"
+              }
+              methods {
+                name: "iswblank_l"
+              }
+              methods {
+                name: "iswcntrl_l"
+              }
+              methods {
+                name: "iswdigit_l"
+              }
+              methods {
+                name: "iswlower_l"
+              }
+              methods {
+                name: "iswprint_l"
+              }
+              methods {
+                name: "iswpunct_l"
+              }
+              methods {
+                name: "iswspace_l"
+              }
+              methods {
+                name: "iswupper_l"
+              }
+              methods {
+                name: "iswxdigit_l"
+              }
+              methods {
+                name: "isxdigit_l"
+              }
+              methods {
+                name: "localeconv"
+              }
+              methods {
+                name: "mbrlen"
+              }
+              methods {
+                name: "mbrtowc"
+              }
+              methods {
+                name: "mbsnrtowcs"
+              }
+              methods {
+                name: "mbsrtowcs"
+              }
+              methods {
+                name: "mbtowc"
+              }
+              methods {
+                name: "newlocale"
+              }
+              methods {
+                name: "setlocale"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "sscanf"
+              }
+              methods {
+                name: "strcoll_l"
+              }
+              methods {
+                name: "strftime_l"
+              }
+              methods {
+                name: "strtold_l"
+              }
+              methods {
+                name: "strtoll_l"
+              }
+              methods {
+                name: "strtoull_l"
+              }
+              methods {
+                name: "strxfrm_l"
+              }
+              methods {
+                name: "tolower_l"
+              }
+              methods {
+                name: "toupper_l"
+              }
+              methods {
+                name: "towlower_l"
+              }
+              methods {
+                name: "towupper_l"
+              }
+              methods {
+                name: "uselocale"
+              }
+              methods {
+                name: "vsnprintf"
+              }
+              methods {
+                name: "vsscanf"
+              }
+              methods {
+                name: "wcrtomb"
+              }
+              methods {
+                name: "wcscoll_l"
+              }
+              methods {
+                name: "wcslen"
+              }
+              methods {
+                name: "wcsnrtombs"
+              }
+              methods {
+                name: "wcsxfrm_l"
+              }
+              methods {
+                name: "wctob"
+              }
+              methods {
+                name: "wmemset"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "strtod"
+              }
+              methods {
+                name: "strtof"
+              }
+              methods {
+                name: "strtol"
+              }
+              methods {
+                name: "strtold"
+              }
+              methods {
+                name: "strtoll"
+              }
+              methods {
+                name: "strtoul"
+              }
+              methods {
+                name: "strtoull"
+              }
+              methods {
+                name: "swprintf"
+              }
+              methods {
+                name: "wcstod"
+              }
+              methods {
+                name: "wcstof"
+              }
+              methods {
+                name: "wcstol"
+              }
+              methods {
+                name: "wcstold"
+              }
+              methods {
+                name: "wcstoll"
+              }
+              methods {
+                name: "wcstoul"
+              }
+              methods {
+                name: "wcstoull"
+              }
+              methods {
+                name: "wmemchr"
+              }
+              methods {
+                name: "wmemcmp"
+              }
+              methods {
+                name: "wmemmove"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "android_set_abort_message"
+              }
+              methods {
+                name: "closelog"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "openlog"
+              }
+              methods {
+                name: "syslog"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "posix_memalign"
+              }
+              methods {
+                name: "pthread_create"
+              }
+              fields {
+                name: "__sF"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "META-INF/CERT.SF"
+      value {
+        name: "META-INF/CERT.SF"
+        type: FILE
+        size: 2126
+        content_id: "ea979a47 "
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjninamespacea2.so"
+      value {
+        name: "lib/armeabi-v7a/libjninamespacea2.so"
+        type: FILE
+        size: 20284
+        content_id: "dc341ec5 "
+        app_info {
+          package_name: "libjninamespacea2.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libjnicommon.so"
+      value {
+        name: "lib/arm64-v8a/libjnicommon.so"
+        type: FILE
+        size: 11224
+        content_id: "ace0e712 "
+        app_info {
+          package_name: "libjnicommon.so"
+          external_api_packages {
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjninamespacea1.so"
+      value {
+        name: "lib/armeabi-v7a/libjninamespacea1.so"
+        type: FILE
+        size: 20288
+        content_id: "dc33aa66 "
+        app_info {
+          package_name: "libjninamespacea1.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjnicommon.so"
+      value {
+        name: "lib/armeabi-v7a/libjnicommon.so"
+        type: FILE
+        size: 20228
+        content_id: "bdee0f18 "
+        app_info {
+          package_name: "libjnicommon.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libnativehelper_compat_libc++.so"
+      value {
+        name: "lib/armeabi-v7a/libnativehelper_compat_libc++.so"
+        type: FILE
+        size: 205044
+        content_id: "7a3c63cb "
+        app_info {
+          package_name: "libnativehelper_compat_libc++.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memmove"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__android_log_write"
+              }
+              methods {
+                name: "__aeabi_memclr4"
+              }
+              methods {
+                name: "__aeabi_memcpy4"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__aeabi_memclr"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "__vsnprintf_chk"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "asprintf"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "__assert2"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "memalign"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "raise"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+              fields {
+                name: "__sF"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "META-INF/CERT.RSA"
+      value {
+        name: "META-INF/CERT.RSA"
+        type: FILE
+        size: 1722
+        content_id: "685bacac "
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libnativehelper_compat_libc++.so"
+      value {
+        name: "lib/arm64-v8a/libnativehelper_compat_libc++.so"
+        type: FILE
+        size: 332808
+        content_id: "88e93f11 "
+        app_info {
+          package_name: "libnativehelper_compat_libc++.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__android_log_write"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dl_iterate_phdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "__cxa_atexit"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              methods {
+                name: "__vsnprintf_chk"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "asprintf"
+              }
+              methods {
+                name: "memcpy"
+              }
+              methods {
+                name: "memmove"
+              }
+              methods {
+                name: "strerror_r"
+              }
+              methods {
+                name: "strlen"
+              }
+              methods {
+                name: "pthread_cond_broadcast"
+              }
+              methods {
+                name: "pthread_cond_destroy"
+              }
+              methods {
+                name: "pthread_cond_signal"
+              }
+              methods {
+                name: "pthread_cond_timedwait"
+              }
+              methods {
+                name: "pthread_cond_wait"
+              }
+              methods {
+                name: "pthread_getspecific"
+              }
+              methods {
+                name: "pthread_setspecific"
+              }
+              methods {
+                name: "memset"
+              }
+              methods {
+                name: "pthread_equal"
+              }
+              methods {
+                name: "pthread_mutex_destroy"
+              }
+              methods {
+                name: "pthread_mutex_init"
+              }
+              methods {
+                name: "pthread_mutex_lock"
+              }
+              methods {
+                name: "pthread_mutex_trylock"
+              }
+              methods {
+                name: "pthread_mutex_unlock"
+              }
+              methods {
+                name: "pthread_mutexattr_destroy"
+              }
+              methods {
+                name: "pthread_mutexattr_init"
+              }
+              methods {
+                name: "pthread_mutexattr_settype"
+              }
+              methods {
+                name: "pthread_self"
+              }
+              methods {
+                name: "__errno"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "sched_yield"
+              }
+              methods {
+                name: "nanosleep"
+              }
+              methods {
+                name: "pthread_detach"
+              }
+              methods {
+                name: "pthread_join"
+              }
+              methods {
+                name: "pthread_key_create"
+              }
+              methods {
+                name: "sysconf"
+              }
+              methods {
+                name: "android_set_abort_message"
+              }
+              methods {
+                name: "closelog"
+              }
+              methods {
+                name: "fputc"
+              }
+              methods {
+                name: "openlog"
+              }
+              methods {
+                name: "syslog"
+              }
+              methods {
+                name: "vasprintf"
+              }
+              methods {
+                name: "vfprintf"
+              }
+              methods {
+                name: "pthread_once"
+              }
+              methods {
+                name: "free"
+              }
+              methods {
+                name: "isalpha"
+              }
+              methods {
+                name: "isupper"
+              }
+              methods {
+                name: "isxdigit"
+              }
+              methods {
+                name: "malloc"
+              }
+              methods {
+                name: "memcmp"
+              }
+              methods {
+                name: "realloc"
+              }
+              methods {
+                name: "calloc"
+              }
+              methods {
+                name: "posix_memalign"
+              }
+              methods {
+                name: "strcmp"
+              }
+              methods {
+                name: "pthread_create"
+              }
+              fields {
+                name: "__sF"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "lib/armeabi-v7a/libjninamespaceb.so"
+      value {
+        name: "lib/armeabi-v7a/libjninamespaceb.so"
+        type: FILE
+        size: 20288
+        content_id: "ac442c9c "
+        app_info {
+          package_name: "libjninamespaceb.so"
+          external_api_packages {
+            classes {
+              name: "*global*"
+              methods {
+                name: "_Z15incrementGlobalv"
+              }
+              methods {
+                name: "_Z9getGlobalv"
+              }
+              methods {
+                name: "__android_log_print"
+              }
+              methods {
+                name: "__aeabi_memcpy"
+              }
+              methods {
+                name: "__aeabi_memclr8"
+              }
+              methods {
+                name: "__gnu_Unwind_Find_exidx"
+              }
+            }
+            classes {
+              name: "libdl.so"
+              methods {
+                name: "dladdr"
+              }
+            }
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+              methods {
+                name: "fprintf"
+              }
+              methods {
+                name: "fflush"
+              }
+              methods {
+                name: "abort"
+              }
+              methods {
+                name: "snprintf"
+              }
+              methods {
+                name: "__stack_chk_fail"
+              }
+              fields {
+                name: "__sF"
+              }
+              fields {
+                name: "__stack_chk_guard"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "resources.arsc"
+      value {
+        name: "resources.arsc"
+        type: FILE
+        size: 384
+        content_id: "30535c6a "
+      }
+    }
+    entries {
+      key: "classes.dex"
+      value {
+        name: "classes.dex"
+        type: FILE
+        size: 604096
+        content_id: "f1b3224f "
+      }
+    }
+    entries {
+      key: "lib/arm64-v8a/libjni_test_dlclose.so"
+      value {
+        name: "lib/arm64-v8a/libjni_test_dlclose.so"
+        type: FILE
+        size: 11000
+        content_id: "74c870f5 "
+        app_info {
+          package_name: "libjni_test_dlclose.so"
+          external_api_packages {
+            classes {
+              name: "libc.so"
+              methods {
+                name: "__cxa_finalize"
+              }
+            }
+          }
+          internal_api_packages {
+          }
+        }
+      }
+    }
+    entries {
+      key: "META-INF/MANIFEST.MF"
+      value {
+        name: "META-INF/MANIFEST.MF"
+        type: FILE
+        size: 2027
+        content_id: "5bbd78a2 "
+      }
+    }
+  }
+}
diff --git a/tools/release-parser/tests/resources/HelloActivity.apk b/tools/release-parser/tests/resources/HelloActivity.apk
new file mode 100644
index 0000000..741ede1
--- /dev/null
+++ b/tools/release-parser/tests/resources/HelloActivity.apk
Binary files differ
diff --git a/tools/release-parser/tests/resources/HelloActivity.apk.pb.txt b/tools/release-parser/tests/resources/HelloActivity.apk.pb.txt
new file mode 100644
index 0000000..ee7a6c8
--- /dev/null
+++ b/tools/release-parser/tests/resources/HelloActivity.apk.pb.txt
@@ -0,0 +1,146 @@
+name: "HelloActivity.apk"
+type: APK
+size: 16866
+content_id: "NaBXq25sn0o/hUvkvNHfmI9vsBvpEnMV1o+PJWNtRwc="
+code_id: "f9855bc7 f1b3224f "
+app_info {
+  package_name: "com.example.android.helloactivity"
+  activities: "HelloActivity"
+  properties {
+    key: "compileSdkVersionCodename"
+    value: "Q"
+  }
+  properties {
+    key: "minSdkVersion"
+    value: "Q"
+  }
+  properties {
+    key: "platformBuildVersionName"
+    value: "Q"
+  }
+  properties {
+    key: "compileSdkVersion"
+    value: "0x1c"
+  }
+  properties {
+    key: "targetSdkVersion"
+    value: "Q"
+  }
+  properties {
+    key: "label"
+    value: "Hello,"
+  }
+  properties {
+    key: "versionName"
+    value: "Q"
+  }
+  properties {
+    key: "platformBuildVersionCode"
+    value: "0x1c"
+  }
+  properties {
+    key: "versionCode"
+    value: "0x1c"
+  }
+  external_api_packages {
+    name: "com.example.android.helloactivity"
+    classes {
+      name: "android.view.LayoutInflater"
+      methods {
+        name: "inflate"
+        parameters: "int"
+        parameters: "android.view.ViewGroup"
+        return_type: "android.view.View"
+      }
+    }
+    classes {
+      name: "android.app.Activity"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "getLayoutInflater"
+        return_type: "android.view.LayoutInflater"
+      }
+      methods {
+        name: "onCreate"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "setContentView"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+    }
+  }
+  internal_api_packages {
+    name: "com.example.android.helloactivity"
+  }
+  package_file_content {
+    entries {
+      key: "META-INF/CERT.SF"
+      value {
+        name: "META-INF/CERT.SF"
+        type: FILE
+        size: 521
+        content_id: "ea979a47 "
+      }
+    }
+    entries {
+      key: "AndroidManifest.xml"
+      value {
+        name: "AndroidManifest.xml"
+        type: FILE
+        size: 1792
+        content_id: "f9855bc7 "
+      }
+    }
+    entries {
+      key: "META-INF/CERT.RSA"
+      value {
+        name: "META-INF/CERT.RSA"
+        type: FILE
+        size: 1722
+        content_id: "685bacac "
+      }
+    }
+    entries {
+      key: "resources.arsc"
+      value {
+        name: "resources.arsc"
+        type: FILE
+        size: 1224
+        content_id: "30535c6a "
+      }
+    }
+    entries {
+      key: "classes.dex"
+      value {
+        name: "classes.dex"
+        type: FILE
+        size: 1072
+        content_id: "f1b3224f "
+      }
+    }
+    entries {
+      key: "res/layout/hello_activity.xml"
+      value {
+        name: "res/layout/hello_activity.xml"
+        type: FILE
+        size: 504
+        content_id: "3177f95b "
+      }
+    }
+    entries {
+      key: "META-INF/MANIFEST.MF"
+      value {
+        name: "META-INF/MANIFEST.MF"
+        type: FILE
+        size: 422
+        content_id: "5bbd78a2 "
+      }
+    }
+  }
+}
diff --git a/tools/release-parser/tests/resources/Shell.apk b/tools/release-parser/tests/resources/Shell.apk
new file mode 100644
index 0000000..786e4fd
--- /dev/null
+++ b/tools/release-parser/tests/resources/Shell.apk
Binary files differ
diff --git a/tools/release-parser/tests/resources/Shell.apk.pb.txt b/tools/release-parser/tests/resources/Shell.apk.pb.txt
new file mode 100644
index 0000000..cbc05a8
--- /dev/null
+++ b/tools/release-parser/tests/resources/Shell.apk.pb.txt
@@ -0,0 +1,6620 @@
+name: "Shell.apk"
+type: APK
+size: 803551
+content_id: "vkz3X7DyuwrFSkQw/RfAJhyf5/gG4FelAGXZaRvH09c="
+code_id: "f1b3224f f9855bc7 "
+app_info {
+  package_name: "com.android.shell"
+  uses_permissions: "android.permission.SEND_SMS"
+  uses_permissions: "android.permission.CALL_PHONE"
+  uses_permissions: "android.permission.READ_PHONE_STATE"
+  uses_permissions: "android.permission.READ_CONTACTS"
+  uses_permissions: "android.permission.WRITE_CONTACTS"
+  uses_permissions: "android.permission.READ_CALENDAR"
+  uses_permissions: "android.permission.WRITE_CALENDAR"
+  uses_permissions: "android.permission.READ_USER_DICTIONARY"
+  uses_permissions: "android.permission.WRITE_USER_DICTIONARY"
+  uses_permissions: "android.permission.ACCESS_FINE_LOCATION"
+  uses_permissions: "android.permission.ACCESS_COARSE_LOCATION"
+  uses_permissions: "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+  uses_permissions: "android.permission.ACCESS_NETWORK_STATE"
+  uses_permissions: "android.permission.ACCESS_WIFI_STATE"
+  uses_permissions: "android.permission.BLUETOOTH"
+  uses_permissions: "android.permission.LOCAL_MAC_ADDRESS"
+  uses_permissions: "android.permission.EXPAND_STATUS_BAR"
+  uses_permissions: "android.permission.DISABLE_KEYGUARD"
+  uses_permissions: "android.permission.MANAGE_NETWORK_POLICY"
+  uses_permissions: "android.permission.MANAGE_USB"
+  uses_permissions: "android.permission.USE_RESERVED_DISK"
+  uses_permissions: "android.permission.FOREGROUND_SERVICE"
+  uses_permissions: "android.permission.REAL_GET_TASKS"
+  uses_permissions: "android.permission.CHANGE_CONFIGURATION"
+  uses_permissions: "android.permission.REORDER_TASKS"
+  uses_permissions: "android.permission.SET_ANIMATION_SCALE"
+  uses_permissions: "android.permission.SET_PREFERRED_APPLICATIONS"
+  uses_permissions: "android.permission.WRITE_SETTINGS"
+  uses_permissions: "android.permission.WRITE_SECURE_SETTINGS"
+  uses_permissions: "android.permission.BROADCAST_STICKY"
+  uses_permissions: "android.permission.SET_DEBUG_APP"
+  uses_permissions: "android.permission.SET_PROCESS_LIMIT"
+  uses_permissions: "android.permission.SET_ALWAYS_FINISH"
+  uses_permissions: "android.permission.DUMP"
+  uses_permissions: "android.permission.SIGNAL_PERSISTENT_PROCESSES"
+  uses_permissions: "android.permission.KILL_BACKGROUND_PROCESSES"
+  uses_permissions: "android.permission.FORCE_BACK"
+  uses_permissions: "android.permission.BATTERY_STATS"
+  uses_permissions: "android.permission.PACKAGE_USAGE_STATS"
+  uses_permissions: "android.permission.INTERNAL_SYSTEM_WINDOW"
+  uses_permissions: "android.permission.INJECT_EVENTS"
+  uses_permissions: "android.permission.RETRIEVE_WINDOW_CONTENT"
+  uses_permissions: "android.permission.SET_ACTIVITY_WATCHER"
+  uses_permissions: "android.permission.READ_INPUT_STATE"
+  uses_permissions: "android.permission.SET_ORIENTATION"
+  uses_permissions: "android.permission.INSTALL_PACKAGES"
+  uses_permissions: "android.permission.MOVE_PACKAGE"
+  uses_permissions: "android.permission.CLEAR_APP_USER_DATA"
+  uses_permissions: "android.permission.CLEAR_APP_CACHE"
+  uses_permissions: "android.permission.DELETE_CACHE_FILES"
+  uses_permissions: "android.permission.DELETE_PACKAGES"
+  uses_permissions: "android.permission.ACCESS_SURFACE_FLINGER"
+  uses_permissions: "android.permission.READ_FRAME_BUFFER"
+  uses_permissions: "android.permission.DEVICE_POWER"
+  uses_permissions: "android.permission.INSTALL_LOCATION_PROVIDER"
+  uses_permissions: "android.permission.BACKUP"
+  uses_permissions: "android.permission.FORCE_STOP_PACKAGES"
+  uses_permissions: "android.permission.STOP_APP_SWITCHES"
+  uses_permissions: "android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY"
+  uses_permissions: "android.permission.GRANT_RUNTIME_PERMISSIONS"
+  uses_permissions: "android.permission.REVOKE_RUNTIME_PERMISSIONS"
+  uses_permissions: "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+  uses_permissions: "android.permission.SET_KEYBOARD_LAYOUT"
+  uses_permissions: "android.permission.GET_DETAILED_TASKS"
+  uses_permissions: "android.permission.SET_SCREEN_COMPATIBILITY"
+  uses_permissions: "android.permission.READ_EXTERNAL_STORAGE"
+  uses_permissions: "android.permission.WRITE_EXTERNAL_STORAGE"
+  uses_permissions: "android.permission.WRITE_MEDIA_STORAGE"
+  uses_permissions: "android.permission.INTERACT_ACROSS_USERS"
+  uses_permissions: "android.permission.INTERACT_ACROSS_USERS_FULL"
+  uses_permissions: "android.permission.CREATE_USERS"
+  uses_permissions: "android.permission.MANAGE_DEVICE_ADMINS"
+  uses_permissions: "android.permission.ACCESS_LOWPAN_STATE"
+  uses_permissions: "android.permission.CHANGE_LOWPAN_STATE"
+  uses_permissions: "android.permission.READ_LOWPAN_CREDENTIAL"
+  uses_permissions: "android.permission.BLUETOOTH_STACK"
+  uses_permissions: "android.permission.GET_ACCOUNTS"
+  uses_permissions: "android.permission.RETRIEVE_WINDOW_TOKEN"
+  uses_permissions: "android.permission.FRAME_STATS"
+  uses_permissions: "android.permission.BIND_APPWIDGET"
+  uses_permissions: "android.permission.UPDATE_APP_OPS_STATS"
+  uses_permissions: "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"
+  uses_permissions: "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+  uses_permissions: "android.permission.CHANGE_APP_IDLE_STATE"
+  uses_permissions: "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"
+  uses_permissions: "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+  uses_permissions: "android.permission.MOUNT_FORMAT_FILESYSTEMS"
+  uses_permissions: "android.permission.MODIFY_PHONE_STATE"
+  uses_permissions: "android.permission.REGISTER_CALL_PROVIDER"
+  uses_permissions: "android.permission.REGISTER_CONNECTION_MANAGER"
+  uses_permissions: "android.permission.REGISTER_SIM_SUBSCRIPTION"
+  uses_permissions: "android.permission.GET_APP_OPS_STATS"
+  uses_permissions: "android.permission.MANAGE_APP_OPS_MODES"
+  uses_permissions: "android.permission.VIBRATE"
+  uses_permissions: "android.permission.MANAGE_ACTIVITY_STACKS"
+  uses_permissions: "android.permission.START_TASKS_FROM_RECENTS"
+  uses_permissions: "android.permission.ACTIVITY_EMBEDDING"
+  uses_permissions: "android.permission.CONNECTIVITY_INTERNAL"
+  uses_permissions: "android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+  uses_permissions: "android.permission.MANAGE_AUTO_FILL"
+  uses_permissions: "android.permission.NETWORK_SETTINGS"
+  uses_permissions: "android.permission.CHANGE_WIFI_STATE"
+  uses_permissions: "android.permission.SET_TIME"
+  uses_permissions: "android.permission.SET_TIME_ZONE"
+  uses_permissions: "android.permission.DISABLE_HIDDEN_API_CHECKS"
+  uses_permissions: "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"
+  uses_permissions: "android.permission.WAKE_LOCK"
+  uses_permissions: "android.permission.CHANGE_OVERLAY_PACKAGES"
+  uses_permissions: "android.permission.RESTRICTED_VR_ACCESS"
+  uses_permissions: "android.permission.MANAGE_BIND_INSTANT_SERVICE"
+  uses_permissions: "android.permission.SET_HARMFUL_APP_WARNINGS"
+  uses_permissions: "android.permission.MANAGE_SENSORS"
+  uses_permissions: "android.permission.MANAGE_AUDIO_POLICY"
+  uses_permissions: "android.permission.MANAGE_CAMERA"
+  uses_permissions: "android.permission.MANAGE_BLUETOOTH_WHEN_PERMISSION_REVIEW_REQUIRED"
+  uses_permissions: "android.permission.MANAGE_WIFI_WHEN_PERMISSION_REVIEW_REQUIRED"
+  uses_permissions: "android.permission.WATCH_APPOPS"
+  uses_permissions: "android.permission.CONTROL_KEYGUARD"
+  activities: ".BugreportWarningActivity"
+  services: ".BugreportProgressService"
+  providers: "android.support.v4.content.FileProvider"
+  providers: ".BugreportStorageProvider"
+  properties {
+    key: "compileSdkVersionCodename"
+    value: "9"
+  }
+  properties {
+    key: "coreApp"
+    value: "0xffffffff"
+  }
+  properties {
+    key: "targetSdkVersion"
+    value: "0x1c"
+  }
+  properties {
+    key: "label"
+    value: "@0x7f050000"
+  }
+  properties {
+    key: "versionName"
+    value: "9"
+  }
+  properties {
+    key: "versionCode"
+    value: "0x1c"
+  }
+  properties {
+    key: "directBootAware"
+    value: "0xffffffff"
+  }
+  properties {
+    key: "minSdkVersion"
+    value: "0x1c"
+  }
+  properties {
+    key: "sharedUserId"
+    value: "android.uid.shell"
+  }
+  properties {
+    key: "platformBuildVersionName"
+    value: "0x9"
+  }
+  properties {
+    key: "compileSdkVersion"
+    value: "0x1c"
+  }
+  properties {
+    key: "platformBuildVersionCode"
+    value: "0x1c"
+  }
+  properties {
+    key: "defaultToDeviceProtectedStorage"
+    value: "0xffffffff"
+  }
+  external_api_packages {
+    name: "com.android.shell"
+    classes {
+      name: "android.transition.Transition"
+      methods {
+        name: "addListener"
+        parameters: "android.transition.Transition.TransitionListener"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "addTarget"
+        parameters: "android.view.View"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "clone"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "getTargetIds"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getTargetNames"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getTargetTypes"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getTargets"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "removeListener"
+        parameters: "android.transition.Transition.TransitionListener"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "removeTarget"
+        parameters: "android.view.View"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "setEpicenterCallback"
+        parameters: "android.transition.Transition.EpicenterCallback"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.FileInputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.util.Patterns"
+      fields {
+        name: "EMAIL_ADDRESS"
+        type: "java.util.regex.Pattern"
+      }
+    }
+    classes {
+      name: "android.media.AudioAttributes"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getContentType"
+        return_type: "int"
+      }
+      methods {
+        name: "getFlags"
+        return_type: "int"
+      }
+      methods {
+        name: "getUsage"
+        return_type: "int"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.view.ViewGroup"
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        parameters: "int"
+        parameters: "android.view.ViewGroup.LayoutParams"
+        return_type: "void"
+      }
+      methods {
+        name: "checkLayoutParams"
+        parameters: "android.view.ViewGroup.LayoutParams"
+        return_type: "boolean"
+      }
+      methods {
+        name: "dispatchGenericMotionEvent"
+        parameters: "android.view.MotionEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "dispatchKeyEvent"
+        parameters: "android.view.KeyEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "dispatchPopulateAccessibilityEvent"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "draw"
+        parameters: "android.graphics.Canvas"
+        return_type: "void"
+      }
+      methods {
+        name: "drawChild"
+        parameters: "android.graphics.Canvas"
+        parameters: "android.view.View"
+        parameters: "long"
+        return_type: "boolean"
+      }
+      methods {
+        name: "drawableStateChanged"
+        return_type: "void"
+      }
+      methods {
+        name: "endViewTransition"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "getBackground"
+        return_type: "android.graphics.drawable.Drawable"
+      }
+      methods {
+        name: "getBottom"
+        return_type: "int"
+      }
+      methods {
+        name: "getChildAt"
+        parameters: "int"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "getChildCount"
+        return_type: "int"
+      }
+      methods {
+        name: "getLeft"
+        return_type: "int"
+      }
+      methods {
+        name: "getParent"
+        return_type: "android.view.ViewParent"
+      }
+      methods {
+        name: "getRight"
+        return_type: "int"
+      }
+      methods {
+        name: "getSuggestedMinimumHeight"
+        return_type: "int"
+      }
+      methods {
+        name: "getSuggestedMinimumWidth"
+        return_type: "int"
+      }
+      methods {
+        name: "getTag"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getTop"
+        return_type: "int"
+      }
+      methods {
+        name: "getWidth"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOfChild"
+        parameters: "android.view.View"
+        return_type: "int"
+      }
+      methods {
+        name: "isTransitionGroup"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onAttachedToWindow"
+        return_type: "void"
+      }
+      methods {
+        name: "onDetachedFromWindow"
+        return_type: "void"
+      }
+      methods {
+        name: "onDraw"
+        parameters: "android.graphics.Canvas"
+        return_type: "void"
+      }
+      methods {
+        name: "onInterceptTouchEvent"
+        parameters: "android.view.MotionEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onKeyDown"
+        parameters: "int"
+        parameters: "android.view.KeyEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onKeyUp"
+        parameters: "int"
+        parameters: "android.view.KeyEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onRestoreInstanceState"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "onSaveInstanceState"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "onSizeChanged"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onTouchEvent"
+        parameters: "android.view.MotionEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "post"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeCallbacks"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeView"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "removeViewAt"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "requestChildFocus"
+        parameters: "android.view.View"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "requestChildRectangleOnScreen"
+        parameters: "android.view.View"
+        parameters: "android.graphics.Rect"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "requestDisallowInterceptTouchEvent"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "requestLayout"
+        return_type: "void"
+      }
+      methods {
+        name: "setFitsSystemWindows"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setVisibility"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "startViewTransition"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "verifyDrawable"
+        parameters: "android.graphics.drawable.Drawable"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.util.Collection"
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Method"
+      methods {
+        name: "getAnnotation"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.annotation.Annotation"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getParameterTypes"
+        return_type: "java.lang.Class[]"
+      }
+      methods {
+        name: "invoke"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.util.Log"
+      methods {
+        name: "d"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "e"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "e"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "i"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "i"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "isLoggable"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "v"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "w"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "w"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "w"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+      methods {
+        name: "wtf"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "wtf"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Constructor"
+      methods {
+        name: "isAccessible"
+        return_type: "boolean"
+      }
+      methods {
+        name: "newInstance"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.ClipData"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.CharSequence"
+        parameters: "java.lang.String[]"
+        parameters: "android.content.ClipData.Item"
+        return_type: "void"
+      }
+      methods {
+        name: "addItem"
+        parameters: "android.content.ClipData.Item"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.IWindowManager"
+      methods {
+        name: "dismissKeyguard"
+        parameters: "com.android.internal.policy.IKeyguardDismissCallback"
+        parameters: "java.lang.CharSequence"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.graphics.Bitmap"
+      methods {
+        name: "compress"
+        parameters: "android.graphics.Bitmap.CompressFormat"
+        parameters: "int"
+        parameters: "java.io.OutputStream"
+        return_type: "boolean"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+      methods {
+        name: "setHasAlpha"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Message"
+      methods {
+        name: "getData"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "obtain"
+        return_type: "android.os.Message"
+      }
+      methods {
+        name: "obtain"
+        parameters: "android.os.Handler"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "android.os.Message"
+      }
+      methods {
+        name: "sendToTarget"
+        return_type: "void"
+      }
+      methods {
+        name: "setData"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      fields {
+        name: "arg1"
+        type: "int"
+      }
+      fields {
+        name: "arg2"
+        type: "int"
+      }
+      fields {
+        name: "obj"
+        type: "java.lang.Object"
+      }
+      fields {
+        name: "replyTo"
+        type: "android.os.Messenger"
+      }
+      fields {
+        name: "what"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.util.AndroidRuntimeException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.ResultReceiver"
+      methods {
+        name: "<init>"
+        parameters: "android.os.Handler"
+        return_type: "void"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.content.res.TypedArray"
+      methods {
+        name: "getFloat"
+        parameters: "int"
+        parameters: "float"
+        return_type: "float"
+      }
+      methods {
+        name: "getInt"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getInteger"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getResourceId"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "hasValue"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.SoundEffectConstants"
+      methods {
+        name: "getContantForFocusDirection"
+        parameters: "int"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "com.android.internal.content.FileSystemProvider"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "includeFile"
+        parameters: "android.database.MatrixCursor"
+        parameters: "java.lang.String"
+        parameters: "java.io.File"
+        return_type: "android.database.MatrixCursor.RowBuilder"
+      }
+      methods {
+        name: "onCreate"
+        parameters: "java.lang.String[]"
+        return_type: "void"
+      }
+      methods {
+        name: "queryDocument"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String[]"
+        return_type: "android.database.Cursor"
+      }
+    }
+    classes {
+      name: "android.widget.EdgeEffect"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        return_type: "void"
+      }
+      methods {
+        name: "draw"
+        parameters: "android.graphics.Canvas"
+        return_type: "boolean"
+      }
+      methods {
+        name: "finish"
+        return_type: "void"
+      }
+      methods {
+        name: "isFinished"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onAbsorb"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onPull"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "onPull"
+        parameters: "float"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "onRelease"
+        return_type: "void"
+      }
+      methods {
+        name: "setSize"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.Executor"
+      methods {
+        name: "execute"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.UserHandle"
+      methods {
+        name: "getIdentifier"
+        return_type: "int"
+      }
+      fields {
+        name: "SYSTEM"
+        type: "android.os.UserHandle"
+      }
+    }
+    classes {
+      name: "android.graphics.PorterDuffColorFilter"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "android.graphics.PorterDuff.Mode"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.AlertDialog"
+      methods {
+        name: "cancel"
+        return_type: "void"
+      }
+      methods {
+        name: "dismiss"
+        return_type: "void"
+      }
+      methods {
+        name: "getButton"
+        parameters: "int"
+        return_type: "android.widget.Button"
+      }
+      methods {
+        name: "getWindow"
+        return_type: "android.view.Window"
+      }
+      methods {
+        name: "setTitle"
+        parameters: "java.lang.CharSequence"
+        return_type: "void"
+      }
+      methods {
+        name: "show"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.animation.ValueAnimator"
+      methods {
+        name: "getFrameDelay"
+        return_type: "long"
+      }
+      methods {
+        name: "getValues"
+        return_type: "android.animation.PropertyValuesHolder[]"
+      }
+    }
+    classes {
+      name: "android.view.MotionEvent"
+      methods {
+        name: "findPointerIndex"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getAction"
+        return_type: "int"
+      }
+      methods {
+        name: "getActionIndex"
+        return_type: "int"
+      }
+      methods {
+        name: "getActionMasked"
+        return_type: "int"
+      }
+      methods {
+        name: "getAxisValue"
+        parameters: "int"
+        return_type: "float"
+      }
+      methods {
+        name: "getEdgeFlags"
+        return_type: "int"
+      }
+      methods {
+        name: "getPointerCount"
+        return_type: "int"
+      }
+      methods {
+        name: "getPointerId"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getSource"
+        return_type: "int"
+      }
+      methods {
+        name: "getX"
+        return_type: "float"
+      }
+      methods {
+        name: "getX"
+        parameters: "int"
+        return_type: "float"
+      }
+      methods {
+        name: "getY"
+        return_type: "float"
+      }
+      methods {
+        name: "getY"
+        parameters: "int"
+        return_type: "float"
+      }
+      methods {
+        name: "obtain"
+        parameters: "long"
+        parameters: "long"
+        parameters: "int"
+        parameters: "float"
+        parameters: "float"
+        parameters: "int"
+        return_type: "android.view.MotionEvent"
+      }
+      methods {
+        name: "obtain"
+        parameters: "android.view.MotionEvent"
+        return_type: "android.view.MotionEvent"
+      }
+      methods {
+        name: "offsetLocation"
+        parameters: "float"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+      methods {
+        name: "transform"
+        parameters: "android.graphics.Matrix"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.UUID"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "fromString"
+        parameters: "java.lang.String"
+        return_type: "java.util.UUID"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "randomUUID"
+        return_type: "java.util.UUID"
+      }
+    }
+    classes {
+      name: "android.graphics.RectF"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "set"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      fields {
+        name: "bottom"
+        type: "float"
+      }
+      fields {
+        name: "left"
+        type: "float"
+      }
+      fields {
+        name: "right"
+        type: "float"
+      }
+      fields {
+        name: "top"
+        type: "float"
+      }
+    }
+    classes {
+      name: "java.io.ByteArrayInputStream"
+      methods {
+        name: "<init>"
+        parameters: "byte[]"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.animation.AnimatorSet"
+      methods {
+        name: "getChildAnimations"
+        return_type: "java.util.ArrayList"
+      }
+    }
+    classes {
+      name: "java.lang.AssertionError"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Objects"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hash"
+        parameters: "java.lang.Object[]"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.util.zip.ZipOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.OutputStream"
+        return_type: "void"
+      }
+      methods {
+        name: "closeEntry"
+        return_type: "void"
+      }
+      methods {
+        name: "putNextEntry"
+        parameters: "java.util.zip.ZipEntry"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.transition.TransitionManager"
+      methods {
+        name: "beginDelayedTransition"
+        parameters: "android.view.ViewGroup"
+        parameters: "android.transition.Transition"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Math"
+      methods {
+        name: "abs"
+        parameters: "float"
+        return_type: "float"
+      }
+      methods {
+        name: "abs"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "max"
+        parameters: "float"
+        parameters: "float"
+        return_type: "float"
+      }
+      methods {
+        name: "max"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "min"
+        parameters: "float"
+        parameters: "float"
+        return_type: "float"
+      }
+      methods {
+        name: "min"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "round"
+        parameters: "float"
+        return_type: "int"
+      }
+      methods {
+        name: "sin"
+        parameters: "double"
+        return_type: "double"
+      }
+    }
+    classes {
+      name: "android.graphics.Canvas"
+      methods {
+        name: "clipRect"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "android.graphics.Region.Op"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clipRect"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clipRect"
+        parameters: "android.graphics.Rect"
+        return_type: "boolean"
+      }
+      methods {
+        name: "drawRect"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "android.graphics.Paint"
+        return_type: "void"
+      }
+      methods {
+        name: "getClipBounds"
+        parameters: "android.graphics.Rect"
+        return_type: "boolean"
+      }
+      methods {
+        name: "restoreToCount"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "rotate"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "rotate"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "save"
+        return_type: "int"
+      }
+      methods {
+        name: "translate"
+        parameters: "float"
+        parameters: "float"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.media.session.MediaController.PlaybackInfo"
+      methods {
+        name: "getAudioAttributes"
+        return_type: "android.media.AudioAttributes"
+      }
+      methods {
+        name: "getCurrentVolume"
+        return_type: "int"
+      }
+      methods {
+        name: "getMaxVolume"
+        return_type: "int"
+      }
+      methods {
+        name: "getPlaybackType"
+        return_type: "int"
+      }
+      methods {
+        name: "getVolumeControl"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.view.animation.AccelerateInterpolator"
+      methods {
+        name: "<init>"
+        parameters: "float"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.ClassLoader"
+      methods {
+        name: "loadClass"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.io.FileOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.NotificationManager"
+      methods {
+        name: "cancel"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "createNotificationChannel"
+        parameters: "android.app.NotificationChannel"
+        return_type: "void"
+      }
+      methods {
+        name: "from"
+        parameters: "android.content.Context"
+        return_type: "android.app.NotificationManager"
+      }
+      methods {
+        name: "notify"
+        parameters: "int"
+        parameters: "android.app.Notification"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.HandlerThread"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "getLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "isAlive"
+        return_type: "boolean"
+      }
+      methods {
+        name: "quit"
+        return_type: "boolean"
+      }
+      methods {
+        name: "quitSafely"
+        return_type: "boolean"
+      }
+      methods {
+        name: "start"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.net.Uri.Builder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "authority"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+      methods {
+        name: "build"
+        return_type: "android.net.Uri"
+      }
+      methods {
+        name: "encodedPath"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+      methods {
+        name: "scheme"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri.Builder"
+      }
+    }
+    classes {
+      name: "java.util.Collections"
+      methods {
+        name: "reverse"
+        parameters: "java.util.List"
+        return_type: "void"
+      }
+      methods {
+        name: "singletonList"
+        parameters: "java.lang.Object"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "sort"
+        parameters: "java.util.List"
+        parameters: "java.util.Comparator"
+        return_type: "void"
+      }
+      methods {
+        name: "unmodifiableList"
+        parameters: "java.util.List"
+        return_type: "java.util.List"
+      }
+      fields {
+        name: "EMPTY_LIST"
+        type: "java.util.List"
+      }
+    }
+    classes {
+      name: "android.view.animation.Animation.AnimationListener"
+      methods {
+        name: "onAnimationEnd"
+        parameters: "android.view.animation.Animation"
+        return_type: "void"
+      }
+      methods {
+        name: "onAnimationRepeat"
+        parameters: "android.view.animation.Animation"
+        return_type: "void"
+      }
+      methods {
+        name: "onAnimationStart"
+        parameters: "android.view.animation.Animation"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.hardware.display.DisplayManagerGlobal"
+      methods {
+        name: "getInstance"
+        return_type: "android.hardware.display.DisplayManagerGlobal"
+      }
+      methods {
+        name: "getRealDisplay"
+        parameters: "int"
+        return_type: "android.view.Display"
+      }
+    }
+    classes {
+      name: "android.os.Parcel"
+      methods {
+        name: "createIntArray"
+        return_type: "int[]"
+      }
+      methods {
+        name: "createStringArrayList"
+        return_type: "java.util.ArrayList"
+      }
+      methods {
+        name: "createTypedArray"
+        parameters: "android.os.Parcelable.Creator"
+        return_type: "java.lang.Object[]"
+      }
+      methods {
+        name: "createTypedArrayList"
+        parameters: "android.os.Parcelable.Creator"
+        return_type: "java.util.ArrayList"
+      }
+      methods {
+        name: "dataPosition"
+        return_type: "int"
+      }
+      methods {
+        name: "dataSize"
+        return_type: "int"
+      }
+      methods {
+        name: "enforceInterface"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "obtain"
+        return_type: "android.os.Parcel"
+      }
+      methods {
+        name: "readBundle"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "readException"
+        return_type: "void"
+      }
+      methods {
+        name: "readFloat"
+        return_type: "float"
+      }
+      methods {
+        name: "readInt"
+        return_type: "int"
+      }
+      methods {
+        name: "readIntArray"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "readLong"
+        return_type: "long"
+      }
+      methods {
+        name: "readParcelable"
+        parameters: "java.lang.ClassLoader"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "readParcelableArray"
+        parameters: "java.lang.ClassLoader"
+        return_type: "android.os.Parcelable[]"
+      }
+      methods {
+        name: "readString"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "readStrongBinder"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+      methods {
+        name: "setDataPosition"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeBundle"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "writeFloat"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "writeInt"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeIntArray"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "writeInterfaceToken"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "writeLong"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "writeNoException"
+        return_type: "void"
+      }
+      methods {
+        name: "writeParcelable"
+        parameters: "android.os.Parcelable"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeParcelableArray"
+        parameters: "android.os.Parcelable[]"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeString"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "writeStringList"
+        parameters: "java.util.List"
+        return_type: "void"
+      }
+      methods {
+        name: "writeStrongBinder"
+        parameters: "android.os.IBinder"
+        return_type: "void"
+      }
+      methods {
+        name: "writeTypedArray"
+        parameters: "android.os.Parcelable[]"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "writeTypedList"
+        parameters: "java.util.List"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.StringBuilder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "append"
+        parameters: "char"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "float"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "int"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "long"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "java.lang.String"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "append"
+        parameters: "boolean"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "delete"
+        parameters: "int"
+        parameters: "int"
+        return_type: "java.lang.StringBuilder"
+      }
+      methods {
+        name: "length"
+        return_type: "int"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.media.session.MediaController.Callback"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Bundle"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "containsKey"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getBinder"
+        parameters: "java.lang.String"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "getBoolean"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getBoolean"
+        parameters: "java.lang.String"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getBundle"
+        parameters: "java.lang.String"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "getCharSequence"
+        parameters: "java.lang.String"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getInt"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+      methods {
+        name: "getInt"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getIntArray"
+        parameters: "java.lang.String"
+        return_type: "int[]"
+      }
+      methods {
+        name: "getLong"
+        parameters: "java.lang.String"
+        return_type: "long"
+      }
+      methods {
+        name: "getParcelable"
+        parameters: "java.lang.String"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "getParcelableArray"
+        parameters: "java.lang.String"
+        return_type: "android.os.Parcelable[]"
+      }
+      methods {
+        name: "getParcelableArrayList"
+        parameters: "java.lang.String"
+        return_type: "java.util.ArrayList"
+      }
+      methods {
+        name: "getSparseParcelableArray"
+        parameters: "java.lang.String"
+        return_type: "android.util.SparseArray"
+      }
+      methods {
+        name: "getString"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getStringArray"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String[]"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "putAll"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "putBinder"
+        parameters: "java.lang.String"
+        parameters: "android.os.IBinder"
+        return_type: "void"
+      }
+      methods {
+        name: "putBoolean"
+        parameters: "java.lang.String"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "putBundle"
+        parameters: "java.lang.String"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "putInt"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "putIntArray"
+        parameters: "java.lang.String"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "putParcelable"
+        parameters: "java.lang.String"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "putParcelableArray"
+        parameters: "java.lang.String"
+        parameters: "android.os.Parcelable[]"
+        return_type: "void"
+      }
+      methods {
+        name: "putParcelableArrayList"
+        parameters: "java.lang.String"
+        parameters: "java.util.ArrayList"
+        return_type: "void"
+      }
+      methods {
+        name: "putSparseParcelableArray"
+        parameters: "java.lang.String"
+        parameters: "android.util.SparseArray"
+        return_type: "void"
+      }
+      methods {
+        name: "putString"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "putStringArray"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String[]"
+        return_type: "void"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "setClassLoader"
+        parameters: "java.lang.ClassLoader"
+        return_type: "void"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.os.Build"
+      fields {
+        name: "IS_USER"
+        type: "boolean"
+      }
+    }
+    classes {
+      name: "java.lang.Throwable"
+      methods {
+        name: "addSuppressed"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.ViewParent"
+      methods {
+        name: "getParent"
+        return_type: "android.view.ViewParent"
+      }
+      methods {
+        name: "onNestedFling"
+        parameters: "android.view.View"
+        parameters: "float"
+        parameters: "float"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onNestedPreFling"
+        parameters: "android.view.View"
+        parameters: "float"
+        parameters: "float"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onNestedPreScroll"
+        parameters: "android.view.View"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "onNestedScroll"
+        parameters: "android.view.View"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onNestedScrollAccepted"
+        parameters: "android.view.View"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onStartNestedScroll"
+        parameters: "android.view.View"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "onStopNestedScroll"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "requestDisallowInterceptTouchEvent"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "com.google.android.collect.Lists"
+      methods {
+        name: "newArrayList"
+        parameters: "java.lang.Object[]"
+        return_type: "java.util.ArrayList"
+      }
+    }
+    classes {
+      name: "android.content.ClipData.Item"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.CharSequence"
+        parameters: "java.lang.String"
+        parameters: "android.content.Intent"
+        parameters: "android.net.Uri"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.webkit.MimeTypeMap"
+      methods {
+        name: "getMimeTypeFromExtension"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getSingleton"
+        return_type: "android.webkit.MimeTypeMap"
+      }
+    }
+    classes {
+      name: "android.media.session.MediaSession.QueueItem"
+      methods {
+        name: "getDescription"
+        return_type: "android.media.MediaDescription"
+      }
+      methods {
+        name: "getQueueId"
+        return_type: "long"
+      }
+    }
+    classes {
+      name: "java.lang.Enum"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "java.lang.Class"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Enum"
+      }
+    }
+    classes {
+      name: "android.content.ComponentName"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.System"
+      methods {
+        name: "arraycopy"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "currentTimeMillis"
+        return_type: "long"
+      }
+      methods {
+        name: "identityHashCode"
+        parameters: "java.lang.Object"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.text.NumberFormat"
+      methods {
+        name: "format"
+        parameters: "double"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPercentInstance"
+        return_type: "java.text.NumberFormat"
+      }
+      methods {
+        name: "setMaximumFractionDigits"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setMinimumFractionDigits"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.IWindowManager.Stub"
+      methods {
+        name: "asInterface"
+        parameters: "android.os.IBinder"
+        return_type: "android.view.IWindowManager"
+      }
+    }
+    classes {
+      name: "java.util.ArrayList"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.util.Collection"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "clone"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "java.lang.Object"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "remove"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "set"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "toArray"
+        return_type: "java.lang.Object[]"
+      }
+    }
+    classes {
+      name: "android.app.FragmentTransaction"
+      methods {
+        name: "add"
+        parameters: "android.app.Fragment"
+        parameters: "java.lang.String"
+        return_type: "android.app.FragmentTransaction"
+      }
+      methods {
+        name: "commit"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.util.Pair"
+      methods {
+        name: "create"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "android.util.Pair"
+      }
+      fields {
+        name: "second"
+        type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.graphics.drawable.Drawable"
+      methods {
+        name: "draw"
+        parameters: "android.graphics.Canvas"
+        return_type: "void"
+      }
+      methods {
+        name: "getIntrinsicWidth"
+        return_type: "int"
+      }
+      methods {
+        name: "getOpacity"
+        return_type: "int"
+      }
+      methods {
+        name: "isAutoMirrored"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isStateful"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isVisible"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setAlpha"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setBounds"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setLayoutDirection"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setState"
+        parameters: "int[]"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setVisible"
+        parameters: "boolean"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.graphics.Paint"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "setAlpha"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setColor"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setColorFilter"
+        parameters: "android.graphics.ColorFilter"
+        return_type: "android.graphics.ColorFilter"
+      }
+    }
+    classes {
+      name: "java.lang.Thread"
+      methods {
+        name: "currentThread"
+        return_type: "java.lang.Thread"
+      }
+    }
+    classes {
+      name: "android.os.IBinder"
+      methods {
+        name: "linkToDeath"
+        parameters: "android.os.IBinder.DeathRecipient"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "queryLocalInterface"
+        parameters: "java.lang.String"
+        return_type: "android.os.IInterface"
+      }
+      methods {
+        name: "transact"
+        parameters: "int"
+        parameters: "android.os.Parcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "unlinkToDeath"
+        parameters: "android.os.IBinder.DeathRecipient"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.view.animation.AnimationUtils"
+      methods {
+        name: "currentAnimationTimeMillis"
+        return_type: "long"
+      }
+      methods {
+        name: "loadAnimation"
+        parameters: "android.content.Context"
+        parameters: "int"
+        return_type: "android.view.animation.Animation"
+      }
+    }
+    classes {
+      name: "android.service.media.MediaBrowserService.Result"
+      methods {
+        name: "sendResult"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.View.MeasureSpec"
+      methods {
+        name: "getMode"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getSize"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "makeMeasureSpec"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.IllegalStateException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.View"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        return_type: "void"
+      }
+      methods {
+        name: "addFocusables"
+        parameters: "java.util.ArrayList"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "addOnAttachStateChangeListener"
+        parameters: "android.view.View.OnAttachStateChangeListener"
+        return_type: "void"
+      }
+      methods {
+        name: "addTouchables"
+        parameters: "java.util.ArrayList"
+        return_type: "void"
+      }
+      methods {
+        name: "canScrollHorizontally"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clearAnimation"
+        return_type: "void"
+      }
+      methods {
+        name: "combineMeasuredStates"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "dispatchApplyWindowInsets"
+        parameters: "android.view.WindowInsets"
+        return_type: "android.view.WindowInsets"
+      }
+      methods {
+        name: "dispatchGenericMotionEvent"
+        parameters: "android.view.MotionEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "dispatchPopulateAccessibilityEvent"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "findViewById"
+        parameters: "int"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "getAlpha"
+        return_type: "float"
+      }
+      methods {
+        name: "getAnimation"
+        return_type: "android.view.animation.Animation"
+      }
+      methods {
+        name: "getBackground"
+        return_type: "android.graphics.drawable.Drawable"
+      }
+      methods {
+        name: "getBottom"
+        return_type: "int"
+      }
+      methods {
+        name: "getDrawingRect"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "getElevation"
+        return_type: "float"
+      }
+      methods {
+        name: "getFitsSystemWindows"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getHeight"
+        return_type: "int"
+      }
+      methods {
+        name: "getHitRect"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "getId"
+        return_type: "int"
+      }
+      methods {
+        name: "getImportantForAccessibility"
+        return_type: "int"
+      }
+      methods {
+        name: "getLayerType"
+        return_type: "int"
+      }
+      methods {
+        name: "getLayoutDirection"
+        return_type: "int"
+      }
+      methods {
+        name: "getLayoutParams"
+        return_type: "android.view.ViewGroup.LayoutParams"
+      }
+      methods {
+        name: "getLeft"
+        return_type: "int"
+      }
+      methods {
+        name: "getLocationInWindow"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "getLocationOnScreen"
+        parameters: "int[]"
+        return_type: "void"
+      }
+      methods {
+        name: "getMatrix"
+        return_type: "android.graphics.Matrix"
+      }
+      methods {
+        name: "getMeasuredHeight"
+        return_type: "int"
+      }
+      methods {
+        name: "getMeasuredState"
+        return_type: "int"
+      }
+      methods {
+        name: "getMeasuredWidth"
+        return_type: "int"
+      }
+      methods {
+        name: "getParent"
+        return_type: "android.view.ViewParent"
+      }
+      methods {
+        name: "getRight"
+        return_type: "int"
+      }
+      methods {
+        name: "getScrollX"
+        return_type: "int"
+      }
+      methods {
+        name: "getScrollY"
+        return_type: "int"
+      }
+      methods {
+        name: "getTag"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getTop"
+        return_type: "int"
+      }
+      methods {
+        name: "getTransitionName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getTranslationY"
+        return_type: "float"
+      }
+      methods {
+        name: "getViewTreeObserver"
+        return_type: "android.view.ViewTreeObserver"
+      }
+      methods {
+        name: "getVisibility"
+        return_type: "int"
+      }
+      methods {
+        name: "getWidth"
+        return_type: "int"
+      }
+      methods {
+        name: "getWindowToken"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "getZ"
+        return_type: "float"
+      }
+      methods {
+        name: "hasOverlappingRendering"
+        return_type: "boolean"
+      }
+      methods {
+        name: "inflate"
+        parameters: "android.content.Context"
+        parameters: "int"
+        parameters: "android.view.ViewGroup"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "invalidate"
+        return_type: "void"
+      }
+      methods {
+        name: "invalidate"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "isAttachedToWindow"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isFocused"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isLaidOut"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isLayoutRequested"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isOpaque"
+        return_type: "boolean"
+      }
+      methods {
+        name: "layout"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "measure"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "offsetLeftAndRight"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "offsetTopAndBottom"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "post"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "postDelayed"
+        parameters: "java.lang.Runnable"
+        parameters: "long"
+        return_type: "boolean"
+      }
+      methods {
+        name: "postInvalidate"
+        return_type: "void"
+      }
+      methods {
+        name: "postInvalidate"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "postInvalidateOnAnimation"
+        return_type: "void"
+      }
+      methods {
+        name: "postInvalidateOnAnimation"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "postOnAnimation"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+      methods {
+        name: "removeOnAttachStateChangeListener"
+        parameters: "android.view.View.OnAttachStateChangeListener"
+        return_type: "void"
+      }
+      methods {
+        name: "requestApplyInsets"
+        return_type: "void"
+      }
+      methods {
+        name: "requestFitSystemWindows"
+        return_type: "void"
+      }
+      methods {
+        name: "requestFocus"
+        return_type: "boolean"
+      }
+      methods {
+        name: "requestFocus"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "requestFocus"
+        parameters: "int"
+        parameters: "android.graphics.Rect"
+        return_type: "boolean"
+      }
+      methods {
+        name: "resolveSizeAndState"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "restoreHierarchyState"
+        parameters: "android.util.SparseArray"
+        return_type: "void"
+      }
+      methods {
+        name: "saveHierarchyState"
+        parameters: "android.util.SparseArray"
+        return_type: "void"
+      }
+      methods {
+        name: "sendAccessibilityEvent"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setAccessibilityDelegate"
+        parameters: "android.view.View.AccessibilityDelegate"
+        return_type: "void"
+      }
+      methods {
+        name: "setAlpha"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "setElevation"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "setId"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setImportantForAccessibility"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setLayerPaint"
+        parameters: "android.graphics.Paint"
+        return_type: "void"
+      }
+      methods {
+        name: "setLayerType"
+        parameters: "int"
+        parameters: "android.graphics.Paint"
+        return_type: "void"
+      }
+      methods {
+        name: "setOnApplyWindowInsetsListener"
+        parameters: "android.view.View.OnApplyWindowInsetsListener"
+        return_type: "void"
+      }
+      methods {
+        name: "setSaveFromParentEnabled"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setTag"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "setTransitionName"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "setTranslationY"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "setVisibility"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "startAnimation"
+        parameters: "android.view.animation.Animation"
+        return_type: "void"
+      }
+      methods {
+        name: "stopNestedScroll"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.ViewGroup.MarginLayoutParams"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        parameters: "android.util.AttributeSet"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.view.ViewGroup.LayoutParams"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.view.ViewGroup.MarginLayoutParams"
+        return_type: "void"
+      }
+      fields {
+        name: "bottomMargin"
+        type: "int"
+      }
+      fields {
+        name: "leftMargin"
+        type: "int"
+      }
+      fields {
+        name: "rightMargin"
+        type: "int"
+      }
+      fields {
+        name: "topMargin"
+        type: "int"
+      }
+      fields {
+        name: "width"
+        type: "int"
+      }
+    }
+    classes {
+      name: "java.io.Writer"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.BroadcastReceiver"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "goAsync"
+        return_type: "android.content.BroadcastReceiver.PendingResult"
+      }
+    }
+    classes {
+      name: "android.content.SharedPreferences.Editor"
+      methods {
+        name: "apply"
+        return_type: "void"
+      }
+      methods {
+        name: "putInt"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "android.content.SharedPreferences.Editor"
+      }
+    }
+    classes {
+      name: "android.os.SystemProperties"
+      methods {
+        name: "get"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Set"
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "containsAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.IllegalArgumentException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.List"
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "addAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "containsAll"
+        parameters: "java.util.Collection"
+        return_type: "boolean"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "subList"
+        parameters: "int"
+        parameters: "int"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "toArray"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.Object[]"
+      }
+    }
+    classes {
+      name: "java.lang.ThreadLocal"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.media.session.PlaybackState.CustomAction"
+      methods {
+        name: "getAction"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getExtras"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "getIcon"
+        return_type: "int"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.CharSequence"
+      }
+    }
+    classes {
+      name: "android.app.Service"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "onCreate"
+        return_type: "void"
+      }
+      methods {
+        name: "onDestroy"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Environment"
+      methods {
+        name: "getExternalStorageDirectory"
+        return_type: "java.io.File"
+      }
+    }
+    classes {
+      name: "android.os.SystemClock"
+      methods {
+        name: "uptimeMillis"
+        return_type: "long"
+      }
+    }
+    classes {
+      name: "android.media.MediaDescription"
+      methods {
+        name: "getDescription"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getExtras"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "getIconBitmap"
+        return_type: "android.graphics.Bitmap"
+      }
+      methods {
+        name: "getIconUri"
+        return_type: "android.net.Uri"
+      }
+      methods {
+        name: "getMediaId"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getMediaUri"
+        return_type: "android.net.Uri"
+      }
+      methods {
+        name: "getSubtitle"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getTitle"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.util.SparseArray"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "append"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "delete"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "indexOfKey"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "keyAt"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "put"
+        parameters: "int"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "remove"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+      methods {
+        name: "valueAt"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.io.PrintWriter"
+      methods {
+        name: "<init>"
+        parameters: "java.io.Writer"
+        return_type: "void"
+      }
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+      methods {
+        name: "print"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "print"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "print"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "print"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "println"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.media.MediaMetadata"
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Modifier"
+      methods {
+        name: "isPublic"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isStatic"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.view.VelocityTracker"
+      methods {
+        name: "addMovement"
+        parameters: "android.view.MotionEvent"
+        return_type: "void"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "computeCurrentVelocity"
+        parameters: "int"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "getXVelocity"
+        parameters: "int"
+        return_type: "float"
+      }
+      methods {
+        name: "getYVelocity"
+        parameters: "int"
+        return_type: "float"
+      }
+      methods {
+        name: "obtain"
+        return_type: "android.view.VelocityTracker"
+      }
+      methods {
+        name: "recycle"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.FileUtils"
+      methods {
+        name: "deleteOlderFiles"
+        parameters: "java.io.File"
+        parameters: "int"
+        parameters: "long"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isValidExtFilename"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.view.WindowManager.LayoutParams"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "windowAnimations"
+        type: "int"
+      }
+    }
+    classes {
+      name: "java.util.Iterator"
+      methods {
+        name: "hasNext"
+        return_type: "boolean"
+      }
+      methods {
+        name: "next"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "remove"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.net.Uri"
+      methods {
+        name: "decode"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "encode"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "encode"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getEncodedPath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getLastPathSegment"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.InvocationTargetException"
+      methods {
+        name: "getCause"
+        return_type: "java.lang.Throwable"
+      }
+    }
+    classes {
+      name: "java.util.HashMap"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "containsKey"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "entrySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "values"
+        return_type: "java.util.Collection"
+      }
+    }
+    classes {
+      name: "android.os.Parcelable.Creator"
+      methods {
+        name: "createFromParcel"
+        parameters: "android.os.Parcel"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.graphics.Point"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      fields {
+        name: "x"
+        type: "int"
+      }
+      fields {
+        name: "y"
+        type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.Object"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "clone"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getClass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.annotation.ElementType"
+      fields {
+        name: "METHOD"
+        type: "java.lang.annotation.ElementType"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.annotation.ElementType"
+      }
+    }
+    classes {
+      name: "android.view.animation.Animation"
+      methods {
+        name: "cancel"
+        return_type: "void"
+      }
+      methods {
+        name: "setAnimationListener"
+        parameters: "android.view.animation.Animation.AnimationListener"
+        return_type: "void"
+      }
+      methods {
+        name: "start"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.View.BaseSavedState"
+      methods {
+        name: "<init>"
+        parameters: "android.os.Parcel"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "EMPTY_STATE"
+        type: "android.view.AbsSavedState"
+      }
+    }
+    classes {
+      name: "java.lang.Package"
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.view.animation.AnimationSet"
+      methods {
+        name: "<init>"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "addAnimation"
+        parameters: "android.view.animation.Animation"
+        return_type: "void"
+      }
+      methods {
+        name: "getAnimations"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getTransformation"
+        parameters: "long"
+        parameters: "android.view.animation.Transformation"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getTransformation"
+        parameters: "long"
+        parameters: "android.view.animation.Transformation"
+        parameters: "float"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.os.Vibrator"
+      methods {
+        name: "vibrate"
+        parameters: "long"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Binder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "getCallingPid"
+        return_type: "int"
+      }
+      methods {
+        name: "getCallingUid"
+        return_type: "int"
+      }
+      methods {
+        name: "onTransact"
+        parameters: "int"
+        parameters: "android.os.Parcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.nio.charset.StandardCharsets"
+      fields {
+        name: "UTF_8"
+        type: "java.nio.charset.Charset"
+      }
+    }
+    classes {
+      name: "android.content.res.Configuration"
+      fields {
+        name: "uiMode"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.provider.DocumentsContract"
+      methods {
+        name: "buildChildDocumentsUri"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "android.net.Uri"
+      }
+    }
+    classes {
+      name: "android.app.Notification.TvExtender"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Process"
+      methods {
+        name: "myPid"
+        return_type: "int"
+      }
+      methods {
+        name: "myUid"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.view.WindowInsets"
+      methods {
+        name: "getSystemWindowInsetBottom"
+        return_type: "int"
+      }
+      methods {
+        name: "getSystemWindowInsetLeft"
+        return_type: "int"
+      }
+      methods {
+        name: "getSystemWindowInsetRight"
+        return_type: "int"
+      }
+      methods {
+        name: "getSystemWindowInsetTop"
+        return_type: "int"
+      }
+      methods {
+        name: "isConsumed"
+        return_type: "boolean"
+      }
+      methods {
+        name: "replaceSystemWindowInsets"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "android.view.WindowInsets"
+      }
+    }
+    classes {
+      name: "android.media.session.PlaybackState"
+      methods {
+        name: "getActions"
+        return_type: "long"
+      }
+      methods {
+        name: "getActiveQueueItemId"
+        return_type: "long"
+      }
+      methods {
+        name: "getBufferedPosition"
+        return_type: "long"
+      }
+      methods {
+        name: "getCustomActions"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getErrorMessage"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getExtras"
+        return_type: "android.os.Bundle"
+      }
+      methods {
+        name: "getLastPositionUpdateTime"
+        return_type: "long"
+      }
+      methods {
+        name: "getPlaybackSpeed"
+        return_type: "float"
+      }
+      methods {
+        name: "getPosition"
+        return_type: "long"
+      }
+      methods {
+        name: "getState"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.content.pm.ProviderInfo"
+      methods {
+        name: "loadXmlMetaData"
+        parameters: "android.content.pm.PackageManager"
+        parameters: "java.lang.String"
+        return_type: "android.content.res.XmlResourceParser"
+      }
+      fields {
+        name: "authority"
+        type: "java.lang.String"
+      }
+      fields {
+        name: "exported"
+        type: "boolean"
+      }
+      fields {
+        name: "grantUriPermissions"
+        type: "boolean"
+      }
+    }
+    classes {
+      name: "android.content.Context"
+      methods {
+        name: "getCacheDir"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+      methods {
+        name: "getColor"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getExternalCacheDir"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getExternalCacheDirs"
+        return_type: "java.io.File[]"
+      }
+      methods {
+        name: "getExternalFilesDir"
+        parameters: "java.lang.String"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getExternalFilesDirs"
+        parameters: "java.lang.String"
+        return_type: "java.io.File[]"
+      }
+      methods {
+        name: "getExternalMediaDirs"
+        return_type: "java.io.File[]"
+      }
+      methods {
+        name: "getFilesDir"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getPackageManager"
+        return_type: "android.content.pm.PackageManager"
+      }
+      methods {
+        name: "getPackageName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getResources"
+        return_type: "android.content.res.Resources"
+      }
+      methods {
+        name: "getSharedPreferences"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "android.content.SharedPreferences"
+      }
+      methods {
+        name: "getString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getString"
+        parameters: "int"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getSystemService"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getSystemService"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getTheme"
+        return_type: "android.content.res.Resources.Theme"
+      }
+      methods {
+        name: "obtainStyledAttributes"
+        parameters: "android.util.AttributeSet"
+        parameters: "int[]"
+        return_type: "android.content.res.TypedArray"
+      }
+      methods {
+        name: "sendBroadcastAsUser"
+        parameters: "android.content.Intent"
+        parameters: "android.os.UserHandle"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "startActivity"
+        parameters: "android.content.Intent"
+        return_type: "void"
+      }
+      methods {
+        name: "startService"
+        parameters: "android.content.Intent"
+        return_type: "android.content.ComponentName"
+      }
+    }
+    classes {
+      name: "android.content.res.Resources"
+      methods {
+        name: "getConfiguration"
+        return_type: "android.content.res.Configuration"
+      }
+      methods {
+        name: "getDisplayMetrics"
+        return_type: "android.util.DisplayMetrics"
+      }
+      methods {
+        name: "getQuantityString"
+        parameters: "int"
+        parameters: "int"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getResourceName"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getResourceTypeName"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getText"
+        parameters: "int"
+        return_type: "java.lang.CharSequence"
+      }
+    }
+    classes {
+      name: "java.lang.ref.WeakReference"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.animation.PropertyValuesHolder"
+      methods {
+        name: "getPropertyName"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.animation.Animator"
+      methods {
+        name: "addListener"
+        parameters: "android.animation.Animator.AnimatorListener"
+        return_type: "void"
+      }
+      methods {
+        name: "cancel"
+        return_type: "void"
+      }
+      methods {
+        name: "end"
+        return_type: "void"
+      }
+      methods {
+        name: "removeListener"
+        parameters: "android.animation.Animator.AnimatorListener"
+        return_type: "void"
+      }
+      methods {
+        name: "setTarget"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "start"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.ServiceManager"
+      methods {
+        name: "getService"
+        parameters: "java.lang.String"
+        return_type: "android.os.IBinder"
+      }
+    }
+    classes {
+      name: "android.view.animation.AlphaAnimation"
+      methods {
+        name: "<init>"
+        parameters: "float"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "setDuration"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "setInterpolator"
+        parameters: "android.view.animation.Interpolator"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.SharedPreferences"
+      methods {
+        name: "edit"
+        return_type: "android.content.SharedPreferences.Editor"
+      }
+      methods {
+        name: "getInt"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "com.android.internal.logging.MetricsLogger"
+      methods {
+        name: "action"
+        parameters: "android.content.Context"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "histogram"
+        parameters: "android.content.Context"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Enumeration"
+      methods {
+        name: "hasMoreElements"
+        return_type: "boolean"
+      }
+      methods {
+        name: "nextElement"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "java.util.HashSet"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "add"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "clear"
+        return_type: "void"
+      }
+      methods {
+        name: "contains"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.util.ConcurrentModificationException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.BroadcastReceiver.PendingResult"
+      methods {
+        name: "finish"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "com.android.internal.app.AlertActivity"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "onCreate"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.database.MatrixCursor.RowBuilder"
+      methods {
+        name: "add"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object"
+        return_type: "android.database.MatrixCursor.RowBuilder"
+      }
+    }
+    classes {
+      name: "java.util.NoSuchElementException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.String"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "charAt"
+        parameters: "int"
+        return_type: "char"
+      }
+      methods {
+        name: "endsWith"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "format"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getBytes"
+        parameters: "java.nio.charset.Charset"
+        return_type: "byte[]"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "indexOf"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "lastIndexOf"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "length"
+        return_type: "int"
+      }
+      methods {
+        name: "replace"
+        parameters: "java.lang.CharSequence"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "replaceFirst"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "startsWith"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "substring"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "substring"
+        parameters: "int"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toLowerCase"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toUpperCase"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "float"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.content.res.Resources.Theme"
+      methods {
+        name: "resolveAttribute"
+        parameters: "int"
+        parameters: "android.util.TypedValue"
+        parameters: "boolean"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "java.io.BufferedOutputStream"
+      methods {
+        name: "<init>"
+        parameters: "java.io.OutputStream"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.widget.OverScroller"
+      methods {
+        name: "abortAnimation"
+        return_type: "void"
+      }
+      methods {
+        name: "computeScrollOffset"
+        return_type: "boolean"
+      }
+      methods {
+        name: "fling"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "getCurrVelocity"
+        return_type: "float"
+      }
+      methods {
+        name: "getCurrX"
+        return_type: "int"
+      }
+      methods {
+        name: "getCurrY"
+        return_type: "int"
+      }
+      methods {
+        name: "getFinalX"
+        return_type: "int"
+      }
+      methods {
+        name: "getFinalY"
+        return_type: "int"
+      }
+      methods {
+        name: "isFinished"
+        return_type: "boolean"
+      }
+      methods {
+        name: "springBack"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "startScroll"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "startScroll"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Long"
+      methods {
+        name: "toString"
+        parameters: "long"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "long"
+        return_type: "java.lang.Long"
+      }
+    }
+    classes {
+      name: "java.util.zip.ZipFile"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        return_type: "void"
+      }
+      methods {
+        name: "entries"
+        return_type: "java.util.Enumeration"
+      }
+      methods {
+        name: "getInputStream"
+        parameters: "java.util.zip.ZipEntry"
+        return_type: "java.io.InputStream"
+      }
+    }
+    classes {
+      name: "android.os.Messenger"
+      methods {
+        name: "<init>"
+        parameters: "android.os.Handler"
+        return_type: "void"
+      }
+      methods {
+        name: "getBinder"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "send"
+        parameters: "android.os.Message"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.res.XmlResourceParser"
+      methods {
+        name: "getAttributeValue"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "next"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.app.Activity"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "getFragmentManager"
+        return_type: "android.app.FragmentManager"
+      }
+      methods {
+        name: "onCreate"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "onSaveInstanceState"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.accessibility.AccessibilityNodeProvider"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.Display"
+      methods {
+        name: "getRealSize"
+        parameters: "android.graphics.Point"
+        return_type: "void"
+      }
+      methods {
+        name: "getRotation"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.app.Fragment"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "onActivityCreated"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+      methods {
+        name: "onDestroy"
+        return_type: "void"
+      }
+      methods {
+        name: "onPause"
+        return_type: "void"
+      }
+      methods {
+        name: "onResume"
+        return_type: "void"
+      }
+      methods {
+        name: "onStart"
+        return_type: "void"
+      }
+      methods {
+        name: "onStop"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.util.AttributeSet"
+      methods {
+        name: "getAttributeValue"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPositionDescription"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.Executors"
+      methods {
+        name: "newFixedThreadPool"
+        parameters: "int"
+        return_type: "java.util.concurrent.ExecutorService"
+      }
+    }
+    classes {
+      name: "android.view.accessibility.AccessibilityEvent"
+      methods {
+        name: "getEventType"
+        return_type: "int"
+      }
+      methods {
+        name: "setClassName"
+        parameters: "java.lang.CharSequence"
+        return_type: "void"
+      }
+      methods {
+        name: "setScrollX"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setScrollY"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setScrollable"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.widget.EditText"
+      methods {
+        name: "getText"
+        return_type: "android.text.Editable"
+      }
+      methods {
+        name: "setEnabled"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setOnFocusChangeListener"
+        parameters: "android.view.View.OnFocusChangeListener"
+        return_type: "void"
+      }
+      methods {
+        name: "setText"
+        parameters: "java.lang.CharSequence"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Field"
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "set"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "setAccessible"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setBoolean"
+        parameters: "java.lang.Object"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "setInt"
+        parameters: "java.lang.Object"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.animation.AnimatorInflater"
+      methods {
+        name: "loadAnimator"
+        parameters: "android.content.Context"
+        parameters: "int"
+        return_type: "android.animation.Animator"
+      }
+    }
+    classes {
+      name: "android.media.browse.MediaBrowser.MediaItem"
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "java.lang.annotation.RetentionPolicy"
+      fields {
+        name: "RUNTIME"
+        type: "java.lang.annotation.RetentionPolicy"
+      }
+    }
+    classes {
+      name: "android.os.AsyncTask"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.Window"
+      methods {
+        name: "getAttributes"
+        return_type: "android.view.WindowManager.LayoutParams"
+      }
+      methods {
+        name: "peekDecorView"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "setAttributes"
+        parameters: "android.view.WindowManager.LayoutParams"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.database.MatrixCursor"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String[]"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String[]"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "addRow"
+        parameters: "java.lang.Object[]"
+        return_type: "void"
+      }
+      methods {
+        name: "newRow"
+        return_type: "android.database.MatrixCursor.RowBuilder"
+      }
+    }
+    classes {
+      name: "android.os.ParcelFileDescriptor"
+      methods {
+        name: "open"
+        parameters: "java.io.File"
+        parameters: "int"
+        return_type: "android.os.ParcelFileDescriptor"
+      }
+      methods {
+        name: "parseMode"
+        parameters: "java.lang.String"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.atomic.AtomicInteger"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.FileNotFoundException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.SecurityException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.ViewTreeObserver"
+      methods {
+        name: "addOnPreDrawListener"
+        parameters: "android.view.ViewTreeObserver.OnPreDrawListener"
+        return_type: "void"
+      }
+      methods {
+        name: "isAlive"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeOnPreDrawListener"
+        parameters: "android.view.ViewTreeObserver.OnPreDrawListener"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.KeyEvent"
+      methods {
+        name: "getAction"
+        return_type: "int"
+      }
+      methods {
+        name: "getKeyCode"
+        return_type: "int"
+      }
+      methods {
+        name: "hasModifiers"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "hasNoModifiers"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isAltPressed"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isShiftPressed"
+        return_type: "boolean"
+      }
+      methods {
+        name: "startTracking"
+        return_type: "void"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "java.lang.AutoCloseable"
+      methods {
+        name: "close"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Map"
+      methods {
+        name: "containsKey"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "entrySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "keySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "putAll"
+        parameters: "java.util.Map"
+        return_type: "void"
+      }
+      methods {
+        name: "remove"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "size"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "java.lang.UnsupportedOperationException"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.widget.FrameLayout"
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        parameters: "int"
+        parameters: "android.view.ViewGroup.LayoutParams"
+        return_type: "void"
+      }
+      methods {
+        name: "addView"
+        parameters: "android.view.View"
+        parameters: "android.view.ViewGroup.LayoutParams"
+        return_type: "void"
+      }
+      methods {
+        name: "computeHorizontalScrollExtent"
+        return_type: "int"
+      }
+      methods {
+        name: "computeHorizontalScrollOffset"
+        return_type: "int"
+      }
+      methods {
+        name: "computeHorizontalScrollRange"
+        return_type: "int"
+      }
+      methods {
+        name: "computeVerticalScrollExtent"
+        return_type: "int"
+      }
+      methods {
+        name: "computeVerticalScrollOffset"
+        return_type: "int"
+      }
+      methods {
+        name: "dispatchKeyEvent"
+        parameters: "android.view.KeyEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "draw"
+        parameters: "android.graphics.Canvas"
+        return_type: "void"
+      }
+      methods {
+        name: "onAttachedToWindow"
+        return_type: "void"
+      }
+      methods {
+        name: "onLayout"
+        parameters: "boolean"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onMeasure"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onRestoreInstanceState"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "onSaveInstanceState"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "onScrollChanged"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "onSizeChanged"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "requestChildFocus"
+        parameters: "android.view.View"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "requestDisallowInterceptTouchEvent"
+        parameters: "boolean"
+        return_type: "void"
+      }
+      methods {
+        name: "requestLayout"
+        return_type: "void"
+      }
+      methods {
+        name: "scrollTo"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.accounts.Account"
+      fields {
+        name: "name"
+        type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.app.Notification.Action.Builder"
+      methods {
+        name: "<init>"
+        parameters: "android.graphics.drawable.Icon"
+        parameters: "java.lang.CharSequence"
+        parameters: "android.app.PendingIntent"
+        return_type: "void"
+      }
+      methods {
+        name: "build"
+        return_type: "android.app.Notification.Action"
+      }
+    }
+    classes {
+      name: "android.view.View.AccessibilityDelegate"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "dispatchPopulateAccessibilityEvent"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getAccessibilityNodeProvider"
+        parameters: "android.view.View"
+        return_type: "android.view.accessibility.AccessibilityNodeProvider"
+      }
+      methods {
+        name: "onInitializeAccessibilityEvent"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "void"
+      }
+      methods {
+        name: "onInitializeAccessibilityNodeInfo"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityNodeInfo"
+        return_type: "void"
+      }
+      methods {
+        name: "onPopulateAccessibilityEvent"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "void"
+      }
+      methods {
+        name: "onRequestSendAccessibilityEvent"
+        parameters: "android.view.ViewGroup"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "boolean"
+      }
+      methods {
+        name: "performAccessibilityAction"
+        parameters: "android.view.View"
+        parameters: "int"
+        parameters: "android.os.Bundle"
+        return_type: "boolean"
+      }
+      methods {
+        name: "sendAccessibilityEvent"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "sendAccessibilityEventUnchecked"
+        parameters: "android.view.View"
+        parameters: "android.view.accessibility.AccessibilityEvent"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.zip.ZipEntry"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getTime"
+        return_type: "long"
+      }
+      methods {
+        name: "isDirectory"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setTime"
+        parameters: "long"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.Looper"
+      methods {
+        name: "getMainLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "getThread"
+        return_type: "java.lang.Thread"
+      }
+      methods {
+        name: "myLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "quit"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.pm.PackageManager"
+      methods {
+        name: "getPackagesForUid"
+        parameters: "int"
+        return_type: "java.lang.String[]"
+      }
+      methods {
+        name: "hasSystemFeature"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "resolveContentProvider"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "android.content.pm.ProviderInfo"
+      }
+    }
+    classes {
+      name: "java.lang.Class"
+      methods {
+        name: "forName"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "forName"
+        parameters: "java.lang.String"
+        parameters: "boolean"
+        parameters: "java.lang.ClassLoader"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getAnnotation"
+        parameters: "java.lang.Class"
+        return_type: "java.lang.annotation.Annotation"
+      }
+      methods {
+        name: "getCanonicalName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getClassLoader"
+        return_type: "java.lang.ClassLoader"
+      }
+      methods {
+        name: "getComponentType"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "getConstructor"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Constructor"
+      }
+      methods {
+        name: "getDeclaredConstructor"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Constructor"
+      }
+      methods {
+        name: "getDeclaredField"
+        parameters: "java.lang.String"
+        return_type: "java.lang.reflect.Field"
+      }
+      methods {
+        name: "getDeclaredMethod"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Method"
+      }
+      methods {
+        name: "getDeclaredMethods"
+        return_type: "java.lang.reflect.Method[]"
+      }
+      methods {
+        name: "getInterfaces"
+        return_type: "java.lang.Class[]"
+      }
+      methods {
+        name: "getMethod"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Class[]"
+        return_type: "java.lang.reflect.Method"
+      }
+      methods {
+        name: "getModifiers"
+        return_type: "int"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getPackage"
+        return_type: "java.lang.Package"
+      }
+      methods {
+        name: "getSimpleName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getSuperclass"
+        return_type: "java.lang.Class"
+      }
+      methods {
+        name: "isAnonymousClass"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isAssignableFrom"
+        parameters: "java.lang.Class"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isInstance"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isMemberClass"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.content.ContentProvider"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "attachInfo"
+        parameters: "android.content.Context"
+        parameters: "android.content.pm.ProviderInfo"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.NullPointerException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.FocusFinder"
+      methods {
+        name: "findNextFocus"
+        parameters: "android.view.ViewGroup"
+        parameters: "android.view.View"
+        parameters: "int"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "findNextFocusFromRect"
+        parameters: "android.view.ViewGroup"
+        parameters: "android.graphics.Rect"
+        parameters: "int"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "getInstance"
+        return_type: "android.view.FocusFinder"
+      }
+    }
+    classes {
+      name: "android.app.FragmentManager"
+      methods {
+        name: "beginTransaction"
+        return_type: "android.app.FragmentTransaction"
+      }
+      methods {
+        name: "executePendingTransactions"
+        return_type: "boolean"
+      }
+      methods {
+        name: "findFragmentByTag"
+        parameters: "java.lang.String"
+        return_type: "android.app.Fragment"
+      }
+    }
+    classes {
+      name: "android.os.Handler"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.os.Looper"
+        return_type: "void"
+      }
+      methods {
+        name: "getLooper"
+        return_type: "android.os.Looper"
+      }
+      methods {
+        name: "handleMessage"
+        parameters: "android.os.Message"
+        return_type: "void"
+      }
+      methods {
+        name: "hasMessages"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "post"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "postAtFrontOfQueue"
+        parameters: "java.lang.Runnable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "removeCallbacks"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+      methods {
+        name: "removeCallbacksAndMessages"
+        parameters: "java.lang.Object"
+        return_type: "void"
+      }
+      methods {
+        name: "removeMessages"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "sendEmptyMessage"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "sendMessageAtTime"
+        parameters: "android.os.Message"
+        parameters: "long"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.graphics.Region.Op"
+      fields {
+        name: "DIFFERENCE"
+        type: "android.graphics.Region.Op"
+      }
+    }
+    classes {
+      name: "android.graphics.Matrix"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "invert"
+        parameters: "android.graphics.Matrix"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isIdentity"
+        return_type: "boolean"
+      }
+      methods {
+        name: "mapRect"
+        parameters: "android.graphics.RectF"
+        return_type: "boolean"
+      }
+      methods {
+        name: "preConcat"
+        parameters: "android.graphics.Matrix"
+        return_type: "boolean"
+      }
+      methods {
+        name: "preTranslate"
+        parameters: "float"
+        parameters: "float"
+        return_type: "boolean"
+      }
+      methods {
+        name: "reset"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Boolean"
+      methods {
+        name: "booleanValue"
+        return_type: "boolean"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "boolean"
+        return_type: "java.lang.Boolean"
+      }
+    }
+    classes {
+      name: "android.widget.FrameLayout.LayoutParams"
+      fields {
+        name: "bottomMargin"
+        type: "int"
+      }
+      fields {
+        name: "leftMargin"
+        type: "int"
+      }
+      fields {
+        name: "rightMargin"
+        type: "int"
+      }
+      fields {
+        name: "topMargin"
+        type: "int"
+      }
+      fields {
+        name: "width"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.widget.Scroller"
+      methods {
+        name: "abortAnimation"
+        return_type: "void"
+      }
+      methods {
+        name: "computeScrollOffset"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getCurrX"
+        return_type: "int"
+      }
+      methods {
+        name: "getCurrY"
+        return_type: "int"
+      }
+      methods {
+        name: "getFinalX"
+        return_type: "int"
+      }
+      methods {
+        name: "getStartX"
+        return_type: "int"
+      }
+      methods {
+        name: "isFinished"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setFinalX"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "startScroll"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.os.UserManager"
+      methods {
+        name: "getUserProfiles"
+        return_type: "java.util.List"
+      }
+    }
+    classes {
+      name: "android.view.Gravity"
+      methods {
+        name: "apply"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "android.graphics.Rect"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "apply"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "android.graphics.Rect"
+        parameters: "android.graphics.Rect"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "getAbsoluteGravity"
+        parameters: "int"
+        parameters: "int"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.widget.TabHost"
+      methods {
+        name: "onAttachedToWindow"
+        return_type: "void"
+      }
+      methods {
+        name: "onDetachedFromWindow"
+        return_type: "void"
+      }
+      methods {
+        name: "onRestoreInstanceState"
+        parameters: "android.os.Parcelable"
+        return_type: "void"
+      }
+      methods {
+        name: "onSaveInstanceState"
+        return_type: "android.os.Parcelable"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.CopyOnWriteArrayList"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "iterator"
+        return_type: "java.util.Iterator"
+      }
+    }
+    classes {
+      name: "java.util.concurrent.ExecutorService"
+      methods {
+        name: "execute"
+        parameters: "java.lang.Runnable"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.animation.ScaleAnimation"
+      methods {
+        name: "<init>"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "float"
+        parameters: "int"
+        parameters: "float"
+        parameters: "int"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "setDuration"
+        parameters: "long"
+        return_type: "void"
+      }
+      methods {
+        name: "setInterpolator"
+        parameters: "android.view.animation.Interpolator"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.CharSequence"
+      methods {
+        name: "toString"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "java.lang.RuntimeException"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.Throwable"
+        return_type: "void"
+      }
+      methods {
+        name: "getMessage"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.media.MediaDescription.Builder"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "build"
+        return_type: "android.media.MediaDescription"
+      }
+      methods {
+        name: "setDescription"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setExtras"
+        parameters: "android.os.Bundle"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setIconBitmap"
+        parameters: "android.graphics.Bitmap"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setIconUri"
+        parameters: "android.net.Uri"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setMediaId"
+        parameters: "java.lang.String"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setMediaUri"
+        parameters: "android.net.Uri"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setSubtitle"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+      methods {
+        name: "setTitle"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.media.MediaDescription.Builder"
+      }
+    }
+    classes {
+      name: "java.util.Map.Entry"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getKey"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getValue"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.animation.AnimatorListenerAdapter"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.transition.TransitionSet"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "addTransition"
+        parameters: "android.transition.Transition"
+        return_type: "android.transition.TransitionSet"
+      }
+      methods {
+        name: "getTargets"
+        return_type: "java.util.List"
+      }
+      methods {
+        name: "getTransitionAt"
+        parameters: "int"
+        return_type: "android.transition.Transition"
+      }
+      methods {
+        name: "getTransitionCount"
+        return_type: "int"
+      }
+      methods {
+        name: "setOrdering"
+        parameters: "int"
+        return_type: "android.transition.TransitionSet"
+      }
+    }
+    classes {
+      name: "android.view.LayoutInflater"
+      methods {
+        name: "cloneInContext"
+        parameters: "android.content.Context"
+        return_type: "android.view.LayoutInflater"
+      }
+      methods {
+        name: "from"
+        parameters: "android.content.Context"
+        return_type: "android.view.LayoutInflater"
+      }
+      methods {
+        name: "getFactory"
+        return_type: "android.view.LayoutInflater.Factory"
+      }
+      methods {
+        name: "inflate"
+        parameters: "int"
+        parameters: "android.view.ViewGroup"
+        return_type: "android.view.View"
+      }
+      methods {
+        name: "setFactory2"
+        parameters: "android.view.LayoutInflater.Factory2"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.Arrays"
+      methods {
+        name: "fill"
+        parameters: "float[]"
+        parameters: "float"
+        return_type: "void"
+      }
+      methods {
+        name: "fill"
+        parameters: "int[]"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "hashCode"
+        parameters: "java.lang.Object[]"
+        return_type: "int"
+      }
+      methods {
+        name: "toString"
+        parameters: "java.lang.Object[]"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.view.SurfaceControl"
+      methods {
+        name: "screenshot"
+        parameters: "android.graphics.Rect"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "android.graphics.Bitmap"
+      }
+    }
+    classes {
+      name: "android.view.accessibility.AccessibilityRecord"
+      methods {
+        name: "setMaxScrollX"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "setMaxScrollY"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.util.TypedValue"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "getDimension"
+        parameters: "android.util.DisplayMetrics"
+        return_type: "float"
+      }
+    }
+    classes {
+      name: "android.graphics.Rect"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "contains"
+        parameters: "int"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "contains"
+        parameters: "android.graphics.Rect"
+        return_type: "boolean"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "height"
+        return_type: "int"
+      }
+      methods {
+        name: "intersect"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "intersects"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "offset"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "set"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "set"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "setEmpty"
+        return_type: "void"
+      }
+      methods {
+        name: "toShortString"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "width"
+        return_type: "int"
+      }
+      fields {
+        name: "bottom"
+        type: "int"
+      }
+      fields {
+        name: "left"
+        type: "int"
+      }
+      fields {
+        name: "right"
+        type: "int"
+      }
+      fields {
+        name: "top"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.accounts.AccountManager"
+      methods {
+        name: "getAccountsAsUser"
+        parameters: "int"
+        return_type: "android.accounts.Account[]"
+      }
+    }
+    classes {
+      name: "android.media.session.MediaController"
+      methods {
+        name: "sendCommand"
+        parameters: "java.lang.String"
+        parameters: "android.os.Bundle"
+        parameters: "android.os.ResultReceiver"
+        return_type: "void"
+      }
+      methods {
+        name: "unregisterCallback"
+        parameters: "android.media.session.MediaController.Callback"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Runnable"
+      methods {
+        name: "run"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.content.Intent"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        parameters: "java.lang.Class"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "addCategory"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "addFlags"
+        parameters: "int"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "createChooser"
+        parameters: "android.content.Intent"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "getAction"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getExtra"
+        parameters: "java.lang.String"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "getIntExtra"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "getParcelableExtra"
+        parameters: "java.lang.String"
+        return_type: "android.os.Parcelable"
+      }
+      methods {
+        name: "getStringExtra"
+        parameters: "java.lang.String"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "hasExtra"
+        parameters: "java.lang.String"
+        return_type: "boolean"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "int"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "android.os.Parcelable"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "boolean"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "putExtra"
+        parameters: "java.lang.String"
+        parameters: "java.lang.String[]"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "putParcelableArrayListExtra"
+        parameters: "java.lang.String"
+        parameters: "java.util.ArrayList"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setAction"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setClass"
+        parameters: "android.content.Context"
+        parameters: "java.lang.Class"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setClipData"
+        parameters: "android.content.ClipData"
+        return_type: "void"
+      }
+      methods {
+        name: "setDataAndType"
+        parameters: "android.net.Uri"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+      methods {
+        name: "setType"
+        parameters: "java.lang.String"
+        return_type: "android.content.Intent"
+      }
+    }
+    classes {
+      name: "android.text.TextUtils"
+      methods {
+        name: "equals"
+        parameters: "java.lang.CharSequence"
+        parameters: "java.lang.CharSequence"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getTrimmedLength"
+        parameters: "java.lang.CharSequence"
+        return_type: "int"
+      }
+      methods {
+        name: "isEmpty"
+        parameters: "java.lang.CharSequence"
+        return_type: "boolean"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "java.lang.CharSequence"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CHAR_SEQUENCE_CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.app.PendingIntent"
+      methods {
+        name: "getService"
+        parameters: "android.content.Context"
+        parameters: "int"
+        parameters: "android.content.Intent"
+        parameters: "int"
+        return_type: "android.app.PendingIntent"
+      }
+      methods {
+        name: "writeToParcel"
+        parameters: "android.os.Parcel"
+        parameters: "int"
+        return_type: "void"
+      }
+      fields {
+        name: "CREATOR"
+        type: "android.os.Parcelable.Creator"
+      }
+    }
+    classes {
+      name: "android.graphics.Bitmap.CompressFormat"
+      fields {
+        name: "PNG"
+        type: "android.graphics.Bitmap.CompressFormat"
+      }
+    }
+    classes {
+      name: "android.util.SparseIntArray"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "put"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.lang.Integer"
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "intValue"
+        return_type: "int"
+      }
+      methods {
+        name: "numberOfTrailingZeros"
+        parameters: "int"
+        return_type: "int"
+      }
+      methods {
+        name: "toHexString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "toString"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "valueOf"
+        parameters: "int"
+        return_type: "java.lang.Integer"
+      }
+      fields {
+        name: "TYPE"
+        type: "java.lang.Class"
+      }
+    }
+    classes {
+      name: "java.lang.reflect.Array"
+      methods {
+        name: "newInstance"
+        parameters: "java.lang.Class"
+        parameters: "int"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "android.media.session.MediaSessionManager.RemoteUserInfo"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.regex.Pattern"
+      methods {
+        name: "matcher"
+        parameters: "java.lang.CharSequence"
+        return_type: "java.util.regex.Matcher"
+      }
+    }
+    classes {
+      name: "android.os.Build.VERSION"
+      fields {
+        name: "SDK_INT"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.view.ViewGroup.LayoutParams"
+      methods {
+        name: "<init>"
+        parameters: "int"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        parameters: "android.util.AttributeSet"
+        return_type: "void"
+      }
+      fields {
+        name: "width"
+        type: "int"
+      }
+    }
+    classes {
+      name: "android.transition.Transition.EpicenterCallback"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.AlertDialog.Builder"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        return_type: "void"
+      }
+      methods {
+        name: "create"
+        return_type: "android.app.AlertDialog"
+      }
+      methods {
+        name: "setCancelable"
+        parameters: "boolean"
+        return_type: "android.app.AlertDialog.Builder"
+      }
+      methods {
+        name: "setNegativeButton"
+        parameters: "java.lang.CharSequence"
+        parameters: "android.content.DialogInterface.OnClickListener"
+        return_type: "android.app.AlertDialog.Builder"
+      }
+      methods {
+        name: "setPositiveButton"
+        parameters: "java.lang.CharSequence"
+        parameters: "android.content.DialogInterface.OnClickListener"
+        return_type: "android.app.AlertDialog.Builder"
+      }
+      methods {
+        name: "setTitle"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.app.AlertDialog.Builder"
+      }
+      methods {
+        name: "setView"
+        parameters: "android.view.View"
+        return_type: "android.app.AlertDialog.Builder"
+      }
+    }
+    classes {
+      name: "android.view.accessibility.AccessibilityNodeInfo"
+      methods {
+        name: "addAction"
+        parameters: "int"
+        return_type: "void"
+      }
+      methods {
+        name: "equals"
+        parameters: "java.lang.Object"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getActions"
+        return_type: "int"
+      }
+      methods {
+        name: "getBoundsInParent"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "getBoundsInScreen"
+        parameters: "android.graphics.Rect"
+        return_type: "void"
+      }
+      methods {
+        name: "getClassName"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getContentDescription"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getPackageName"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getText"
+        return_type: "java.lang.CharSequence"
+      }
+      methods {
+        name: "getViewIdResourceName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "hashCode"
+        return_type: "int"
+      }
+      methods {
+        name: "isCheckable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isChecked"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isClickable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isEnabled"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isFocusable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isFocused"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isLongClickable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isPassword"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isScrollable"
+        return_type: "boolean"
+      }
+      methods {
+        name: "isSelected"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setClassName"
+        parameters: "java.lang.CharSequence"
+        return_type: "void"
+      }
+      methods {
+        name: "setParent"
+        parameters: "android.view.View"
+        return_type: "void"
+      }
+      methods {
+        name: "setScrollable"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.io.File"
+      methods {
+        name: "<init>"
+        parameters: "java.io.File"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "canRead"
+        return_type: "boolean"
+      }
+      methods {
+        name: "delete"
+        return_type: "boolean"
+      }
+      methods {
+        name: "exists"
+        return_type: "boolean"
+      }
+      methods {
+        name: "getAbsolutePath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getCanonicalFile"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getCanonicalPath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getName"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "getParentFile"
+        return_type: "java.io.File"
+      }
+      methods {
+        name: "getPath"
+        return_type: "java.lang.String"
+      }
+      methods {
+        name: "lastModified"
+        return_type: "long"
+      }
+      methods {
+        name: "length"
+        return_type: "long"
+      }
+      methods {
+        name: "mkdir"
+        return_type: "boolean"
+      }
+      methods {
+        name: "renameTo"
+        parameters: "java.io.File"
+        return_type: "boolean"
+      }
+    }
+    classes {
+      name: "android.graphics.PorterDuff.Mode"
+      fields {
+        name: "SRC_OVER"
+        type: "android.graphics.PorterDuff.Mode"
+      }
+    }
+    classes {
+      name: "android.widget.Button"
+      methods {
+        name: "setOnClickListener"
+        parameters: "android.view.View.OnClickListener"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "java.util.WeakHashMap"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "get"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+      methods {
+        name: "isEmpty"
+        return_type: "boolean"
+      }
+      methods {
+        name: "keySet"
+        return_type: "java.util.Set"
+      }
+      methods {
+        name: "put"
+        parameters: "java.lang.Object"
+        parameters: "java.lang.Object"
+        return_type: "java.lang.Object"
+      }
+    }
+    classes {
+      name: "com.android.internal.app.AlertController.AlertParams"
+      fields {
+        name: "mNegativeButtonListener"
+        type: "android.content.DialogInterface.OnClickListener"
+      }
+      fields {
+        name: "mNegativeButtonText"
+        type: "java.lang.CharSequence"
+      }
+      fields {
+        name: "mPositiveButtonListener"
+        type: "android.content.DialogInterface.OnClickListener"
+      }
+      fields {
+        name: "mPositiveButtonText"
+        type: "java.lang.CharSequence"
+      }
+      fields {
+        name: "mView"
+        type: "android.view.View"
+      }
+    }
+    classes {
+      name: "android.widget.CheckBox"
+      methods {
+        name: "isChecked"
+        return_type: "boolean"
+      }
+      methods {
+        name: "setChecked"
+        parameters: "boolean"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.view.animation.DecelerateInterpolator"
+      methods {
+        name: "<init>"
+        parameters: "float"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.service.media.MediaBrowserService.BrowserRoot"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "android.os.Bundle"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.widget.Toast"
+      methods {
+        name: "makeText"
+        parameters: "android.content.Context"
+        parameters: "int"
+        parameters: "int"
+        return_type: "android.widget.Toast"
+      }
+      methods {
+        name: "makeText"
+        parameters: "android.content.Context"
+        parameters: "java.lang.CharSequence"
+        parameters: "int"
+        return_type: "android.widget.Toast"
+      }
+      methods {
+        name: "show"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.service.media.MediaBrowserService"
+      methods {
+        name: "<init>"
+        return_type: "void"
+      }
+      methods {
+        name: "onBind"
+        parameters: "android.content.Intent"
+        return_type: "android.os.IBinder"
+      }
+      methods {
+        name: "onCreate"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "libcore.io.Streams"
+      methods {
+        name: "copy"
+        parameters: "java.io.InputStream"
+        parameters: "java.io.OutputStream"
+        return_type: "int"
+      }
+    }
+    classes {
+      name: "android.text.format.DateUtils"
+      methods {
+        name: "formatDateTime"
+        parameters: "android.content.Context"
+        parameters: "long"
+        parameters: "int"
+        return_type: "java.lang.String"
+      }
+    }
+    classes {
+      name: "android.widget.TabHost.OnTabChangeListener"
+      methods {
+        name: "onTabChanged"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.NotificationChannel"
+      methods {
+        name: "<init>"
+        parameters: "java.lang.String"
+        parameters: "java.lang.CharSequence"
+        parameters: "int"
+        return_type: "void"
+      }
+    }
+    classes {
+      name: "android.app.Notification.Builder"
+      methods {
+        name: "<init>"
+        parameters: "android.content.Context"
+        parameters: "java.lang.String"
+        return_type: "void"
+      }
+      methods {
+        name: "addExtras"
+        parameters: "android.os.Bundle"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "build"
+        return_type: "android.app.Notification"
+      }
+      methods {
+        name: "extend"
+        parameters: "android.app.Notification.Extender"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setActions"
+        parameters: "android.app.Notification.Action[]"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setColor"
+        parameters: "int"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setContentIntent"
+        parameters: "android.app.PendingIntent"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setContentText"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setContentTitle"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setDeleteIntent"
+        parameters: "android.app.PendingIntent"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setLocalOnly"
+        parameters: "boolean"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setOngoing"
+        parameters: "boolean"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setProgress"
+        parameters: "int"
+        parameters: "int"
+        parameters: "boolean"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setSmallIcon"
+        parameters: "int"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setSubText"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.app.Notification.Builder"
+      }
+      methods {
+        name: "setTicker"
+        parameters: "java.lang.CharSequence"
+        return_type: "android.app.Notification.Builder"
+      }
+    }
+    classes {
+      name: "java.util.regex.Matcher"
+      methods {
+        name: "matches"
+        return_type: "boolean"
+      }
+    }
+  }
+  internal_api_packages {
+    name: "com.android.shell"
+  }
+  package_file_content {
+    entries {
+      key: "res/layout/dialog_bugreport_info.xml"
+      value {
+        name: "res/layout/dialog_bugreport_info.xml"
+        type: FILE
+        size: 1944
+        content_id: "77afe9cf "
+      }
+    }
+    entries {
+      key: "META-INF/CERT.SF"
+      value {
+        name: "META-INF/CERT.SF"
+        type: FILE
+        size: 845
+        content_id: "ea979a47 "
+      }
+    }
+    entries {
+      key: "AndroidManifest.xml"
+      value {
+        name: "AndroidManifest.xml"
+        type: FILE
+        size: 23708
+        content_id: "f9855bc7 "
+      }
+    }
+    entries {
+      key: "META-INF/CERT.RSA"
+      value {
+        name: "META-INF/CERT.RSA"
+        type: FILE
+        size: 1722
+        content_id: "685bacac "
+      }
+    }
+    entries {
+      key: "res/layout/confirm_repeat.xml"
+      value {
+        name: "res/layout/confirm_repeat.xml"
+        type: FILE
+        size: 1112
+        content_id: "fd510e79 "
+      }
+    }
+    entries {
+      key: "resources.arsc"
+      value {
+        name: "resources.arsc"
+        type: FILE
+        size: 175164
+        content_id: "30535c6a "
+      }
+    }
+    entries {
+      key: "res/xml/file_provider_paths.xml"
+      value {
+        name: "res/xml/file_provider_paths.xml"
+        type: FILE
+        size: 400
+        content_id: "a81e9b05 "
+      }
+    }
+    entries {
+      key: "classes.dex"
+      value {
+        name: "classes.dex"
+        type: FILE
+        size: 609720
+        content_id: "f1b3224f "
+      }
+    }
+    entries {
+      key: "res/drawable/ic_bug_report_black_24dp.xml"
+      value {
+        name: "res/drawable/ic_bug_report_black_24dp.xml"
+        type: FILE
+        size: 1572
+        content_id: "c2a89254 "
+      }
+    }
+    entries {
+      key: "META-INF/MANIFEST.MF"
+      value {
+        name: "META-INF/MANIFEST.MF"
+        type: FILE
+        size: 746
+        content_id: "5bbd78a2 "
+      }
+    }
+  }
+}
diff --git a/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml b/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml
new file mode 100644
index 0000000..9704e0f
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device has a Vulkan
+     driver that supports API version 1.1 (0x00401000) -->
+<permissions>
+    <feature name="android.hardware.vulkan.version" version="4198400" />
+</permissions>
diff --git a/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml.pb.txt b/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml.pb.txt
new file mode 100644
index 0000000..fc553fe
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.hardware.vulkan.version.xml.pb.txt
@@ -0,0 +1,17 @@
+name: "android.hardware.vulkan.version.xml"
+type: XML
+size: 902
+content_id: "4mumUfnT2Ikv63sHKqXR9RrCpentnPxm9b8Wl1W8bmQ="
+device_permissions {
+  key: "feature"
+  value {
+    name: "feature"
+    permissions {
+      name: "android.hardware.vulkan.version"
+      elements {
+        name: "version"
+        value: "4198400"
+      }
+    }
+  }
+}
diff --git a/tools/release-parser/tests/resources/android.test.runner.odex b/tools/release-parser/tests/resources/android.test.runner.odex
new file mode 100644
index 0000000..ed96d35
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.test.runner.odex
Binary files differ
diff --git a/tools/release-parser/tests/resources/android.test.runner.odex.pb.txt b/tools/release-parser/tests/resources/android.test.runner.odex.pb.txt
new file mode 100644
index 0000000..d237fae
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.test.runner.odex.pb.txt
@@ -0,0 +1,83 @@
+name: "android.test.runner.odex"
+type: ODEX
+size: 17392
+content_id: "CZnRjQmCUjBNQi6p3iiIsh4RUzzCTbdjstEoNhcbD3E="
+code_id: "e5468a50 "
+dependencies: "system/framework/arm64/boot.art"
+dependencies: "system/framework/arm64/boot-core-libart.art"
+dependencies: "system/framework/arm64/boot-conscrypt.art"
+dependencies: "system/framework/arm64/boot-okhttp.art"
+dependencies: "system/framework/arm64/boot-bouncycastle.art"
+dependencies: "system/framework/arm64/boot-apache-xml.art"
+dependencies: "system/framework/arm64/boot-ext.art"
+dependencies: "system/framework/arm64/boot-framework.art"
+dependencies: "system/framework/arm64/boot-telephony-common.art"
+dependencies: "system/framework/arm64/boot-voip-common.art"
+dependencies: "system/framework/arm64/boot-ims-common.art"
+dependencies: "system/framework/arm64/boot-android.hidl.base-V1.0-java.art"
+dependencies: "system/framework/arm64/boot-android.hidl.manager-V1.0-java.art"
+dependencies: "system/framework/arm64/boot-framework-oahl-backward-compatibility.art"
+dependencies: "system/framework/arm64/boot-android.test.base.art"
+dependencies: "system/framework/arm64/boot-com.google.vr.platform.art"
+oat_info {
+  version: "138\000"
+  adler32_checksum: 1475110939
+  instruction_set: 2
+  dex_file_count: 1
+  oat_dex_files_offset: 3620
+  executable_offset: 4096
+  image_file_location_oat_checksum: 779445782
+  image_file_location_oat_data_begin: 1898192896
+  key_value_store_size: 2439
+  key_value_store {
+    key: "compiler-filter"
+    value: "quicken"
+  }
+  key_value_store {
+    key: "image-location"
+    value: "out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-core-libart.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-conscrypt.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-okhttp.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-bouncycastle.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-apache-xml.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-ext.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-framework.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-telephony-common.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-voip-common.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-ims-common.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.hidl.base-V1.0-java.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.hidl.manager-V1.0-java.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-framework-oahl-backward-compatibility.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.test.base.art:out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-com.google.vr.platform.art"
+  }
+  key_value_store {
+    key: "classpath"
+    value: "&"
+  }
+  key_value_store {
+    key: "debuggable"
+    value: "false"
+  }
+  key_value_store {
+    key: "concurrent-copying"
+    value: "true"
+  }
+  key_value_store {
+    key: "native-debuggable"
+    value: "false"
+  }
+  key_value_store {
+    key: "compilation-reason"
+    value: "prebuilt"
+  }
+  key_value_store {
+    key: "dex2oat-host"
+    value: "X86_64"
+  }
+  key_value_store {
+    key: "pic"
+    value: "true"
+  }
+  key_value_store {
+    key: "dex2oat-cmdline"
+    value: "out/host/linux-x86/bin/dex2oatd --runtime-arg -Xms64m --runtime-arg -Xmx512m --class-loader-context=& --boot-image=out/target/product/sailfish/dex_bootjars/system/framework/boot.art --dex-file=out/target/common/obj/JAVA_LIBRARIES/android.test.runner_intermediates/javalib.jar --dex-location=/system/framework/android.test.runner.jar --oat-file=out/target/product/sailfish/obj/JAVA_LIBRARIES/android.test.runner_intermediates/oat/arm64/javalib.odex --android-root=out/target/product/sailfish/system --instruction-set=arm64 --instruction-set-variant=kryo --instruction-set-features=default --runtime-arg -Xnorelocate --compile-pic --no-generate-debug-info --generate-build-id --abort-on-hard-verifier-error --force-determinism --no-inline-from=core-oj.jar --compiler-filter=quicken --generate-mini-debug-info --compilation-reason=prebuilt"
+  }
+  oat_dex_info {
+    dex_file_location_data: "/system/framework/android.test.runner.jar"
+    dex_file_location_checksum: 3846605392
+    dex_file_offset: 40
+    lookup_table_offset: 3108
+    class_offsets_offset: 2516
+    method_bss_mapping_offset: 3028
+  }
+  valid: true
+  bits: 64
+  architecture: "arm"
+}
diff --git a/tools/release-parser/tests/resources/android.test.runner.vdex b/tools/release-parser/tests/resources/android.test.runner.vdex
new file mode 100644
index 0000000..b44e7db
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.test.runner.vdex
Binary files differ
diff --git a/tools/release-parser/tests/resources/android.test.runner.vdex.pb.txt b/tools/release-parser/tests/resources/android.test.runner.vdex.pb.txt
new file mode 100644
index 0000000..4e93976
--- /dev/null
+++ b/tools/release-parser/tests/resources/android.test.runner.vdex.pb.txt
@@ -0,0 +1,17 @@
+name: "android.test.runner.vdex"
+type: VDEX
+size: 106224
+content_id: "vSvV6rpBmJ5wf1Ni6XfyL2+cDfR8b3PAvNthT1TwW1I="
+code_id: "e5468a50 "
+vdex_info {
+  verifier_deps_version: "019\000"
+  dex_section_version: "002\000"
+  number_of_dex_files: 1
+  verifier_deps_size: 2636
+  checksums: 3846605392
+  dex_section_headers {
+    dex_size: 22460
+    dex_shared_data_size: 77208
+    quickening_info_size: 3884
+  }
+}
diff --git a/tools/release-parser/tests/resources/boot-framework.art b/tools/release-parser/tests/resources/boot-framework.art
new file mode 100644
index 0000000..00a87de
--- /dev/null
+++ b/tools/release-parser/tests/resources/boot-framework.art
Binary files differ
diff --git a/tools/release-parser/tests/resources/boot-framework.art.pb.txt b/tools/release-parser/tests/resources/boot-framework.art.pb.txt
new file mode 100644
index 0000000..4d919aa
--- /dev/null
+++ b/tools/release-parser/tests/resources/boot-framework.art.pb.txt
@@ -0,0 +1,20 @@
+name: "boot-framework.art"
+type: ART
+size: 11558912
+content_id: "+3pO8URmb2E6aYWRQXK6LoHxW/5pufe1V/bx7rGRJvQ="
+code_id: "533a429a "
+art_info {
+  valid: true
+  version: "056\000"
+  image_begin: 1885188096
+  image_size: 11468816
+  oat_checksum: 1396327066
+  oat_file_begin: 1918078976
+  oat_data_begin: 1918083072
+  oat_data_end: 1933225908
+  oat_file_end: 1953853440
+  image_roots: 1879078944
+  pointer_size: 8
+  compile_pic: 1
+  data_size: 5399272
+}
diff --git a/tools/release-parser/tests/resources/boot-framework.oat b/tools/release-parser/tests/resources/boot-framework.oat
new file mode 100644
index 0000000..b49e589
--- /dev/null
+++ b/tools/release-parser/tests/resources/boot-framework.oat
Binary files differ
diff --git a/tools/release-parser/tests/resources/boot-framework.oat.pb.txt b/tools/release-parser/tests/resources/boot-framework.oat.pb.txt
new file mode 100644
index 0000000..55f6173
--- /dev/null
+++ b/tools/release-parser/tests/resources/boot-framework.oat.pb.txt
@@ -0,0 +1,101 @@
+name: "boot-framework.oat"
+type: OAT
+size: 15599048
+content_id: "3x7nAT8P+6fzuagnGVB6QqYJ4caUcMG3uF+jkaXh52M="
+code_id: "f0acaa05 2d59fc59 34ad167d "
+dependencies: "system/framework/arm64/boot.art"
+dependencies: "system/framework/arm64/boot-core-libart.art"
+dependencies: "system/framework/arm64/boot-conscrypt.art"
+dependencies: "system/framework/arm64/boot-okhttp.art"
+dependencies: "system/framework/arm64/boot-bouncycastle.art"
+dependencies: "system/framework/arm64/boot-apache-xml.art"
+dependencies: "system/framework/arm64/boot-ext.art"
+dependencies: "system/framework/arm64/boot-framework.art"
+dependencies: "system/framework/arm64/boot-telephony-common.art"
+dependencies: "system/framework/arm64/boot-voip-common.art"
+dependencies: "system/framework/arm64/boot-ims-common.art"
+dependencies: "system/framework/arm64/boot-android.hidl.base-V1.0-java.art"
+dependencies: "system/framework/arm64/boot-android.hidl.manager-V1.0-java.art"
+dependencies: "system/framework/arm64/boot-framework-oahl-backward-compatibility.art"
+dependencies: "system/framework/arm64/boot-android.test.base.art"
+dependencies: "system/framework/arm64/boot-com.google.vr.platform.art"
+oat_info {
+  version: "138\000"
+  adler32_checksum: 1396327066
+  instruction_set: 2
+  dex_file_count: 3
+  oat_dex_files_offset: 3978029
+  executable_offset: 3981312
+  jni_dlsym_lookup_offset: 3981312
+  quick_generic_jni_trampoline_offset: 3981328
+  quick_imt_conflict_trampoline_offset: 3981344
+  quick_resolution_trampoline_offset: 3981360
+  quick_to_interpreter_bridge_offset: 3981376
+  image_file_location_oat_checksum: 3553912596
+  key_value_store_size: 4999
+  key_value_store {
+    key: "compiler-filter"
+    value: "speed-profile"
+  }
+  key_value_store {
+    key: "bootclasspath"
+    value: "/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-core-libart.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-conscrypt.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-okhttp.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-bouncycastle.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-apache-xml.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-ext.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-framework.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-telephony-common.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-voip-common.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-ims-common.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.hidl.base-V1.0-java.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.hidl.manager-V1.0-java.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-framework-oahl-backward-compatibility.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-android.test.base.art:/system/framework/out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot-com.google.vr.platform.art"
+  }
+  key_value_store {
+    key: "debuggable"
+    value: "false"
+  }
+  key_value_store {
+    key: "concurrent-copying"
+    value: "true"
+  }
+  key_value_store {
+    key: "native-debuggable"
+    value: "false"
+  }
+  key_value_store {
+    key: "dex2oat-host"
+    value: "X86_64"
+  }
+  key_value_store {
+    key: "pic"
+    value: "true"
+  }
+  key_value_store {
+    key: "dex2oat-cmdline"
+    value: "out/host/linux-x86/bin/dex2oatd --runtime-arg -Xms64m --runtime-arg -Xmx64m --compiler-filter=speed-profile --profile-file=out/target/product/sailfish/dex_bootjars/system/framework/boot.prof --dex-file=out/target/common/obj/JAVA_LIBRARIES/core-oj_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/conscrypt_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/okhttp_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/bouncycastle_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/apache-xml_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/voip-common_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/ims-common_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/android.hidl.base-V1.0-java_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/android.hidl.manager-V1.0-java_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/framework-oahl-backward-compatibility_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/android.test.base_intermediates/javalib.jar --dex-file=out/target/common/obj/JAVA_LIBRARIES/com.google.vr.platform_intermediates/javalib.jar --dex-location=/system/framework/core-oj.jar --dex-location=/system/framework/core-libart.jar --dex-location=/system/framework/conscrypt.jar --dex-location=/system/framework/okhttp.jar --dex-location=/system/framework/bouncycastle.jar --dex-location=/system/framework/apache-xml.jar --dex-location=/system/framework/ext.jar --dex-location=/system/framework/framework.jar --dex-location=/system/framework/telephony-common.jar --dex-location=/system/framework/voip-common.jar --dex-location=/system/framework/ims-common.jar --dex-location=/system/framework/android.hidl.base-V1.0-java.jar --dex-location=/system/framework/android.hidl.manager-V1.0-java.jar --dex-location=/system/framework/framework-oahl-backward-compatibility.jar --dex-location=/system/framework/android.test.base.jar --dex-location=/system/framework/com.google.vr.platform.jar --oat-symbols=out/target/product/sailfish/symbols/system/framework/arm64/boot.oat --oat-file=out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.oat --oat-location=/system/framework/arm64/boot.oat --image=out/target/product/sailfish/dex_bootjars/system/framework/arm64/boot.art --base=0x70000000 --instruction-set=arm64 --instruction-set-variant=kryo --instruction-set-features=default --android-root=out/target/product/sailfish/system --runtime-arg -Xnorelocate --compile-pic --no-generate-debug-info --generate-build-id --multi-image --no-inline-from=core-oj.jar --abort-on-hard-verifier-error --abort-on-soft-verifier-error --generate-mini-debug-info"
+  }
+  oat_dex_info {
+    dex_file_location_data: "/system/framework/framework.jar"
+    dex_file_location_checksum: 4037847557
+    dex_file_offset: 48
+    lookup_table_offset: 152772
+    class_offsets_offset: 5076
+    method_bss_mapping_offset: 152532
+    type_bss_mapping_offset: 429000
+    string_bss_mapping_offset: 429396
+  }
+  oat_dex_info {
+    dex_file_location_data: "/system/framework/framework.jar!classes2.dex"
+    dex_file_location_checksum: 760872025
+    dex_file_offset: 1679460
+    lookup_table_offset: 177812
+    class_offsets_offset: 70612
+    method_bss_mapping_offset: 152612
+    type_bss_mapping_offset: 430680
+    string_bss_mapping_offset: 431060
+  }
+  oat_dex_info {
+    dex_file_location_data: "/system/framework/framework.jar!classes3.dex"
+    dex_file_location_checksum: 883758717
+    dex_file_offset: 3269632
+    lookup_table_offset: 202544
+    class_offsets_offset: 136148
+    method_bss_mapping_offset: 152692
+    type_bss_mapping_offset: 432416
+    string_bss_mapping_offset: 432444
+  }
+  valid: true
+  bits: 64
+  architecture: "arm"
+}
diff --git a/tools/release-parser/tests/resources/build.prop b/tools/release-parser/tests/resources/build.prop
new file mode 100644
index 0000000..a4536cf
--- /dev/null
+++ b/tools/release-parser/tests/resources/build.prop
@@ -0,0 +1,60 @@
+
+# begin build properties
+# autogenerated by buildinfo.sh
+ro.build.id=PPR1.180610.011
+ro.build.display.id=marlin-userdebug 9 PPR1.180610.011 4904810 dev-keys
+ro.build.version.incremental=4904810
+ro.build.version.sdk=28
+ro.build.version.preview_sdk=0
+ro.build.version.codename=REL
+ro.build.version.all_codenames=REL
+ro.build.version.release=9
+ro.build.version.security_patch=2018-08-05
+ro.build.version.base_os=
+ro.build.version.min_supported_target_sdk=17
+ro.build.date=Fri Jul 20 20:55:13 UTC 2018
+ro.build.date.utc=1532120113
+ro.build.type=userdebug
+ro.build.user=android-build
+ro.build.host=vpee7.mtv.corp.google.com
+ro.build.tags=dev-keys
+ro.build.flavor=marlin-userdebug
+ro.build.system_root_image=true
+ro.build.ab_update=true
+ro.product.model=Pixel XL
+ro.product.brand=google
+ro.product.name=marlin
+ro.product.device=marlin
+# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
+# use ro.product.cpu.abilist instead.
+ro.product.cpu.abi=arm64-v8a
+ro.product.cpu.abilist=arm64-v8a,armeabi-v7a,armeabi
+ro.product.cpu.abilist32=armeabi-v7a,armeabi
+ro.product.cpu.abilist64=arm64-v8a
+ro.product.manufacturer=Google
+ro.product.locale=en-US
+ro.wifi.channels=
+# ro.build.product is obsolete; use ro.product.device
+ro.build.product=marlin
+# Do not try to parse description, fingerprint, or thumbprint
+ro.build.description=marlin-userdebug 9 PPR1.180610.011 4904810 dev-keys
+ro.build.fingerprint=google/marlin/marlin:9/PPR1.180610.011/4904810:userdebug/dev-keys
+ro.build.characteristics=nosdcard
+# end build properties
+
+#
+# ADDITIONAL_BUILD_PROPERTIES
+#
+ro.bionic.ld.warning=1
+ro.art.hiddenapi.warning=1
+ro.treble.enabled=true
+persist.sys.dalvik.vm.lib.2=libart.so
+dalvik.vm.isa.arm64.variant=kryo
+dalvik.vm.isa.arm64.features=default
+dalvik.vm.isa.arm.variant=kryo
+dalvik.vm.isa.arm.features=default
+dalvik.vm.lockprof.threshold=500
+net.bt.name=Android
+dalvik.vm.stack-trace-dir=/data/anr
+ro.build.expect.bootloader=8996-012001-1804121206
+ro.build.expect.baseband=8996-130181-1807171030
diff --git a/tools/release-parser/tests/resources/build.prop.pb.txt b/tools/release-parser/tests/resources/build.prop.pb.txt
new file mode 100644
index 0000000..e7f2080
--- /dev/null
+++ b/tools/release-parser/tests/resources/build.prop.pb.txt
@@ -0,0 +1,196 @@
+name: "build.prop"
+type: BUILD_PROP
+size: 1988
+content_id: "i6246nDfNazcxKN1Srx8OxJawb97iNNfPBPQZPf0AfQ="
+properties {
+  key: "dalvik.vm.isa.arm64.features"
+  value: "default"
+}
+properties {
+  key: "ro.build.version.all_codenames"
+  value: "REL"
+}
+properties {
+  key: "ro.product.manufacturer"
+  value: "Google"
+}
+properties {
+  key: "ro.build.version.codename"
+  value: "REL"
+}
+properties {
+  key: "ro.build.version.release"
+  value: "9"
+}
+properties {
+  key: "ro.build.date"
+  value: "Fri Jul 20 20:55:13 UTC 2018"
+}
+properties {
+  key: "ro.build.host"
+  value: "vpee7.mtv.corp.google.com"
+}
+properties {
+  key: "ro.build.date.utc"
+  value: "1532120113"
+}
+properties {
+  key: "ro.bionic.ld.warning"
+  value: "1"
+}
+properties {
+  key: "ro.build.system_root_image"
+  value: "true"
+}
+properties {
+  key: "ro.build.ab_update"
+  value: "true"
+}
+properties {
+  key: "dalvik.vm.isa.arm64.variant"
+  value: "kryo"
+}
+properties {
+  key: "ro.build.tags"
+  value: "dev-keys"
+}
+properties {
+  key: "net.bt.name"
+  value: "Android"
+}
+properties {
+  key: "ro.product.cpu.abilist64"
+  value: "arm64-v8a"
+}
+properties {
+  key: "ro.build.expect.bootloader"
+  value: "8996-012001-1804121206"
+}
+properties {
+  key: "ro.product.name"
+  value: "marlin"
+}
+properties {
+  key: "ro.art.hiddenapi.warning"
+  value: "1"
+}
+properties {
+  key: "ro.product.brand"
+  value: "google"
+}
+properties {
+  key: "ro.build.expect.baseband"
+  value: "8996-130181-1807171030"
+}
+properties {
+  key: "ro.build.id"
+  value: "PPR1.180610.011"
+}
+properties {
+  key: "ro.product.locale"
+  value: "en-US"
+}
+properties {
+  key: "ro.build.flavor"
+  value: "marlin-userdebug"
+}
+properties {
+  key: "persist.sys.dalvik.vm.lib.2"
+  value: "libart.so"
+}
+properties {
+  key: "ro.build.characteristics"
+  value: "nosdcard"
+}
+properties {
+  key: "dalvik.vm.stack-trace-dir"
+  value: "/data/anr"
+}
+properties {
+  key: "ro.product.model"
+  value: "Pixel XL"
+}
+properties {
+  key: "ro.build.display.id"
+  value: "marlin-userdebug 9 PPR1.180610.011 4904810 dev-keys"
+}
+properties {
+  key: "dalvik.vm.isa.arm.features"
+  value: "default"
+}
+properties {
+  key: "ro.build.user"
+  value: "android-build"
+}
+properties {
+  key: "ro.product.cpu.abi"
+  value: "arm64-v8a"
+}
+properties {
+  key: "ro.build.product"
+  value: "marlin"
+}
+properties {
+  key: "ro.product.device"
+  value: "marlin"
+}
+properties {
+  key: "ro.build.type"
+  value: "userdebug"
+}
+properties {
+  key: "ro.build.description"
+  value: "marlin-userdebug 9 PPR1.180610.011 4904810 dev-keys"
+}
+properties {
+  key: "ro.build.fingerprint"
+  value: "google/marlin/marlin:9/PPR1.180610.011/4904810:userdebug/dev-keys"
+}
+properties {
+  key: "ro.product.cpu.abilist32"
+  value: "armeabi-v7a,armeabi"
+}
+properties {
+  key: "ro.treble.enabled"
+  value: "true"
+}
+properties {
+  key: "ro.build.version.sdk"
+  value: "28"
+}
+properties {
+  key: "ro.build.version.min_supported_target_sdk"
+  value: "17"
+}
+properties {
+  key: "dalvik.vm.lockprof.threshold"
+  value: "500"
+}
+properties {
+  key: "ro.build.version.incremental"
+  value: "4904810"
+}
+properties {
+  key: "ro.build.version.preview_sdk"
+  value: "0"
+}
+properties {
+  key: "dalvik.vm.isa.arm.variant"
+  value: "kryo"
+}
+properties {
+  key: "ro.build.version.base_os"
+  value: ""
+}
+properties {
+  key: "ro.build.version.security_patch"
+  value: "2018-08-05"
+}
+properties {
+  key: "ro.wifi.channels"
+  value: ""
+}
+properties {
+  key: "ro.product.cpu.abilist"
+  value: "arm64-v8a,armeabi-v7a,armeabi"
+}
diff --git a/tools/release-parser/tests/resources/libEGL.so b/tools/release-parser/tests/resources/libEGL.so
new file mode 100755
index 0000000..fbe4b4d
--- /dev/null
+++ b/tools/release-parser/tests/resources/libEGL.so
Binary files differ
diff --git a/tools/release-parser/tests/resources/libEGL.so.pb.txt b/tools/release-parser/tests/resources/libEGL.so.pb.txt
new file mode 100644
index 0000000..cf9dc36
--- /dev/null
+++ b/tools/release-parser/tests/resources/libEGL.so.pb.txt
@@ -0,0 +1,194 @@
+package_name: "libEGL.so"
+external_api_packages {
+}
+internal_api_packages {
+  classes {
+    name: "libEGL.so"
+    methods {
+      name: "eglCreateNativeClientBufferANDROID"
+    }
+    methods {
+      name: "eglQueryContext"
+    }
+    methods {
+      name: "eglDestroyStreamKHR"
+    }
+    methods {
+      name: "eglCreatePixmapSurface"
+    }
+    methods {
+      name: "eglDestroySurface"
+    }
+    methods {
+      name: "eglGetCurrentDisplay"
+    }
+    methods {
+      name: "eglQueryStreamu64KHR"
+    }
+    methods {
+      name: "eglQueryString"
+    }
+    methods {
+      name: "eglGetDisplay"
+    }
+    methods {
+      name: "eglGetCurrentContext"
+    }
+    methods {
+      name: "eglCreatePbufferSurface"
+    }
+    methods {
+      name: "eglReleaseTexImage"
+    }
+    methods {
+      name: "eglBindAPI"
+    }
+    methods {
+      name: "eglGetStreamFileDescriptorKHR"
+    }
+    methods {
+      name: "eglTerminate"
+    }
+    methods {
+      name: "eglCreateStreamFromFileDescriptorKHR"
+    }
+    methods {
+      name: "eglCreateStreamProducerSurfaceKHR"
+    }
+    methods {
+      name: "eglStreamConsumerGLTextureExternalKHR"
+    }
+    methods {
+      name: "eglQueryAPI"
+    }
+    methods {
+      name: "eglQueryStreamTimeKHR"
+    }
+    methods {
+      name: "eglSurfaceAttrib"
+    }
+    methods {
+      name: "eglBindTexImage"
+    }
+    methods {
+      name: "eglGetCurrentSurface"
+    }
+    methods {
+      name: "eglGetError"
+    }
+    methods {
+      name: "eglPresentationTimeANDROID"
+    }
+    methods {
+      name: "eglReleaseThread"
+    }
+    methods {
+      name: "eglQuerySurface"
+    }
+    methods {
+      name: "eglSwapBuffersWithDamageKHR"
+    }
+    methods {
+      name: "eglInitialize"
+    }
+    methods {
+      name: "eglGetSystemTimeFrequencyNV"
+    }
+    methods {
+      name: "eglClientWaitSyncKHR"
+    }
+    methods {
+      name: "eglDestroyImageKHR"
+    }
+    methods {
+      name: "eglCreateImageKHR"
+    }
+    methods {
+      name: "eglGetProcAddress"
+    }
+    methods {
+      name: "eglLockSurfaceKHR"
+    }
+    methods {
+      name: "eglMakeCurrent"
+    }
+    methods {
+      name: "eglStreamConsumerReleaseKHR"
+    }
+    methods {
+      name: "eglWaitSyncKHR"
+    }
+    methods {
+      name: "eglCreatePbufferFromClientBuffer"
+    }
+    methods {
+      name: "eglCreateStreamKHR"
+    }
+    methods {
+      name: "eglGetSyncAttribKHR"
+    }
+    methods {
+      name: "eglQueryStreamKHR"
+    }
+    methods {
+      name: "eglStreamConsumerAcquireKHR"
+    }
+    methods {
+      name: "eglWaitNative"
+    }
+    methods {
+      name: "eglCreateWindowSurface"
+    }
+    methods {
+      name: "eglSetDamageRegionKHR"
+    }
+    methods {
+      name: "eglGetConfigAttrib"
+    }
+    methods {
+      name: "eglSwapBuffers"
+    }
+    methods {
+      name: "eglWaitClient"
+    }
+    methods {
+      name: "eglCopyBuffers"
+    }
+    methods {
+      name: "eglDestroySyncKHR"
+    }
+    methods {
+      name: "eglDestroyContext"
+    }
+    methods {
+      name: "eglChooseConfig"
+    }
+    methods {
+      name: "eglGetSystemTimeNV"
+    }
+    methods {
+      name: "eglCreateSyncKHR"
+    }
+    methods {
+      name: "eglUnlockSurfaceKHR"
+    }
+    methods {
+      name: "eglWaitGL"
+    }
+    methods {
+      name: "eglCreateContext"
+    }
+    methods {
+      name: "eglStreamAttribKHR"
+    }
+    methods {
+      name: "eglGetConfigs"
+    }
+    methods {
+      name: "eglSignalSyncKHR"
+    }
+    methods {
+      name: "eglSwapInterval"
+    }
+  }
+}
diff --git a/tools/release-parser/tests/resources/platform.xml b/tools/release-parser/tests/resources/platform.xml
new file mode 100644
index 0000000..ab90e1b
--- /dev/null
+++ b/tools/release-parser/tests/resources/platform.xml
@@ -0,0 +1,216 @@
+<?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.
+-->
+
+<!-- This file is used to define the mappings between lower-level system
+     user and group IDs and the higher-level permission names managed
+     by the platform.
+
+     Be VERY careful when editing this file!  Mistakes made here can open
+     big security holes.
+-->
+<permissions>
+
+    <!-- ================================================================== -->
+    <!-- ================================================================== -->
+    <!-- ================================================================== -->
+
+    <!-- The following tags are associating low-level group IDs with
+         permission names.  By specifying such a mapping, you are saying
+         that any application process granted the given permission will
+         also be running with the given group ID attached to its process,
+         so it can perform any filesystem (read, write, execute) operations
+         allowed for that group. -->
+
+    <permission name="android.permission.BLUETOOTH_ADMIN" >
+        <group gid="net_bt_admin" />
+    </permission>
+
+    <permission name="android.permission.BLUETOOTH" >
+        <group gid="net_bt" />
+    </permission>
+
+    <permission name="android.permission.BLUETOOTH_STACK" >
+        <group gid="bluetooth" />
+        <group gid="wakelock" />
+        <group gid="uhid" />
+    </permission>
+
+    <permission name="android.permission.NET_TUNNELING" >
+        <group gid="vpn" />
+    </permission>
+
+    <permission name="android.permission.INTERNET" >
+        <group gid="inet" />
+    </permission>
+
+    <permission name="android.permission.READ_LOGS" >
+        <group gid="log" />
+    </permission>
+
+    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
+        <group gid="media_rw" />
+    </permission>
+
+    <permission name="android.permission.ACCESS_MTP" >
+        <group gid="mtp" />
+    </permission>
+
+    <permission name="android.permission.NET_ADMIN" >
+        <group gid="net_admin" />
+    </permission>
+
+    <!-- The group that /cache belongs to, linked to the permission
+         set on the applications that can access /cache -->
+    <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" >
+        <group gid="cache" />
+    </permission>
+
+    <!-- RW permissions to any system resources owned by group 'diag'.
+         This is for carrier and manufacture diagnostics tools that must be
+         installable from the framework. Be careful. -->
+    <permission name="android.permission.DIAGNOSTIC" >
+        <group gid="input" />
+        <group gid="diag" />
+    </permission>
+
+    <!-- Group that can read detailed network usage statistics -->
+    <permission name="android.permission.READ_NETWORK_USAGE_HISTORY">
+        <group gid="net_bw_stats" />
+    </permission>
+
+    <!-- Group that can modify how network statistics are accounted -->
+    <permission name="android.permission.UPDATE_DEVICE_STATS">
+        <group gid="net_bw_acct" />
+    </permission>
+
+    <permission name="android.permission.LOOP_RADIO" >
+        <group gid="loop_radio" />
+    </permission>
+
+    <!-- Hotword training apps sometimes need a GID to talk with low-level
+         hardware; give them audio for now until full HAL support is added. -->
+    <permission name="android.permission.MANAGE_VOICE_KEYPHRASES">
+        <group gid="audio" />
+    </permission>
+
+    <permission name="android.permission.ACCESS_BROADCAST_RADIO" >
+        <!-- /dev/fm is gid media, not audio -->
+        <group gid="media" />
+    </permission>
+
+    <permission name="android.permission.USE_RESERVED_DISK">
+        <group gid="reserved_disk" />
+    </permission>
+
+    <!-- These are permissions that were mapped to gids but we need
+         to keep them here until an upgrade from L to the current
+         version is to be supported. These permissions are built-in
+         and in L were not stored in packages.xml as a result if they
+         are not defined here while parsing packages.xml we would
+         ignore these permissions being granted to apps and not
+         propagate the granted state. From N we are storing the
+         built-in permissions in packages.xml as the saved storage
+         is negligible (one tag with the permission) compared to
+         the fragility as one can remove a built-in permission which
+         no longer needs to be mapped to gids and break grant propagation. -->
+    <permission name="android.permission.READ_EXTERNAL_STORAGE" />
+    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <!-- ================================================================== -->
+    <!-- ================================================================== -->
+    <!-- ================================================================== -->
+
+    <!-- The following tags are assigning high-level permissions to specific
+         user IDs.  These are used to allow specific core system users to
+         perform the given operations with the higher-level framework.  For
+         example, we give a wide variety of permissions to the shell user
+         since that is the user the adb shell runs under and developers and
+         others should have a fairly open environment in which to
+         interact with the system. -->
+
+    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
+    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
+    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
+    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
+    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
+    <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
+
+    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" />
+    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" />
+    <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
+    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+
+    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
+    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
+    <assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" />
+    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
+    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="cameraserver" />
+    <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="cameraserver" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
+    <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
+
+    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
+
+    <assign-permission name="android.permission.DUMP" uid="incidentd" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="incidentd" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="incidentd" />
+
+    <assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
+    <assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
+
+    <assign-permission name="android.permission.DUMP" uid="statsd" />
+    <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
+    <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
+    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="statsd" />
+
+    <!-- This is a list of all the libraries available for application
+         code to link against. -->
+
+    <library name="android.test.base"
+            file="/system/framework/android.test.base.jar" />
+    <library name="android.test.mock"
+            file="/system/framework/android.test.mock.jar" />
+    <library name="android.test.runner"
+            file="/system/framework/android.test.runner.jar" />
+    <library name="javax.obex"
+            file="/system/framework/javax.obex.jar" />
+    <library name="org.apache.http.legacy"
+            file="/system/framework/org.apache.http.legacy.boot.jar" />
+
+    <!-- These are the standard packages that are white-listed to always have internet
+         access while in power save mode, even if they aren't in the foreground. -->
+    <allow-in-power-save package="com.android.providers.downloads" />
+
+    <!-- These are the standard packages that are white-listed to always have internet
+         access while in data mode, even if they aren't in the foreground. -->
+    <allow-in-data-usage-save package="com.android.providers.downloads" />
+
+    <!-- This is a core platform component that needs to freely run in the background -->
+    <allow-in-power-save package="com.android.cellbroadcastreceiver" />
+    <allow-in-power-save package="com.android.shell" />
+
+    <!-- Whitelist system providers -->
+    <allow-in-power-save-except-idle package="com.android.providers.calendar" />
+    <allow-in-power-save-except-idle package="com.android.providers.contacts" />
+
+    <!-- These are the packages that are white-listed to be able to run as system user -->
+    <system-user-whitelisted-app package="com.android.settings" />
+
+    <!-- These are the packages that shouldn't run as system user -->
+    <system-user-blacklisted-app package="com.android.wallpaper.livepicker" />
+</permissions>
diff --git a/tools/release-parser/tests/resources/platform.xml.pb.txt b/tools/release-parser/tests/resources/platform.xml.pb.txt
new file mode 100644
index 0000000..14b589e
--- /dev/null
+++ b/tools/release-parser/tests/resources/platform.xml.pb.txt
@@ -0,0 +1,428 @@
+name: "platform.xml"
+type: XML
+size: 10323
+content_id: "XX3tGVfCUz80Ws9at+dFNjir72S/cJUcb4y/g9QBnOM="
+device_permissions {
+  key: "allow-in-power-save"
+  value {
+    name: "allow-in-power-save"
+    permissions {
+      name: "com.android.providers.downloads"
+    }
+    permissions {
+      name: "com.android.cellbroadcastreceiver"
+    }
+    permissions {
+      name: "com.android.shell"
+    }
+  }
+}
+device_permissions {
+  key: "library"
+  value {
+    name: "library"
+    permissions {
+      name: "android.test.base"
+      elements {
+        name: "file"
+        value: "/system/framework/android.test.base.jar"
+      }
+    }
+    permissions {
+      name: "android.test.mock"
+      elements {
+        name: "file"
+        value: "/system/framework/android.test.mock.jar"
+      }
+    }
+    permissions {
+      name: "android.test.runner"
+      elements {
+        name: "file"
+        value: "/system/framework/android.test.runner.jar"
+      }
+    }
+    permissions {
+      name: "javax.obex"
+      elements {
+        name: "file"
+        value: "/system/framework/javax.obex.jar"
+      }
+    }
+    permissions {
+      name: "org.apache.http.legacy"
+      elements {
+        name: "file"
+        value: "/system/framework/org.apache.http.legacy.boot.jar"
+      }
+    }
+  }
+}
+device_permissions {
+  key: "system-user-whitelisted-app"
+  value {
+    name: "system-user-whitelisted-app"
+    permissions {
+      name: "com.android.settings"
+    }
+  }
+}
+device_permissions {
+  key: "permission"
+  value {
+    name: "permission"
+    permissions {
+      name: "android.permission.BLUETOOTH_ADMIN"
+      elements {
+        name: "gid"
+        value: "net_bt_admin"
+      }
+    }
+    permissions {
+      name: "android.permission.BLUETOOTH"
+      elements {
+        name: "gid"
+        value: "net_bt"
+      }
+    }
+    permissions {
+      name: "android.permission.BLUETOOTH_STACK"
+      elements {
+        name: "gid"
+        value: "bluetooth"
+      }
+      elements {
+        name: "gid"
+        value: "wakelock"
+      }
+      elements {
+        name: "gid"
+        value: "uhid"
+      }
+    }
+    permissions {
+      name: "android.permission.NET_TUNNELING"
+      elements {
+        name: "gid"
+        value: "vpn"
+      }
+    }
+    permissions {
+      name: "android.permission.INTERNET"
+      elements {
+        name: "gid"
+        value: "inet"
+      }
+    }
+    permissions {
+      name: "android.permission.READ_LOGS"
+      elements {
+        name: "gid"
+        value: "log"
+      }
+    }
+    permissions {
+      name: "android.permission.WRITE_MEDIA_STORAGE"
+      elements {
+        name: "gid"
+        value: "media_rw"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_MTP"
+      elements {
+        name: "gid"
+        value: "mtp"
+      }
+    }
+    permissions {
+      name: "android.permission.NET_ADMIN"
+      elements {
+        name: "gid"
+        value: "net_admin"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_CACHE_FILESYSTEM"
+      elements {
+        name: "gid"
+        value: "cache"
+      }
+    }
+    permissions {
+      name: "android.permission.DIAGNOSTIC"
+      elements {
+        name: "gid"
+        value: "input"
+      }
+      elements {
+        name: "gid"
+        value: "diag"
+      }
+    }
+    permissions {
+      name: "android.permission.READ_NETWORK_USAGE_HISTORY"
+      elements {
+        name: "gid"
+        value: "net_bw_stats"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_DEVICE_STATS"
+      elements {
+        name: "gid"
+        value: "net_bw_acct"
+      }
+    }
+    permissions {
+      name: "android.permission.LOOP_RADIO"
+      elements {
+        name: "gid"
+        value: "loop_radio"
+      }
+    }
+    permissions {
+      name: "android.permission.MANAGE_VOICE_KEYPHRASES"
+      elements {
+        name: "gid"
+        value: "audio"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_BROADCAST_RADIO"
+      elements {
+        name: "gid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.USE_RESERVED_DISK"
+      elements {
+        name: "gid"
+        value: "reserved_disk"
+      }
+    }
+    permissions {
+      name: "android.permission.READ_EXTERNAL_STORAGE"
+    }
+    permissions {
+      name: "android.permission.WRITE_EXTERNAL_STORAGE"
+    }
+  }
+}
+device_permissions {
+  key: "assign-permission"
+  value {
+    name: "assign-permission"
+    permissions {
+      name: "android.permission.MODIFY_AUDIO_SETTINGS"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_SURFACE_FLINGER"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.WAKE_LOCK"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_DEVICE_STATS"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_APP_OPS_STATS"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
+      elements {
+        name: "uid"
+        value: "media"
+      }
+    }
+    permissions {
+      name: "android.permission.MODIFY_AUDIO_SETTINGS"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_SURFACE_FLINGER"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.WAKE_LOCK"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_DEVICE_STATS"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_APP_OPS_STATS"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.PACKAGE_USAGE_STATS"
+      elements {
+        name: "uid"
+        value: "audioserver"
+      }
+    }
+    permissions {
+      name: "android.permission.MODIFY_AUDIO_SETTINGS"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_SURFACE_FLINGER"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.WAKE_LOCK"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_DEVICE_STATS"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_APP_OPS_STATS"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.PACKAGE_USAGE_STATS"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.WATCH_APPOPS"
+      elements {
+        name: "uid"
+        value: "cameraserver"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_SURFACE_FLINGER"
+      elements {
+        name: "uid"
+        value: "graphics"
+      }
+    }
+    permissions {
+      name: "android.permission.DUMP"
+      elements {
+        name: "uid"
+        value: "incidentd"
+      }
+    }
+    permissions {
+      name: "android.permission.PACKAGE_USAGE_STATS"
+      elements {
+        name: "uid"
+        value: "incidentd"
+      }
+    }
+    permissions {
+      name: "android.permission.INTERACT_ACROSS_USERS"
+      elements {
+        name: "uid"
+        value: "incidentd"
+      }
+    }
+    permissions {
+      name: "android.permission.ACCESS_LOWPAN_STATE"
+      elements {
+        name: "uid"
+        value: "lowpan"
+      }
+    }
+    permissions {
+      name: "android.permission.MANAGE_LOWPAN_INTERFACES"
+      elements {
+        name: "uid"
+        value: "lowpan"
+      }
+    }
+    permissions {
+      name: "android.permission.DUMP"
+      elements {
+        name: "uid"
+        value: "statsd"
+      }
+    }
+    permissions {
+      name: "android.permission.PACKAGE_USAGE_STATS"
+      elements {
+        name: "uid"
+        value: "statsd"
+      }
+    }
+    permissions {
+      name: "android.permission.STATSCOMPANION"
+      elements {
+        name: "uid"
+        value: "statsd"
+      }
+    }
+    permissions {
+      name: "android.permission.UPDATE_APP_OPS_STATS"
+      elements {
+        name: "uid"
+        value: "statsd"
+      }
+    }
+  }
+}
diff --git a/tools/release-parser/tests/run_test.sh b/tools/release-parser/tests/run_test.sh
new file mode 100755
index 0000000..3dba97e
--- /dev/null
+++ b/tools/release-parser/tests/run_test.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# 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.
+
+# Helper script to run unit tests for release parser libraries
+
+HARNESS_DIR=$(dirname ${0})/../../..
+source ${HARNESS_DIR}/test_defs.sh
+
+JARS="
+    release-parser\
+    release-parser-tests"
+
+run_tests "com.android.cts.releaseparser.UnitTests" "${JARS}" "${@}"
\ No newline at end of file
diff --git a/tools/release-parser/tests/src/com/android/cts/releaseparser/ApkParserTest.java b/tools/release-parser/tests/src/com/android/cts/releaseparser/ApkParserTest.java
new file mode 100644
index 0000000..8b30082
--- /dev/null
+++ b/tools/release-parser/tests/src/com/android/cts/releaseparser/ApkParserTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+
+/** Unit tests for {@link ApkParser} */
+@RunWith(JUnit4.class)
+public class ApkParserTest {
+    private static final String PB_TXT = ".pb.txt";
+    // HelloActivity.apk's source code: android/cts/tests/tests/jni/AndroidManifest.xml
+    private static final String TEST_SIMPLE_APK = "HelloActivity.apk";
+
+    // Shell.apk's source code:
+    // android/frameworks/base/packages/Shell/AndroidManifest.xml
+    private static final String TEST_SYS_APK = "Shell.apk";
+
+    // CtsJniTestCases.apk's source code:
+    // android/development/samples/HelloActivity/AndroidManifest.xml
+    private static final String TEST_SO_APK = "CtsJniTestCases.apk";
+
+    /**
+     * Test {@link ApkParser} with an simple APK
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSimpleApk() throws Exception {
+        testApkParser(TEST_SIMPLE_APK);
+    }
+
+    /**
+     * Test {@link ApkParser} with an Sys(priv-app) APK
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSysApk() throws Exception {
+        testApkParser(TEST_SYS_APK);
+    }
+
+    /**
+     * Test {@link ApkParser} with an APK with Shared Objects/Nactive Code
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSoApk() throws Exception {
+        testApkParser(TEST_SO_APK);
+    }
+
+    private void testApkParser(String fileName) throws Exception {
+        File apkFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        ApkParser aParser = new ApkParser(apkFile);
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+
+        Entry fileEntry = fileEntryBuilder.build();
+        Entry.Builder expectedfileEntryBuilder = Entry.newBuilder();
+
+        String txtProtobufFileName = fileName + PB_TXT;
+        TextFormat.getParser()
+                .merge(
+                        ClassUtils.openResourceAsStreamReader(getClass(), txtProtobufFileName),
+                        expectedfileEntryBuilder);
+        assertTrue(
+                String.format(
+                        "ApkParser does not return the same Entry of %s as %s.\n%s",
+                        fileName, txtProtobufFileName, TextFormat.printToString(fileEntry)),
+                fileEntry.equals(expectedfileEntryBuilder.build()));
+    }
+}
diff --git a/tools/release-parser/tests/src/com/android/cts/releaseparser/FileParserTest.java b/tools/release-parser/tests/src/com/android/cts/releaseparser/FileParserTest.java
new file mode 100644
index 0000000..c0964fd
--- /dev/null
+++ b/tools/release-parser/tests/src/com/android/cts/releaseparser/FileParserTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+
+/** Unit tests for {@link SoParser} */
+@RunWith(JUnit4.class)
+public class FileParserTest {
+    private static final String PB_TXT = ".pb.txt";
+    // ref: android/frameworks/base/test-runner
+    private static final String TEST_VDEX = "android.test.runner.vdex";
+    // ref: android/frameworks
+    private static final String TEST_ART = "boot-framework.art";
+    // ref: android/frameworks
+    private static final String TEST_OAT = "boot-framework.oat";
+    // ref: android/frameworks/base/test-runner
+    private static final String TEST_ODEX = "android.test.runner.odex";
+    // ref: android/build/make/core/Makefile, system_build_prop
+    private static final String TEST_BUILD_PROP = "build.prop";
+    // ref: android/frameworks/base/data/etc/platform.xml
+    private static final String TEST_PLATFORM_XML = "platform.xml";
+    // ref: android/frameworks/native/data/etc/android.hardware.vulkan.version-1_0_3.xml
+    private static final String TEST_DEVICE_FEATURE_XML = "android.hardware.vulkan.version.xml";
+
+    /**
+     * Test {@link VdexParser} with an Vdex file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testVdex() throws Exception {
+        String fileName = TEST_VDEX;
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        VdexParser aParser = new VdexParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    /**
+     * Test {@link ArtParser} with an Art file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testArt() throws Exception {
+        String fileName = TEST_ART;
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        ArtParser aParser = new ArtParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    /**
+     * Test {@link OatParser} with an Oat file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testOat() throws Exception {
+        String fileName = TEST_OAT;
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        OatParser aParser = new OatParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    /**
+     * Test {@link OdexParser} with an Odex file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testOdex() throws Exception {
+        String fileName = TEST_ODEX;
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        OdexParser aParser = new OdexParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    /**
+     * Test {@link BuildPropParser} with build.prop file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testBuildProp() throws Exception {
+        String fileName = TEST_BUILD_PROP;
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        BuildPropParser aParser = new BuildPropParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    /**
+     * Test {@link XmlParser} with a platform.xml file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testPlatformXml() throws Exception {
+        testXmlParser(TEST_PLATFORM_XML);
+    }
+
+    /**
+     * Test {@link XmlParser} with a feature xml file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testFeatureXml() throws Exception {
+        testXmlParser(TEST_DEVICE_FEATURE_XML);
+    }
+
+    private void testXmlParser(String fileName) throws Exception {
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        XmlParser aParser = new XmlParser(aFile);
+
+        Entry.Builder fileEntryBuilder = aParser.getFileEntryBuilder();
+        fileEntryBuilder.setName(fileName);
+        testFileParser(fileName, fileEntryBuilder.build());
+    }
+
+    private void testFileParser(String fileName, Entry fileEntry) throws Exception {
+        Entry.Builder expectedEntryBuilder = Entry.newBuilder();
+        String txtProtobufFileName = fileName + PB_TXT;
+        TextFormat.getParser()
+                .merge(
+                        ClassUtils.openResourceAsStreamReader(getClass(), txtProtobufFileName),
+                        expectedEntryBuilder);
+        assertTrue(
+                String.format(
+                        "Parser does not return the same Entry message of %s with %s.\n%s\n%s",
+                        fileName,
+                        txtProtobufFileName,
+                        TextFormat.printToString(fileEntry),
+                        TextFormat.printToString(expectedEntryBuilder)),
+                fileEntry.equals(expectedEntryBuilder.build()));
+    }
+}
diff --git a/tools/release-parser/tests/src/com/android/cts/releaseparser/SoParserTest.java b/tools/release-parser/tests/src/com/android/cts/releaseparser/SoParserTest.java
new file mode 100644
index 0000000..bc5cbc5
--- /dev/null
+++ b/tools/release-parser/tests/src/com/android/cts/releaseparser/SoParserTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.releaseparser;
+
+import com.android.cts.releaseparser.ReleaseProto.*;
+import com.google.protobuf.TextFormat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+
+/** Unit tests for {@link SoParser} */
+@RunWith(JUnit4.class)
+public class SoParserTest {
+    // ref: android/frameworks/native/opengl/libs/libEGL.map.txt
+    private static final String TEST_NDK_SO = "libEGL.so";
+    private static final String TEST_NDK_SO_TXT = "libEGL.so.pb.txt";
+
+    // ref: android/cts/tests/aslr/AndroidTest.xml
+    private static final String TEST_CTS_GTEST_EXE = "CtsAslrMallocTestCases32";
+    private static final String TEST_CTS_GTEST_EXE_PB_TXT = "CtsAslrMallocTestCases32.pb.txt";
+
+    /**
+     * Test {@link SoParser} with an NDK SO file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testNdkSo() throws Exception {
+        testSoParser(TEST_NDK_SO, TEST_NDK_SO_TXT, true);
+    }
+
+    /**
+     * Test {@link SoParser} with an CTS GTEST EXE file
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testCtsGtestExe() throws Exception {
+        testSoParser(TEST_CTS_GTEST_EXE, TEST_CTS_GTEST_EXE_PB_TXT, false);
+    }
+
+    private void testSoParser(String fileName, String txtProtobufFileName, boolean parseInternalApi)
+            throws Exception {
+        File aFile = ClassUtils.getResrouceFile(getClass(), fileName);
+        SoParser aParser = new SoParser(aFile);
+        aParser.setPackageName(fileName);
+        aParser.setParseInternalApi(parseInternalApi);
+        AppInfo appInfo = aParser.getAppInfo();
+
+        AppInfo.Builder expectedAppInfoBuilder = AppInfo.newBuilder();
+        TextFormat.getParser()
+                .merge(
+                        ClassUtils.getResrouceContentString(getClass(), txtProtobufFileName),
+                        expectedAppInfoBuilder);
+        assertTrue(
+                String.format(
+                        "SoParser does not return the same AppInfo of %s as %s.\n%s",
+                        fileName, txtProtobufFileName, TextFormat.printToString(appInfo)),
+                appInfo.equals(expectedAppInfoBuilder.build()));
+    }
+}
diff --git a/tools/release-parser/tests/src/com/android/cts/releaseparser/UnitTests.java b/tools/release-parser/tests/src/com/android/cts/releaseparser/UnitTests.java
new file mode 100644
index 0000000..fe7416e
--- /dev/null
+++ b/tools/release-parser/tests/src/com/android/cts/releaseparser/UnitTests.java
@@ -0,0 +1,36 @@
+/*
+ * 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.releaseparser;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * A test suite for all util unit tests.
+ *
+ * <p>All tests listed here should be self-contained, and do not require any external dependencies.
+ */
+@RunWith(Suite.class)
+@SuiteClasses({
+    ApkParserTest.class,
+    SoParserTest.class,
+    FileParserTest.class,
+})
+public class UnitTests {
+    // empty on purpose
+}
diff --git a/tools/selinux/SELinuxNeverallowTestFrame.py b/tools/selinux/SELinuxNeverallowTestFrame.py
index 20f953f..17aa5fe 100644
--- a/tools/selinux/SELinuxNeverallowTestFrame.py
+++ b/tools/selinux/SELinuxNeverallowTestFrame.py
@@ -35,7 +35,7 @@
  * Neverallow Rules SELinux tests.
  */
 public class SELinuxNeverallowRulesTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
-    private static final int P_SEPOLICY_VERSION = 28;
+    private static final int Q_SEPOLICY_VERSION = 29;
     private File sepolicyAnalyze;
     private File devicePolicyFile;
     private File deviceSystemPolicyFile;
@@ -121,7 +121,7 @@
         // If sepolicy is split and vendor sepolicy version is behind platform's,
         // only test against platform policy.
         File policyFile =
-                (isSepolicySplit() && mVendorSepolicyVersion < P_SEPOLICY_VERSION) ?
+                (isSepolicySplit() && mVendorSepolicyVersion < Q_SEPOLICY_VERSION) ?
                 deviceSystemPolicyFile :
                 devicePolicyFile;